From a67ea5cd8a198c7bf3c16fb6f7a1312e4397461b Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 14 Jul 2023 23:55:54 +0800 Subject: [PATCH] Implemented geofencing logic for student_status and study_groups with landmark labels --- Pipfile | 1 + Pipfile.lock | 53 +++++++++++-------- stude/landmarks/models.py | 4 +- stude/student_status/admin.py | 12 ++++- .../migrations/0003_studentstatus_landmark.py | 20 +++++++ stude/student_status/models.py | 4 +- stude/student_status/serializers.py | 14 ++++- stude/study_groups/models.py | 2 +- stude/study_groups/serializers.py | 2 + 9 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 stude/student_status/migrations/0003_studentstatus_landmark.py diff --git a/Pipfile b/Pipfile index dc192fe..3e23d2f 100644 --- a/Pipfile +++ b/Pipfile @@ -15,6 +15,7 @@ daphne = "*" psycopg2 = "*" gdal = {path = "./packages/GDAL-3.4.3-cp311-cp311-win_amd64.whl"} django-leaflet = "*" +django-extra-fields = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 564bee3..3d11d05 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "efd1134f98df71c3c1209f70d5d962b6609fe23b044b57f80bba89db477b549f" + "sha256": "975af7eaaedebb1c31a1828fdf307574edc293e0de15ecfd9cf7107f98d642f5" }, "pipfile-spec": 6, "requires": { @@ -221,28 +221,32 @@ }, "cryptography": { "hashes": [ - "sha256:059e348f9a3c1950937e1b5d7ba1f8e968508ab181e75fc32b879452f08356db", - "sha256:1a5472d40c8f8e91ff7a3d8ac6dfa363d8e3138b961529c996f3e2df0c7a411a", - "sha256:1a8e6c2de6fbbcc5e14fd27fb24414507cb3333198ea9ab1258d916f00bc3039", - "sha256:1fee5aacc7367487b4e22484d3c7e547992ed726d14864ee33c0176ae43b0d7c", - "sha256:5d092fdfedaec4cbbffbf98cddc915ba145313a6fdaab83c6e67f4e6c218e6f3", - "sha256:5f0ff6e18d13a3de56f609dd1fd11470918f770c6bd5d00d632076c727d35485", - "sha256:7bfc55a5eae8b86a287747053140ba221afc65eb06207bedf6e019b8934b477c", - "sha256:7fa01527046ca5facdf973eef2535a27fec4cb651e4daec4d043ef63f6ecd4ca", - "sha256:8dde71c4169ec5ccc1087bb7521d54251c016f126f922ab2dfe6649170a3b8c5", - "sha256:8f4ab7021127a9b4323537300a2acfb450124b2def3756f64dc3a3d2160ee4b5", - "sha256:948224d76c4b6457349d47c0c98657557f429b4e93057cf5a2f71d603e2fc3a3", - "sha256:9a6c7a3c87d595608a39980ebaa04d5a37f94024c9f24eb7d10262b92f739ddb", - "sha256:b46e37db3cc267b4dea1f56da7346c9727e1209aa98487179ee8ebed09d21e43", - "sha256:b4ceb5324b998ce2003bc17d519080b4ec8d5b7b70794cbd2836101406a9be31", - "sha256:cb33ccf15e89f7ed89b235cff9d49e2e62c6c981a6061c9c8bb47ed7951190bc", - "sha256:d198820aba55660b4d74f7b5fd1f17db3aa5eb3e6893b0a41b75e84e4f9e0e4b", - "sha256:d34579085401d3f49762d2f7d6634d6b6c2ae1242202e860f4d26b046e3a1006", - "sha256:eb8163f5e549a22888c18b0d53d6bb62a20510060a22fd5a995ec8a05268df8a", - "sha256:f73bff05db2a3e5974a6fd248af2566134d8981fd7ab012e5dd4ddb1d9a70699" + "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711", + "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7", + "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd", + "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e", + "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58", + "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0", + "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d", + "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83", + "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831", + "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766", + "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b", + "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c", + "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182", + "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f", + "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa", + "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4", + "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a", + "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2", + "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76", + "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5", + "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee", + "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f", + "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14" ], "markers": "python_version >= '3.7'", - "version": "==41.0.1" + "version": "==41.0.2" }, "daphne": { "hashes": [ @@ -268,6 +272,13 @@ "index": "pypi", "version": "==4.2.3" }, + "django-extra-fields": { + "hashes": [ + "sha256:2334e914b346c0a19a7765bf0ff7895c46cf35d5f40315a68418f44b7ddbb33b" + ], + "index": "pypi", + "version": "==3.0.2" + }, "django-leaflet": { "hashes": [ "sha256:2f6dc8c7187fd22e62b6f2b7b42eed86920f81bec312aa654981ebe5bc8e0866", diff --git a/stude/landmarks/models.py b/stude/landmarks/models.py index 8ed5a66..b7be508 100644 --- a/stude/landmarks/models.py +++ b/stude/landmarks/models.py @@ -8,7 +8,7 @@ from django.dispatch import receiver class Landmark(models.Model): name = models.CharField(max_length=64) - location = gis_models.PolygonField() + location = gis_models.PolygonField(srid=4326) def __str__(self): return self.name @@ -178,7 +178,7 @@ def populate_landmarks(sender, **kwargs): 'POLYGON ((124.655534 8.485857, 124.655629 8.485588, 124.655795 8.485647, 124.655755 8.485757, 124.656271 8.485946, 124.656212 8.486104, 124.655534 8.485857))', srid=SRID ) - ) + ) Landmark.objects.get_or_create( name='Science Complex', location=GEOSGeometry( diff --git a/stude/student_status/admin.py b/stude/student_status/admin.py index 5ed1467..fbfe8be 100644 --- a/stude/student_status/admin.py +++ b/stude/student_status/admin.py @@ -2,4 +2,14 @@ from django.contrib import admin from .models import StudentStatus from leaflet.admin import LeafletGeoAdmin -admin.site.register(StudentStatus, LeafletGeoAdmin) + +class StudentStatusAdmin(LeafletGeoAdmin): + # define which fields are required + def get_form(self, request, obj=None, **kwargs): + form = super(StudentStatusAdmin, self).get_form(request, obj, **kwargs) + form.base_fields['landmark'].required = False + return form + + +# Register the new StudentStatus model +admin.site.register(StudentStatus, StudentStatusAdmin) diff --git a/stude/student_status/migrations/0003_studentstatus_landmark.py b/stude/student_status/migrations/0003_studentstatus_landmark.py new file mode 100644 index 0000000..0d53ac6 --- /dev/null +++ b/stude/student_status/migrations/0003_studentstatus_landmark.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.3 on 2023-07-14 14:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('landmarks', '0001_initial'), + ('student_status', '0002_initial'), + ] + + operations = [ + migrations.AddField( + model_name='studentstatus', + name='landmark', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='landmarks.landmark'), + ), + ] diff --git a/stude/student_status/models.py b/stude/student_status/models.py index d84fb43..b7bed57 100644 --- a/stude/student_status/models.py +++ b/stude/student_status/models.py @@ -9,11 +9,13 @@ from django.contrib.gis.geos import Point class StudentStatus(models.Model): user = models.OneToOneField( CustomUser, on_delete=models.CASCADE, primary_key=True) - location = gis_models.PointField(blank=True, null=True) + location = gis_models.PointField(blank=True, null=True, srid=4326) subject = models.ForeignKey( 'subjects.Subject', on_delete=models.SET_NULL, null=True) active = models.BooleanField(default=False) timestamp = models.DateField(auto_now_add=True) + landmark = models.ForeignKey( + 'landmarks.Landmark', on_delete=models.SET_NULL, null=True) study_group = models.ManyToManyField( 'study_groups.StudyGroup', through='study_groups.StudyGroupMembership', blank=True) diff --git a/stude/student_status/serializers.py b/stude/student_status/serializers.py index ea3aa81..5a2baca 100644 --- a/stude/student_status/serializers.py +++ b/stude/student_status/serializers.py @@ -2,17 +2,22 @@ from rest_framework import serializers from .models import StudentStatus from subjects.models import Subject from django.contrib.gis.geos import Point +from drf_extra_fields.geo_fields import PointField +from landmarks.models import Landmark class StudentStatusSerializer(serializers.ModelSerializer): subject = serializers.SlugRelatedField( queryset=Subject.objects.all(), slug_field='name', required=True) user = serializers.CharField(source='user.full_name', read_only=True) + location = PointField() + landmark = serializers.SlugRelatedField( + queryset=Landmark.objects.all(), many=False, slug_field='name', required=False, allow_null=True) class Meta: model = StudentStatus fields = '__all__' - read_only_fields = ['user'] + read_only_fields = ['user', 'landmark'] def create(self, validated_data): user = self.context['request'].user @@ -26,5 +31,12 @@ class StudentStatusSerializer(serializers.ModelSerializer): if active is not None and active is False: validated_data['location'] = Point(0, 0) validated_data['subject'] = None + validated_data['landmark'] = None + else: + # Check each landmark to see if our location is within it + for landmark in Landmark.objects.all(): + if landmark.location.contains(validated_data['location']): + validated_data['landmark'] = landmark + break return super().update(instance, validated_data) diff --git a/stude/study_groups/models.py b/stude/study_groups/models.py index 66e3682..9aa761e 100644 --- a/stude/study_groups/models.py +++ b/stude/study_groups/models.py @@ -9,7 +9,7 @@ class StudyGroup(models.Model): name = models.CharField(max_length=48) users = models.ManyToManyField( 'student_status.StudentStatus', through='StudyGroupMembership') - location = gis_models.PointField(blank=True, null=True) + location = gis_models.PointField(blank=True, null=True, srid=4326) subject = models.ForeignKey(Subject, on_delete=models.CASCADE) active = models.BooleanField(default=False) timestamp = models.DateField(auto_now_add=True) diff --git a/stude/study_groups/serializers.py b/stude/study_groups/serializers.py index 5c1ec6e..a0203cb 100644 --- a/stude/study_groups/serializers.py +++ b/stude/study_groups/serializers.py @@ -2,6 +2,7 @@ from rest_framework import serializers from .models import StudyGroup, StudyGroupMembership from accounts.models import CustomUser from subjects.models import Subject +from drf_extra_fields.geo_fields import PointField class StudyGroupSerializer(serializers.ModelSerializer): @@ -9,6 +10,7 @@ class StudyGroupSerializer(serializers.ModelSerializer): queryset=CustomUser.objects.all(), many=True, slug_field='name', required=False, allow_null=True) subject = serializers.SlugRelatedField( many=False, slug_field='name', queryset=Subject.objects.all(), required=True, allow_null=False) + location = PointField() class Meta: model = StudyGroup