Set up Django users, groups and integrate permission management into an application.

Problem

You want to set up Django users, groups and integrate them with permission management in a Django application.

Solution

To create a superuser you can use the python manage.py createsuperuser command line utility, use the User.objects.create_superuser() method or use the Django admin to change a user's superuser status. To create a regular user you can use the User.objects.create_user() method or user the Django admin. You can manage a user's properties (e.g. superuser, staff, password ) directly manipulating his User model record or through the Django admin.

You can create and manage a group in the Django admin. It's possible to assign Create-Update-Delete permissions for individual Django models to a user or group in the Django admin. You can create a permission check in the body of a view method directly accessing a user's properties available in request.user (e.g. if request.user.is_anonymous()). It's also possible to apply a permission check directly to a view method or URL definition using the @login_required, @user_passes_test or @permission_required decorators. In addition, you can also apply a permission check in a Django template accessing the user or perms properties.

How it works

Django has a very robust built-in mechanism to manage users and restrict an application's access based on various rules. By default, Django only enforces permission management on the Django admin which provides full access to the database connected to an application. Permission management on the Django application itself (i.e. the one accessed by end-users) needs to be integrated and is described toward the end of this recipe.

User types, sub-types, groups and permissions

There are two main types of Django users: User and AnonymousUser. If a user authenticates himself (i.e. provides a valid username/password) Django recognizes him as a User. On the other hand, if a user just surfs an application without any authentication, Django recognizes him as an AnonymousUser.

Any User can be further classified into one of various sub-types:

Django also offers the concept of a Group to grant a set of users the same set of permissions without having to assign them individually. For example, you can grant permissions to a group and then assign users to the group to make permission management easier. In this manner, you can revoke or add permissions in a single step to a set of users, as well quickly give new users the same permissions.

In addition, you can assign Django permissions granularly to a User or Group in order for them to Create-Update-Delete records on Django models, a process which is done through Permission model records. Or you can also assign coarser grained Django permissions on URL/view methods or template content to grant access to a User, Group or even Permission assignee.

Now that you know the basic concepts behind Django's user system, let's explore the more common operations in detail.

Create users

The first user you'll want to create is a superuser. If you already followed the Set up the Django admin site recipe, then you'll already have a superuser. Either way, I'll recap the process here since you can have any number of superusers. Listing 1 illustrates the various ways in which you can create a superuser.

Listing 1 - Create Django superuser

[user@coffeehouse ~]$ python manage.py createsuperuser
Username (leave blank to use 'admin'): 
Email address: admin@coffeehouse.com
Password: 
Password (again): 
Superuser created successfully.

[user@coffeehouse ~]$ python manage.py createsuperuser --username=bigboss 
                      --email=bigboss@coffeehouse.com
Password: 
Password (again): 
Superuser created successfully.

[user@coffeehouse ~]$ python manage.py shell
Python 2.7.3 (default, Apr 10 2013, 06:20:15) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_superuser(username='angelinvestor',
                                 email='angelinvestor@coffeehouse.com',
                                 password='seedfunding')
>>>
Note A Django username must be unique

Django enforces user uniqueness through the username value, so it's not possible to create two users with the same username.

As you can see in listing 1, you can create a superuser with the createsuperuser command of the manage.py utility, where you're asked for a username, email and password. You can also see that it's possible to create a superuser with the inline arguments --username and --email, in which case you're only prompted for a password.

In addition, it's also possible to create a superuser through the Django shell using the User model class with the create_superuser() method. Notice how the create_superuser() method requires the username, email and password arguments.

When you create a superuser in any of the previous ways, the user is also automatically set as a staff member and active, so you don't need to make any additional updates to access the Django admin or proceed with authentication.

Sometimes you just want to create a regular user, in which case you can use Django's shell utility and create a user directly through the User model. This process is illustrated in listing 2.

Listing 2 - Create regular Django user through shell

[user@coffeehouse ~]$ python manage.py shell
Python 2.7.3 (default, Apr 10 2013, 06:20:15) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='downtownbarista',
                                 email='downtownbarista@coffeehouse.com',
                                 password='cappuccino')
>>> user.is_staff
False
>>> user.is_active
True
>>> user.is_superuser
False

As you can see in listing 2, after you gain access to the Django shell you import the User model class and invoke the create_user() method with the username, email and password arguments. The result of the create_user() method contains the newly created user.

To confirm the create_user() method generates a regular user, you can see in listing 2 a call to the various User model attributes, confirming the user is neither staff or superuser and is just marked as active.

Finally, it's also possible to create users through the Django admin. To do this you'll first need to make sure you have a superuser to access the Django admin. Once you access the Django admin, you'll see a screen like the one Figure 1, click on the 'Users' link. The 'Users' link takes you to a screen like the one in Figure 2, click on the button 'Add User+' in the top right. The 'Add User+' button takes you to a screen like the one in Figure 3 where you can introduce the credentials for a new user.

If you wish to change the sub-type (i.e. superuser, staff) of a user created in this manner, you can also do it in the Django admin, a process that's described in the next section.

Django admin site home page
Figure 1.- Django admin site home page
Django admin Users list
Figure 2.- Django admin Users list
Django admin to create new user
Figure 3.- Django admin to create new user

Manage users

Once a user is in a Django application, you'll end up managing him, this could be either revoking his privileges, adding to his privileges or even editing his profile information. You can manage Django users in two ways, in the Django admin or by manipulating a User model in the Django shell or directly in your application.

The easiest way to manage users is directly in the Django admin. Once you access the Django admin you'll see a screen like the one in Figure 1, if you click on the 'Users' link you'll be taken to a screen like the one in Figure 2 which contains a list of Django users. Each Django user presented in Figure 2 has his username as a link, if you click on this link you'll be taken to the user's page which is illustrated in Figure 4, Figure 5 & Figure 6 where you can edit a user's profile.

Django admin change user page Part 1
Figure 4.- Django admin change user page - Part 1
Django admin change user page Part 2
Figure 5.- Django admin change user page - Part 2
Django admin change user page Part 3
Figure 6.- Django admin change user page - Part 3

The first part of a user's profile you can edit is illustrated in Figure 4, where you can edit his username, his password -- by clicking on the 'small form' link at the end of the small text -- his first name, his last name, as well as his email. In addition, you can see there are three check-boxes where it's possible to change a user's active, staff and superuser status.

If you scroll down, you'll see the second part of a user's profile you can edit which is illustrated in Figure 5. Here you can assign a user to different groups, as well as assign a user individual CRUD permissions over the various Django models. Note I would advise you to carefully evaluate the need to assign individual CRUD permissions to a user, a more flexible approach is to create a group and assign it CRUD permissions and then assign users to this group as needed, this way the permissions become easier to track and reusable for other users.

And if you scroll to the end, you'll see the third part of a user's profile you can edit which is illustrated in Figure 6. Here you can view and update a user's last login, as well as the date a user was created. At the bottom right of the page, you can see the various save buttons to store any changes made to the page. And in addition, at the bottom left there's a 'Delete' button to remove a user completely, however, I would advise you to consider just unchecking a user's active status to restrict access. This last step is sufficient to block a user from accessing an application again and it keeps his other data untouched in case you want to undo the action.

Another option to modify a user's profile is to directly manipulate his User model record. As illustrated in listing 3, you first make a query for the desired user and then modify the model attributes or execute one of the User model helper methods.

Listing 3 - Manage Django user through shell

[user@coffeehouse ~]$ python manage.py shell
Python 2.7.3 (default, Apr 10 2013, 06:20:15) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=1)
>>> user.username = 'superadmin'
>>> user.save()
>>> userbig = User.objects.get(username='bigboss')
>>> userbig.is_superuser
True
>>> userbig.superuser = False
>>> userbig.first_name = 'Big'
>>> userbig.last_name = 'Boss'
>>> userbig.save()
>>> userbig.is_superuser
False
>>> userbig.get_full_name()
u'Big Boss'
>>> userbarista = User.objects.get(email='downtownbarista@coffeehouse.com')
>>> userbarista.email ='barista@coffeehouse.com'
>>> userbarista.save()
>>> userbarista.set_password('mynewpass')
>>> userbarista.check_password('oldpass')
False
>>> userbarista.check_password('mynewpass')
True

As you can see in listing 3, you can modify the same User profile values as those presented in the Django admin in Figures 4, 5 & 6. Just be aware that because you're making a query, any changes made to fields must be followed by a call to the save() method on the reference for fields to be persisted. Table 1 and table 2 contain a full list of fields and methods available on the User model.

Table 1 - Django django.contrib.auth.models.User fields
FieldDescription
username(Required) 30 characters or fewer and can contain alphanumeric, _, @, +, . and - characters.
first_name(Optional) 30 characters or fewer.
last_name(Optional) 30 characters or fewer.
email(Optional) Email address.
password(Required) A hash of, and metadata about, the password. Note that Django doesn't store the raw password.
groupsA many-to-many relationship to django.contrib.auth.models.Group
user_permissionsA many-to-many relationship to django.contrib.auth.Permission
is_staff(Boolean) Designates whether a user can access the admin site.
is_active(Boolean) Designates whether a user is considered active.
is_superuser(Boolean) Designates whether a user has all permissions without explicitly assigning them.
last_loginA datetime of the user's last login, set to NULL if the user has never logged in
date_joinedA datetime designating when the account was created. Is set to the current date/time by default when the account is created.

 

 

Table 2 - Django django.contrib.auth.models.User methods
MethodDescription
get_username()Returns the username for the user. Since the User model can be changed for another, this method is the recommended approach instead of referencing the username attribute directly.
is_anonymous()For a User this method always returns False, it's only used as a way to differentiate between User and AnonymousUser.
is_authenticated()For a User this method always returns True, it's only used to to find out whether the user has gone through the AuthenticationMiddleware (representing the currently logged-in user).
get_full_name()Returns the first_name and the last_name fields, with a space in between.
get_short_name()Returns the first_name.
set_password(raw_password)Sets the user's password to the given raw string, taking care of the password hashing. Note that when the raw_password is None, the password is set to an unusable password, as if set_unusable_password() were used.
check_password(raw_password)Returns True if the given raw string is the correct password for the user, talking care of the password hashing for making the comparison.
set_unusable_password()Marks the user as having no password set. Note this isn't the same as having a blank string for a password. check_password() for this user will never return True. This is helpful if authentication takes place against an existing external source (e.g.LDAP directory).
has_usable_password()Returns False if set_unusable_password() has been called for the user.
get_group_permissions(obj=None)Returns a set of group permission strings for the user. If the obj is passed, only returns the group permissions for the specific object.
get_all_permissions(obj=None)Returns a set of group and user permission strings for the user. If the obj is passed, only returns the group permissions for the specific object.
has_perm(perm, obj=None)Returns True if the user has the specified permission, where perm is in the format <app label>.<permission codename>". Note if the user is inactive, this method always returns False. If the obj is passed, the check ocurrs on the specific object and not on the model.
has_perms(perm_list, obj=None)Returns True if the user has each of the specified permissions, where each perm is in the format <app label>.<permission codename>". Note if the user is inactive, this method always returns False. If the obj is passed, the check ocurrs on the specific object and not on the model.
has_module_perms(package_name)Returns True if the user has permissions in the given package (i.e. the Django app label). If the user is inactive, this method always returns False.
email_user(subject, message, from_email=None, **kwargs)Sends an email to the user. If from_email is None, Django uses the DEFAULT_FROM_EMAIL in settings.py. Also note this method relies on Django's send_mail() method to which it passes the **kwargs argument. See the Django email shortcut methods for more details on the send_mail() method and **kwargs values

 

Note User data under the hood

User model data is stored in the database auth_user table.

 

Create and manage groups

Django groups can be created in the Django admin. With a superuser account access the Django admin and you'll see a screen like the one Figure 1, click on the 'Groups' link. The 'Groups' link takes you to a screen like the one in Figure 7, click on the button 'Add Group+' in the top right. The 'Add Group+' button takes you to a screen like the one in Figure 8 where you can create a new group introducing its name.

Django admin Groups list
Figure 7.- Django admin Groups list
Django admin to create new user
Figure 8.- Django admin to create new group

As you can see in figure 8, all that's need to create a group is a name and you can optionally specify permissions given to the group to Create-Delete-Update Django models in the application.

The management of groups is simpler than users and can also be completely done from the Django admin. What you'll end up doing most of the time is assigning users to groups. To assign a user to a group, when you're editing a user you'll see a selection grid for just this purpose, which is illustrated in Figure 5. To edit a group's properties -- name & Create-Delete-Update Django model permission -- you can do so from the same page where you created it illustrated in Figure 8. To delete a group from the Groups list illustrated in Figure 7, you select the group you wish to delete and select the action from the drop-down list, as illustrated in figure 9.

Django admin to delete group
Figure 9.- Django admin to delete group

Note Group data under the hood

Group model data is stored in the database auth_group table. And User-Group relationships are stored in the auth_user_groups table.

Create and manage permissions

One type of permission you've already come in contact in previous sections is the one associated with the ability to Create, Delete or Update Django model records. In the user's section, you can see in Figure 5 a selection grid to assign these types of permissions on an individual basis, as well as in the group's section, you can see in Figure 8 the same selection grid to assign these types of permissions to a group.

This type of Create, Delete or Update permission is managed at the database level through Permission model records, records which are automatically created when a Django model is installed. Managing this type of permission is very straightforward, as the assignment is made directly on a User -- illustrated in Figure 5 -- or Group illustrated in Figure 8.

Note Permission data under the hood

Permission model data is stored in the database auth_permission table, which also references the django_content_type table that maintains a list of installed Django models.

In addition, Permission-User relationships are stored in the auth_user_user_permissions table and Permission-Group relationships are stored in the auth_group_permissions table.

The other type of permission available in Django is to block access to resources (e.g. a URL/view method or template content) based on User properties, Group properties or inclusively Permission properties assigned to a user or group.

Listing 4 illustrates a series of examples to make permission checks inside the logic of a view method or outright restrict a whole view method based on a permission check. Note that it's thanks to Django's AuthenticationMiddleware which is enabled by default -- see the recipe Use Django middleware to modify requests & responses if you've never heard of Django middleware -- the User model record is available on all application requests.

Listing 4 - Permission check in view methods


# Internal check to see if user is anonymous or not
def homepage(request): 
    if request.user.is_anonymous():
        # Logic for AnonymousUser
    else:
	# Logic for User


# Method check to see if user is logged in or not
from django.contrib.auth.decorators import login_required

@login_required
def profile(request):
    # Logic for profile 


# Method check to see if user belongs to group called 'Barista'
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group

@user_passes_test(lambda u: Group.objects.get(name='Baristas') in u.groups.all())
def dashboard(request):
    # Logic for dashboard


# Explicit method check to see if user is authenticated and has permissions to change Store model

# Explicit method with test
def user_of_stores(user):
    if user.is_authenticated() and user.has_perm("stores.change_store"):
        return True
    else:
        return False

# Method check using method
@user_passes_test(user_of_stores)
def store_manager(request):
    # Logic for store_manager


# Method check to see if user has permissions to add Store model

from django.contrib.auth.decorators import permission_required

@permission_required('stores.add_store')
def store_creator(request):
    # Logic for store_creator

The first example in listing checks if the user is anonymous or not -- using the is_anonymous() method of the User model -- which is helpful for cases when you want to execute different workflows based on the requesting user. Note that you can use any User model field or method -- presented in Table 1 or Table 2 -- to perform a check (e.g.is_staff, is_superuser).

The second example uses the @login_required decorator to restrict the execution of a method to those users that are logged in, a process that's common for view methods that generate user-specific or premium content.

The third example uses the @user_passes_test decorator and defines an in-line test. The snippet lambda u: Group.objects.get(name='Baristas') in u.groups.all() fetches the Group model record with the name Baristas and checks that its part of the requesting user's groups. If the requesting user does not belong to the Barista group then the test fails and access is denied, otherwise the user is allowed to execute the view method.

The fourth example also uses the @user_passes_test decorator, but instead of defining an inline test it relies on the user_of_stores() method to perform the test logic. This is particularly helpful if the test is complex, which can make it hard to follow inline logic, compared to having a regular method. As you can also see in listing 4, the user_of_stores() verifies if the user is authenticated and also if he has update permissions on the Store model -- note the string stores.change_store is the syntax used by Django's Permission model records.

The last example in listing 4 uses the @permission_required decorator which is designed to validate if a user has a given Permission record. In this case, notice the decorator has the input string stores.add_store which indicates that only users that have permission to add a Store model can execute the view method.

Note What happens when a user fails a permission check ?

For inline validation checks (e.g.if request.user.is_anonymous():) you have absolute control, so you can redirect a user to any page or add flash messages to display on a template.

For the decorator validation checks @login_required, @user_passes_test and @permission_required the default failure behavior is to redirect a user to Django's login page. Django's default login page URL is /account/login/, a value that can be overridden with the LOGIN_URL variable in settings.py.

For the @permission_required decorator it's possible to redirect a failed test to Django's HTTP 403 (Forbidden) page by adding the raise_exception=True attribute (e.g. @permission_required('stores.add_store',raise_exception=True)).

In certain circumstances you can have a Djano workflow which doesn't involve a view method and simply sends control from a URL directly to a static template. In such cases, it's also possible to enforce a permission check directly in the URL definition. Listing 5 illustrates similar validation checks like the ones in listing 4 but applied to URL definitions in urls.py.

Listing 5 - Permission check in urls.py for static templates

from django.conf.urls import patterns, include, url
from django.views.generic import TemplateView


from django.contrib.auth.decorators import login_required,permission_required,user_passes_test
from django.contrib.auth.models import Group

urlpatterns = [
      url(r'^online/baristas/',
         user_passes_test(lambda u: Group.objects.get(name='Baristas') in u.groups.all())
         (TemplateView.as_view(template_name='online/baristas.html')),name="onlinebaristas"),      
      url(r'^online/dashboard/',
         permission_required('stores.add_store')
         (TemplateView.as_view(template_name='online/dashboard.html')),name="onlinedashboard"),
      url(r'^online/',
         login_required(TemplateView.as_view(template_name='online/index.html')),name='online'),      
]

As you can see in listing 5, after you import the required decorators you just need to integrate the validation tests in the URL definition. The user_passes_test and permission_required methods are declared as standalone methods followed by the URL definition (e.g. user_pass_test()(TemplateView.as_view...)). The login_required method though takes the TemplateView statement as its input. It should be pointed out the behavior for failed tests in listing 5 is the same as those in listing 4, described in the sidebar 'What happens when a user fails a permission check ?'

Another possibility that can arise for validation checks is to perform them on a group of view methods/URLs, so that instead of adding a decorator to each individual view method -- as illustrated in listing 4 -- you only do it once for a whole group. This view method/URL grouping process is particularly common when defining URLs in urls.py through the include() method. Listing 6 illustrates how to enforce validation checks on sets of URLs that use the include() method.

Listing 6 - Permission check in urls.py for include() definition

from django.conf.urls import patterns, include, url
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern


class DecoratedURLPattern(RegexURLPattern):
    def resolve(self, *args, **kwargs):
        result = super(DecoratedURLPattern, self).resolve(*args, **kwargs)
        if result:
            result.func = self._decorate_with(result.func)
        return result

class DecoratedRegexURLResolver(RegexURLResolver):
    def resolve(self, *args, **kwargs):
        result = super(DecoratedRegexURLResolver, self).resolve(*args, **kwargs)
        if result:
            result.func = self._decorate_with(result.func)
        return result

def decorated_includes(func, includes, *args, **kwargs):
    urlconf_module, app_name, namespace = includes
    patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module)    
    for item in patterns:
        if isinstance(item, RegexURLPattern):
            item.__class__ = DecoratedURLPattern
            item._decorate_with = func
            
        elif isinstance(item, RegexURLResolver):
            item.__class__ = DecoratedRegexURLResolver
            item._decorate_with = func

    return urlconf_module, app_name, namespace


from django.contrib.auth.decorators import login_required,permission_required,user_passes_test
from django.contrib.auth.models import Group

from coffeehouse.drinks.urls import urlpatterns as drinks_url_patterns

urlpatterns = [
    url(r'^drinks/', 
       decorated_includes(login_required,include(drinks_url_patterns,namespace="drinks"))),
    url(r'^stores/', 
      decorated_includes(permission_required('stores.add_store'),
      include('coffeehouse.stores.urls',namespace="stores"))),
    url(r'^baristas/admin/', 
      decorated_includes(user_passes_test(lambda u: Group.objects.get(name='Baristas') in u.groups.all()),
      include('coffeehouse.baristas.urls',namespace="drinks"))),
]

Because Django doesn't have built-in support for permissions checks in include() definitions, you can see in listing 6 we first define two custom classes followed by the custom method decorated_includes. If you follow the sequence in listing 6, you can see the decorated_includes() method accepts two input arguments, first the permission test (e.g.login_required, permission_required) and then the standard include() method with the URL definitions. It should also be pointed out the behavior for failed tests in listing 6 is the same as those in listing 5 and 4, described in the sidebar 'What happens when a user fails a permission check ?'

Finally, one last option that's available for permissions checks is in Django templates, a process that's helpful if you want to show/hide content (e.g. links) based on a user's permissions. Listing 7 illustrates a series of Django template syntax examples.

Listing 7 - Permission check in templates


       {% if user.is_authenticated %}
          {#  Content for authenticated users  #}
       {% endif %}

       {% if perms.stores.add_store %}
          {#  Content for users that can add stores #}
       {% endif %}

       {% for group in user.groups.all %}
          {% if group.name == 'Baristas'  %}
               {# Content for users with 'Baristas' group #}
          {% endif %}
       {% endfor %}

You can see in listing 7 all of the examples directly access the user and perms variables. These variables are available on all Django templates thanks to Django's auth context processor which is enabled by default -- see the recipe Use data provided by default Django context processors if you've never heard of context processors.

The first example in listing 7 checks to see if the user is authenticated through the is_authenticate field. The second example verifies if perms -- which holds a user's permissions -- has access to create Store model records, using Django's Permission syntax stores.add_store

And the third example in listing 7 loops over the users groups, checking to see if one of them is the Baristas group, if it's it outputs content for users that belong to this group.

Note Loops in Django templates to check a property are very inefficient

Although the last example in listing 7 works, it's a very inefficient mechanism. A better solution is to create a custom filter and perform a direct query in the filter (e.g. {% if user|has_group:"Baristas" %}, with the has_group containing the bulk of the logic check).

In this case I opted for the syntax in listing 7 to keep everything in one place, but be aware the more efficient solution for this type of logic is to use a custom filter.