|
1 |
| -django-entity |
| 1 | +[](https://travis-ci.org/ambitioninc/django-entity) |
| 2 | +Django Entity |
2 | 3 | =============
|
| 4 | +Django Entity is an app that provides Django projects with the ability to mirror their entities and entity relationships in a separate, well-contained and easily-accessible table. |
3 | 5 |
|
4 |
| -Entity relationship management for Django |
| 6 | +Django Entity provides large-scale projects with the ability to better segregate their apps while minimizing the application-specific code in those apps that has to deal with entities and their relationships in the main project. |
| 7 | + |
| 8 | +What is an entity? An entity is any model in your Django project. For example, an entity could be a Django User model or a Group of Users. Similarly an entity relationship defines a super and sub relationship among different types of entities. For example, a Group would be a super entity of a User. The Django Entity app allows you to easily express this relationship in your model definition and sync it to a centralized place that is accessible by any other app in your project. |
| 9 | + |
| 10 | +## A Use Case |
| 11 | +Imagine that you have a Django project that defines many types of groupings of your users. For example, let's say in your enterprise project, you allow users to define their manager, their company position, and their regional branch location. Similarly, let's say that you have an app that can email groups of users based on their manager (or anyone who is under the managers of that manager), their position, or their region. This email app would likely have to know application-specific modeling of these relationships in order to be built. Similarly, doing things like querying for all users under a manager hierachy can be an expensive lookup depending on how it is modeled. |
| 12 | + |
| 13 | +Using Django Entity, the email app could be written to take an Entity model rather than having to understand the complex relationships of each group. The Entity model passed to the email app could be a CompanyPosition model, and the get_sub_entities(entity_type=ContentType.objects.get_for_model(User)) would return all of the User models under that CompanyPosition model. This allows the email app to be completely segregated from how the main project defines its relationships. Similarly, the query to obtain all User models under a CompanyPosition could be much more efficient than querying directly from the project (depending on how the project has its models structured). |
| 14 | + |
| 15 | +## How Does It Work? |
| 16 | +In order to sync entities and their relationships from your project to the Django Entity table, you must first create a model that inherits BaseEntityModel. |
| 17 | + |
| 18 | + from entity import BaseEntityModel |
| 19 | + |
| 20 | + class Account(BaseEntityModel): |
| 21 | + email = models.CharField(max_length=64) |
| 22 | + |
| 23 | +When you update your models to inherit this mixin, they will automatically be synced to the Entity table when they are updated or deleted. The first time that you migrate a model in your application, you must remember to sync all of the entities so that the current ones get synced to the entity table. This can be accomplished with |
| 24 | + |
| 25 | + python manage.py sync_entities |
| 26 | + |
| 27 | +Similarly, you can directly call the function to sync entities in a celery processing job or in your own application code. |
| 28 | + |
| 29 | + from entity import sync_entities |
| 30 | + |
| 31 | + sync_entities() |
| 32 | + |
| 33 | +After the entities have been synced, they can then be accessed in the primary Entity table. |
| 34 | + |
| 35 | + # Create an Account model defined from above |
| 36 | + account = Account.objects.create(email='[email protected]') |
| 37 | + |
| 38 | + # Get its entity object from the entity table |
| 39 | + entity = Entity.objects.get(entity_type=ContentType.objects.get_for_model(Account), entity_id=account.id) |
| 40 | + |
| 41 | +## How Do I Specify Relationships And Additonal Metadata About My Entities? |
| 42 | +Django Entity provides the ability to model relationships of your entities to other entities. It also provides further capabilities for you to store additional metadata about your entities so that it can be quickly retrieved without having to access the main project tables. Here are additional functions defined in the BaseEntityModel that allow you to model your relationships and metadata. The next section describes how to query based on these relationships and retrieve the metadata in the Entity table. |
| 43 | + |
| 44 | +- **get_entity_meta(self)**: Return a dictionary of any JSON-serializable data. This data will be serialized into JSON and stored as a string for later access by any application. This function provides your project with the ability to save application-specific data in the metadata that can later be retrieved or viewed without having to access the main project tables. Defaults to returning None. |
| 45 | + |
| 46 | +- **is_entity_active(self)**: Returns True if this entity is active or False otherwise. This function provides the ability to specify if a given entity in your project is active. Sometimes it is valuable to retain information about entities in your system but be able to access them in different ways based on if they are active or inactive. For example, Django's User model provides the ability to specify if the User is still active without deleting the User model. This attribute can be mirrored here in this function. Defaults to returning True. |
| 47 | + |
| 48 | +- **get_super_entities(self)**: Returns a list of all of the models in your project that have a "super" relationship with this entity. In other words, what models in your project enclose this entity? For example, a Django User could have a Group super entity that encapsulates the current User models and any other User models in the same Group. Defaults to returning an empty list. |
| 49 | + |
| 50 | +- **is_super_entity_relationship_active(self, super_entity_model_obj)**: Returns True if the entity has an active relationship with the given super entity model object. Similar to how entities can be active or inactive, their relationships to super entities can be active or inactive. This allows entities to still belong to a larger super entity, but be excluded from queries to the relationships of entities. For example, a User of a Group may be temporarily banned from the Group, but the User's Group relationship may still be important for other things. This function defaults to returning True. |
| 51 | + |
| 52 | +## Now That My Entities And Relationships Are Specified, How Do I Use It? |
| 53 | +Let's start off with an example of two entities, an Account and a Group. |
| 54 | + |
| 55 | + from django.db import models |
| 56 | + from entity import BaseEntityModel |
| 57 | + |
| 58 | + class Group(BaseEntityModel): |
| 59 | + name = models.CharField(max_length=64) |
| 60 | + |
| 61 | + def get_entity_meta(self): |
| 62 | + """ |
| 63 | + Save the name as metadata about the entity. |
| 64 | + """ |
| 65 | + return {'name': self.name} |
| 66 | + |
| 67 | + class Account(BaseEntityModel): |
| 68 | + email = models.CharField(max_length=64) |
| 69 | + group = models.ForeignKey(Group) |
| 70 | + is_active = models.BooleanField(default=True) |
| 71 | + |
| 72 | + def get_entity_meta(self): |
| 73 | + """ |
| 74 | + Save the email and group of the Account as additional metadata. |
| 75 | + """ |
| 76 | + return { |
| 77 | + 'email': self.email, |
| 78 | + 'group': self.group.name, |
| 79 | + } |
| 80 | + |
| 81 | + def get_super_entities(self): |
| 82 | + """ |
| 83 | + The group is a super entity of the Account. |
| 84 | + """ |
| 85 | + return [self.group] |
| 86 | + |
| 87 | + def is_entity_active(self): |
| 88 | + return self.is_active |
| 89 | + |
| 90 | +The Account and Group entities have defined how they want their metadata mirrored along with how their relationship is set up. In this case, Accounts belong to Groups. We can create an example Account and Group and then access their mirrored metadata in the following way. |
| 91 | + |
| 92 | + group = Group.objects.create(name='Hello Group') |
| 93 | + account = Account.objects.create(email='[email protected]', group=group) |
| 94 | + |
| 95 | + # Entity syncing happens automatically behind the scenes. Grab the entity of the account and group. |
| 96 | + # Check out their metadata. |
| 97 | + account_entity = Entity.objects.get(entity_type=ContentType.objects.get_for_model(Account), entity_id=account.id) |
| 98 | + print account_entity.entity_meta |
| 99 | + {'email': '[email protected]', 'group': 'Hello Group'} |
| 100 | + |
| 101 | + group_entity = Entity.objects.get(entity_type=ContentType.objects.get_for_model(Group), entity_id=group.id) |
| 102 | + print group_entity.entity_meta |
| 103 | + {'name': 'Hello Group'} |
| 104 | + |
| 105 | +The entity metadata can be very powerful for REST APIs and other components that wish to return data about entities within the application without actually having to query the project's tables. |
| 106 | + |
| 107 | +Once the entities are obtained, it is also easy to query for relationships among the entities. |
| 108 | + |
| 109 | + # Print off the sub entity metadata of the group |
| 110 | + for entity in group_entity.get_sub_entities(): |
| 111 | + print entity.entity_meta |
| 112 | + {'email': '[email protected]', 'group': 'Hello Group'} |
| 113 | + |
| 114 | + # Similarly, print off the super entities of the account |
| 115 | + for entity in account_entity.get_super_entities(): |
| 116 | + print entity.entity_meta |
| 117 | + {'name': 'Hello Group'} |
| 118 | + |
| 119 | + # Make the account inactive and query for active sub entities from the Group. |
| 120 | + # It should not return anything since the account is inactive |
| 121 | + account.is_active = False |
| 122 | + account.save() |
| 123 | + |
| 124 | + print len(group_entity.get_sub_entities(is_active=True)) |
| 125 | + 0 |
| 126 | + # The account still remains a sub entity, just an inactive one |
| 127 | + print len(group_entity.get_sub_entities()) |
| 128 | + 1 |
| 129 | + |
| 130 | +One can also filter on the sub/super entities by their type. This is useful if the entity has many relationships of different types. |
| 131 | + |
| 132 | + for entity in group_entity.get_sub_entities(entity_type=ContentType.objects.get_for_model(Account)): |
| 133 | + print entity.entity_meta |
| 134 | + {'email': '[email protected]', 'group': 'Hello Group'} |
| 135 | + |
| 136 | + # Groups are not a sub entity of themselves, so this function returns nothing |
| 137 | + print len(group_entity.get_sub_entities(entity_type=ContentType.objects.get_for_model(Group))) |
| 138 | + 0 |
| 139 | + |
| 140 | +## Caveats With Django Entity |
| 141 | +Django Entity has some current caveats worth noting. Currently, Djagno Entity links with post_save and post_delete signals so that any BaseEntityModel will be mirrored when updated. However, if the BaseEntityModel uses other models in its metadata or in defining its relationships to other models, these will not be updated when those other models are updated. For example, if there is a GroupMembership model that defines a if a User is active within a Group, changing the GroupMembership model will not remirror the Entity tables since GroupMembership does not inherit from BaseEntityModel. Future methods will be put in place to eliminate this caveat. |
| 142 | + |
| 143 | +Note that if a user wishes to use a custom model manager for a BaseEntityModel, the user will have to make their model manager inherit EntityModelManager. If the user does not do this, entity syncing upon bulk methods will not work properly. |
0 commit comments