Skip to content

Commit 2a7b43e

Browse files
committed
feat(firebase): introduce firebase_id to replace canonical_id
- remove canonical_id and use firebase_id instead - rename legacy_task_id to firebase_id - rename legacy_group_id to firebase_id - add resuable models: FirebasePushModel, FirebasePullModel
1 parent 1e1e33e commit 2a7b43e

23 files changed

+524
-87
lines changed

apps/common/firebase.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from firebase_admin.db import Reference as FbReference
66

7-
from apps.common.models import FirebasePushStatusEnum, FirebaseResource
7+
from apps.common.models import FirebasePushResource, FirebasePushStatusEnum
88
from main.celery import app
99
from main.config import Config
1010

@@ -14,7 +14,7 @@
1414
class InvalidObjectPushException(Exception): ...
1515

1616

17-
class FirebasePush[T: FirebaseResource](abc.ABC):
17+
class FirebasePush[T: FirebasePushResource](abc.ABC):
1818
model: type[T]
1919

2020
def __init__(
@@ -30,7 +30,7 @@ def handle_new_object_on_firebase(self, model_obj: T, fb_reference: FbReference)
3030
def handle_object_update_on_firebase(self, model_obj: T, fb_reference: FbReference): ...
3131

3232
@abc.abstractmethod
33-
def get_firebase_path(self, canonical_id: str, model: type[T]) -> str: ...
33+
def get_firebase_path(self, firebase_id: str, model: type[T]) -> str: ...
3434

3535
def push(self) -> None:
3636
model_obj = self.model.objects.get(id=self.obj_id)
@@ -39,7 +39,7 @@ def push(self) -> None:
3939

4040
try:
4141
model_ref = Config.FIREBASE_HELPER.ref(
42-
self.get_firebase_path(model_obj.canonical_id, self.model),
42+
self.get_firebase_path(model_obj.firebase_id, self.model),
4343
)
4444
fb_model: typing.Any = model_ref.get()
4545

apps/common/models.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import typing
33

44
from django.db import models
5-
from django.db.models.functions import Cast, Coalesce
65
from django.utils import timezone
76
from django.utils.translation import gettext_lazy
87
from django_choices_field import IntegerChoicesField
98
from django_stubs_ext.db.models import TypedModelMeta
9+
from ulid import ULID
1010

1111
from apps.user.models import User
1212
from main.db import Model
@@ -114,9 +114,12 @@ class IconEnum(models.IntegerChoices):
114114
SWIPE_LEFT = 32, "swipe-left"
115115

116116

117-
class FirebaseResource(Model):
117+
class FirebasePushResource(Model):
118+
# NOTE: We should not directly use old_id. This is ID reference to old system
118119
old_id = models.CharField(max_length=30, db_index=True, null=True, blank=True)
119120

121+
firebase_id = models.CharField(max_length=30, unique=True, default=ULID)
122+
120123
firebase_push_status: int | None = IntegerChoicesField( # type: ignore[reportAssignmentType]
121124
choices_enum=FirebasePushStatusEnum,
122125
null=True,
@@ -125,17 +128,7 @@ class FirebaseResource(Model):
125128
firebase_last_pushed = models.DateTimeField(
126129
null=True,
127130
blank=True,
128-
help_text=gettext_lazy("The latest time when project was pushed to firebase"),
129-
)
130-
131-
canonical_id = models.GeneratedField( # type: ignore[reportAttributeAccessIssue]
132-
expression=Coalesce(
133-
models.F("old_id"),
134-
Cast(models.F("id"), models.CharField()),
135-
),
136-
output_field=models.CharField(),
137-
db_persist=True,
138-
unique=True,
131+
help_text=gettext_lazy("The latest time when resource was pushed to firebase"),
139132
)
140133

141134
@property
@@ -163,6 +156,19 @@ class Meta(TypedModelMeta): # type: ignore[reportIncompatibleVariableOverride]
163156
abstract = True
164157

165158

159+
class FirebasePullResource(Model):
160+
firebase_id = models.CharField(max_length=30, unique=True)
161+
162+
firebase_last_pulled = models.DateTimeField(
163+
null=True,
164+
blank=True,
165+
help_text=gettext_lazy("The latest time when resource was pulled from firebase"),
166+
)
167+
168+
class Meta(TypedModelMeta): # type: ignore[reportIncompatibleVariableOverride]
169+
abstract = True
170+
171+
166172
class AssetMimetypeEnum(models.IntegerChoices):
167173
GEOJSON = 100, "application/geo+json"
168174

apps/common/tasks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.core import management
77
from firebase_admin.db import Reference
88

9-
from apps.common.models import FirebasePushStatusEnum, FirebaseResource
9+
from apps.common.models import FirebasePushResource, FirebasePushStatusEnum
1010
from main.cache import CeleryLock
1111
from main.config import Config
1212
from main.logging import log_extra
@@ -28,7 +28,7 @@ def clear_expired_django_sessions():
2828

2929
# TODO(tnagorra): We might need to create a common class
3030
@shared_task
31-
def push_django_to_firebase[T: FirebaseResource](
31+
def push_django_to_firebase[T: FirebasePushResource](
3232
obj_id: int,
3333
model: type[T],
3434
handle_new_object_on_firebase: Callable[[T, Reference], None],
@@ -43,7 +43,7 @@ def push_django_to_firebase[T: FirebaseResource](
4343

4444
try:
4545
model_ref = Config.FIREBASE_HELPER.ref(
46-
get_firebase_path(model_obj.canonical_id, model_obj),
46+
get_firebase_path(model_obj.firebase_id, model_obj),
4747
)
4848
fb_model: typing.Any = model_ref.get()
4949

apps/contributor/factories.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# pyright: reportIncompatibleVariableOverride=false
33
# pyright: reportMissingTypeArgument=false
44
import typing
5-
import uuid
65

76
import factory
87
from factory.django import DjangoModelFactory
@@ -21,14 +20,16 @@ class ContributorUserFactory(DjangoModelFactory):
2120
class Meta:
2221
model = ContributorUser
2322

24-
user_id = factory.LazyAttribute(lambda _: f"contributor-user-id-{uuid.uuid4().int % 1000000}")
23+
firebase_id = factory.LazyFunction(lambda: str(ULID()))
24+
user_id = factory.LazyFunction(lambda: str(ULID()))
2525
username = factory.Sequence(lambda n: f"Contributor User {n}")
2626

2727

2828
class ContributorUserGroupFactory(DjangoModelFactory):
2929
class Meta:
3030
model = ContributorUserGroup
3131

32+
firebase_id = factory.LazyFunction(lambda: str(ULID()))
3233
client_id = factory.LazyFunction(lambda: str(ULID()))
3334
name = factory.Sequence(lambda n: f"Contributor User Group {n}")
3435
description = "Some description"
@@ -52,6 +53,7 @@ class ContributorTeamFactory(DjangoModelFactory):
5253
class Meta:
5354
model = ContributorTeam
5455

56+
firebase_id = factory.LazyFunction(lambda: str(ULID()))
5557
client_id = factory.LazyFunction(lambda: str(ULID()))
5658
name = factory.Sequence(lambda n: f"Contributor User Team {n}")
5759

apps/contributor/firebase.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ def handle_object_update_on_firebase(self, model_obj: ContributorTeam, fb_refere
4343
)
4444

4545
@typing.override
46-
def get_firebase_path(self, canonical_id: str, model=ContributorTeam):
47-
return Config.FirebaseKeys.contributor_team(canonical_id)
46+
def get_firebase_path(self, firebase_id: str, model=ContributorTeam):
47+
return Config.FirebaseKeys.contributor_team(firebase_id)
4848

4949
@staticmethod
5050
@typing.override
@@ -74,8 +74,8 @@ def handle_object_update_on_firebase(self, model_obj: ContributorUser, fb_refere
7474
)
7575

7676
@typing.override
77-
def get_firebase_path(self, canonical_id: str, model=ContributorUser):
78-
return Config.FirebaseKeys.contributor_user(canonical_id)
77+
def get_firebase_path(self, firebase_id: str, model=ContributorUser):
78+
return Config.FirebaseKeys.contributor_user(firebase_id)
7979

8080
@staticmethod
8181
@typing.override
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Generated by Django 5.1.6 on 2025-08-01 06:05
2+
3+
import apps.common.models
4+
import django_choices_field.fields
5+
from django.db import migrations, models
6+
from ulid import ULID
7+
8+
def set_firebase_id_for_team(apps, schema_editor):
9+
Model = apps.get_model('contributor', 'ContributorTeam')
10+
for obj in Model.cte_objects.all():
11+
obj.firebase_id = str(ULID())
12+
obj.save(update_fields=['firebase_id'])
13+
14+
def set_firebase_id_for_user(apps, schema_editor):
15+
Model = apps.get_model('contributor', 'ContributorUser')
16+
for obj in Model.cte_objects.all():
17+
obj.firebase_id = str(ULID())
18+
obj.save(update_fields=['firebase_id'])
19+
20+
def set_firebase_id_for_user_group(apps, schema_editor):
21+
Model = apps.get_model('contributor', 'ContributorUserGroup')
22+
for obj in Model.cte_objects.all():
23+
obj.firebase_id = str(ULID())
24+
obj.save(update_fields=['firebase_id'])
25+
26+
class Migration(migrations.Migration):
27+
28+
dependencies = [
29+
('contributor', '0008_alter_contributoruser_managers_and_more'),
30+
]
31+
32+
operations = [
33+
migrations.RemoveField(
34+
model_name='contributorteam',
35+
name='canonical_id',
36+
),
37+
migrations.RemoveField(
38+
model_name='contributoruser',
39+
name='canonical_id',
40+
),
41+
migrations.AddField(
42+
model_name='contributorteam',
43+
name='firebase_id',
44+
field=models.CharField(blank=True, max_length=30, null=True),
45+
),
46+
migrations.AddField(
47+
model_name='contributoruser',
48+
name='firebase_id',
49+
field=models.CharField(blank=True, max_length=30, null=True),
50+
),
51+
migrations.AddField(
52+
model_name='contributorusergroup',
53+
name='firebase_id',
54+
field=models.CharField(blank=True, max_length=30, null=True),
55+
),
56+
migrations.AddField(
57+
model_name='contributorusergroup',
58+
name='firebase_last_pushed',
59+
field=models.DateTimeField(blank=True, help_text='The latest time when resource was pushed to firebase', null=True),
60+
),
61+
migrations.AddField(
62+
model_name='contributorusergroup',
63+
name='firebase_push_status',
64+
field=django_choices_field.fields.IntegerChoicesField(blank=True, choices=[(1, 'Pending'), (2, 'Processing'), (3, 'Success'), (4, 'Failed')], choices_enum=apps.common.models.FirebasePushStatusEnum, null=True),
65+
),
66+
migrations.AlterField(
67+
model_name='contributorteam',
68+
name='firebase_last_pushed',
69+
field=models.DateTimeField(blank=True, help_text='The latest time when resource was pushed to firebase', null=True),
70+
),
71+
migrations.AlterField(
72+
model_name='contributoruser',
73+
name='firebase_last_pushed',
74+
field=models.DateTimeField(blank=True, help_text='The latest time when resource was pushed to firebase', null=True),
75+
),
76+
migrations.AlterField(
77+
model_name='contributorusergroup',
78+
name='old_id',
79+
field=models.CharField(blank=True, db_index=True, max_length=30, null=True),
80+
),
81+
migrations.RunPython(set_firebase_id_for_team, reverse_code=migrations.RunPython.noop),
82+
migrations.RunPython(set_firebase_id_for_user, reverse_code=migrations.RunPython.noop),
83+
migrations.RunPython(set_firebase_id_for_user_group, reverse_code=migrations.RunPython.noop),
84+
migrations.AlterField(
85+
model_name='contributorteam',
86+
name='firebase_id',
87+
field=models.CharField(default=ULID, max_length=30, unique=True),
88+
),
89+
migrations.AlterField(
90+
model_name='contributoruser',
91+
name='firebase_id',
92+
field=models.CharField(default=ULID, max_length=30, unique=True),
93+
),
94+
migrations.AlterField(
95+
model_name='contributorusergroup',
96+
name='firebase_id',
97+
field=models.CharField(default=ULID, max_length=30, unique=True),
98+
),
99+
]

apps/contributor/models.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@
77
from django.utils.translation import gettext
88
from django_choices_field import IntegerChoicesField
99

10-
from apps.common.models import ArchivableResource, FirebaseResource, UserResource
10+
from apps.common.models import ArchivableResource, FirebasePushResource, UserResource
1111

1212

1313
# NOTE: Users are created from Apps (Web/Mobile)
14-
class ContributorUser(FirebaseResource):
15-
# NOTE: Sync with firebase
14+
class ContributorUser(FirebasePushResource):
15+
# TODO(tnagorra): Remove this later and use firebase_id instead
1616
user_id = models.CharField(
1717
max_length=30,
1818
db_index=True,
1919
unique=True,
2020
help_text="Firebase User ID",
2121
)
22+
2223
team: "ContributorTeam | None" = models.ForeignKey( # type: ignore[reportIncompatibleVariableOverride]
2324
"ContributorTeam",
2425
on_delete=models.SET_NULL,
@@ -35,8 +36,7 @@ def __str__(self):
3536
return self.username
3637

3738

38-
class ContributorUserGroup(ArchivableResource, UserResource): # type: ignore[reportIncompatibleVariableOverride]
39-
old_id = models.CharField(max_length=30, db_index=True, null=True)
39+
class ContributorUserGroup(ArchivableResource, UserResource, FirebasePushResource): # type: ignore[reportIncompatibleVariableOverride]
4040
name = models.CharField(max_length=255)
4141
description = models.TextField()
4242

@@ -45,6 +45,7 @@ def __str__(self):
4545
return self.name
4646

4747

48+
# NOTE: Extend FirebasePullResource later if necessary
4849
class ContributorUserGroupMembership(models.Model):
4950
user_group: ContributorUserGroup = models.ForeignKey(ContributorUserGroup, on_delete=models.CASCADE) # type: ignore[reportIncompatibleVariableOverride]
5051
user: ContributorUser = models.ForeignKey(ContributorUser, on_delete=models.CASCADE) # type: ignore[reportIncompatibleVariableOverride]
@@ -81,7 +82,7 @@ def __str__(self):
8182

8283

8384
# TEAM
84-
class ContributorTeam(ArchivableResource, UserResource, FirebaseResource): # type: ignore[reportIncompatibleVariableOverride]
85+
class ContributorTeam(ArchivableResource, UserResource, FirebasePushResource): # type: ignore[reportIncompatibleVariableOverride]
8586
name = models.CharField(max_length=255)
8687
token = models.UUIDField(default=uuid.uuid4, unique=True)
8788

apps/mapping/factories.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# pyright: reportMissingTypeArgument=false
44
import typing
55

6+
import factory
67
from factory.django import DjangoModelFactory
8+
from ulid import ULID
79

810
from .models import (
911
MappingSession,
@@ -17,6 +19,8 @@ class MappingSessionFactory(DjangoModelFactory):
1719
class Meta:
1820
model = MappingSession
1921

22+
# NOTE: Adding firebase_id just to pass validation when creating using factory
23+
firebase_id = factory.LazyFunction(lambda: str(ULID()))
2024
app_version = "v1"
2125
client_type = MappingSessionClientTypeEnum.MOBILE_ANDROID
2226

@@ -25,11 +29,17 @@ class MappingSessionUserGroupFactory(DjangoModelFactory):
2529
class Meta:
2630
model = MappingSessionUserGroup
2731

32+
# NOTE: Adding firebase_id just to pass validation when creating using factory
33+
firebase_id = factory.LazyFunction(lambda: str(ULID()))
34+
2835

2936
class MappingSessionResultFactory(DjangoModelFactory):
3037
class Meta:
3138
model = MappingSessionResult
3239

40+
# NOTE: Adding firebase_id just to pass validation when creating using factory
41+
firebase_id = factory.LazyFunction(lambda: str(ULID()))
42+
3343

3444
# NOTE: Make sure to add type hints for each factory class defined below
3545
# NOTE: This needs to be at the end of this file

0 commit comments

Comments
 (0)