• 12 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/2/22

Assign Permissions Using Groups

Restrict Aspects of Site Functionality With Permissions

When developing a site, you will often find that different users need to interact with parts of the site differently. You may need to restrict some functionality to specific users. Use permissions to do this. 

For our website, we're going to restrict access for creating, editing, and deleting   Photo  and  Blog  models to users with the type  CREATOR  .

Every model created in Django automatically has four permissions created alongside it. For the  Photo  model, these are: 

  • blog.add_photo  - or more generally  <app>.add_<model>

  • blog.change_photo  - or  <app>.change_<model>

  • blog.delete_photo  - or  <app>.delete_<model>

  • blog.view_photo  - or  <app>.view_<model>

Django uses these permissions internally to manage access to the admin site, but you can use them in your code too.

Step 1: Restrict Access in the View

First, let’s look at how to restrict view access based on permissions.

Much like how you use the  @login_required  decorator to restrict access to users based on whether they have logged in, you can use the  @permission_required  decorator to limit access based on a permission. In this case, the only difference is that you specify the permission required as an argument to the decorator.

Let’s restrict access to the  photo_upload  view to users who have the  blog.add_photo  permission only.

from django.contrib.auth.decorators import login_required, permission_required



@login_required
@permission_required('blog.add_photo', raise_exception=True)
def photo_upload(request):
   ...

Now try to access http://localhost:8000/photo/upload while logged in. You will find that you will be returned a 403 forbidden response. 

Step 2: Restrict Access in the Template

You can also check whether a user has permissions in a template. You can use this to show or hide content based on their access rights.

Let’s only show the  Upload Photo  link if a user has the  blog.add_photo  permission. To do this, use the  perms  attribute, which is automatically loaded into the template context:

# blog/templates/blog/base.html
…
{% if perms.blog.add_photo %}
    <a href="{% url 'photo_upload' %}">Upload a Photo</a>
{% endif %}

And now, navigate to the homepage.

Screenshot of homepage
Screenshot of homepage

The link is gone.

Access to these pages is now restricted based on permission.

How can I grant these permissions to users?

Programmatically, via the Django shell.

Step 3: Grant Permissions to a User

To add the permissions, you can use the  user_permissions.add()  method.

>>> from authentication.models import User
>>> user = User.objects.get(username='johnsmith')
>>> from django.contrib.auth.models import Permission
>>> permission = Permission.objects.get(codename='add_photo')
>>> user.user_permissions.add(permission)

If you go back to the site, you should see the  Upload Photo  link back up, and be able to access the upload photo page.

Screenshot of Home page
Screenshot of homepage

It works! One user now has the permission. However, it's not a scalable approach if you want to apply it to many users. Using groups solves this problem. 

Assign Permissions to Multiple Users With Groups

Now that the site has restricted areas  based on permissions, you will need to allow access to different users based on a specification.

To do this, organize users into groups, which is a way of grouping a subset of users. This grouping exists as a table in the database.

For our site, we will have two groups:  Creator  and  Subscriber  . We want these groups to be created automatically, so if someone were to pull a copy of this project and set it up on their machine, they would not need to create these groups and assign the appropriate permissions manually.

To do this, we need to write a custom migration.   

What is a custom migration? 

The migrations you have encountered so far have been related to schema changes in the database and automatically generated by Django.

Custom migrations allow you to manipulate data already in the database or even create new model instances based on specific criteria. They are handy if you have to migrate data to a new data type without losing any information. 

As they are migrations, they can be stored in your version control history. They can then be pulled down and run by anyone with access to the project, which allows the application to be reproducible in different environments. They will also be run whenever someone first sets up the project.

First, we will write a custom migration to create the  creators  and  subscribers  groups. Second, we will add the users to their appropriate group based on their  role  attribute. 

To create a custom migration, you need to generate an empty one by using the flag  --empty  and then specifying an app where the migration will be generated, in our case  authentication  :

python manage.py makemigrations --empty authentication

 Now, if you look at the  migrations  directory of the  authentication  app, you will see a new migration has been generated. Let’s see what it contains.

# Generated by Django 3.2.1 on 2021-04-06 01:35

from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('authentication', '0001_initial'),
    ]

    operations = [
    ]

The  dependencies  property lists the migrations that must run before this one is, while the  operations  is a list of operations the migration will carry out.

To write custom code to execute in the migration, you must write it as a function taking two arguments,  apps  and  schema_editor  . You can’t access models directly via imports, so you have to use the  apps.get_model()  function to retrieve them.

Let’s write a function,  create_groups , that will create the groups  creators  and  subscribers  , and then assign the existing users in the database to them appropriately using the Group.user_set.add() function.

def create_groups(apps, schema_migration):
    User = apps.get_model('authentication', 'User')
    Group = apps.get_model('auth', 'Group')
    Permission = apps.get_model('auth', 'Permission')

    add_photo = Permission.objects.get(codename='add_photo')
    change_photo = Permission.objects.get(codename='change_photo')
    delete_photo = Permission.objects.get(codename='delete_photo')
    view_photo = Permission.objects.get(codename='view_photo')

    creator_permissions = [
        add_photo,
        change_photo,
        delete_photo,
        view_photo,
    ]

    creators = Group(name='creators')
    creators.save()

creators.permissions.set(creator_permissions)

    subscribers = Group(name='subscribers')
    subscribers.save()
    subscribers.permissions.add(view_photo)

    for user in User.objects.all():
        if user.role == 'CREATOR':
            creators.user_set.add(user)
        if user.role == 'SUBSCRIBER':
            subscribers.user_set.add(user)

Then add the migration to the  operations by specifying the function,  create_groups  as an argument to the class  migrations.RunPython .

class Migration(migrations.Migration):

    dependencies = [
        ('authentication', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(create_groups)
    ]

Now run the migration.

(fotoblog) ~/fotoblog (master)
→ python manage.py migrate
Operations to perform:
   Apply all migrations: admin, auth, authentication, blog, contenttypes, sessions
Running migrations:
 Applying authentication.0002_auto_20210406_0135... OK

It looks like it worked!

Feel free to go to your site as a logged in creator. You should see that the  Upload Photo  functionality is appearing again. Then view the page as a subscriber. You should see that the button has disappeared. 

Now that you know how to add these permissions, it’s your turn to build out the rest of the permissions functionality!

Exercise: Complete the Permissions Functionality

Now that you know how to configure the permissions for the  Photo Upload  functionality, it's time to build out the rest of the permissions.

There are four tasks:

  1. Use the  permission_required  decorator to restrict access to the   create_multiple_photos  ,  edit_blog  , and  blog_and_photo_upload  views.

  2. Use the  perms  attribute in a template to optionally display the  Upload Multiple Photos,  Create Blog , and  Edit Blog  links.

  3. Update the  save()  method on the  User  to add the user to the appropriate group.

  4. Create a custom migration to assign the correct permissions for the  Blog  model to the groups. These will be the same as the  Photo  permissions. 

If you get stuck or want to check your implementation, you can find a solution in the GitHub repo

Configure Fine-Grained Access With Custom Permissions

The four default permissions created by Django are useful, but what if you want more fine-grained permissions? For example, you may want a user type to only be able to edit a particular field on a model, such as the  title  field of the  Blog  .  

To do this, create custom permissions. The easiest way of doing this is to define it as part of the model.

Specify custom permissions by setting the  permissions  attribute in a  Meta  class of a model, like so: 

class Blog(models.Model):
    ...

    class Meta:
        permissions = [
            ('change_blog_title', 'Can change the title of a blog')
        ]

You can then assign this permission to either groups or users just like the built-in permissions.

Per-object permissions are something that Django does not handle well out of the box. If you need to use permissions in this way, some useful third-party libraries can help. Django Guardian and Rules are two popular solutions to this problem, and you can always check out the Permissions section of Django Packages for other offerings.

Let's Recap!

  • Django provides four default permissions with every model, which correspond to the four CRUD operations.

  • You can use the  permission_required  decorator to restrict access to a view based on a permission.

  • You can assign permissions to users individually or multiple users using groups.

  • Custom migrations allow you to make customizable changes to the database that are repeatable across different machines.

  • Custom permissions can be specified in a models  Meta  class.

  • It's best to use a third-party package for per-object permissions.

Now that you can restrict access based on permissions and write custom migrations, you're ready to learn about many-to-many fields.

Example of certificate of achievement
Example of certificate of achievement