Skip to content

Commit 55f3dfa

Browse files
committed
Add permission for the region level validator and change location_json field
1 parent 8a0c922 commit 55f3dfa

9 files changed

+81
-101
lines changed

local_units/admin.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
HealthData,
1515
HospitalType,
1616
LocalUnit,
17+
LocalUnitChangeRequest,
1718
LocalUnitLevel,
1819
LocalUnitType,
1920
PrimaryHCC,
@@ -64,6 +65,41 @@ def save_model(self, request, obj, form, change):
6465
super().save_model(request, obj, form, change)
6566

6667

68+
@admin.register(LocalUnitChangeRequest)
69+
class LocalUnitChangeRequestAdmin(admin.ModelAdmin):
70+
autocomplete_fields = (
71+
"local_unit",
72+
"triggered_by",
73+
)
74+
search_fields = (
75+
"local_unit__id",
76+
"local_unit__english_branch_name",
77+
"local_unit__local_branch_name",
78+
)
79+
list_filter = ("status",)
80+
list_display = (
81+
"local_unit",
82+
"status",
83+
"current_validator",
84+
)
85+
readonly_fields = (
86+
"previous_data",
87+
"rejected_data",
88+
"rejected_reason",
89+
)
90+
ordering = ("id",)
91+
92+
def get_queryset(self, request):
93+
return (
94+
super()
95+
.get_queryset(request)
96+
.select_related(
97+
"local_unit",
98+
"triggered_by",
99+
)
100+
)
101+
102+
67103
@admin.register(DelegationOffice)
68104
class DelegationOfficeAdmin(admin.OSMGeoAdmin):
69105
search_fields = ("name", "city", "country__name")

local_units/migrations/0018_localunit_deprecated_reason_and_more.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.16 on 2024-11-29 11:02
1+
# Generated by Django 4.2.16 on 2024-12-11 09:28
22

33
import django.db.models.deletion
44
from django.conf import settings
@@ -36,14 +36,12 @@ class Migration(migrations.Migration):
3636
migrations.AddField(
3737
model_name="localunit",
3838
name="is_deprecated",
39-
field=models.BooleanField(blank=True, default=False, null=True, verbose_name="Is deprecated?"),
39+
field=models.BooleanField(default=False, verbose_name="Is deprecated?"),
4040
),
4141
migrations.AddField(
4242
model_name="localunit",
43-
name="status",
44-
field=models.IntegerField(
45-
blank=True, choices=[(1, "Verified"), (2, "Unverified")], default=2, null=True, verbose_name="status"
46-
),
43+
name="is_locked",
44+
field=models.BooleanField(default=False, verbose_name="Is locked?"),
4745
),
4846
migrations.CreateModel(
4947
name="LocalUnitChangeRequest",
@@ -62,7 +60,7 @@ class Migration(migrations.Migration):
6260
choices=[(1, "Local"), (2, "Regional"), (3, "Global")], default=1, verbose_name="Current validator"
6361
),
6462
),
65-
("triggered_at", models.DateTimeField(auto_now=True, verbose_name="Triggered at")),
63+
("triggered_at", models.DateTimeField(auto_now_add=True, verbose_name="Triggered at")),
6664
("updated_at", models.DateTimeField(auto_now=True, verbose_name="Updated at")),
6765
("rejected_data", models.JSONField(default=dict, verbose_name="Rejected data")),
6866
("rejected_reason", models.TextField(blank=True, null=True, verbose_name="Rejected reason")),
@@ -96,5 +94,8 @@ class Migration(migrations.Migration):
9694
),
9795
),
9896
],
97+
options={
98+
"ordering": ("id",),
99+
},
99100
),
100101
]

local_units/migrations/0019_alter_localunit_is_deprecated_alter_localunit_status_and_more.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

local_units/migrations/0020_alter_localunitchangerequest_options_and_more.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

local_units/permissions.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44

55
class ValidateLocalUnitPermission(permissions.BasePermission):
6-
message = "You need to be super user/ country admin to validate local unit"
6+
message = "You need to be super user/ country admin/ region admin to validate local unit"
77

88
def has_object_permission(self, request, view, object):
99
user = request.user
@@ -16,7 +16,14 @@ def has_object_permission(self, request, view, object):
1616
codename__startswith="country_admin_",
1717
).values_list("codename", flat=True)
1818
]
19-
if object.country_id in country_admin_ids:
19+
region_admin_ids = [
20+
int(codename.replace("region_admin_", ""))
21+
for codename in Permission.objects.filter(
22+
group__user=user,
23+
codename__startswith="region_admin_",
24+
).values_list("codename", flat=True)
25+
]
26+
if object.country_id in country_admin_ids or object.region_id in region_admin_ids:
2027
return True
2128
return False
2229

local_units/serializers.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
)
3232

3333

34+
class LocationSerializer(serializers.Serializer):
35+
lat = serializers.FloatField(required=True)
36+
lng = serializers.FloatField(required=True)
37+
38+
3439
class GeneralMedicalServiceSerializer(serializers.ModelSerializer):
3540
class Meta:
3641
model = GeneralMedicalService
@@ -191,7 +196,7 @@ class LocalUnitDetailSerializer(serializers.ModelSerializer):
191196
type_details = LocalUnitTypeSerializer(source="type", read_only=True)
192197
level_details = LocalUnitLevelSerializer(source="level", read_only=True)
193198
health = HealthDataSerializer(required=False, allow_null=True)
194-
location_details = serializers.SerializerMethodField()
199+
location_geojson = serializers.SerializerMethodField()
195200
visibility_display = serializers.CharField(source="get_visibility_display", read_only=True)
196201
validated = serializers.BooleanField(read_only=True)
197202

@@ -220,13 +225,13 @@ class Meta:
220225
"level",
221226
"health",
222227
"visibility_display",
223-
"location_details",
228+
"location_geojson",
224229
"type_details",
225230
"level_details",
226231
"country_details",
227232
)
228233

229-
def get_location_details(self, unit) -> dict:
234+
def get_location_geojson(self, unit) -> dict:
230235
return json.loads(unit.location.geojson)
231236

232237

@@ -235,10 +240,11 @@ class PrivateLocalUnitDetailSerializer(NestedCreateMixin, NestedUpdateMixin):
235240
type_details = LocalUnitTypeSerializer(source="type", read_only=True)
236241
level_details = LocalUnitLevelSerializer(source="level", read_only=True)
237242
health = HealthDataSerializer(required=False, allow_null=True)
238-
location_details = serializers.SerializerMethodField(read_only=True)
243+
# NOTE: location_geojson contains the geojson of the location
244+
location_geojson = serializers.SerializerMethodField(read_only=True)
239245
visibility_display = serializers.CharField(source="get_visibility_display", read_only=True)
240246
validated = serializers.BooleanField(read_only=True)
241-
location_json = serializers.JSONField(required=True, write_only=True)
247+
location_json = LocationSerializer(required=True)
242248
location = serializers.CharField(required=False)
243249
modified_by_details = LocalUnitMiniUserSerializer(source="modified_by", read_only=True)
244250
created_by_details = LocalUnitMiniUserSerializer(source="created_by", read_only=True)
@@ -272,7 +278,7 @@ class Meta:
272278
"level",
273279
"health",
274280
"visibility_display",
275-
"location_details",
281+
"location_geojson",
276282
"type_details",
277283
"level_details",
278284
"country_details",
@@ -288,7 +294,7 @@ class Meta:
288294
"is_locked",
289295
)
290296

291-
def get_location_details(self, unit) -> dict:
297+
def get_location_geojson(self, unit) -> dict:
292298
return json.loads(unit.location.geojson)
293299

294300
def get_version_id(self, resource):
@@ -319,8 +325,6 @@ def create(self, validated_data):
319325
location_json = validated_data.pop("location_json")
320326
lat = location_json.get("lat")
321327
lng = location_json.get("lng")
322-
if not lat and not lng:
323-
raise serializers.ValidationError(gettext("Combination of lat/lon is required"))
324328
input_point = Point(lng, lat)
325329
if country.bbox:
326330
country_json = json.loads(country.countrygeoms.geom.geojson)
@@ -349,8 +353,6 @@ def update(self, instance, validated_data):
349353
location_json = validated_data.pop("location_json")
350354
lat = location_json.get("lat")
351355
lng = location_json.get("lng")
352-
if not lat and not lng:
353-
raise serializers.ValidationError(gettext("Combination of lat/lon is required"))
354356
input_point = Point(lng, lat)
355357
if country.bbox:
356358
country_json = json.loads(country.countrygeoms.geom.geojson)
@@ -377,7 +379,8 @@ def update(self, instance, validated_data):
377379

378380

379381
class LocalUnitSerializer(serializers.ModelSerializer):
380-
location_details = serializers.SerializerMethodField()
382+
# NOTE: location_geojson contains the geojson of the location
383+
location_geojson = serializers.SerializerMethodField()
381384
country_details = LocalUnitCountrySerializer(source="country", read_only=True)
382385
type_details = LocalUnitTypeSerializer(source="type", read_only=True)
383386
health_details = MiniHealthDataSerializer(read_only=True, source="health")
@@ -390,7 +393,7 @@ class Meta:
390393
"country",
391394
"local_branch_name",
392395
"english_branch_name",
393-
"location_details",
396+
"location_geojson",
394397
"type",
395398
"validated",
396399
"address_loc",
@@ -401,12 +404,13 @@ class Meta:
401404
"health_details",
402405
)
403406

404-
def get_location_details(self, unit) -> dict:
407+
def get_location_geojson(self, unit) -> dict:
405408
return json.loads(unit.location.geojson)
406409

407410

408411
class PrivateLocalUnitSerializer(serializers.ModelSerializer):
409-
location_details = serializers.SerializerMethodField()
412+
# NOTE: location_geojson contains the geojson of the location
413+
location_geojson = serializers.SerializerMethodField()
410414
country_details = LocalUnitCountrySerializer(source="country", read_only=True)
411415
type_details = LocalUnitTypeSerializer(source="type", read_only=True)
412416
health_details = MiniHealthDataSerializer(read_only=True, source="health")
@@ -421,7 +425,7 @@ class Meta:
421425
"country",
422426
"local_branch_name",
423427
"english_branch_name",
424-
"location_details",
428+
"location_geojson",
425429
"type",
426430
"validated",
427431
"address_loc",
@@ -439,7 +443,7 @@ class Meta:
439443
"is_locked",
440444
)
441445

442-
def get_location_details(self, unit) -> dict:
446+
def get_location_geojson(self, unit) -> dict:
443447
return json.loads(unit.location.geojson)
444448

445449

local_units/test_views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def test_detail(self):
171171
self.authenticate()
172172
response = self.client.get(f"/api/v2/local-units/{local_unit.id}/")
173173
self.assertEqual(response.status_code, 200)
174-
self.assertEqual(response.data["location_details"]["coordinates"], [12, 38])
174+
self.assertEqual(response.data["location_geojson"]["coordinates"], [12, 38])
175175
self.assertEqual(response.data["country_details"]["name"], "Nepal")
176176
self.assertEqual(response.data["country_details"]["iso3"], "NLP")
177177
self.assertEqual(response.data["type_details"]["name"], "Code 0")
@@ -190,7 +190,7 @@ def test_detail(self):
190190
local_unit = LocalUnitFactory.create(country=self.country, type=self.type, visibility=VisibilityChoices.PUBLIC)
191191
response = self.client.get(f"/api/v2/public-local-units/{local_unit.id}/")
192192
self.assertEqual(response.status_code, 200)
193-
self.assertEqual(response.data["location_details"]["coordinates"], [12, 38])
193+
self.assertEqual(response.data["location_geojson"]["coordinates"], [12, 38])
194194
self.assertEqual(response.data["country_details"]["name"], "Nepal")
195195
self.assertEqual(response.data["country_details"]["iso3"], "NLP")
196196
self.assertEqual(response.data["type_details"]["name"], "Code 0")

local_units/utils.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

local_units/views.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
PrivateLocalUnitSerializer,
3939
RejectedReasonSerialzier,
4040
)
41-
from local_units.utils import get_local_unit_snapshot_data
4241
from main.permissions import DenyGuestUserPermission
4342

4443

@@ -48,7 +47,7 @@ class PrivateLocalUnitViewSet(viewsets.ModelViewSet):
4847
"country",
4948
"type",
5049
"level",
51-
)
50+
).exclude(is_deprecated=True)
5251
filterset_class = LocalUnitFilters
5352
search_fields = (
5453
"local_branch_name",
@@ -72,7 +71,7 @@ def create(self, request, *args, **kwargs):
7271
# Creating a new change request for the local unit
7372
LocalUnitChangeRequest.objects.create(
7473
local_unit=serializer.instance,
75-
previous_data=get_local_unit_snapshot_data(serializer.data),
74+
previous_data=serializer.data,
7675
status=LocalUnitChangeRequest.Status.PENDING,
7776
triggered_by=request.user,
7877
)
@@ -95,11 +94,11 @@ def update(self, request, *args, **kwargs):
9594
# Creating a new change request for the local unit
9695
LocalUnitChangeRequest.objects.create(
9796
local_unit=local_unit,
98-
previous_data=get_local_unit_snapshot_data(serializer.data),
97+
previous_data=serializer,
9998
status=LocalUnitChangeRequest.Status.PENDING,
10099
triggered_by=request.user,
101100
)
102-
return response.Response(serializer.data)
101+
return response.Response(serializer)
103102

104103
@extend_schema(request=None, responses=PrivateLocalUnitSerializer)
105104
@action(
@@ -166,7 +165,7 @@ def get_revert(self, request, pk=None, version=None):
166165
change_request_instance.rejected_reason = reason
167166
change_request_instance.updated_by = request.user
168167
change_request_instance.updated_at = timezone.now()
169-
change_request_instance.rejected_data = get_local_unit_snapshot_data(full_serializer.data)
168+
change_request_instance.rejected_data = full_serializer.data
170169
change_request_instance.save(update_fields=["status", "rejected_reason", "updated_at", "updated_by", "rejected_data"])
171170

172171
# Reverting the last change request related to this local unit

0 commit comments

Comments
 (0)