Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 6e71887

Browse files
author
Wes Kendall
committed
added an entity model manager that sends out signals when bulk operations occur. Entities are resynced when bulk operations occur and it is tested
1 parent 45fef41 commit 6e71887

File tree

4 files changed

+109
-7
lines changed

4 files changed

+109
-7
lines changed

entity/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .sync import sync_entities
22

3-
from .models import EntityModelMixin, Entity
3+
from .models import EntityModelMixin, Entity, BaseEntityModel

entity/models.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
from django.contrib.contenttypes import generic
22
from django.contrib.contenttypes.models import ContentType
33
from django.db import models
4+
from django.db.models.query import QuerySet
45
from django.db.models.signals import post_save, post_delete
5-
from django.dispatch import receiver
6+
from django.dispatch import receiver, Signal
67
from jsonfield import JSONField
78

89

10+
# Create a signal that is emitted after bulk operations occur
11+
post_bulk_operation = Signal()
12+
13+
914
class Entity(models.Model):
1015
"""
1116
Describes an entity and its relevant metadata. Also defines
@@ -177,6 +182,41 @@ def is_super_entity_relationship_active(self, model_obj):
177182
return True
178183

179184

185+
class EntityQuerySet(QuerySet):
186+
"""
187+
Overrides bulk operations on a queryset to emit a signal when they occur.
188+
"""
189+
def update(self, **kwargs):
190+
ret_val = super(EntityQuerySet, self).update(**kwargs)
191+
post_bulk_operation.send(sender=self)
192+
return ret_val
193+
194+
195+
class EntityModelManager(models.Manager):
196+
"""
197+
Defines a model manager for Entity models. This model manager provides additional
198+
functionality on top of the regular managers, such as emitting signals when bulk
199+
updates and creates are issued.
200+
"""
201+
def get_queryset(self):
202+
return EntityQuerySet(self.model)
203+
204+
def bulk_create(self, objs, batch_size=None):
205+
ret_val = super(EntityModelManager, self).bulk_create(objs, batch_size=batch_size)
206+
post_bulk_operation.send(sender=self)
207+
return ret_val
208+
209+
210+
class BaseEntityModel(models.Model, EntityModelMixin):
211+
"""
212+
Defines base properties for an Entity model defined by a third-party application.
213+
"""
214+
class Meta:
215+
abstract = True
216+
217+
objects = EntityModelManager()
218+
219+
180220
def sync_entity_signal_handler(sender, model_obj, is_deleted):
181221
"""
182222
Filters post save/delete signals for entities by checking if they
@@ -189,6 +229,16 @@ def sync_entity_signal_handler(sender, model_obj, is_deleted):
189229
sync_entity(model_obj, is_deleted)
190230

191231

232+
def sync_entities_signal_handler(sender):
233+
"""
234+
When a bulk operation occurs on a model manager, sync all the entities
235+
if the model of the manager is an entity class.
236+
"""
237+
if issubclass(sender.model, EntityModelMixin):
238+
from .sync import sync_entities
239+
sync_entities()
240+
241+
192242
@receiver(post_delete)
193243
def delete_entity_signal_handler(sender, *args, **kwargs):
194244
"""
@@ -205,3 +255,11 @@ def save_entity_signal_handler(sender, *args, **kwargs):
205255
the entity mirror table.
206256
"""
207257
sync_entity_signal_handler(sender, kwargs['instance'], False)
258+
259+
260+
@receiver(post_bulk_operation)
261+
def bulk_operation_signal_handler(sender, *args, **kwargs):
262+
"""
263+
When a bulk operation has happened on a model, sync all the entities again.
264+
"""
265+
sync_entities_signal_handler(sender)

test_project/models.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
from django.db import models
22

3-
from entity import EntityModelMixin, Entity
3+
from entity import BaseEntityModel, Entity
4+
from entity.models import EntityModelManager
45

56

6-
class TeamGroup(models.Model, EntityModelMixin):
7+
class TeamGroup(BaseEntityModel):
78
"""
89
A grouping of teams.
910
"""
1011
name = models.CharField(max_length=256)
1112

1213

13-
class Team(models.Model, EntityModelMixin):
14+
class Team(BaseEntityModel):
1415
"""
1516
A team entity model. Encapsulates accounts.
1617
"""
@@ -27,7 +28,7 @@ def get_super_entities(self):
2728
return [self.team_group] if self.team_group is not None else []
2829

2930

30-
class Account(models.Model, EntityModelMixin):
31+
class Account(BaseEntityModel):
3132
"""
3233
An account entity model
3334
"""
@@ -82,8 +83,10 @@ class DummyModel(models.Model):
8283
"""
8384
dummy_data = models.CharField(max_length=64)
8485

86+
objects = EntityModelManager()
8587

86-
class BaseEntityClass(models.Model, EntityModelMixin):
88+
89+
class BaseEntityClass(BaseEntityModel):
8790
"""
8891
A base class that inherits EntityModelMixin. Helps ensure that mutliple-inherited
8992
entities are still synced properly.

test_project/tests/sync_tests.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,47 @@ class TestEntitySignalSync(EntityTestCase):
107107
Tests that entities (from the test models) are properly synced upon post_save and
108108
post_delete calls.
109109
"""
110+
def test_post_bulk_create(self):
111+
"""
112+
Tests that entities can have bulk creates applied to them and still be synced.
113+
"""
114+
# Bulk create five accounts
115+
accounts = [Account() for i in range(5)]
116+
Account.objects.bulk_create(accounts)
117+
# Verify that there are 5 entities
118+
self.assertEquals(Entity.objects.all().count(), 5)
119+
120+
def test_post_bulk_update(self):
121+
"""
122+
Calls a bulk update on a list of entities. Verifies that the models are appropriately
123+
synced.
124+
"""
125+
# Create five accounts
126+
for i in range(5):
127+
Account.objects.create(email='[email protected]')
128+
# Verify that there are five entities all with the '[email protected]' email
129+
for entity in Entity.objects.all():
130+
self.assertEquals(entity.entity_meta['email'], '[email protected]')
131+
self.assertEquals(Entity.objects.all().count(), 5)
132+
133+
# Bulk update the account emails to a different one
134+
Account.objects.all().update(email='[email protected]')
135+
136+
# Verify that the email was updated properly in all entities
137+
for entity in Entity.objects.all():
138+
self.assertEquals(entity.entity_meta['email'], '[email protected]')
139+
self.assertEquals(Entity.objects.all().count(), 5)
140+
141+
def test_post_bulk_update_dummy(self):
142+
"""
143+
Tests that even if the dummy model is using the special model manager for bulk
144+
updates, it still does not get synced since it doesn't inherit EntityModelMixin.
145+
"""
146+
# Create five dummy models with a bulk update
147+
DummyModel.objects.bulk_create([DummyModel() for i in range(5)])
148+
# There should be no synced entities
149+
self.assertEquals(Entity.objects.all().count(), 0)
150+
110151
def test_post_save_dummy_data(self):
111152
"""
112153
Tests that dummy data that does not inherit from EntityModelMixin is not synced

0 commit comments

Comments
 (0)