== Row-level permissions model == The row-level permissions system uses generic relations to decrease the number of tables to only one. The generic relations allow us to create row-level permissions with any instance of any model and connect it to an "owner" object. To allow more freedom, the owner object is also a generic relation. This means that you can use your own models as an owner of a row-level permission. The actual model code is below: {{{ #!python class RowLevelPermission(models.Model): model_id = models.PositiveIntegerField("'Type' ID") model_ct = models.ForeignKey(ContentType, verbose_name="'Type' content model", related_name="model_ct") owner_id = models.PositiveIntegerField("'Owner' ID") owner_ct = models.ForeignKey(ContentType, verbose_name="'Owner' content model", related_name="owner_ct") negative = models.BooleanField() permission = models.ForeignKey(Permission) model = models.GenericForeignKey(fk_field='model_id', ct_field='model_ct') owner = models.GenericForeignKey(fk_field='owner_id', ct_field='owner_ct') objects = RowLevelPermissionManager() class Meta: verbose_name = _('row level permission') verbose_name_plural = _('row level permissions') unique_together = (('model_ct', 'model_id', 'owner_id', 'owner_ct', 'permission'),) }}} This does not modify the current permissions table at all, and can be layered on top of it if the developer wishes to but it can be ignored easily. == How row permissions are enabled == Row permissions are enabled using the meta class, please see RowLevelPermissions for more information on how to enable them. How this is done is in django.db.models.base.ModelBase under {{{__new__}}} is the following snippet: {{{ #!python if getattr(new_class._meta, 'row_level_permissions', None): from django.contrib.auth.models import RowLevelPermission gen_rel = django.db.models.GenericRelation(RowLevelPermission, object_id_field="model_id", content_model_field="model_ct") new_class.add_to_class("row_level_permissions", gen_rel) }}} {{{django.db.models.options.Options}}} has been modified to set {{{row_level_permissions}}} as disabled by default. == Owner objects == Currently, you can set up an owner by including the following relation: {{{ #!python row_level_permissions_owned = models.GenericRelation(RowLevelPermission, object_id_field="owner_id", content_model_field="owner_ct", related_name="owner") }}} I might be changing this around in the near future, but I only expect it to be used a few times, and I don't see a large need to make this a similiar process to the enabling of row-level permissions on objects. Please give feedback if you think otherwise. == Checking of row-level permissions == Checking of RLP are done in the following order: User RLP->Group RLP->User Model Level->Group Model Level. Stopping at the first positive or negative. The {{{has_perm()}}} method has been modified to now check for row-level permissions and has an optional parameter for a model instance, which is required to check row-level permissions. {{{ #!python def has_perm(self, perm, object=None): "Returns True if the user has the specified permission." if not self.is_active: return False if self.is_superuser: return True if object and object._meta.row_level_permissions: row_level_permission = self.check_row_level_permission(perm, object) if row_level_permission is not None: return row_level_permission return perm in self.get_all_permissions() }}} The check_row_level_permission checks the user RLPs first and then checks the group RLPs. The user RLPs are determined by using a filter method. The group RLP uses an SQL query that works out to be: {{{ #!sql SELECT rlp."negative" FROM "auth_user_groups" ug, "auth_rowlevelpermission" rlp WHERE rlp."owner_id"=ug."group_id" AND ug."user_id"=%s AND rlp."owner_ct_id"=%s AND rlp."model_id"=%s AND rlp."model_ct_id"=%s AND rlp."permission_id"=%s; }}} == Integration into administration application == Being worked on. Will post when it is complete or near enough to have a better idea. == Test cases of row-level permissions == === API === || '''Test Case''' || '''Status''' || ||Creating RLP using manager's create method||Pass|| ||Creating RLP using manager's default permission create method||Pass|| ||Creating RLP using init method||Pass|| ||Loading RLPs from object||Pass|| ||Loading RLPs from owner(user)||Pass|| ||Loading RLPs from owner(group)||Pass|| ||Deleting RLP||Pass|| ||Checking of Permissions(will expand on)||Pass|| ||Duplicate RLPs(causes error)||Pass|| ||Enabling RLPs||-|| === Admin === || '''Test Case''' || '''Status''' || ||Creating RLP||-|| ||Creating RLP w/ wrong permissions||-|| ||Deleting RLP||-|| ||Editing RLP||-|| ||Duplicate RLPs by creating duplicate||-|| ||Duplicate RLPs by editing||-||