Extending OpenWISP Users

Note

This page is for developers who want to customize or extend OpenWISP Users, whether for bug fixes, new features, or contributions.

For user guides and general information, please see:

One of the core values of the OpenWISP project is Software Reusability, which ensures long-term sustainability. For this reason, OpenWISP Users provides a set of base classes that can be imported, extended, and reused to create derivative apps.

This is extremely beneficial if you want to add additional fields to the User model, such as requesting a Social Security Number during registration.

To implement your custom version of OpenWISP Users, follow the steps described in this section.

If you have any doubts, refer to the code in the test project and the sample app. These resources will serve as your source of truth: replicate and adapt that code to get a basic derivative of OpenWISP Users working.

Important

If you plan on using a customized version of this module, we suggest to start with it since the beginning, because migrating your data from the default module to your extended version may be time consuming.

1. Initialize Your Custom Module

The first thing you need to do is create a new Django app which will contain your custom version of OpenWISP Users.

A Django app is nothing more than a Python package (a directory of Python scripts). In the following examples, we'll call this Django app myusers, but you can name it however you like:

django-admin startapp myusers

Keep in mind that the command mentioned above must be called from a directory that is available in your PYTHON_PATH so that you can then import the result into your project.

Now you need to add myusers to INSTALLED_APPS in your settings.py, ensuring also that openwisp_users has been removed:

INSTALLED_APPS = [
    # ... other apps ...
    # 'openwisp_users'  <-- comment out or delete this line
    "myusers"
]

For more information about how to work with Django projects and Django apps, please refer to the Django documentation.

2. Install OpenWISP Users

Install (and add to the requirements of your project) openwisp-users:

pip install openwisp-users

3. Add EXTENDED_APPS

Add the following to your settings.py:

EXTENDED_APPS = ("openwisp_users",)

4. Add openwisp_utils.staticfiles.DependencyFinder

Add openwisp_utils.staticfiles.DependencyFinder to STATICFILES_FINDERS in your settings.py:

STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
    "openwisp_utils.staticfiles.DependencyFinder",
]

5. Add openwisp_utils.loaders.DependencyLoader

Add openwisp_utils.loaders.DependencyLoader to TEMPLATES before django.template.loaders.app_directories.Loader in your settings.py:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "OPTIONS": {
            "loaders": [
                "django.template.loaders.filesystem.Loader",
                "openwisp_utils.loaders.DependencyLoader",
                "django.template.loaders.app_directories.Loader",
            ],
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    }
]

6. Inherit the AppConfig Class

Please refer to the following files in the sample app of the test project:

You have to replicate and adapt that code in your project.

For more information regarding the concept of AppConfig please refer to the "Applications" section in the django documentation.

7. Create Your Custom Models

For the purpose of showing an example, we added a simple social_security_number field in the User model to the models of the sample app in the test project.

You can add fields in a similar way in your models.py file.

For doubts regarding how to use, extend, or develop models please refer to the "Models" section in the django documentation.

8. Add Swapper Configurations

Once you have created the models, add the following to your settings.py:

# Setting models for swapper module
AUTH_USER_MODEL = "myusers.User"
OPENWISP_USERS_GROUP_MODEL = "myusers.Group"
OPENWISP_USERS_ORGANIZATION_MODEL = "myusers.Organization"
OPENWISP_USERS_ORGANIZATIONUSER_MODEL = "myusers.OrganizationUser"
OPENWISP_USERS_ORGANIZATIONOWNER_MODEL = "myusers.OrganizationOwner"
# The following model is not used in OpenWISP yet
# but users are free to implement it in their projects if needed
# for more information refer to the django-organizations docs:
# https://django-organizations.readthedocs.io/
OPENWISP_USERS_ORGANIZATIONINVITATION_MODEL = (
    "myusers.OrganizationInvitation"
)

Substitute myusers with the name you chose in step 1.

9. Create Database Migrations

Create database migrations:

./manage.py makemigrations

Now, manually create a file 0004_default_groups.py in the migrations directory just created by the makemigrations command and copy the contents of the sample_users/migrations/0004_default_groups.py.

Then, run the migrations:

./manage.py migrate

Note

The 0004_default_groups is required because other OpenWISP modules depend on it. If it's not created as documented here, the migrations of other OpenWISP modules will fail.

10. Create the admin

Refer to the admin.py file of the sample app.

To introduce changes to the admin, you can do it in two main ways which are described below.

For more information regarding how the Django admin works, or how it can be customized, please refer to "The Django admin site" section in the Django documentation.

1. Monkey Patching

If the changes you need to add are relatively small, you can resort to monkey patching.

For example:

from openwisp_users.admin import (
    UserAdmin,
    GroupAdmin,
    OrganizationAdmin,
    OrganizationOwnerAdmin,
    BaseOrganizationUserAdmin,
)

# OrganizationAdmin.field += ['example_field'] <-- Monkey patching changes example

For your convenience in adding fields in User forms, we provide the following functions:

usermodel_add_form

When monkey patching the UserAdmin class to add fields in the "Add user" form, you can use this function. In the example, Social Security Number is added in the add form:

Social Security Number in Add form

usermodel_change_form

When monkey patching the UserAdmin class to add fields in the "Change user" form to change/modify the user form's profile section, you can use this function. In the example, Social Security Number is added in the change form:

Social Security Number in Change form

2. Inheriting Admin Classes

If you need to introduce significant changes and/or you don't want to resort to monkey patching, you can proceed as follows:

from django.contrib import admin
from openwisp_users.admin import (
    UserAdmin as BaseUserAdmin,
    GroupAdmin as BaseGroupAdmin,
    OrganizationAdmin as BaseOrganizationAdmin,
    OrganizationOwnerAdmin as BaseOrganizationOwnerAdmin,
    OrganizationUserAdmin as BaseOrganizationUserAdmin,
)
from swapper import load_model
from django.contrib.auth import get_user_model

Group = load_model("openwisp_users", "Group")
Organization = load_model("openwisp_users", "Organization")
OrganizationOwner = load_model("openwisp_users", "OrganizationOwner")
OrganizationUser = load_model("openwisp_users", "OrganizationUser")
User = get_user_model()

admin.site.unregister(Group)
admin.site.unregister(Organization)
admin.site.unregister(OrganizationOwner)
admin.site.unregister(OrganizationUser)
admin.site.unregister(User)


@admin.register(Group)
class GroupAdmin(BaseGroupAdmin):
    pass


@admin.register(Organization)
class OrganizationAdmin(BaseOrganizationAdmin):
    pass


@admin.register(OrganizationOwner)
class OrganizationOwnerAdmin(BaseOrganizationOwnerAdmin):
    pass


@admin.register(OrganizationUser)
class OrganizationUserAdmin(BaseOrganizationUserAdmin):
    pass


@admin.register(User)
class UserAdmin(BaseUserAdmin):
    pass

11. Create Root URL Configuration

Please refer to the urls.py file in the sample project.

For more information about URL configuration in Django, please refer to the "URL dispatcher" section in the Django documentation.

12. Import the Automated Tests

When developing a custom application based on this module, it's a good idea to import and run the base tests too, so that you can be sure the changes you're introducing are not breaking some of the existing features of OpenWISP Users.

In case you need to add breaking changes, you can overwrite the tests defined in the base classes to test your own behavior.

See the tests of the sample app to find out how to do this.

You can then run tests with:

# the --parallel flag is optional
./manage.py test --parallel myusers

Substitute myusers with the name you chose in step 1.

Other Base Classes that can be Inherited and Extended

The following steps are not required and are intended for more advanced customization.

Extending the API Views

The API view classes can be extended into other Django applications as well. Note that it is not required for extending OpenWISP Users to your app and this change is required only if you plan to make changes to the API views.

Create a view file as done in API views.py.

Remember to use these views in root URL configurations in point 11.

For more information about Django views, please refer to the views section in the Django documentation.