Our task is to design the backend efficiently following the DRY (Don't Repeat Yourself) principle, by grouping users and assigning permissions accordingly. Users inherit the permissions of their groups.
Let's consider a trip booking service, how they work with different plans and packages. There is a list of product which subscriber gets on subscribing to different packages, provided by the company. Generally, the idea they follow is the level-wise distribution of different products.
Let's see the different packages available on tour booking service :
- Starter plan : In this package, subscriber will get the facility of non-AC bus travel and 1-day stay in a non-AC room only. Let's say the trip is from Delhi to Haridwar(a religious place in Uttarakhand).
- Golden Plan : It will be somewhat costly than the Starter Plan. In this plan, subscriber will be given 2-day stay in a non-AC room, travelling in a AC bus and the trip will be from Delhi to Haridwar, Rishikesh and Mussoorie.
- Diamond Plan: This is the most costly plan, in which subscriber will be provided 3-day plan with AC bus and AC room stay, along with the trip to Haridwar, Rishikesh and Mussoorie and also trip to the Water Park.
Define Custom User Model with Permissions
Create a Django app calledusers. Insideusers/models.py:
Pythonfromdjango.contrib.auth.modelsimportAbstractUserfromdjango.utilsimporttimezonefromdjango.dbimportmodelsclassUser(AbstractUser):first_name=models.CharField(_('First Name of User'),blank=True,max_length=20)last_name=models.CharField(_('Last Name of User'),blank=True,max_length=20)classMeta:permissions=(("can_go_in_non_ac_bus","To provide non-AC Bus facility"),("can_go_in_ac_bus","To provide AC-Bus facility"),("can_stay_ac-room","To provide staying at AC room"),("can_stay_ac-room","To provide staying at Non-AC room"),("can_go_dehradoon","Trip to Dehradoon"),("can_go_mussoorie","Trip to Mussoorie"),("can_go_haridwaar","Trip to Haridwaar"),("can_go_rishikesh","Trip to Rishikesh"))
Migrate your Database
python manage.py makemigrations users
python manage.py migrate
Create Groups and Assign Permissions
Option A: Using Django Admin Panel
- Login to Django admin.
- Click on Groups.
- Create groups like level0, level1, level2.
- Assign relevant permissions to each group.
Option B: Programmatically Creating Groups and Assigning Permissions
Open Django shell:
python manage.py shell
Then run:
Pythonfromdjango.contrib.auth.modelsimportGroup,Permissionfromdjango.contrib.contenttypes.modelsimportContentTypefromusers.modelsimportUserlevel0,created=Group.objects.get_or_create(name='level0')level1,created=Group.objects.get_or_create(name='level1')level2,created=Group.objects.get_or_create(name='level2')user_ct=ContentType.objects.get_for_model(User)perm_haridwar,created=Permission.objects.get_or_create(codename='can_go_haridwar',name='Can go to Haridwar',content_type=user_ct)level0.permissions.add(perm_haridwar)
Explanation:
- Groupsrepresent user levels (e.g., Starter, Golden, Diamond).
- ContentTypelinks permissions to the model (User here).
- Permission.objects.get_or_create either fetches or creates a permission based on the codename.
- We assign permissions to groups using .permissions.add().
- When a user is assigned to a group, they inherit all permissions from that group.
Assign Users to Groups
You can add users to groups either through the Admin Panel or programmatically:
Pythonfromdjango.contrib.auth.modelsimportGroupfromusers.modelsimportUseruser=User.objects.get(username='john')group=Group.objects.get(name='level0')user.groups.add(group)
Explanation:
- We fetch a user and a group.
- Weuse user.groups.add() to add the user to that group.
- The user will then automatically have all permissions assigned to that group.
Restrict Access Based on Permissions in Views
For Function-Based Views
Use Django's built-in permission_required decorator or create a custom group-based decorator:
Pythonfromdjango.contrib.auth.decoratorsimportuser_passes_testdefgroup_required(*group_names):defin_groups(u):ifu.is_authenticated:ifbool(u.groups.filter(name__in=group_names))oru.is_superuser:returnTruereturnFalsereturnuser_passes_test(in_groups)# Usage@group_required('level0')defmy_view(request):# Your view logic here...
Explanation:
- user_passes_testruns thein_groupsfunction to check if the user belongs to any required groups.
- If the user is not authenticated or not in the allowed groups, access is denied.
- This decorator protects views so only authorized groups can access them.
For Class-Based Views
Create a mixin to check group membership:
Pythonfromdjango.contrib.auth.mixinsimportAccessMixinclassGroupRequiredMixin(AccessMixin):group_required=[]# List of groups allowed to access the viewdefdispatch(self,request,*args,**kwargs):ifnotrequest.user.is_authenticated:returnself.handle_no_permission()user_groups=request.user.groups.values_list('name',flat=True)ifnotany(groupinuser_groupsforgroupinself.group_required):returnself.handle_no_permission()returnsuper().dispatch(request,*args,**kwargs)
Usage:
Pythonfromdjango.viewsimportViewclassDemoView(GroupRequiredMixin,View):group_required=['admin','manager']defget(self,request,*args,**kwargs):# View logic...
Explanation:
- GroupRequiredMixin extendsAccessMixinwhich helps to handle permission denials.
- In dispatch method (called on every request), we check:
- If user is authenticated.
- If user belongs to any of the allowed groups.
- If checks fail, access is denied (usually by redirecting to login or 403 page).
- Otherwise, the request is processed normally.
Read Next Article:Extending and customizing django-allauth