Use cases¶
Projects management with Proxy Models¶
John Boss is the project leader. Marcus Worker and Julius Backend are the django backend guys; Teresa Html is the front-end developer and Jack College is the student that has to learn to write good backends. The Celery pipeline is owned by Marcus, and Jack must see it without intercations. Teresa can’t see the pipeline, but John has full permissions as project leader. As part of the backend group, Julius has the right of viewing and editing, but not to stop (delete) the pipeline.
1) Define models in models.py:
from groups_manager.models import Group, GroupType
class Project(Group):
# objects = ProjectManager()
class Meta:
proxy = True
def save(self, *args, **kwargs):
if not self.group_type:
self.group_type = GroupType.objects.get_or_create(label='Project')[0]
super(Project, self).save(*args, **kwargs)
class WorkGroup(Group):
# objects = WorkGroupManager()
class Meta:
proxy = True
def save(self, *args, **kwargs):
if not self.group_type:
self.group_type = GroupType.objects.get_or_create(label='Workgroup')[0]
super(WorkGroup, self).save(*args, **kwargs)
class Pipeline(models.Model):
name = models.CharField(max_length=100)
class Meta:
permissions = (('view_pipeline', 'View Pipeline'), )
Warning
Remember to define a view_modelname
permission.
2) Connect creation and deletion signals to the proxy models (This step is required if you want to sync with django auth models):
from django.db.models.signals import post_save, post_delete
from groups_manager.models import group_save, group_delete
post_save.connect(group_save, sender=Project)
post_delete.connect(group_delete, sender=Project)
post_save.connect(group_save, sender=WorkGroup)
post_delete.connect(group_delete, sender=WorkGroup)
3) Creates groups:
project_main = testproject_models.Project(name='Workgroups Main Project')
project_main.save()
django_backend = testproject_models.WorkGroup(name='WorkGroup Backend', parent=project_main)
django_backend.save()
django_backend_watchers = testproject_models.WorkGroup(name='Backend Watchers',
parent=django_backend)
django_backend_watchers.save()
django_frontend = testproject_models.WorkGroup(name='WorkGroup FrontEnd', parent=project_main)
django_frontend.save()
4) Creates members and assign them to groups:
john = models.Member.objects.create(first_name='John', last_name='Boss')
project_main.add_member(john)
marcus = models.Member.objects.create(first_name='Marcus', last_name='Worker')
julius = models.Member.objects.create(first_name='Julius', last_name='Backend')
django_backend.add_member(marcus)
django_backend.add_member(julius)
teresa = models.Member.objects.create(first_name='Teresa', last_name='Html')
django_frontend.add_member(teresa)
jack = models.Member.objects.create(first_name='Jack', last_name='College')
django_backend_watchers.add_member(jack)
5) Create the pipeline and assign custom permissions:
custom_permissions = {
'owner': ['view', 'change', 'delete'],
'group': ['view', 'change'],
'groups_upstream': ['view', 'change', 'delete'],
'groups_downstream': ['view'],
'groups_siblings': [],
}
pipeline = testproject_models.Pipeline.objects.create(name='Test Runner')
marcus.assing_object(django_backend, pipeline, custom_permissions=custom_permissions)
Note
The full tested example is available in repository source code, testproject
’s tests.py
under test_proxy_models
method.
Projects management with Model Mixins¶
Mixins allows to create shared apps based on django-groups-manager. The mixins approach has pros and cons.
- Pros:
- models are completely customizable (add all fields you need);
- all fields are in the same table (with subclassed models, only extra fields are stored in the subclass table);
- better for shared applications (the “original” django-groups-manager tables don’t share entries from different models).
- Cons:
- all external foreign keys must be declared in the concrete model;
- all signals must be declared with concrete models.
Model mixins example¶
The following models allow to manage a set of Organizations with related members (from organization
app).
In this example, a last_edit_date
is added to models, and member display name has the user email (if defined).
1) Define models in models.py:
from groups_manager.models import GroupMixin, MemberMixin, GroupMemberMixin, GroupMemberRoleMixin, \
GroupEntity, GroupType, \
group_save, group_delete, member_save, member_delete, group_member_save, group_member_delete
class OrganizationMemberRole(GroupMemberRoleMixin):
pass
class OrganizationGroupMember(GroupMemberMixin):
group = models.ForeignKey('OrganizationGroup', related_name='group_membership')
member = models.ForeignKey('OrganizationMember', related_name='group_membership')
roles = models.ManyToManyField(OrganizationMemberRole, blank=True)
class OrganizationGroup(GroupMixin):
last_edit_date = models.DateTimeField(auto_now=True, null=True)
short_name = models.CharField(max_length=50, default='', blank=True)
country = CountryField(null=True, blank=True)
city = models.CharField(max_length=200, blank=True, default='')
group_type = models.ForeignKey(GroupType, null=True, blank=True, on_delete=models.SET_NULL,
related_name='%(app_label)s_%(class)s_set')
group_entities = models.ManyToManyField(GroupEntity, null=True, blank=True,
related_name='%(app_label)s_%(class)s_set')
django_group = models.ForeignKey(DjangoGroup, null=True, blank=True, on_delete=models.SET_NULL)
group_members = models.ManyToManyField('OrganizationMember', through=OrganizationGroupMember,
through_fields=('group', 'member'),
related_name='%(app_label)s_%(class)s_set')
class Meta:
permissions = (('manage_organization', 'Manage Organization'),
('view_organization', 'View Organization'))
class GroupsManagerMeta:
member_model = 'organizations.OrganizationMember'
group_member_model = 'organizations.OrganizationGroupMember'
def save(self, *args, **kwargs):
if not self.short_name:
self.short_name = self.name
super(OrganizationGroup, self).save(*args, **kwargs)
@property
def members_names(self):
return [member.full_name for member in self.group_members.all()]
class OrganizationMember(MemberMixin):
last_edit_date = models.DateTimeField(auto_now=True, null=True)
django_user = models.ForeignKey(DjangoUser, null=True, blank=True, on_delete=models.SET_NULL,
related_name='%(app_label)s_%(class)s_set')
class GroupsManagerMeta:
group_model = 'organizations.OrganizationGroup'
group_member_model = 'organizations.OrganizationGroupMember'
def __unicode__(self):
if self.email:
return '%s (%s)' % (self.full_name, self.email)
return self.full_name
def __str__(self):
if self.email:
return '%s (%s)' % (self.full_name, self.email)
return self.full_name
2) Connect creation and deletion signals to the models
(This step is required if you want to sync with django auth models):
post_save.connect(group_save, sender=OrganizationGroup)
post_delete.connect(group_delete, sender=OrganizationGroup)
post_save.connect(member_save, sender=OrganizationMember)
post_delete.connect(member_delete, sender=OrganizationMember)
post_save.connect(group_member_save, sender=OrganizationGroupMember)
post_delete.connect(group_member_delete, sender=OrganizationGroupMember)
3) Customize the flag for AUTH_MODEL_SYNC
If you plan to create a reusable app and to let users decide if sync or not with Django auth models
independently from groups_manager
settings, you should define a separated function that
returns the boolean value from your own settings:
def organization_with_mixin_get_auth_models_sync_func(instance):
return organization.SETTINGS['DJANGO_AUTH_MODEL_SYNC'] # example
def organization_group_member_save(*args, **kwargs):
group_member_save(*args, get_auth_models_sync_func=organization_get_auth_models_sync_func, **kwargs)
def organization_group_member_delete(*args, **kwargs):
group_member_delete(*args, get_auth_models_sync_func=organization_get_auth_models_sync_func, **kwargs)
post_save.connect(organization_group_member_save, sender=OrganizationGroupMember)
post_delete.connect(organization_group_member_delete, sender=OrganizationGroupMember)
Note
The full tested example is available in repository source code, testproject
’s tests.py
under test_model_mixins
method.
Resource assignment via role permissions¶
John Money is the commercial referent of the company; Patrick Html is the web developer. The company has only one group, but different roles. John can view and sell the site, and Patrick can view, change and delete the site.
1) Define models in models.py:
class Site(Group):
name = models.CharField(max_length=100)
class Meta:
permissions = (('view_site', 'View site'),
('sell_site', 'Sell site'), )
2) Create models and relations:
from groups_manager.models import Group, GroupMemberRole, Member
from models import Site
# Group
company = Group.objects.create(name='Company')
# Group Member roles
commercial_referent = GroupMemberRole.objects.create(label='Commercial referent')
web_developer = GroupMemberRole.objects.create(label='Web developer')
# Members
john = Member.objects.create(first_name='John', last_name='Money')
patrick = Member.objects.create(first_name='Patrick', last_name='Html')
# Add to company
company.add_member(john, [commercial_referent])
company.add_member(patrick, [web_developer])
# Create the site
site = Site.objects.create(name='Django groups manager website')
3) Define custom permissions and assign the site object:
custom_permissions = {
'owner': {'commercial-referent': ['sell_site'],
'web-developer': ['change', 'delete'],
'default': ['view']},
'group': ['view'],
'groups_upstream': ['view', 'change', 'delete'],
'groups_downstream': ['view'],
'groups_siblings': ['view'],
}
john.assign_object(company, site, custom_permissions=custom_permissions)
patrick.assign_object(company, site, custom_permissions=custom_permissions)
4) Check permissions:
john.has_perms(['view_site', 'sell_site'], site) # True
john.has_perm('change_site', site) # False
patrick.has_perms(['view_site', 'change_site', 'delete_site'], site) # True
patrick.has_perm('sell_site', site) # False
Note
The full tested example is available in repository source code, testproject
’s tests.py
under test_roles
method.
Resource assignment via group type permissions¶
Permissions can also be applied to related groups filtered by group types.
Instead of simply using a list to specify permissions one can use a dict
to
specify which group types get which permissions.
Example¶
John Money is the commercial referent of the company; Patrick Html is the web developer. John and Patrick can view the site, but only Patrick can change and delete it.
1) Define models in models.py:
class Site(Group):
name = models.CharField(max_length=100)
class Meta:
permissions = (('view_site', 'View site'),
('sell_site', 'Sell site'), )
2) Create models and relations:
from groups_manager.models import Group, GroupType, Member
from models import Site
# Parent Group
company = Group.objects.create(name='Company')
# Group Types
developer = GroupType.objects.create(label='developer')
referent = GroupType.objects.create(label='referent')
# Child groups
developers = Group.objects.create(name='Developers', group_type=developer, parent=company)
referents = Group.objects.create(name='Referents', group_type=referent, parent=company)
# Members
john = Member.objects.create(first_name='John', last_name='Money')
patrick = Member.objects.create(first_name='Patrick', last_name='Html')
# Add to groups
referents.add_member(john)
developers.add_member(patrick)
# Create the site
site = Site.objects.create(name='Django groups manager website')
3) Define custom permissions and assign the site object:
custom_permissions = {
'owner': [],
'group': ['view'],
'groups_downstream': {'developer': ['change', 'delete'], 'default': ['view']},
}
john.assign_object(company, site, custom_permissions=custom_permissions)
4) Check permissions:
john.has_perm('view_site', site) # True
john.has_perm('change_site', site) # False
john.has_perm('delete_site', site) # False
patrick.has_perms(['view_site', 'change_site', 'delete_site'], site) # True
Note
The full tested example is available in repository source code, testproject
’s tests.py
under test_group_types_permissions
method.
Custom member model¶
By default, Group
’s attribute members
returns a list of Member
instances.
If you want to create also a custom Member model in addition to custom Group, maybe you
want to obtain a list of custom Member model instances with members
attribute.
This can be obtained with GroupsManagerMeta
’s member_model
attribute. This class
must be defined in Group subclass/proxy.
The value of the attribute is in <application>.<model_name>
form.
1) Define models in models.py:
from groups_manager.models import Group, Member
class Organization(Group):
class GroupsModelMeta:
model_name = 'myApp.OrganizationMember'
class OrganizationMember(Member):
pass
2) Call Organization members attribute:
org_a = Organization.objects.create(name='Org, Inc.')
boss = OrganizationMember.objects.create(first_name='John', last_name='Boss')
org_a.add_member(boss)
org_members = org_a.members # [<OrganizationMember: John Boss>]
Note
A tested example is available in repository source code, testproject
’s tests.py
under
test_proxy_model_custom_member
and test_subclassed_model_custom_member
methods.
Custom signals¶
If you redefine models via proxy or subclass and you need to manage sync permissions with
a different setting (like MY_APP['AUTH_MODELS_SYNC']
you need to use different signals functions
when saving objects and relations.
Signals functions accept kwargs:
get_auth_models_sync_func
: a function that returns a boolean (default honoursGROUPS_MANAGER['AUTH_MODELS_SYNC']
setting), that also take aninstance
parameter to allow additional checks;prefix
andsuffix
on group_save``: overrideGROUPS_MANAGER['GROUP_NAME_PREFIX']
andGROUPS_MANAGER['GROUP_NAME_SUFFIX']
;prefix
andsuffix
on member_save``: overrideGROUPS_MANAGER['USER_USERNAME_PREFIX']
andGROUPS_MANAGER['USER_USERNAME_SUFFIX']
;
So, for example, your wrapping functions will be like this:
class ProjectGroup(Group):
class Meta:
permissions = (('view_projectgroup', 'View Project Group'), )
class GroupsManagerMeta:
member_model = 'testproject.ProjectGroupMember'
group_member_model = 'testproject.ProjectGroupMember'
class ProjectMember(Member):
class Meta:
permissions = (('view_projectmember', 'View Project Member'), )
class ProjectGroupMember(GroupMember):
pass
def project_get_auth_models_sync_func(instance):
return MY_APP['AUTH_MODELS_SYNC']
def project_group_save(*args, **kwargs):
group_save(*args, get_auth_models_sync_func=project_get_auth_models_sync_func,
prefix='PGS_', suffix='_Project', **kwargs)
def project_group_delete(*args, **kwargs):
group_delete(*args, get_auth_models_sync_func=project_get_auth_models_sync_func, **kwargs)
def project_member_save(*args, **kwargs):
member_save(*args, get_auth_models_sync_func=project_get_auth_models_sync_func,
prefix='PMS_', suffix='_Member', **kwargs)
def project_member_delete(*args, **kwargs):
member_delete(*args, get_auth_models_sync_func=project_get_auth_models_sync_func, **kwargs)
def project_group_member_save(*args, **kwargs):
group_member_save(*args, get_auth_models_sync_func=project_get_auth_models_sync_func, **kwargs)
def project_group_member_delete(*args, **kwargs):
group_member_delete(*args, get_auth_models_sync_func=project_get_auth_models_sync_func, **kwargs)
post_save.connect(project_group_save, sender=ProjectGroup)
post_delete.connect(project_group_delete, sender=ProjectGroup)
post_save.connect(project_member_save, sender=ProjectMember)
post_delete.connect(project_member_delete, sender=ProjectMember)
post_save.connect(project_group_member_save, sender=ProjectGroupMember)
post_delete.connect(project_group_member_delete, sender=ProjectGroupMember)
Note
A tested example is available in repository source code, testproject
’s tests.py
under
test_signals_kwargs
method.
Expiring memberships¶
Members can be added to groups with an optional date that specifies when the membership expires.
expiration_date
property is only used to indicate when the
membership expires and has no effect on the permissions.
How this property is used is up to the user of the library.
This can be useful for example to filter out expired memberships or periodically delete them.
Set expiration date to one week from today
import datetime
from django.utils import timezone
john = models.Member.objects.create(first_name='John', last_name='Boss')
expiration = timezone.now() + datetime.timedelta(days=7)
project_main.add_member(john, expiration_date=expiration)