diff --git a/apps/common/admin.py b/apps/common/admin.py index eb3db9f5..bb43be96 100644 --- a/apps/common/admin.py +++ b/apps/common/admin.py @@ -94,3 +94,19 @@ def save_model(self, request, obj, form, change): obj.archived_by = None obj.archived_at = None super().save_model(request, obj, form, change) # type: ignore[reportAttributeAccessIssue] + + +class FirebaseResourceAdmin(UserResourceAdmin, admin.ModelAdmin): + @typing.override + def get_readonly_fields(self, *args, **kwargs): + readonly_fields = super().get_readonly_fields(*args, **kwargs) # type: ignore[reportAttributeAccessIssue] + return [ + *dict.fromkeys( + [ + *readonly_fields, + "firebase_id", + "firebase_last_pushed", + "firebase_push_status", + ], + ), + ] diff --git a/apps/contributor/admin.py b/apps/contributor/admin.py index d6044196..2a523d0e 100644 --- a/apps/contributor/admin.py +++ b/apps/contributor/admin.py @@ -6,9 +6,9 @@ from django.utils.html import format_html from djangoql.admin import DjangoQLSearchMixin -from apps.common.admin import ArchivableResourceAdmin +from apps.common.admin import ArchivableResourceAdmin, FirebaseResourceAdmin -from .firebase import FirebaseContributorTeam, firebase_contributor_user +from .firebase import FirebaseContributorTeam, FirebaseContributorUser from .models import ContributorTeam, ContributorUser, ContributorUserGroup, ContributorUserGroupMembership @@ -42,16 +42,16 @@ def has_delete_permission(self, *args, **kwargs): @typing.override def save_model(self, request, obj, form, change): super().save_model(request, obj, form, change) # type: ignore[reportAttributeAccessIssue] - transaction.on_commit(lambda: firebase_contributor_user.delay(obj.id)) + transaction.on_commit(lambda: FirebaseContributorUser(obj.id).push()) @admin.register(ContributorUserGroup) -class ContributorUserGroupAdmin(DjangoQLSearchMixin, admin.ModelAdmin): +class ContributorUserGroupAdmin(DjangoQLSearchMixin, ArchivableResourceAdmin, FirebaseResourceAdmin): pass @admin.register(ContributorTeam) -class ContributorTeamAdmin(ArchivableResourceAdmin, DjangoQLSearchMixin, admin.ModelAdmin): +class ContributorTeamAdmin(ArchivableResourceAdmin, DjangoQLSearchMixin, FirebaseResourceAdmin, admin.ModelAdmin): list_display = ( "name", "is_archived", diff --git a/apps/contributor/firebase.py b/apps/contributor/firebase.py index cd1db6ef..691bd7ae 100644 --- a/apps/contributor/firebase.py +++ b/apps/contributor/firebase.py @@ -1,14 +1,13 @@ import logging import typing -from celery import shared_task from firebase_admin.db import Reference as FbReference from pyfirebase_mapswipe import extended_models as firebase_ext_models from pyfirebase_mapswipe import models as firebase_models from pyfirebase_mapswipe import utils as firebase_utils from apps.common.firebase import FirebasePush -from apps.contributor.models import ContributorTeam, ContributorUser +from apps.contributor.models import ContributorTeam, ContributorUser, ContributorUserGroup from main.celery import app from main.config import Config @@ -93,6 +92,47 @@ def get_firebase_path(self, firebase_id: str, model=ContributorUser): def task(obj_id: int) -> None: ... -@shared_task -def firebase_contributor_user(obj_id: int): - FirebaseContributorUser(obj_id).push() +class FirebaseContributorUserGroup(FirebasePush[ContributorUserGroup, firebase_ext_models.FbUserGroup]): + model_class = ContributorUserGroup + firebase_model_class = firebase_ext_models.FbUserGroup + + @typing.override + def handle_new_object_on_firebase(self, model_obj: ContributorUserGroup, fb_reference: FbReference): + contributor_user_group_data = firebase_ext_models.FbUserGroup( + createdAt=int(model_obj.created_at.timestamp()), + createdBy=model_obj.created_by.old_id or str(model_obj.created_by_id), + description=model_obj.description, + name=model_obj.name, + nameKey=model_obj.name.lower().strip(), + users=None, + ) + + fb_reference.set( + value=firebase_utils.serialize(contributor_user_group_data), + ) + + @typing.override + def handle_object_update_on_firebase( + self, + model_obj: ContributorUserGroup, + fb_obj: firebase_ext_models.FbUserGroup, + fb_reference: FbReference, + ): + fb_reference.update( + value=firebase_utils.serialize( + firebase_models.FbUserGroupUpdateInput( + description=model_obj.description, + name=model_obj.name, + nameKey=model_obj.name.lower().strip(), + ), + ), + ) + + @typing.override + def get_firebase_path(self, firebase_id: str, model=ContributorUserGroup): + return Config.FirebaseKeys.contributor_user_group(firebase_id) + + @staticmethod + @typing.override + @app.task() + def task(obj_id: int) -> None: ... diff --git a/apps/contributor/serializers.py b/apps/contributor/serializers.py index 709b5eb5..fd742436 100644 --- a/apps/contributor/serializers.py +++ b/apps/contributor/serializers.py @@ -1,4 +1,9 @@ +import typing + +from django.db import transaction + from apps.common.serializers import ArchivableResourceSerializer, UserResourceSerializer +from apps.contributor.firebase import FirebaseContributorUserGroup from .models import ContributorUserGroup @@ -14,3 +19,15 @@ class Meta: # type: ignore[reportIncompatibleVariableOverride] "description", "is_archived", ) + + @typing.override + def create(self, validated_data: dict[str, typing.Any]) -> ContributorUserGroup: + user_group = super().create(validated_data) + transaction.on_commit(lambda: FirebaseContributorUserGroup(user_group.id).push()) + return user_group + + @typing.override + def update(self, instance: ContributorUserGroup, validated_data: dict[typing.Any, typing.Any]): + user_group = super().update(instance, validated_data) + transaction.on_commit(lambda: FirebaseContributorUserGroup(user_group.id).push()) + return user_group diff --git a/apps/project/admin.py b/apps/project/admin.py index f5fd33a5..bd5732d5 100644 --- a/apps/project/admin.py +++ b/apps/project/admin.py @@ -7,6 +7,8 @@ from django.utils.translation import gettext_lazy as _ from djangoql.admin import DjangoQLSearchMixin +from apps.common.admin import ArchivableResourceAdmin, FirebaseResourceAdmin + from .models import Organization, Project, ProjectAsset @@ -34,13 +36,13 @@ def queryset(self, request: HttpRequest, queryset: QuerySet[Any]) -> QuerySet[An @admin.register(Organization) -class OrganizationAdmin(DjangoQLSearchMixin, admin.ModelAdmin): +class OrganizationAdmin(DjangoQLSearchMixin, ArchivableResourceAdmin, FirebaseResourceAdmin, admin.ModelAdmin): list_display = ("name",) list_filter = ("name",) @admin.register(Project) -class ProjectAdmin(DjangoQLSearchMixin, admin.ModelAdmin): +class ProjectAdmin(DjangoQLSearchMixin, FirebaseResourceAdmin, admin.ModelAdmin): list_display = ("topic", "requesting_organization", "project_type", "is_private", "region") list_filter = (IncludedInTeamFilter, "topic", "project_type", "is_private", "region") list_select_related = True diff --git a/main/config.py b/main/config.py index 84ec0cc5..f42fe861 100644 --- a/main/config.py +++ b/main/config.py @@ -79,6 +79,10 @@ def contributor_team(contributor_team_id: str): def contributor_user(user_id: str): return f"/v2/users/{user_id}" + @staticmethod + def contributor_user_group(group_id: str | int): + return f"/v2/userGroups/{group_id}" + # FIXME: Import utils/geo/raster_tile_server/config.py here # FIXME: Import utils/geo/vector_tile_server/config.py here