immunity-utils
==============
.. image:: https://github.com/edge-servers/immunity-utils/workflows/Immunity%20Utils%20CI%20Build/badge.svg?branch=master
:target: https://github.com/edge-servers/immunity-utils/actions?query=workflow%3A%22Immunity+Utils+CI+Build%22
:alt: CI build status
.. image:: https://coveralls.io/repos/github/immunity/immunity-utils/badge.svg
:target: https://coveralls.io/github/immunity/immunity-utils
:alt: Test coverage
.. image:: https://img.shields.io/librariesio/release/github/immunity/immunity-utils
:target: https://libraries.io/github/immunity/immunity-utils#repository_dependencies
:alt: Dependency monitoring
.. image:: https://badge.fury.io/py/immunity-utils.svg
:target: http://badge.fury.io/py/immunity-utils
:alt: pypi
.. image:: https://pepy.tech/badge/immunity-utils
:target: https://pepy.tech/project/immunity-utils
:alt: downloads
.. image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square
:target: https://gitter.im/immunity/general
:alt: support chat
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://pypi.org/project/black/
:alt: code style: black
------------
Python and Django functions, classes and settings re-used across different Immunity modules,
stored here with the aim of avoiding code duplication and ease maintenance.
**Don't repeat yourself!**
.. image:: https://raw.githubusercontent.com/immunity/immunity2-docs/master/assets/design/immunity-logo-black.svg
:target: http://immunity.org
Current features
----------------
* `Configurable admin theme <#using-the-admin_theme>`_
* `Immunity Dashboard <#immunity-dashboard>`_
* `Configurable navigation menu <#main-navigation-menu>`_
* `Improved admin filters <#admin-filters>`_
* `OpenAPI / Swagger documentation <#immunity_api_docs>`_
* `Model utilities <#model-utilities>`_
* `Storage utilities <#storage-utilities>`_
* `Admin utilities <#admin-utilities>`_
* `Code utilities <#code-utilities>`_
* `Admin Theme utilities <#admin-theme-utilities>`_
* `REST API utilities <#rest-api-utilities>`_
* `Test utilities <#test-utilities>`_
* `Collection of Usage Metrics <#collection-of-usage-metrics>`_
* `Quality assurance checks <#quality-assurance-checks>`_
------------
.. contents:: **Table of Contents**:
:backlinks: none
:depth: 3
------------
Install stable version from pypi
--------------------------------
Install from pypi:
.. code-block:: shell
pip install immunity-utils
# install optional dependencies for REST framework
pip install immunity-utils[rest]
# install optional dependencies for tests (flake8, black and isort)
pip install immunity-utils[qa]
# or install everything
pip install immunity-utils[rest,qa]
Install development version
---------------------------
Install tarball:
.. code-block:: shell
pip install https://github.com/edge-servers/immunity-utils/tarball/master
Alternatively you can install via pip using git:
.. code-block:: shell
pip install -e git+git://github.com/immunity/immunity-utils#egg=immunity-utils
Using the ``admin_theme``
-------------------------
**The admin theme requires Django >= 2.2.**.
Add ``immunity_utils.admin_theme`` to ``INSTALLED_APPS`` in ``settings.py``:
.. code-block:: python
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'immunity_utils.admin_theme', # <----- add this
# add when using autocomplete filter
'admin_auto_filters', # <----- add this
'django.contrib.sites',
# admin
'django.contrib.admin',
]
Using ``DependencyLoader`` and ``DependencyFinder``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add the list of all packages extended to ``EXTENDED_APPS`` in ``settings.py``.
For example, if you've extended ``django_x509``:
.. code-block:: python
EXTENDED_APPS = ['django_x509']
``DependencyFinder``
~~~~~~~~~~~~~~~~~~~~
This is a static finder which looks for static files in the ``static``
directory of the apps listed in ``settings.EXTENDED_APPS``.
Add ``immunity_utils.staticfiles.DependencyFinder`` to ``STATICFILES_FINDERS``
in ``settings.py``.
.. code-block:: python
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'immunity_utils.staticfiles.DependencyFinder', # <----- add this
]
``DependencyLoader``
~~~~~~~~~~~~~~~~~~~~
This is a template loader which looks for templates in the ``templates``
directory of the apps listed in ``settings.EXTENDED_APPS``.
Add ``immunity_utils.loaders.DependencyLoader`` to
template ``loaders`` in ``settings.py`` as shown below.
.. code-block:: python
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'OPTIONS': {
'loaders': [
# ... other loaders ...
'immunity_utils.loaders.DependencyLoader', # <----- add this
],
'context_processors': [
# ... omitted ...
],
},
},
]
Supplying custom CSS and JS for the admin theme
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add ``immunity_utils.admin_theme.context_processor.admin_theme_settings`` to
template ``context_processors`` in ``settings.py`` as shown below.
This will allow to set `IMMUNITY
_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`_
and `IMMUNITY
_ADMIN_THEME_JS <#immunity_admin_theme_js>`__ settings
to provide CSS and JS files to customise admin theme.
.. code-block:: python
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'OPTIONS': {
'loaders': [
# ... omitted ...
],
'context_processors': [
# ... other context processors ...
'immunity_utils.admin_theme.context_processor.admin_theme_settings' # <----- add this
],
},
},
]
.. note::
You will have to deploy these static files on your own.
In order to make django able to find and load these files
you may want to use the ``STATICFILES_DIR`` setting in ``settings.py``.
You can learn more in the `Django documentation <https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-STATICFILES_DIRS>`_.
Extend admin theme programmatically
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``immunity_utils.admin_theme.theme.register_theme_link``
""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Allows adding items to `IMMUNITY
_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__.
This function is meant to be used by third party apps or Immunity modules which
aim to extend the core look and feel of the Immunity theme (eg: add new menu icons).
**Syntax:**
.. code-block:: python
register_theme_link(links)
+--------------------+--------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+--------------------------------------------------------------+
| ``links`` | (``list``) List of *link* items to be added to |
| | `IMMUNITY
_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__ |
+--------------------+--------------------------------------------------------------+
``immunity_utils.admin_theme.theme.unregister_theme_link``
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Allows removing items from `IMMUNITY
_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__.
This function is meant to be used by third party apps or Immunity modules which
aim additional functionalities to UI of Immunity (eg: adding a support chatbot).
**Syntax:**
.. code-block:: python
unregister_theme_link(links)
+--------------------+--------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+--------------------------------------------------------------+
| ``links`` | (``list``) List of *link* items to be removed from |
| | `IMMUNITY
_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__ |
+--------------------+--------------------------------------------------------------+
``immunity_utils.admin_theme.theme.register_theme_js``
""""""""""""""""""""""""""""""""""""""""""""""""""""""
Allows adding items to `IMMUNITY
_ADMIN_THEME_JS <#immunity_admin_theme_JS>`__.
**Syntax:**
.. code-block:: python
register_theme_js(js)
+--------------------+---------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+---------------------------------------------------------------+
| ``js`` | (``list``) List of relative path of *js* files to be added to |
| | `IMMUNITY
_ADMIN_THEME_JS <#immunity_admin_theme_js>`__ |
+--------------------+---------------------------------------------------------------+
``immunity_utils.admin_theme.theme.unregister_theme_js``
""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Allows removing items from `IMMUNITY
_ADMIN_THEME_JS <#immunity_admin_theme_JS>`__.
**Syntax:**
.. code-block:: python
unregister_theme_js(js)
+--------------------+--------------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+--------------------------------------------------------------------+
| ``js`` | (``list``) List of relative path of *js* files to be removed from |
| | `IMMUNITY
_ADMIN_THEME_JS <#immunity_admin_theme_js>`__ |
+--------------------+--------------------------------------------------------------------+
Immunity Dashboard
------------------
The ``admin_theme`` sub app of this package provides an admin dashboard
for Immunity which can be manipulated with the functions described in
the next sections.
Example 1, monitoring:
.. figure:: https://raw.githubusercontent.com/immunity/immunity-utils/master/docs/dashboard1.png
:align: center
Example 2, controller:
.. figure:: https://raw.githubusercontent.com/immunity/immunity-utils/master/docs/dashboard2.png
:align: center
``register_dashboard_template``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Allows including a specific django template in the Immunity dashboard.
It is designed to allow the inclusion of the geographic map
shipped by
`Immunity Monitoring <https://github.com/edge-servers/immunity-monitoring>`_
but can be used to include any custom element in the dashboard.
**Note**: it is possible to register templates to be loaded
before or after charts using the ``after_charts`` keyword argument
(see below).
**Syntax:**
.. code-block:: python
register_dashboard_template(position, config)
+--------------------+----------------------------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+----------------------------------------------------------------------------------+
| ``position`` | (``int``) The position of the template. |
+--------------------+----------------------------------------------------------------------------------+
| ``config`` | (``dict``) The configuration of the template. |
+--------------------+----------------------------------------------------------------------------------+
| ``extra_config`` | **optional** (``dict``) Extra configuration you want to pass to custom template. |
+--------------------+----------------------------------------------------------------------------------+
| ``after_charts`` | **optional** (``bool``) Whether the template should be loaded after dashboard |
| | charts. Defaults to ``False``, i.e. templates are loaded before dashboard |
| | charts by default. |
+--------------------+----------------------------------------------------------------------------------+
Following properties can be configured for each template ``config``:
+-----------------+------------------------------------------------------------------------------------------------------+
| **Property** | **Description** |
+-----------------+------------------------------------------------------------------------------------------------------+
| ``template`` | (``str``) Path to pass to the template loader. |
+-----------------+------------------------------------------------------------------------------------------------------+
| ``css`` | (``tuple``) List of CSS files to load in the HTML page. |
+-----------------+------------------------------------------------------------------------------------------------------+
| ``js`` | (``tuple``) List of Javascript files to load in the HTML page. |
+-----------------+------------------------------------------------------------------------------------------------------+
Code example:
.. code-block:: python
from immunity_utils.admin_theme import register_dashboard_template
register_dashboard_template(
position=0,
config={
'template': 'admin/dashboard/device_map.html',
'css': (
'monitoring/css/device-map.css',
'leaflet/leaflet.css',
'monitoring/css/leaflet.fullscreen.css',
),
'js': (
'monitoring/js/device-map.js',
'leaflet/leaflet.js',
'leaflet/leaflet.extras.js',
'monitoring/js/leaflet.fullscreen.min.js'
)
},
extra_config={
'optional_variable': 'any_valid_value',
},
after_charts=True,
)
It is recommended to register dashboard templates from the ``ready``
method of the AppConfig of the app where the templates are defined.
``unregister_dashboard_template``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This function can be used to remove a template from the dashboard.
**Syntax:**
.. code-block:: python
unregister_dashboard_template(template_name)
+-------------------+---------------------------------------------------+
| **Parameter** | **Description** |
+-------------------+---------------------------------------------------+
| ``template_name`` | (``str``) The name of the template to remove. |
+-------------------+---------------------------------------------------+
Code example:
.. code-block:: python
from immunity_utils.admin_theme import unregister_dashboard_template
unregister_dashboard_template('admin/dashboard/device_map.html')
**Note**: an ``ImproperlyConfigured`` exception is raised the
specified dashboard template is not registered.
``register_dashboard_chart``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Adds a chart to the Immunity dashboard.
At the moment only pie charts are supported.
The code works by defining the type of query which will be executed,
and optionally, how the returned values have to be colored and labeled.
**Syntax:**
.. code-block:: python
register_dashboard_chart(position, config)
+--------------------+-------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+-------------------------------------------------------------+
| ``position`` | (``int``) Position of the chart. |
+--------------------+-------------------------------------------------------------+
| ``config`` | (``dict``) Configuration of chart. |
+--------------------+-------------------------------------------------------------+
Following properties can be configured for each chart ``config``:
+------------------+---------------------------------------------------------------------------------------------------------+
| **Property** | **Description** |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``query_params`` | It is a required property in form of ``dict`` containing following properties: |
| | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | **Property** | **Description** | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``name`` | (``str``) Chart title shown in the user interface. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``app_label`` | (``str``) App label of the model that will be used to query the database. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``model`` | (``str``) Name of the model that will be used to query the database. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``group_by`` | (``str``) The property which will be used to group values. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``annotate`` | Alternative to ``group_by``, ``dict`` used for more complex queries. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``aggregate`` | Alternative to ``group_by``, ``dict`` used for more complex queries. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``filter`` | ``dict`` used for filtering queryset. | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``organization_field`` | (``str``) If the model does not have a direct relation with the | |
| | | | ``Organization`` model, then indirect relation can be specified using | |
| | | | this property. E.g.: ``device__organization_id``. | |
| | +------------------------+---------------------------------------------------------------------------+ |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``colors`` | An **optional** ``dict`` which can be used to define colors for each distinct |
| | value shown in the pie charts. |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``labels`` | An **optional** ``dict`` which can be used to define translatable strings for each distinct |
| | value shown in the pie charts. Can be used also to provide fallback human readable values for |
| | raw values stored in the database which would be otherwise hard to understand for the user. |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``filters`` | An **optional** ``dict`` which can be used when using ``aggregate`` and ``annotate`` in |
| | ``query_params`` to define the link that will be generated to filter results (pie charts are |
| | clickable and clicking on a portion of it will show the filtered results). |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``main_filters`` | An **optional** ``dict`` which can be used to add additional filtering on the target link. |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``filtering`` | An **optional** ``str`` which can be set to ``'False'`` (str) to disable filtering on target links. |
| | This is useful when clicking on any section of the chart should take user to the same URL. |
+------------------+---------------------------------------------------------------------------------------------------------+
| ``quick_link`` | An **optional** ``dict`` which contains configuration for the quick link button rendered |
| | below the chart. |
| | |
| | **NOTE**: The chart legend is disabled if configuration for quick link button is provided. |
| | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | **Property** | **Description** | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``url`` | (``str``) URL for the anchor tag | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``label`` | (``str``) Label shown on the button | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``title`` | (``str``) Title attribute of the button element | |
| | +------------------------+---------------------------------------------------------------------------+ |
| | | ``custom_css_classes`` | (``list``) List of CSS classes that'll be applied on the button | |
| | +------------------------+---------------------------------------------------------------------------+ |
+------------------+---------------------------------------------------------------------------------------------------------+
Code example:
.. code-block:: python
from immunity_utils.admin_theme import register_dashboard_chart
register_dashboard_chart(
position=1,
config={
'query_params': {
'name': 'Operator Project Distribution',
'app_label': 'test_project',
'model': 'operator',
'group_by': 'project__name',
},
'colors': {'Utils': 'red', 'User': 'orange'},
'quick_link': {
'url': '/admin/test_project/operator',
'label': 'Open Operators list',
'title': 'View complete list of operators',
'custom_css_classes': ['negative-top-20'],
},
},
)
For real world examples, look at the code of
`Immunity Controller <https://github.com/edge-servers/immunity-controller>`__
and `Immunity Monitoring <https://github.com/edge-servers/immunity-monitoring>`_.
**Note**: an ``ImproperlyConfigured`` exception is raised if a
dashboard element is already registered at same position.
It is recommended to register dashboard charts from the ``ready`` method
of the AppConfig of the app where the models are defined.
Checkout `app.py of the test_project
<https://github.com/edge-servers/immunity-utils/blob/master/tests/test_project/apps.py>`_
for reference.
``unregister_dashboard_chart``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This function can used to remove a chart from the dashboard.
**Syntax:**
.. code-block:: python
unregister_dashboard_chart(chart_name)
+------------------+---------------------------------------------------+
| **Parameter** | **Description** |
+------------------+---------------------------------------------------+
| ``chart_name`` | (``str``) The name of the chart to remove. |
+------------------+---------------------------------------------------+
Code example:
.. code-block:: python
from immunity_utils.admin_theme import unregister_dashboard_chart
unregister_dashboard_chart('Operator Project Distribution')
**Note**: an ``ImproperlyConfigured`` exception is raised the
specified dashboard chart is not registered.
Main navigation menu
--------------------
The ``admin_theme`` sub app of this package provides a navigation menu that can be
manipulated with the functions described in the next sections.
Add ``immunity_utils.admin_theme.context_processor.menu_groups`` to
template ``context_processors`` in ``settings.py`` as shown below.
.. code-block:: python
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'OPTIONS': {
'loaders': [
# ... omitted ...
],
'context_processors': [
# ... other context processors ...
'immunity_utils.admin_theme.context_processor.menu_groups' # <----- add this
],
},
},
]
``register_menu_group``
^^^^^^^^^^^^^^^^^^^^^^^
Allows registering a new menu item or group at the specified position in the Main Navigation Menu.
**Syntax:**
.. code-block:: python
register_menu_group(position, config)
+--------------------+-------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+-------------------------------------------------------------+
| ``position`` | (``int``) Position of the group or item. |
+--------------------+-------------------------------------------------------------+
| ``config`` | (``dict``) Configuration of the goup or item. |
+--------------------+-------------------------------------------------------------+
Code example:
.. code-block:: python
from django.utils.translation import ugettext_lazy as _
from immunity_utils.admin_theme.menu import register_menu_group
register_menu_group(
position=1,
config={
'label': _('My Group'),
'items': {
1: {
'label': _('Users List'),
'model': 'auth.User',
'name': 'changelist',
'icon': 'list-icon',
},
2: {
'label': _('Add User'),
'model': 'auth.User',
'name': 'add',
'icon': 'add-icon',
},
},
'icon': 'user-group-icon',
},
)
register_menu_group(
position=2,
config={
'model': 'test_project.Shelf',
'name': 'changelist',
'label': _('View Shelf'),
'icon': 'shelf-icon',
},
)
register_menu_group(
position=3, config={'label': _('My Link'), 'url': 'https://link.com'}
)
.. note::
An ``ImproperlyConfigured`` exception is raised if a menu element is already registered at the same position.
An ``ImproperlyConfigured`` exception is raised if the supplied configuration does not match with the different types of
possible configurations available (different configurations will be discussed in the next section).
It is recommended to use ``register_menu_group`` in the ``ready`` method of the ``AppConfig``.
``register_menu_items`` is obsoleted by ``register_menu_group`` and will be removed in
future versions. Links added using ``register_menu_items`` will be shown at the top
of navigation menu and above any ``register_menu_group`` items.
Adding a custom link
~~~~~~~~~~~~~~~~~~~~~
To add a link that contains a custom URL the following syntax can be used.
**Syntax:**
.. code-block:: python
register_menu_group(position=1, config={
"label": "Link Label",
"url": "link_url",
"icon": "my-icon"
})
Following is the description of the configuration:
+------------------+--------------------------------------------------------------+
| **Parameter** | **Description** |
+------------------+--------------------------------------------------------------+
| ``label`` | (``str``) Display text for the link. |
+------------------+--------------------------------------------------------------+
| ``url`` | (``str``) url for the link. |
+------------------+--------------------------------------------------------------+
| ``icon`` | An **optional** ``str`` CSS class name for the icon. No icon |
| | is displayed if not provided. |
+------------------+--------------------------------------------------------------+
Adding a model link
~~~~~~~~~~~~~~~~~~~
To add a link that contains URL of add form or change list page of a model
then following syntax can be used. Users will only be able to see links for
models they have permission to either view or edit.
**Syntax:**
.. code-block:: python
# add a link of list page
register_menu_group(
position=1,
config={
'model': 'my_project.MyModel',
'name': 'changelist',
'label': 'MyModel List',
'icon': 'my-model-list-class',
},
)
# add a link of add page
register_menu_group(
position=2,
config={
'model': 'my_project.MyModel',
'name': 'add',
'label': 'MyModel Add Item',
'icon': 'my-model-add-class',
},
)
Following is the description of the configuration:
+------------------+--------------------------------------------------------------+
| **Parameter** | **Description** |
+------------------+--------------------------------------------------------------+
| ``model`` | (``str``) Model of the app for which you to add link. |
+------------------+--------------------------------------------------------------+
| ``name`` | (``str``) url name. eg. changelist or add. |
+------------------+--------------------------------------------------------------+
| ``label`` | An **optional** ``str`` display text for the link. It is |
| | automatically generated if not provided. |
+------------------+--------------------------------------------------------------+
| ``icon`` | An **optional** ``str`` CSS class name for the icon. No icon |
| | is displayed if not provided. |
+------------------+--------------------------------------------------------------+
Adding a menu group
~~~~~~~~~~~~~~~~~~~
To add a nested group of links in the menu the following syntax can be used.
It creates a dropdown in the menu.
**Syntax:**
.. code-block:: python
register_menu_group(
position=1,
config={
'label': 'My Group Label',
'items': {
1: {'label': 'Link Label', 'url': 'link_url', 'icon': 'my-icon'},
2: {
'model': 'my_project.MyModel',
'name': 'changelist',
'label': 'MyModel List',
'icon': 'my-model-list-class',
},
},
'icon': 'my-group-icon-class',
},
)
Following is the description of the configuration:
+------------------+--------------------------------------------------------------+
| **Parameter** | **Description** |
+------------------+--------------------------------------------------------------+
| ``label`` | (``str``) Display name for the link. |
+------------------+--------------------------------------------------------------+
| ``items`` | (``dict``) Items to be displayed in the dropdown. |
| | It can be a dict of custom links or model links |
| | with key as their position in the group. |
+------------------+--------------------------------------------------------------+
| ``icon`` | An **optional** ``str`` CSS class name for the icon. No icon |
| | is displayed if not provided. |
+------------------+--------------------------------------------------------------+
``register_menu_subitem``
^^^^^^^^^^^^^^^^^^^^^^^^^
Allows adding an item to a registered group.
**Syntax:**
.. code-block:: python
register_menu_subitem(group_position, item_position, config)
+--------------------------+----------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------------+----------------------------------------------------------------+
| ``group_position`` | (``int``) Position of the group in which item should be added. |
+--------------------------+----------------------------------------------------------------+
| ``item_position`` | (``int``) Position at which item should be added in the group |
+--------------------------+----------------------------------------------------------------+
| ``config`` | (``dict``) Configuration of the item. |
+--------------------------+----------------------------------------------------------------+
Code example:
.. code-block:: python
from django.utils.translation import ugettext_lazy as _
from immunity_utils.admin_theme.menu import register_menu_subitem
# To register a model link
register_menu_subitem(
group_position=10,
item_position=2,
config={
'label': _('Users List'),
'model': 'auth.User',
'name': 'changelist',
'icon': 'list-icon',
},
)
# To register a custom link
register_menu_subitem(
group_position=10,
item_position=2,
config={'label': _('My Link'), 'url': 'https://link.com'},
)
.. note::
An ``ImproperlyConfigured`` exception is raised if the group is not already
registered at ``group_position``.
An ``ImproperlyConfigured`` exception is raised if the group already has an
item registered at ``item_position``.
It is only possible to register links to specific models or custom URL.
An ``ImproperlyConfigured`` exception is raised if the configuration of
group is provided in the function.
It is recommended to use ``register_menu_subitem`` in the ``ready``
method of the ``AppConfig``.
How to use custom icons in the menu
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create a CSS file and use the following syntax to provide the image for each
icon used in the menu. The CSS class name should be the same as the ``icon``
parameter used in the configuration of a menu item or group. Also icon being used
should be in ``svg`` format.
Example:
.. code-block:: css
.icon-class-name {
mask-image: url(imageurl);
-webkit-mask-image: url(imageurl);
}
Follow the instructions in
`Supplying custom CSS and JS for the admin theme <#supplying-custom-css-and-js-for-the-admin-theme>`_
to know how to configure your Immunity instance to load custom CSS files.
Admin filters
-------------
.. figure:: https://github.com/edge-servers/immunity-utils/raw/media/docs/filter.gif
:align: center
The ``admin_theme`` sub app provides an improved UI for the changelist filter
which occupies less space compared to the original implementation in django:
filters are displayed horizontally on the top (instead of vertically on the side)
and filter options are hidden in dropdown menus which are expanded once clicked.
Multiple filters can be applied at same time with the help of "apply filter" button.
This button is only visible when total number of filters is greater than 4.
When filters in use are less or equal to 4 the "apply filter" button is not visible
and filters work like in the original django implementation
(as soon as a filter option is selected the filter is applied and the page is reloaded).
Model utilities
---------------
``immunity_utils.base.UUIDModel``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Model class which provides a UUID4 primary key.
``immunity_utils.base.TimeStampedEditableModel``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Model class inheriting ``UUIDModel`` which provides two additional fields:
- ``created``
- ``modified``
Which use respectively ``AutoCreatedField``, ``AutoLastModifiedField`` from ``model_utils.fields``
(self-updating fields providing the creation date-time and the last modified date-time).
``immunity_utils.base.FallBackModelMixin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Model mixin that implements ``get_field_value`` method which can be used
to get value of fallback fields.
Custom Fields
-------------
This section describes custom fields defined in ``immunity_utils.fields``
that can be used in Django models:
``immunity_utils.fields.KeyField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A model field which provides a random key or token, widely used across immunity modules.
``immunity_utils.fields.FallbackBooleanChoiceField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This field extends Django's `BooleanField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#booleanfield>`_
and provides additional functionality for handling choices with a fallback value.
The field will use the **fallback value** whenever the field is set to ``None``.
This field is particularly useful when you want to present a choice between enabled
and disabled options, with an additional "Default" option that reflects the fallback value.
.. code-block:: python
from django.db import models
from immunity_utils.fields import FallbackBooleanChoiceField
from myapp import settings as app_settings
class MyModel(models.Model):
is_active = FallbackBooleanChoiceField(
null=True,
blank=True,
default=None,
fallback=app_settings.IS_ACTIVE_FALLBACK,
)
``immunity_utils.fields.FallbackCharChoiceField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This field extends Django's `CharField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#charfield>`_
and provides additional functionality for handling choices with a fallback value.
The field will use the **fallback value** whenever the field is set to ``None``.
.. code-block:: python
from django.db import models
from immunity_utils.fields import FallbackCharChoiceField
from myapp import settings as app_settings
class MyModel(models.Model):
is_first_name_required = FallbackCharChoiceField(
null=True,
blank=True,
max_length=32,
choices=(
('disabled', _('Disabled')),
('allowed', _('Allowed')),
('mandatory', _('Mandatory')),
),
fallback=app_settings.IS_FIRST_NAME_REQUIRED,
)
``immunity_utils.fields.FallbackCharField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This field extends Django's `CharField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#charfield>`_
and provides additional functionality for handling text fields with a fallback value.
It allows populating the form with the fallback value when the actual value is set to ``null`` in the database.
.. code-block:: python
from django.db import models
from immunity_utils.fields import FallbackCharField
from myapp import settings as app_settings
class MyModel(models.Model):
greeting_text = FallbackCharField(
null=True,
blank=True,
max_length=200,
fallback=app_settings.GREETING_TEXT,
)
``immunity_utils.fields.FallbackURLField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This field extends Django's `URLField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#urlfield>`_
and provides additional functionality for handling URL fields with a fallback value.
It allows populating the form with the fallback value when the actual value is set to ``null`` in the database.
.. code-block:: python
from django.db import models
from immunity_utils.fields import FallbackURLField
from myapp import settings as app_settings
class MyModel(models.Model):
password_reset_url = FallbackURLField(
null=True,
blank=True,
max_length=200,
fallback=app_settings.DEFAULT_PASSWORD_RESET_URL,
)
``immunity_utils.fields.FallbackTextField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This extends Django's `TextField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.TextField>`_
and provides additional functionality for handling text fields with a fallback value.
It allows populating the form with the fallback value when the actual value is set to ``null`` in the database.
.. code-block:: python
from django.db import models
from immunity_utils.fields import FallbackTextField
from myapp import settings as app_settings
class MyModel(models.Model):
extra_config = FallbackTextField(
null=True,
blank=True,
max_length=200,
fallback=app_settings.EXTRA_CONFIG,
)
``immunity_utils.fields.FallbackPositiveIntegerField``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This extends Django's `PositiveIntegerField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#positiveintegerfield>`_
and provides additional functionality for handling positive integer fields with a fallback value.
It allows populating the form with the fallback value when the actual value is set to ``null`` in the database.
.. code-block:: python
from django.db import models
from immunity_utils.fields import FallbackPositiveIntegerField
from myapp import settings as app_settings
class MyModel(models.Model):
count = FallbackPositiveIntegerField(
blank=True,
null=True,
fallback=app_settings.DEFAULT_COUNT,
)
Admin utilities
---------------
``immunity_utils.admin.TimeReadonlyAdminMixin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Admin mixin which adds two readonly fields ``created`` and ``modified``.
This is an admin mixin for models inheriting ``TimeStampedEditableModel``
which adds the fields ``created`` and ``modified`` to the database.
``immunity_utils.admin.ReadOnlyAdmin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A read-only ``ModelAdmin`` base class.
Will include the ``id`` field by default, which can be excluded by supplying
the ``exclude`` attribute, eg:
.. code-block:: python
from immunity_utils.admin import ReadOnlyAdmin
class PostAuthReadOnlyAdmin(ReadOnlyAdmin):
exclude = ['id']
``immunity_utils.admin.AlwaysHasChangedMixin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A mixin designed for inline items and model forms, ensures the item
is created even if the default values are unchanged.
Without this, when creating new objects, inline items won't be saved
unless users change the default values.
``immunity_utils.admin.CopyableFieldsAdmin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An admin class that allows to set admin fields to be
read-only and makes it easy to copy the fields contents.
Useful for auto-generated fields such as UUIDs, secret keys, tokens, etc.
``immunity_utils.admin.UUIDAdmin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This class is a subclass of ``CopyableFieldsAdmin`` which
sets ``uuid`` as the only copyable field. This class is kept
for backward compatibility and convenience, since different models
of various Immunity modules show ``uuid`` as the only copyable field.
``immunity_utils.admin.ReceiveUrlAdmin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An admin class that provides an URL as a read-only input field
(to make it easy and quick to copy/paste).
``immunity_utils.admin.HelpTextStackedInline``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. figure:: https://github.com/edge-servers/immunity-utils/raw/media/docs/help-text-stacked-inline.png
:align: center
A stacked inline admin class that displays a help text for entire
inline object. Following is an example:
.. code-block:: python
from immunity_utils.admin import HelpTextStackedInline
class SubnetDivisionRuleInlineAdmin(
MultitenantAdminMixin, TimeReadonlyAdminMixin, HelpTextStackedInline
):
model = Model
# It is required to set "help_text" attribute
help_text = {
# (required) Help text to display
'text': _(
'Please keep in mind that once the subnet division rule is created '
'and used, changing "Size" and "Number of Subnets" and decreasing '
'"Number of IPs" will not be possible.'
),
# (optional) You can provide a link to documentation for user reference
'documentation_url': (
'https://github.com/edge-servers/immunity-utils'
),
# (optional) Icon to be shown along with help text. By default it uses
# "/static/admin/img/icon-alert.svg"
'image_url': '/static/admin/img/icon-alert.svg'
}
``immunity_utils.admin_theme.filters.InputFilter``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``admin_theme`` sub app of this package provides an input filter that can be used in changelist page
to filter ``UUIDField`` or ``CharField``.
Code example:
.. code-block:: python
from django.contrib import admin
from immunity_utils.admin_theme.filters import InputFilter
from my_app.models import MyModel
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_filter = [
('my_field', InputFilter),
'other_field',
# ...
]
By default ``InputFilter`` use exact lookup to filter items which matches to the value being
searched by the user. But this behavior can be changed by modifying ``InputFilter`` as following:
.. code-block:: python
from django.contrib import admin
from immunity_utils.admin_theme.filters import InputFilter
from my_app.models import MyModel
class MyInputFilter(InputFilter):
lookup = 'icontains'
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_filter = [
('my_field', MyInputFilter),
'other_field',
# ...
]
To know about other lookups that can be used please check
`Django Lookup API Reference <https://docs.djangoproject.com/en/3.2/ref/models/lookups/#django.db.models.Lookup>`__
``immunity_utils.admin_theme.filters.SimpleInputFilter``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A stripped down version of ``immunity_utils.admin_theme.filters.InputFilter`` that provides
flexibility to customize filtering. It can be used to filter objects using indirectly
related fields.
The derived filter class should define the ``queryset`` method as shown in following example:
.. code-block:: python
from django.contrib import admin
from immunity_utils.admin_theme.filters import SimpleInputFilter
from my_app.models import MyModel
class MyInputFilter(SimpleInputFilter):
parameter_name = 'shelf'
title = _('Shelf')
def queryset(self, request, queryset):
if self.value() is not None:
return queryset.filter(name__icontains=self.value())
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_filter = [
MyInputFilter,
'other_field',
# ...
]
``immunity_utils.admin_theme.filters.AutocompleteFilter``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``admin_theme`` sub app of this package provides an auto complete
filter that uses django autocomplete widget to load filter data asynchronously.
This filter can be helpful when the number of objects is too large
to load all at once which may cause the slow loading of the page.
.. code-block:: python
from django.contrib import admin
from immunity_utils.admin_theme.filters import AutocompleteFilter
from my_app.models import MyModel, MyOtherModel
class MyAutoCompleteFilter(AutocompleteFilter):
field_name = 'field'
parameter_name = 'field_id'
title = _('My Field')
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
list_filter = [
MyAutoCompleteFilter,
...
]
@admin.register(MyOtherModel)
class MyOtherModelAdmin(admin.ModelAdmin):
search_fields = ['id']
To customize or know more about it, please refer to the
`django-admin-autocomplete-filter documentation
<https://github.com/farhan0581/django-admin-autocomplete-filter#usage>`_.
Code utilities
--------------
``immunity_utils.utils.get_random_key``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Generates an random string of 32 characters.
``immunity_utils.utils.deep_merge_dicts``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Returns a new ``dict`` which is the result of the merge of the two dictionaries,
all elements are deep-copied to avoid modifying the original data structures.
Usage:
.. code-block:: python
from immunity_utils.utils import deep_merge_dicts
mergd_dict = deep_merge_dicts(dict1, dict2)
``immunity_utils.utils.default_or_test``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If the program is being executed during automated tests the value supplied in
the ``test`` argument will be returned, otherwise the one supplied in the
``value`` argument is returned.
.. code-block:: python
from immunity_utils.utils import default_or_test
THROTTLE_RATE = getattr(
settings,
'THROTTLE_RATE',
default_or_test(value='20/day', test=None),
)
``immunity_utils.utils.print_color``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default colors**: ``['white_bold', 'green_bold', 'yellow_bold', 'red_bold']``
If you want to print a string in ``Red Bold``, you can do it as below.
.. code-block:: python
from immunity_utils.utils import print_color
print_color('This is the printed in Red Bold', color_name='red_bold')
You may also provide the ``end`` arguement similar to built-in print method.
``immunity_utils.utils.SorrtedOrderedDict``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Extends ``collections.SortedDict`` and implements logic to sort inserted
items based on ``key`` value. Sorting is done at insert operation which
incurs memory space overhead.
``immunity_utils.tasks.ImmunityCeleryTask``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A custom celery task class that sets hard and soft time limits of celery tasks
using `IMMUNITY
_CELERY_HARD_TIME_LIMIT <#immunity_celery_hard_time_limit>`_
and `IMMUNITY
_CELERY_SOFT_TIME_LIMIT <#immunity_celery_soft_time_limit>`_
settings respectively.
Usage:
.. code-block:: python
from celery import shared_task
from immunity_utils.tasks import ImmunityCeleryTask
@shared_task(base=ImmunityCeleryTask)
def your_celery_task():
pass
**Note:** This task class should be used for regular background tasks
but not for complex background tasks which can take a long time to execute
(eg: firmware upgrades, network operations with retry mechanisms).
``immunity_utils.utils.retryable_request``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A utility function for making HTTP requests with built-in retry logic.
This function is useful for handling transient errors encountered during HTTP
requests by automatically retrying failed requests with exponential backoff.
It provides flexibility in configuring various retry parameters to suit
different use cases.
Usage:
.. code-block:: python
from immunity_utils.utils import retryable_request
response = retryable_request(
method='GET',
url='https://immunity.org',
timeout=(4, 8),
max_retries=3,
backoff_factor=1,
backoff_jitter=0.0,
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=('HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST'),
retry_kwargs=None,
headers={'Authorization': 'Bearer token'}
)
**Paramters:**
- ``method`` (str): The HTTP method to be used for the request in lower
case (e.g., 'get', 'post', etc.).
- ``timeout`` (tuple): A tuple containing two elements: connection timeout
and read timeout in seconds (default: (4, 8)).
- ``max_retries`` (int): The maximum number of retry attempts in case of
request failure (default: 3).
- ``backoff_factor`` (float): A factor by which the retry delay increases
after each retry (default: 1).
- ``backoff_jitter`` (float): A jitter to apply to the backoff factor to prevent
retry storms (default: 0.0).
- ``status_forcelist`` (tuple): A tuple of HTTP status codes for which retries
should be attempted (default: (429, 500, 502, 503, 504)).
- ``allowed_methods`` (tuple): A tuple of HTTP methods that are allowed for
the request (default: ('HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST')).
- ``retry_kwargs`` (dict): Additional keyword arguments to be passed to the
retry mechanism (default: None).
- ``**kwargs``: Additional keyword arguments to be passed to the underlying request
method (e.g. 'headers', etc.).
Note: This method will raise a requests.exceptions.RetryError if the request
remains unsuccessful even after all retry attempts have been exhausted.
This exception indicates that the operation could not be completed successfully
despite the retry mechanism.
Storage utilities
-----------------
``immunity_utils.storage.CompressStaticFilesStorage``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A static storage backend for compression inheriting from `django-compress-staticfiles's <https://pypi.org/project/django-compress-staticfiles/>`_ ``CompressStaticFilesStorage`` class.
Adds support for excluding file types using `IMMUNITY
_STATICFILES_VERSIONED_EXCLUDE <#immunity_staticfiles_versioned_exclude>`_ setting.
To use point ``STATICFILES_STORAGE`` to ``immunity_utils.storage.CompressStaticFilesStorage`` in ``settings.py``.
.. code-block:: python
STATICFILES_STORAGE = 'immunity_utils.storage.CompressStaticFilesStorage'
Admin Theme utilities
---------------------
``immunity_utils.admin_theme.email.send_email``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This function allows sending email in both plain text and HTML version (using the template
and logo that can be customised using `IMMUNITY
_EMAIL_TEMPLATE <#immunity_email_template>`_
and `IMMUNITY
_EMAIL_LOGO <#immunity_email_logo>`_ respectively).
In case the HTML version if not needed it may be disabled by
setting `IMMUNITY
_HTML_EMAIL <#immunity_html_email>`_ to ``False``.
**Syntax:**
.. code-block:: python
send_email(subject, body_text, body_html, recipients, **kwargs)
+--------------------+--------------------------------------------------------------------------------------------+
| **Parameter** | **Description** |
+--------------------+--------------------------------------------------------------------------------------------+
| ``subject`` | (``str``) The subject of the email template. |
+--------------------+--------------------------------------------------------------------------------------------+
| ``body_text`` | (``str``) The body of the text message to be emailed. |
+--------------------+--------------------------------------------------------------------------------------------+
| ``body_html`` | (``str``) The body of the html template to be emailed. |
+--------------------+--------------------------------------------------------------------------------------------+
| ``recipients`` | (``list``) The list of recipients to send the mail to. |
+--------------------+--------------------------------------------------------------------------------------------+
| ``extra_context`` | **optional** (``dict``) Extra context which is passed to the template. |
| | The dictionary keys ``call_to_action_text`` and ``call_to_action_url`` |
| | can be passed to show a call to action button. |
| | Similarly, ``footer`` can be passed to add a footer. |
+--------------------+--------------------------------------------------------------------------------------------+
| ``**kwargs`` | Any additional keyword arguments (e.g. ``attachments``, ``headers``, etc.) |
| | are passed directly to the `django.core.mail.EmailMultiAlternatives |
| | <https://docs.djangoproject.com/en/4.1/topics/email/#sending-alternative-content-types>`_. |
+--------------------+--------------------------------------------------------------------------------------------+
**Note**: Data passed in body should be validated and user supplied data should not be sent directly to the function.
REST API utilities
------------------
``immunity_utils.api.serializers.ValidatedModelSerializer``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A model serializer which calls the model instance ``full_clean()``.
``immunity_utils.api.apps.ApiAppConfig``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you're creating an Immunity module which provides a REST API built with Django REST Framework,
chances is that you may need to define some default settings to control its throttling or other aspects.
Here's how to easily do it:
.. code-block:: python
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from immunity_utils.api.apps import ApiAppConfig
class MyModuleConfig(ApiAppConfig):
name = 'my_immunity_module'
label = 'my_module'
verbose_name = _('My Immunity Module')
# assumes API is enabled by default
API_ENABLED = getattr(settings, 'MY_IMMUNITY
_MODULE_API_ENABLED', True)
# set throttling rates for your module here
REST_FRAMEWORK_SETTINGS = {
'DEFAULT_THROTTLE_RATES': {'my_module': '400/hour'},
}
Every immunity module which has an API should use this class to configure
its own default settings, which will be merged with the settings of the other
modules.
Test utilities
--------------
``immunity_utils.tests.catch_signal``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This method can be used to mock a signal call inorder to easily verify
that the signal has been called.
Usage example as a context-manager:
.. code-block:: python
from immunity_utils.tests import catch_signal
with catch_signal(immunity_signal) as handler:
model_instance.trigger_signal()
handler.assert_called_once_with(
arg1='value1',
arg2='value2',
sender=ModelName,
signal=immunity_signal,
)
``immunity_utils.tests.TimeLoggingTestRunner``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. figure:: https://raw.githubusercontent.com/immunity/immunity-utils/master/docs/TimeLoggingTestRunner.png
:align: center
This class extends the `default test runner provided by Django <https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEST_RUNNER>`_
and logs the time spent by each test, making it easier to spot slow tests by highlighting
time taken by it in yellow (time shall be highlighted in red if it crosses the second threshold).
By default tests are considered slow if they take more than 0.3 seconds but you can control
this with `IMMUNITY
_SLOW_TEST_THRESHOLD <#immunity_slow_test_threshold>`_.
In order to switch to this test runner you have set the following in your `settings.py`:
.. code-block:: python
TEST_RUNNER = 'immunity_utils.tests.TimeLoggingTestRunner'
``immunity_utils.tests.capture_stdout``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This decorator can be used to capture standard output produced by tests,
either to silence it or to write assertions.
Example usage:
.. code-block:: python
from immunity_utils.tests import capture_stdout
@capture_stdout()
def test_something(self):
function_generating_output() # pseudo code
@capture_stdout()
def test_something_again(self, captured_ouput):
# pseudo code
function_generating_output()
# now you can create assertions on the captured output
self.assertIn('expected stdout', captured_ouput.getvalue())
# if there are more than one assertions, clear the captured output first
captured_error.truncate(0)
captured_error.seek(0)
# you can create new assertion now
self.assertIn('another output', captured_ouput.getvalue())
**Notes**:
- If assertions need to be made on the captured output, an additional argument
(in the example above is named ``captured_output``) can be passed as an argument
to the decorated test method, alternatively it can be omitted.
- A ``StingIO`` instance is used for capturing output by default but if needed
it's possible to pass a custom ``StringIO`` instance to the decorator function.
``immunity_utils.tests.capture_stderr``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Equivalent to ``capture_stdout``, but for standard error.
Example usage:
.. code-block:: python
from immunity_utils.tests import capture_stderr
@capture_stderr()
def test_error(self):
function_generating_error() # pseudo code
@capture_stderr()
def test_error_again(self, captured_error):
# pseudo code
function_generating_error()
# now you can create assertions on captured error
self.assertIn('expected error', captured_error.getvalue())
# if there are more than one assertions, clear the captured error first
captured_error.truncate(0)
captured_error.seek(0)
# you can create new assertion now
self.assertIn('another expected error', captured_error.getvalue())
``immunity_utils.tests.capture_any_output``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Equivalent to ``capture_stdout`` and ``capture_stderr``, but captures both types of
output (standard output and standard error).
Example usage:
.. code-block:: python
from immunity_utils.tests import capture_any_output
@capture_any_output()
def test_something_out(self):
function_generating_output() # pseudo code
@capture_any_output()
def test_out_again(self, captured_output, captured_error):
# pseudo code
function_generating_output_and_errors()
# now you can create assertions on captured error
self.assertIn('expected stdout', captured_output.getvalue())
self.assertIn('expected stderr', captured_error.getvalue())
``immunity_utils.tests.AssertNumQueriesSubTestMixin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This mixin overrides the
`assertNumQueries <https://docs.djangoproject.com/en/dev/topics/testing/tools/#django.test.TransactionTestCase.assertNumQueries>`_
assertion from the django test case to run in a ``subTest`` so that the
query check does not block the whole test if it fails.
Example usage:
.. code-block:: python
from django.test import TestCase
from immunity_utils.tests import AssertNumQueriesSubTestMixin
class MyTest(AssertNumQueriesSubTestMixin, TestCase):
def my_test(self):
with self.assertNumQueries(2):
MyModel.objects.count()
# the assertion above will fail but this line will be executed
print('This will be printed anyway.')
``immunity_utils.test_selenium_mixins.SeleniumTestMixin``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This mixin provides basic setup for Selenium tests with method to
open URL and login and logout a user.
Database backends
-----------------
``immunity_utils.db.backends.spatialite``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This backend extends ``django.contrib.gis.db.backends.spatialite``
database backend to implement a workaround for handling
`issue with sqlite 3.36 and spatialite 5 <https://code.djangoproject.com/ticket/32935>`_.
Collection of Usage Metrics
---------------------------
The ``immunity-utils`` module includes an optional
sub-app ``immunity_utils.metric_collection``,
which allows us to collect of the following information
from Immunity instances:
- Immunity Version
- List of enabled Immunity modules and their version
- Operating System identifier, e.g.:
Linux version, Kernel version, target platform (e.g. x86)
- Installation method, if available, e.g. `ansible-immunity2
<https://github.com/edge-servers/ansible-immunity2>`_
or `docker-immunity <https://github.com/edge-servers/docker-immunity>`_
The data above is collected during the following events:
- **Install**: when Immunity is installed the first time
- **Upgrade**: when any Immunity module is upgraded
- **Heartbeat**: once every 24 hours
We collect data on Immunity usage to gauge user engagement, satisfaction,
and upgrade patterns. This informs our development decisions, ensuring
continuous improvement aligned with user needs.
To enhance our understanding and management of this data, we have
integrated `Clean Insights <https://cleaninsights.org/>`_, a privacy-preserving
analytics tool. Clean Insights allows us to responsibly gather and analyze
usage metrics without compromising user privacy. It provides us with the
means to make data-driven decisions while respecting our users' rights and trust.
We have taken great care to ensure no
sensitive or personal data is being tracked.
Opting out from metric collection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can opt-out from sharing this data any time from the "System Info" page.
Alternatively, you can also remove the ``immunity_utils.metric_collection``
app from ``INSTALLED_APPS`` in one of the following ways:
- If you are using the `ansible-immunity2
<https://github.com/edge-servers/ansible-immunity2>`_ role, you can set the
variable ``immunity2_usage_metric_collection`` to ``false`` in your playbook.
- If you are using `docker-immunity
<https://github.com/edge-servers/docker-immunity>`_, you can set set the
environment variable ``METRIC_COLLECTION`` to ``False`` in the ``.env`` file.
However, it would be very helpful to the project if you keep the
colection of these metrics enabled, because the feedback we get from
this data is useful to guide the project in the right direction.
Quality Assurance Checks
------------------------
This package contains some common QA checks that are used in the
automated builds of different Immunity modules.
``immunity-qa-format``
^^^^^^^^^^^^^^^^^^^^^^
This shell script automatically formats Python and CSS code according
to the `Immunity coding style conventions <https://immunity.io/docs/developer/contributing.html#coding-style-conventions>`_.
It runs ``isort`` and ``black`` to format python code
(these two dependencies are required and installed automatically when running
``pip install immunity-utils[qa]``).
The ``stylelint`` and ``jshint`` programs are used to perform style checks on CSS and JS code respectively, but they are optional:
if ``stylelint`` and/or ``jshint`` are not installed, the check(s) will be skipped.
``immunity-qa-check``
^^^^^^^^^^^^^^^^^^^^^
Shell script to run the following quality assurance checks:
* `checkmigrations <#checkmigrations>`_
* `checkcommit <#checkcommit>`_
* `checkendline <#checkendline>`_
* `checkpendingmigrations <#checkpendingmigrations>`_
* `checkrst <#checkrst>`_
* ``flake8`` - Python code linter
* ``isort`` - Sorts python imports alphabetically, and seperated into sections
* ``black`` - Formats python code using a common standard
* ``csslinter`` - Formats and checks CSS code using stylelint common standard
* ``jslinter`` - Checks Javascript code using jshint common standard
If a check requires a flag, it can be passed forward in the same way.
Usage example::
immunity-qa-check --migration-path <path> --message <commit-message>
Any unneeded checks can be skipped by passing ``--skip-<check-name>``
Usage example::
immunity-qa-check --skip-isort
For backward compatibility ``csslinter`` and ``jslinter`` are skipped by default.
To run them in checks pass arguements in this way.
Usage example::
# To activate csslinter
immunity-qa-check --csslinter
# To activate jslinter
immunity-qa-check --jslinter
You can do multiple ``checkmigrations`` by passing the arguments with space-delimited string.
For example, this multiple ``checkmigrations``::
checkmigrations --migrations-to-ignore 3 \
--migration-path ./immunity_users/migrations/ || exit 1
checkmigrations --migrations-to-ignore 2 \
--migration-path ./tests/testapp/migrations/ || exit 1
Can be changed with::
immunity-qa-check --migrations-to-ignore "3 2" \
--migration-path "./immunity_users/migrations/ ./tests/testapp/migrations/"
``checkmigrations``
^^^^^^^^^^^^^^^^^^^
Ensures the latest migrations created have a human readable name.
We want to avoid having many migrations named like ``0003_auto_20150410_3242.py``.
This way we can reconstruct the evolution of our database schemas faster, with
less efforts and hence less costs.
Usage example::
checkmigrations --migration-path ./django_freeradius/migrations/
``checkcommit``
^^^^^^^^^^^^^^^
Ensures the last commit message follows our `commit message style guidelines
<http://immunity.io/docs/developer/contributing.html#commit-message-style-guidelines>`_.
We want to keep the commit log readable, consistent and easy to scan in order
to make it easy to analyze the history of our modules, which is also a very
important activity when performing maintenance.
Usage example::
checkcommit --message "$(git log --format=%B -n 1)"
If, for some reason, you wish to skip this QA check for a specific commit message
you can add ``#noqa`` to the end of your commit message.
Usage example::
[qa] Improved #20
Simulation of a special unplanned case
#noqa
``checkendline``
^^^^^^^^^^^^^^^^
Ensures that a blank line is kept at the end of each file.
``checkpendingmigrations``
^^^^^^^^^^^^^^^^^^^^^^^^^^
Ensures there django migrations are up to date and no new migrations need to
be created.
It accepts an optional ``--migration-module`` flag indicating the django app
name that should be passed to ``./manage.py makemigrations``, eg:
``./manage.py makemigrations $MIGRATION_MODULE``.
``checkrst``
^^^^^^^^^^^^^
Checks the syntax of all ReStructuredText files to ensure they can be published on pypi or using python-sphinx.
Settings
--------
``IMMUNITY
_ADMIN_SITE_CLASS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``immunity_utils.admin_theme.admin.ImmunityAdminSite``
If you need to use a customized admin site class, you can use this setting.
``IMMUNITY
_ADMIN_SITE_TITLE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``Immunity Admin``
Title value used in the ``<title>`` HTML tag of the admin site.
``IMMUNITY
_ADMIN_SITE_HEADER``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``Immunity``
Heading text used in the main ``<h1>`` HTML tag (the logo) of the admin site.
``IMMUNITY
_ADMIN_INDEX_TITLE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``Network administration``
Title shown to users in the index page of the admin site.
``IMMUNITY
_ADMIN_DASHBOARD_ENABLED``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``True``
When ``True``, enables the `Immunity Dashboard <#immunity-dashboard>`_.
Upon login, the user will be greeted with the dashboard instead of the default
Django admin index page.
``IMMUNITY
_ADMIN_THEME_LINKS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``[]``
**Note**: this setting requires
`the admin_theme_settings context processor <#supplying-custom-css-and-js-for-the-admin-theme>`_
in order to work.
Allows to override the default CSS and favicon, as well as add extra
<link> HTML elements if needed.
This setting overrides the default theme, you can reuse the default CSS or replace it entirely.
The following example shows how to keep using the default CSS,
supply an additional CSS and replace the favicon.
Example usage:
.. code-block:: python
IMMUNITY
_ADMIN_THEME_LINKS = [
{'type': 'text/css', 'href': '/static/admin/css/immunity.css', 'rel': 'stylesheet', 'media': 'all'},
{'type': 'text/css', 'href': '/static/admin/css/custom-theme.css', 'rel': 'stylesheet', 'media': 'all'},
{'type': 'image/x-icon', 'href': '/static/favicon.png', 'rel': 'icon'}
]
``IMMUNITY
_ADMIN_THEME_JS``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``[]``
Allows to pass a list of strings representing URLs of custom JS files to load.
Example usage:
.. code-block:: python
IMMUNITY
_ADMIN_THEME_JS = [
'/static/custom-admin-theme.js',
]
``IMMUNITY
_ADMIN_SHOW_USERLINKS_BLOCK``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``False``
When True, enables Django user links on the admin site.
i.e. (USER NAME/ VIEW SITE / CHANGE PASSWORD / LOG OUT).
These links are already shown in the main navigation menu and for this reason are hidden by default.
``IMMUNITY
_API_DOCS``
^^^^^^^^^^^^^^^^^^^^^
**default**: ``True``
Whether the OpenAPI documentation is enabled.
When enabled, you can view the available documentation using the
Swagger endpoint at ``/api/v1/docs/``.
You also need to add the following url to your project urls.py:
.. code-block:: python
urlpatterns += [
url(r'^api/v1/', include('immunity_utils.api.urls')),
]
``IMMUNITY
_API_INFO``
^^^^^^^^^^^^^^^^^^^^^
**default**:
.. code-block:: python
{
'title': 'Immunity API',
'default_version': 'v1',
'description': 'Immunity REST API',
}
Define OpenAPI general information.
NOTE: This setting requires ``IMMUNITY
_API_DOCS = True`` to take effect.
For more information about optional parameters check the
`drf-yasg documentation <https://drf-yasg.readthedocs.io/en/stable/readme.html#quickstart>`_.
``IMMUNITY
_SLOW_TEST_THRESHOLD``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``[0.3, 1]`` (seconds)
It can be used to change the thresholds used by `TimeLoggingTestRunner <#immunity_utilsteststimeloggingtestrunner>`_
to detect slow tests (0.3s by default) and highlight the slowest ones (1s by default) amongst them.
``IMMUNITY
_STATICFILES_VERSIONED_EXCLUDE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**default**: ``['leaflet/*/*.png']``
Allows to pass a list of **Unix shell-style wildcards** for files to be excluded by `CompressStaticFilesStorage <#immunity_utilsstorageCompressStaticFilesStorage>`_.
By default Leaflet PNGs have been excluded to avoid bugs like `immunity/ansible-immunity2#232 <https://github.com/edge-servers/ansible-immunity2/issues/232>`_.
Example usage:
.. code-block:: python
IMMUNITY
_STATICFILES_VERSIONED_EXCLUDE = [
'*png',
]
``IMMUNITY
_HTML_EMAIL``
^^^^^^^^^^^^^^^^^^^^^^^
+---------+----------+
| type | ``bool`` |
+---------+----------+
| default | ``True`` |
+---------+----------+
If ``True``, an HTML themed version of the email can be sent using
the `send_email <#immunity_utilsadmin_themeemailsend_email>`_ function.
``IMMUNITY
_EMAIL_TEMPLATE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
+---------+----------------------------------------+
| type | ``str`` |
+---------+----------------------------------------+
| default | ``immunity_utils/email_template.html`` |
+---------+----------------------------------------+
This setting allows to change the django template used for sending emails with
the `send_email <#immunity_utilsadmin_themeemailsend_email>`_ function.
It is recommended to extend the default email template as in the example below.
.. code-block:: django
{% extends 'immunity_utils/email_template.html' %}
{% block styles %}
{{ block.super }}
<style>
.background {
height: 100%;
background: linear-gradient(to bottom, #8ccbbe 50%, #3797a4 50%);
background-repeat: no-repeat;
background-attachment: fixed;
padding: 50px;
}
.mail-header {
background-color: #3797a4;
color: white;
}
</style>
{% endblock styles %}
Similarly, you can customize the HTML of the template by overriding the ``body`` block.
See `email_template.html <https://github.com/edge-servers/immunity-utils/blob/
master/immunity_utils/admin_theme/templates/immunity_utils/email_template.html>`_
for reference implementation.
``IMMUNITY
_EMAIL_LOGO``
^^^^^^^^^^^^^^^^^^^^^^^
+---------+-------------------------------------------------------------------------------------+
| type | ``str`` |
+---------+-------------------------------------------------------------------------------------+
| default | `Immunity logo <https://raw.githubusercontent.com/immunity/immunity-utils/master/ \ |
| | immunity_utils/static/immunity-utils/images/immunity-logo.png>`_ |
+---------+-------------------------------------------------------------------------------------+
This setting allows to change the logo which is displayed in HTML version of the email.
**Note**: Provide a URL which points to the logo on your own web server. Ensure that the URL provided is
publicly accessible from the internet. Otherwise, the logo may not be displayed in the email.
Please also note that SVG images do not get processed by some email clients
like Gmail so it is recommended to use PNG images.
``IMMUNITY
_CELERY_SOFT_TIME_LIMIT``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+---------+---------------------+
| type | ``int`` |
+---------+---------------------+
| default | ``30`` (in seconds) |
+---------+---------------------+
Sets the soft time limit for celery tasks using
`ImmunityCeleryTask <#immunity_utilstasksimmunitycelerytask>`_.
``IMMUNITY
_CELERY_HARD_TIME_LIMIT``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+---------+----------------------+
| type | ``int`` |
+---------+----------------------+
| default | ``120`` (in seconds) |
+---------+----------------------+
Sets the hard time limit for celery tasks using
`ImmunityCeleryTask <#immunity_utilstasksimmunitycelerytask>`_.
``IMMUNITY
_AUTOCOMPLETE_FILTER_VIEW``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+---------+-------------------------------------------------------------+
| type | ``str`` |
+---------+-------------------------------------------------------------+
| default | ``'immunity_utils.admin_theme.views.AutocompleteJsonView'`` |
+---------+-------------------------------------------------------------+
Dotted path to the ``AutocompleteJsonView`` used by the
``immunity_utils.admin_theme.filters.AutocompleteFilter``.
Installing for development
--------------------------
Install the system dependencies:
.. code-block:: shell
sudo apt-get install sqlite3 libsqlite3-dev
# For running E2E Selenium tests
sudo apt install chromium
Install your forked repo:
.. code-block:: shell
git clone git://github.com/<your_fork>/immunity-utils
cd immunity-utils/
pip install -e .[qa,rest]
Install test requirements:
.. code-block:: shell
pip install -r requirements-test.txt
Install node dependencies used for testing:
.. code-block:: shell
npm install -g stylelint jshint
Set up the pre-push hook to run tests and QA checks automatically right before the git push action, so that if anything fails the push operation will be aborted:
.. code-block:: shell
immunity-pre-push-hook --install
Install WebDriver for Chromium for your browser version from `<https://chromedriver.chromium.org/home>`_
and Extract ``chromedriver`` to one of directories from your ``$PATH`` (example: ``~/.local/bin/``).
Create database:
.. code-block:: shell
cd tests/
./manage.py migrate
./manage.py createsuperuser
Run development server:
.. code-block:: shell
cd tests/
./manage.py runserver
You can access the admin interface of the test project at http://127.0.0.1:8000/admin/.
Run tests with:
.. code-block:: shell
./runtests.py --parallel
Contributing
------------
Please refer to the `Immunity contributing guidelines <http://immunity.io/docs/developer/contributing.html>`_.
Support
-------
See `Immunity Support Channels <http://immunity.org/support.html>`_.
Changelog
---------
See `CHANGES <https://github.com/edge-servers/immunity-utils/blob/master/CHANGES.rst>`_.
License
-------
See `LICENSE <https://github.com/edge-servers/immunity-utils/blob/master/LICENSE>`_.
Attribution
-----------
- `Wireless icon <https://github.com/edge-servers/immunity-utils/blob/master/immunity_utils/admin_theme/static/ui/immunity/images/monitoring-wifi.svg>`_
is licensed by Gregbaker, under `CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>`_ ,
via `Wikimedia Commons <https://commons.wikimedia.org/wiki/File:Wireless-icon.svg>`_.
- `Roboto webfont <https://www.google.com/fonts/specimen/Roboto>`_ is licensed
under the `Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>`_.
WOFF files extracted using `<https://github.com/majodev/google-webfonts-helper>`_.
Raw data
{
"_id": null,
"home_page": "http://immunity.org",
"name": "immunity-utils",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "django, netjson, openwrt, networking, immunity",
"author": "Rohith Asrk",
"author_email": "rohith.asrk@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/f6/27/f387442ccb4f14d76c909a9e0cf698d2211ebf839816df7433321d1da6b0/immunity_utils-1.1a0.tar.gz",
"platform": "Platform Independent",
"description": "immunity-utils\n==============\n\n.. image:: https://github.com/edge-servers/immunity-utils/workflows/Immunity%20Utils%20CI%20Build/badge.svg?branch=master\n :target: https://github.com/edge-servers/immunity-utils/actions?query=workflow%3A%22Immunity+Utils+CI+Build%22\n :alt: CI build status\n\n.. image:: https://coveralls.io/repos/github/immunity/immunity-utils/badge.svg\n :target: https://coveralls.io/github/immunity/immunity-utils\n :alt: Test coverage\n\n.. image:: https://img.shields.io/librariesio/release/github/immunity/immunity-utils\n :target: https://libraries.io/github/immunity/immunity-utils#repository_dependencies\n :alt: Dependency monitoring\n\n.. image:: https://badge.fury.io/py/immunity-utils.svg\n :target: http://badge.fury.io/py/immunity-utils\n :alt: pypi\n\n.. image:: https://pepy.tech/badge/immunity-utils\n :target: https://pepy.tech/project/immunity-utils\n :alt: downloads\n\n.. image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square\n :target: https://gitter.im/immunity/general\n :alt: support chat\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n :target: https://pypi.org/project/black/\n :alt: code style: black\n\n------------\n\nPython and Django functions, classes and settings re-used across different Immunity modules,\nstored here with the aim of avoiding code duplication and ease maintenance.\n\n**Don't repeat yourself!**\n\n.. image:: https://raw.githubusercontent.com/immunity/immunity2-docs/master/assets/design/immunity-logo-black.svg\n :target: http://immunity.org\n\nCurrent features\n----------------\n\n* `Configurable admin theme <#using-the-admin_theme>`_\n* `Immunity Dashboard <#immunity-dashboard>`_\n* `Configurable navigation menu <#main-navigation-menu>`_\n* `Improved admin filters <#admin-filters>`_\n* `OpenAPI / Swagger documentation <#immunity_api_docs>`_\n* `Model utilities <#model-utilities>`_\n* `Storage utilities <#storage-utilities>`_\n* `Admin utilities <#admin-utilities>`_\n* `Code utilities <#code-utilities>`_\n* `Admin Theme utilities <#admin-theme-utilities>`_\n* `REST API utilities <#rest-api-utilities>`_\n* `Test utilities <#test-utilities>`_\n* `Collection of Usage Metrics <#collection-of-usage-metrics>`_\n* `Quality assurance checks <#quality-assurance-checks>`_\n\n------------\n\n.. contents:: **Table of Contents**:\n :backlinks: none\n :depth: 3\n\n------------\n\nInstall stable version from pypi\n--------------------------------\n\nInstall from pypi:\n\n.. code-block:: shell\n\n pip install immunity-utils\n\n # install optional dependencies for REST framework\n pip install immunity-utils[rest]\n\n # install optional dependencies for tests (flake8, black and isort)\n pip install immunity-utils[qa]\n\n # or install everything\n pip install immunity-utils[rest,qa]\n\nInstall development version\n---------------------------\n\nInstall tarball:\n\n.. code-block:: shell\n\n pip install https://github.com/edge-servers/immunity-utils/tarball/master\n\nAlternatively you can install via pip using git:\n\n.. code-block:: shell\n\n pip install -e git+git://github.com/immunity/immunity-utils#egg=immunity-utils\n\nUsing the ``admin_theme``\n-------------------------\n\n**The admin theme requires Django >= 2.2.**.\n\nAdd ``immunity_utils.admin_theme`` to ``INSTALLED_APPS`` in ``settings.py``:\n\n.. code-block:: python\n\n INSTALLED_APPS = [\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.sessions',\n 'django.contrib.messages',\n 'django.contrib.staticfiles',\n\n 'immunity_utils.admin_theme', # <----- add this\n # add when using autocomplete filter\n 'admin_auto_filters', # <----- add this\n\n 'django.contrib.sites',\n # admin\n 'django.contrib.admin',\n ]\n\nUsing ``DependencyLoader`` and ``DependencyFinder``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAdd the list of all packages extended to ``EXTENDED_APPS`` in ``settings.py``.\n\nFor example, if you've extended ``django_x509``:\n\n.. code-block:: python\n\n EXTENDED_APPS = ['django_x509']\n\n``DependencyFinder``\n~~~~~~~~~~~~~~~~~~~~\n\nThis is a static finder which looks for static files in the ``static``\ndirectory of the apps listed in ``settings.EXTENDED_APPS``.\n\nAdd ``immunity_utils.staticfiles.DependencyFinder`` to ``STATICFILES_FINDERS``\nin ``settings.py``.\n\n.. code-block:: python\n\n STATICFILES_FINDERS = [\n 'django.contrib.staticfiles.finders.FileSystemFinder',\n 'django.contrib.staticfiles.finders.AppDirectoriesFinder',\n 'immunity_utils.staticfiles.DependencyFinder', # <----- add this\n ]\n\n``DependencyLoader``\n~~~~~~~~~~~~~~~~~~~~\n\nThis is a template loader which looks for templates in the ``templates``\ndirectory of the apps listed in ``settings.EXTENDED_APPS``.\n\nAdd ``immunity_utils.loaders.DependencyLoader`` to\ntemplate ``loaders`` in ``settings.py`` as shown below.\n\n.. code-block:: python\n\n TEMPLATES = [\n {\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [],\n 'OPTIONS': {\n 'loaders': [\n # ... other loaders ...\n 'immunity_utils.loaders.DependencyLoader', # <----- add this\n ],\n 'context_processors': [\n # ... omitted ...\n ],\n },\n },\n ]\n\nSupplying custom CSS and JS for the admin theme\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAdd ``immunity_utils.admin_theme.context_processor.admin_theme_settings`` to\ntemplate ``context_processors`` in ``settings.py`` as shown below.\nThis will allow to set `IMMUNITY\n_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`_\nand `IMMUNITY\n_ADMIN_THEME_JS <#immunity_admin_theme_js>`__ settings\nto provide CSS and JS files to customise admin theme.\n\n.. code-block:: python\n\n TEMPLATES = [\n {\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [],\n 'OPTIONS': {\n 'loaders': [\n # ... omitted ...\n ],\n 'context_processors': [\n # ... other context processors ...\n 'immunity_utils.admin_theme.context_processor.admin_theme_settings' # <----- add this\n ],\n },\n },\n ]\n\n.. note::\n You will have to deploy these static files on your own.\n\n In order to make django able to find and load these files\n you may want to use the ``STATICFILES_DIR`` setting in ``settings.py``.\n\n You can learn more in the `Django documentation <https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-STATICFILES_DIRS>`_.\n\nExtend admin theme programmatically\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``immunity_utils.admin_theme.theme.register_theme_link``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAllows adding items to `IMMUNITY\n_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__.\n\nThis function is meant to be used by third party apps or Immunity modules which\naim to extend the core look and feel of the Immunity theme (eg: add new menu icons).\n\n**Syntax:**\n\n.. code-block:: python\n\n register_theme_link(links)\n\n+--------------------+--------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+--------------------------------------------------------------+\n| ``links`` | (``list``) List of *link* items to be added to |\n| | `IMMUNITY\n_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__ |\n+--------------------+--------------------------------------------------------------+\n\n``immunity_utils.admin_theme.theme.unregister_theme_link``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAllows removing items from `IMMUNITY\n_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__.\n\nThis function is meant to be used by third party apps or Immunity modules which\naim additional functionalities to UI of Immunity (eg: adding a support chatbot).\n\n**Syntax:**\n\n.. code-block:: python\n\n unregister_theme_link(links)\n\n+--------------------+--------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+--------------------------------------------------------------+\n| ``links`` | (``list``) List of *link* items to be removed from |\n| | `IMMUNITY\n_ADMIN_THEME_LINKS <#immunity_admin_theme_links>`__ |\n+--------------------+--------------------------------------------------------------+\n\n``immunity_utils.admin_theme.theme.register_theme_js``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAllows adding items to `IMMUNITY\n_ADMIN_THEME_JS <#immunity_admin_theme_JS>`__.\n\n**Syntax:**\n\n.. code-block:: python\n\n register_theme_js(js)\n\n+--------------------+---------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+---------------------------------------------------------------+\n| ``js`` | (``list``) List of relative path of *js* files to be added to |\n| | `IMMUNITY\n_ADMIN_THEME_JS <#immunity_admin_theme_js>`__ |\n+--------------------+---------------------------------------------------------------+\n\n``immunity_utils.admin_theme.theme.unregister_theme_js``\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\n\nAllows removing items from `IMMUNITY\n_ADMIN_THEME_JS <#immunity_admin_theme_JS>`__.\n\n**Syntax:**\n\n.. code-block:: python\n\n unregister_theme_js(js)\n\n+--------------------+--------------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+--------------------------------------------------------------------+\n| ``js`` | (``list``) List of relative path of *js* files to be removed from |\n| | `IMMUNITY\n_ADMIN_THEME_JS <#immunity_admin_theme_js>`__ |\n+--------------------+--------------------------------------------------------------------+\n\nImmunity Dashboard\n------------------\n\nThe ``admin_theme`` sub app of this package provides an admin dashboard\nfor Immunity which can be manipulated with the functions described in\nthe next sections.\n\nExample 1, monitoring:\n\n.. figure:: https://raw.githubusercontent.com/immunity/immunity-utils/master/docs/dashboard1.png\n :align: center\n\nExample 2, controller:\n\n.. figure:: https://raw.githubusercontent.com/immunity/immunity-utils/master/docs/dashboard2.png\n :align: center\n\n``register_dashboard_template``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAllows including a specific django template in the Immunity dashboard.\n\nIt is designed to allow the inclusion of the geographic map\nshipped by\n`Immunity Monitoring <https://github.com/edge-servers/immunity-monitoring>`_\nbut can be used to include any custom element in the dashboard.\n\n**Note**: it is possible to register templates to be loaded\nbefore or after charts using the ``after_charts`` keyword argument\n(see below).\n\n**Syntax:**\n\n.. code-block:: python\n\n register_dashboard_template(position, config)\n\n+--------------------+----------------------------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+----------------------------------------------------------------------------------+\n| ``position`` | (``int``) The position of the template. |\n+--------------------+----------------------------------------------------------------------------------+\n| ``config`` | (``dict``) The configuration of the template. |\n+--------------------+----------------------------------------------------------------------------------+\n| ``extra_config`` | **optional** (``dict``) Extra configuration you want to pass to custom template. |\n+--------------------+----------------------------------------------------------------------------------+\n| ``after_charts`` | **optional** (``bool``) Whether the template should be loaded after dashboard |\n| | charts. Defaults to ``False``, i.e. templates are loaded before dashboard |\n| | charts by default. |\n+--------------------+----------------------------------------------------------------------------------+\n\nFollowing properties can be configured for each template ``config``:\n\n+-----------------+------------------------------------------------------------------------------------------------------+\n| **Property** | **Description** |\n+-----------------+------------------------------------------------------------------------------------------------------+\n| ``template`` | (``str``) Path to pass to the template loader. |\n+-----------------+------------------------------------------------------------------------------------------------------+\n| ``css`` | (``tuple``) List of CSS files to load in the HTML page. |\n+-----------------+------------------------------------------------------------------------------------------------------+\n| ``js`` | (``tuple``) List of Javascript files to load in the HTML page. |\n+-----------------+------------------------------------------------------------------------------------------------------+\n\nCode example:\n\n.. code-block:: python\n\n from immunity_utils.admin_theme import register_dashboard_template\n\n register_dashboard_template(\n position=0,\n config={\n 'template': 'admin/dashboard/device_map.html',\n 'css': (\n 'monitoring/css/device-map.css',\n 'leaflet/leaflet.css',\n 'monitoring/css/leaflet.fullscreen.css',\n ),\n 'js': (\n 'monitoring/js/device-map.js',\n 'leaflet/leaflet.js',\n 'leaflet/leaflet.extras.js',\n 'monitoring/js/leaflet.fullscreen.min.js'\n )\n },\n extra_config={\n 'optional_variable': 'any_valid_value',\n },\n after_charts=True,\n )\n\nIt is recommended to register dashboard templates from the ``ready``\nmethod of the AppConfig of the app where the templates are defined.\n\n``unregister_dashboard_template``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis function can be used to remove a template from the dashboard.\n\n**Syntax:**\n\n.. code-block:: python\n\n unregister_dashboard_template(template_name)\n\n+-------------------+---------------------------------------------------+\n| **Parameter** | **Description** |\n+-------------------+---------------------------------------------------+\n| ``template_name`` | (``str``) The name of the template to remove. |\n+-------------------+---------------------------------------------------+\n\nCode example:\n\n.. code-block:: python\n\n from immunity_utils.admin_theme import unregister_dashboard_template\n\n unregister_dashboard_template('admin/dashboard/device_map.html')\n\n**Note**: an ``ImproperlyConfigured`` exception is raised the\nspecified dashboard template is not registered.\n\n``register_dashboard_chart``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAdds a chart to the Immunity dashboard.\n\nAt the moment only pie charts are supported.\n\nThe code works by defining the type of query which will be executed,\nand optionally, how the returned values have to be colored and labeled.\n\n**Syntax:**\n\n.. code-block:: python\n\n register_dashboard_chart(position, config)\n\n+--------------------+-------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+-------------------------------------------------------------+\n| ``position`` | (``int``) Position of the chart. |\n+--------------------+-------------------------------------------------------------+\n| ``config`` | (``dict``) Configuration of chart. |\n+--------------------+-------------------------------------------------------------+\n\nFollowing properties can be configured for each chart ``config``:\n\n+------------------+---------------------------------------------------------------------------------------------------------+\n| **Property** | **Description** |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``query_params`` | It is a required property in form of ``dict`` containing following properties: |\n| | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | **Property** | **Description** | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``name`` | (``str``) Chart title shown in the user interface. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``app_label`` | (``str``) App label of the model that will be used to query the database. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``model`` | (``str``) Name of the model that will be used to query the database. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``group_by`` | (``str``) The property which will be used to group values. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``annotate`` | Alternative to ``group_by``, ``dict`` used for more complex queries. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``aggregate`` | Alternative to ``group_by``, ``dict`` used for more complex queries. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``filter`` | ``dict`` used for filtering queryset. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``organization_field`` | (``str``) If the model does not have a direct relation with the | |\n| | | | ``Organization`` model, then indirect relation can be specified using | |\n| | | | this property. E.g.: ``device__organization_id``. | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``colors`` | An **optional** ``dict`` which can be used to define colors for each distinct |\n| | value shown in the pie charts. |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``labels`` | An **optional** ``dict`` which can be used to define translatable strings for each distinct |\n| | value shown in the pie charts. Can be used also to provide fallback human readable values for |\n| | raw values stored in the database which would be otherwise hard to understand for the user. |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``filters`` | An **optional** ``dict`` which can be used when using ``aggregate`` and ``annotate`` in |\n| | ``query_params`` to define the link that will be generated to filter results (pie charts are |\n| | clickable and clicking on a portion of it will show the filtered results). |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``main_filters`` | An **optional** ``dict`` which can be used to add additional filtering on the target link. |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``filtering`` | An **optional** ``str`` which can be set to ``'False'`` (str) to disable filtering on target links. |\n| | This is useful when clicking on any section of the chart should take user to the same URL. |\n+------------------+---------------------------------------------------------------------------------------------------------+\n| ``quick_link`` | An **optional** ``dict`` which contains configuration for the quick link button rendered |\n| | below the chart. |\n| | |\n| | **NOTE**: The chart legend is disabled if configuration for quick link button is provided. |\n| | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | **Property** | **Description** | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``url`` | (``str``) URL for the anchor tag | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``label`` | (``str``) Label shown on the button | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``title`` | (``str``) Title attribute of the button element | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n| | | ``custom_css_classes`` | (``list``) List of CSS classes that'll be applied on the button | |\n| | +------------------------+---------------------------------------------------------------------------+ |\n+------------------+---------------------------------------------------------------------------------------------------------+\n\nCode example:\n\n.. code-block:: python\n\n from immunity_utils.admin_theme import register_dashboard_chart\n\n register_dashboard_chart(\n position=1,\n config={\n 'query_params': {\n 'name': 'Operator Project Distribution',\n 'app_label': 'test_project',\n 'model': 'operator',\n 'group_by': 'project__name',\n },\n 'colors': {'Utils': 'red', 'User': 'orange'},\n 'quick_link': {\n 'url': '/admin/test_project/operator',\n 'label': 'Open Operators list',\n 'title': 'View complete list of operators',\n 'custom_css_classes': ['negative-top-20'],\n },\n },\n )\n\nFor real world examples, look at the code of\n`Immunity Controller <https://github.com/edge-servers/immunity-controller>`__\nand `Immunity Monitoring <https://github.com/edge-servers/immunity-monitoring>`_.\n\n**Note**: an ``ImproperlyConfigured`` exception is raised if a\ndashboard element is already registered at same position.\n\nIt is recommended to register dashboard charts from the ``ready`` method\nof the AppConfig of the app where the models are defined.\nCheckout `app.py of the test_project\n<https://github.com/edge-servers/immunity-utils/blob/master/tests/test_project/apps.py>`_\nfor reference.\n\n``unregister_dashboard_chart``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis function can used to remove a chart from the dashboard.\n\n**Syntax:**\n\n.. code-block:: python\n\n unregister_dashboard_chart(chart_name)\n\n+------------------+---------------------------------------------------+\n| **Parameter** | **Description** |\n+------------------+---------------------------------------------------+\n| ``chart_name`` | (``str``) The name of the chart to remove. |\n+------------------+---------------------------------------------------+\n\nCode example:\n\n.. code-block:: python\n\n from immunity_utils.admin_theme import unregister_dashboard_chart\n\n unregister_dashboard_chart('Operator Project Distribution')\n\n**Note**: an ``ImproperlyConfigured`` exception is raised the\nspecified dashboard chart is not registered.\n\nMain navigation menu\n--------------------\n\nThe ``admin_theme`` sub app of this package provides a navigation menu that can be\nmanipulated with the functions described in the next sections.\n\nAdd ``immunity_utils.admin_theme.context_processor.menu_groups`` to\ntemplate ``context_processors`` in ``settings.py`` as shown below.\n\n.. code-block:: python\n\n TEMPLATES = [\n {\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [],\n 'OPTIONS': {\n 'loaders': [\n # ... omitted ...\n ],\n 'context_processors': [\n # ... other context processors ...\n 'immunity_utils.admin_theme.context_processor.menu_groups' # <----- add this\n ],\n },\n },\n ]\n\n``register_menu_group``\n^^^^^^^^^^^^^^^^^^^^^^^\n\nAllows registering a new menu item or group at the specified position in the Main Navigation Menu.\n\n**Syntax:**\n\n.. code-block:: python\n\n register_menu_group(position, config)\n\n+--------------------+-------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+-------------------------------------------------------------+\n| ``position`` | (``int``) Position of the group or item. |\n+--------------------+-------------------------------------------------------------+\n| ``config`` | (``dict``) Configuration of the goup or item. |\n+--------------------+-------------------------------------------------------------+\n\nCode example:\n\n.. code-block:: python\n\n from django.utils.translation import ugettext_lazy as _\n from immunity_utils.admin_theme.menu import register_menu_group\n\n register_menu_group(\n position=1,\n config={\n 'label': _('My Group'),\n 'items': {\n 1: {\n 'label': _('Users List'),\n 'model': 'auth.User',\n 'name': 'changelist',\n 'icon': 'list-icon',\n },\n 2: {\n 'label': _('Add User'),\n 'model': 'auth.User',\n 'name': 'add',\n 'icon': 'add-icon',\n },\n },\n 'icon': 'user-group-icon',\n },\n )\n register_menu_group(\n position=2,\n config={\n 'model': 'test_project.Shelf',\n 'name': 'changelist',\n 'label': _('View Shelf'),\n 'icon': 'shelf-icon',\n },\n )\n register_menu_group(\n position=3, config={'label': _('My Link'), 'url': 'https://link.com'}\n )\n\n.. note::\n An ``ImproperlyConfigured`` exception is raised if a menu element is already registered at the same position.\n\n An ``ImproperlyConfigured`` exception is raised if the supplied configuration does not match with the different types of\n possible configurations available (different configurations will be discussed in the next section).\n\n It is recommended to use ``register_menu_group`` in the ``ready`` method of the ``AppConfig``.\n\n ``register_menu_items`` is obsoleted by ``register_menu_group`` and will be removed in\n future versions. Links added using ``register_menu_items`` will be shown at the top\n of navigation menu and above any ``register_menu_group`` items.\n\nAdding a custom link\n~~~~~~~~~~~~~~~~~~~~~\n\nTo add a link that contains a custom URL the following syntax can be used.\n\n**Syntax:**\n\n.. code-block:: python\n\n register_menu_group(position=1, config={\n \"label\": \"Link Label\",\n \"url\": \"link_url\",\n \"icon\": \"my-icon\"\n })\n\nFollowing is the description of the configuration:\n\n+------------------+--------------------------------------------------------------+\n| **Parameter** | **Description** |\n+------------------+--------------------------------------------------------------+\n| ``label`` | (``str``) Display text for the link. |\n+------------------+--------------------------------------------------------------+\n| ``url`` | (``str``) url for the link. |\n+------------------+--------------------------------------------------------------+\n| ``icon`` | An **optional** ``str`` CSS class name for the icon. No icon |\n| | is displayed if not provided. |\n+------------------+--------------------------------------------------------------+\n\nAdding a model link\n~~~~~~~~~~~~~~~~~~~\n\nTo add a link that contains URL of add form or change list page of a model\nthen following syntax can be used. Users will only be able to see links for\nmodels they have permission to either view or edit.\n\n**Syntax:**\n\n.. code-block:: python\n\n # add a link of list page\n register_menu_group(\n position=1,\n config={\n 'model': 'my_project.MyModel',\n 'name': 'changelist',\n 'label': 'MyModel List',\n 'icon': 'my-model-list-class',\n },\n )\n\n # add a link of add page\n register_menu_group(\n position=2,\n config={\n 'model': 'my_project.MyModel',\n 'name': 'add',\n 'label': 'MyModel Add Item',\n 'icon': 'my-model-add-class',\n },\n )\n\nFollowing is the description of the configuration:\n\n+------------------+--------------------------------------------------------------+\n| **Parameter** | **Description** |\n+------------------+--------------------------------------------------------------+\n| ``model`` | (``str``) Model of the app for which you to add link. |\n+------------------+--------------------------------------------------------------+\n| ``name`` | (``str``) url name. eg. changelist or add. |\n+------------------+--------------------------------------------------------------+\n| ``label`` | An **optional** ``str`` display text for the link. It is |\n| | automatically generated if not provided. |\n+------------------+--------------------------------------------------------------+\n| ``icon`` | An **optional** ``str`` CSS class name for the icon. No icon |\n| | is displayed if not provided. |\n+------------------+--------------------------------------------------------------+\n\nAdding a menu group\n~~~~~~~~~~~~~~~~~~~\n\nTo add a nested group of links in the menu the following syntax can be used.\nIt creates a dropdown in the menu.\n\n**Syntax:**\n\n.. code-block:: python\n\n register_menu_group(\n position=1,\n config={\n 'label': 'My Group Label',\n 'items': {\n 1: {'label': 'Link Label', 'url': 'link_url', 'icon': 'my-icon'},\n 2: {\n 'model': 'my_project.MyModel',\n 'name': 'changelist',\n 'label': 'MyModel List',\n 'icon': 'my-model-list-class',\n },\n },\n 'icon': 'my-group-icon-class',\n },\n )\n\nFollowing is the description of the configuration:\n\n+------------------+--------------------------------------------------------------+\n| **Parameter** | **Description** |\n+------------------+--------------------------------------------------------------+\n| ``label`` | (``str``) Display name for the link. |\n+------------------+--------------------------------------------------------------+\n| ``items`` | (``dict``) Items to be displayed in the dropdown. |\n| | It can be a dict of custom links or model links |\n| | with key as their position in the group. |\n+------------------+--------------------------------------------------------------+\n| ``icon`` | An **optional** ``str`` CSS class name for the icon. No icon |\n| | is displayed if not provided. |\n+------------------+--------------------------------------------------------------+\n\n``register_menu_subitem``\n^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAllows adding an item to a registered group.\n\n**Syntax:**\n\n.. code-block:: python\n\n register_menu_subitem(group_position, item_position, config)\n\n+--------------------------+----------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------------+----------------------------------------------------------------+\n| ``group_position`` | (``int``) Position of the group in which item should be added. |\n+--------------------------+----------------------------------------------------------------+\n| ``item_position`` | (``int``) Position at which item should be added in the group |\n+--------------------------+----------------------------------------------------------------+\n| ``config`` | (``dict``) Configuration of the item. |\n+--------------------------+----------------------------------------------------------------+\n\nCode example:\n\n.. code-block:: python\n\n from django.utils.translation import ugettext_lazy as _\n from immunity_utils.admin_theme.menu import register_menu_subitem\n\n # To register a model link\n register_menu_subitem(\n group_position=10,\n item_position=2,\n config={\n 'label': _('Users List'),\n 'model': 'auth.User',\n 'name': 'changelist',\n 'icon': 'list-icon',\n },\n )\n\n # To register a custom link\n register_menu_subitem(\n group_position=10,\n item_position=2,\n config={'label': _('My Link'), 'url': 'https://link.com'},\n )\n\n.. note::\n An ``ImproperlyConfigured`` exception is raised if the group is not already\n registered at ``group_position``.\n\n An ``ImproperlyConfigured`` exception is raised if the group already has an\n item registered at ``item_position``.\n\n It is only possible to register links to specific models or custom URL.\n An ``ImproperlyConfigured`` exception is raised if the configuration of\n group is provided in the function.\n\n It is recommended to use ``register_menu_subitem`` in the ``ready``\n method of the ``AppConfig``.\n\nHow to use custom icons in the menu\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nCreate a CSS file and use the following syntax to provide the image for each\nicon used in the menu. The CSS class name should be the same as the ``icon``\nparameter used in the configuration of a menu item or group. Also icon being used\nshould be in ``svg`` format.\n\nExample:\n\n.. code-block:: css\n\n .icon-class-name {\n mask-image: url(imageurl);\n -webkit-mask-image: url(imageurl);\n }\n\nFollow the instructions in\n`Supplying custom CSS and JS for the admin theme <#supplying-custom-css-and-js-for-the-admin-theme>`_\nto know how to configure your Immunity instance to load custom CSS files.\n\nAdmin filters\n-------------\n\n.. figure:: https://github.com/edge-servers/immunity-utils/raw/media/docs/filter.gif\n :align: center\n\nThe ``admin_theme`` sub app provides an improved UI for the changelist filter\nwhich occupies less space compared to the original implementation in django:\nfilters are displayed horizontally on the top (instead of vertically on the side)\nand filter options are hidden in dropdown menus which are expanded once clicked.\n\nMultiple filters can be applied at same time with the help of \"apply filter\" button.\nThis button is only visible when total number of filters is greater than 4.\nWhen filters in use are less or equal to 4 the \"apply filter\" button is not visible\nand filters work like in the original django implementation\n(as soon as a filter option is selected the filter is applied and the page is reloaded).\n\nModel utilities\n---------------\n\n``immunity_utils.base.UUIDModel``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nModel class which provides a UUID4 primary key.\n\n``immunity_utils.base.TimeStampedEditableModel``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nModel class inheriting ``UUIDModel`` which provides two additional fields:\n\n- ``created``\n- ``modified``\n\nWhich use respectively ``AutoCreatedField``, ``AutoLastModifiedField`` from ``model_utils.fields``\n(self-updating fields providing the creation date-time and the last modified date-time).\n\n``immunity_utils.base.FallBackModelMixin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nModel mixin that implements ``get_field_value`` method which can be used\nto get value of fallback fields.\n\nCustom Fields\n-------------\n\nThis section describes custom fields defined in ``immunity_utils.fields``\nthat can be used in Django models:\n\n``immunity_utils.fields.KeyField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA model field which provides a random key or token, widely used across immunity modules.\n\n``immunity_utils.fields.FallbackBooleanChoiceField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis field extends Django's `BooleanField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#booleanfield>`_\nand provides additional functionality for handling choices with a fallback value.\nThe field will use the **fallback value** whenever the field is set to ``None``.\n\nThis field is particularly useful when you want to present a choice between enabled\nand disabled options, with an additional \"Default\" option that reflects the fallback value.\n\n.. code-block:: python\n\n from django.db import models\n from immunity_utils.fields import FallbackBooleanChoiceField\n from myapp import settings as app_settings\n\n class MyModel(models.Model):\n is_active = FallbackBooleanChoiceField(\n null=True,\n blank=True,\n default=None,\n fallback=app_settings.IS_ACTIVE_FALLBACK,\n )\n\n``immunity_utils.fields.FallbackCharChoiceField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis field extends Django's `CharField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#charfield>`_\nand provides additional functionality for handling choices with a fallback value.\nThe field will use the **fallback value** whenever the field is set to ``None``.\n\n.. code-block:: python\n\n from django.db import models\n from immunity_utils.fields import FallbackCharChoiceField\n from myapp import settings as app_settings\n\n class MyModel(models.Model):\n is_first_name_required = FallbackCharChoiceField(\n null=True,\n blank=True,\n max_length=32,\n choices=(\n ('disabled', _('Disabled')),\n ('allowed', _('Allowed')),\n ('mandatory', _('Mandatory')),\n ),\n fallback=app_settings.IS_FIRST_NAME_REQUIRED,\n )\n\n``immunity_utils.fields.FallbackCharField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis field extends Django's `CharField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#charfield>`_\nand provides additional functionality for handling text fields with a fallback value.\n\nIt allows populating the form with the fallback value when the actual value is set to ``null`` in the database.\n\n.. code-block:: python\n\n from django.db import models\n from immunity_utils.fields import FallbackCharField\n from myapp import settings as app_settings\n\n class MyModel(models.Model):\n greeting_text = FallbackCharField(\n null=True,\n blank=True,\n max_length=200,\n fallback=app_settings.GREETING_TEXT,\n )\n\n``immunity_utils.fields.FallbackURLField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis field extends Django's `URLField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#urlfield>`_\nand provides additional functionality for handling URL fields with a fallback value.\n\nIt allows populating the form with the fallback value when the actual value is set to ``null`` in the database.\n\n.. code-block:: python\n\n from django.db import models\n from immunity_utils.fields import FallbackURLField\n from myapp import settings as app_settings\n\n class MyModel(models.Model):\n password_reset_url = FallbackURLField(\n null=True,\n blank=True,\n max_length=200,\n fallback=app_settings.DEFAULT_PASSWORD_RESET_URL,\n )\n\n``immunity_utils.fields.FallbackTextField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis extends Django's `TextField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#django.db.models.TextField>`_\nand provides additional functionality for handling text fields with a fallback value.\n\nIt allows populating the form with the fallback value when the actual value is set to ``null`` in the database.\n\n.. code-block:: python\n\n from django.db import models\n from immunity_utils.fields import FallbackTextField\n from myapp import settings as app_settings\n\n class MyModel(models.Model):\n extra_config = FallbackTextField(\n null=True,\n blank=True,\n max_length=200,\n fallback=app_settings.EXTRA_CONFIG,\n )\n\n``immunity_utils.fields.FallbackPositiveIntegerField``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis extends Django's `PositiveIntegerField <https://docs.djangoproject.com/en/4.2/ref/models/fields/#positiveintegerfield>`_\nand provides additional functionality for handling positive integer fields with a fallback value.\n\nIt allows populating the form with the fallback value when the actual value is set to ``null`` in the database.\n\n.. code-block:: python\n\n from django.db import models\n from immunity_utils.fields import FallbackPositiveIntegerField\n from myapp import settings as app_settings\n\n class MyModel(models.Model):\n count = FallbackPositiveIntegerField(\n blank=True,\n null=True,\n fallback=app_settings.DEFAULT_COUNT,\n )\n\nAdmin utilities\n---------------\n\n``immunity_utils.admin.TimeReadonlyAdminMixin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAdmin mixin which adds two readonly fields ``created`` and ``modified``.\n\nThis is an admin mixin for models inheriting ``TimeStampedEditableModel``\nwhich adds the fields ``created`` and ``modified`` to the database.\n\n``immunity_utils.admin.ReadOnlyAdmin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA read-only ``ModelAdmin`` base class.\n\nWill include the ``id`` field by default, which can be excluded by supplying\nthe ``exclude`` attribute, eg:\n\n.. code-block:: python\n\n from immunity_utils.admin import ReadOnlyAdmin\n\n class PostAuthReadOnlyAdmin(ReadOnlyAdmin):\n exclude = ['id']\n\n``immunity_utils.admin.AlwaysHasChangedMixin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA mixin designed for inline items and model forms, ensures the item\nis created even if the default values are unchanged.\n\nWithout this, when creating new objects, inline items won't be saved\nunless users change the default values.\n\n``immunity_utils.admin.CopyableFieldsAdmin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAn admin class that allows to set admin fields to be\nread-only and makes it easy to copy the fields contents.\n\nUseful for auto-generated fields such as UUIDs, secret keys, tokens, etc.\n\n``immunity_utils.admin.UUIDAdmin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis class is a subclass of ``CopyableFieldsAdmin`` which\nsets ``uuid`` as the only copyable field. This class is kept\nfor backward compatibility and convenience, since different models\nof various Immunity modules show ``uuid`` as the only copyable field.\n\n``immunity_utils.admin.ReceiveUrlAdmin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAn admin class that provides an URL as a read-only input field\n(to make it easy and quick to copy/paste).\n\n``immunity_utils.admin.HelpTextStackedInline``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. figure:: https://github.com/edge-servers/immunity-utils/raw/media/docs/help-text-stacked-inline.png\n :align: center\n\nA stacked inline admin class that displays a help text for entire\ninline object. Following is an example:\n\n.. code-block:: python\n\n from immunity_utils.admin import HelpTextStackedInline\n\n class SubnetDivisionRuleInlineAdmin(\n MultitenantAdminMixin, TimeReadonlyAdminMixin, HelpTextStackedInline\n ):\n model = Model\n # It is required to set \"help_text\" attribute\n help_text = {\n # (required) Help text to display\n 'text': _(\n 'Please keep in mind that once the subnet division rule is created '\n 'and used, changing \"Size\" and \"Number of Subnets\" and decreasing '\n '\"Number of IPs\" will not be possible.'\n ),\n # (optional) You can provide a link to documentation for user reference\n 'documentation_url': (\n 'https://github.com/edge-servers/immunity-utils'\n ),\n # (optional) Icon to be shown along with help text. By default it uses\n # \"/static/admin/img/icon-alert.svg\"\n 'image_url': '/static/admin/img/icon-alert.svg'\n }\n\n``immunity_utils.admin_theme.filters.InputFilter``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``admin_theme`` sub app of this package provides an input filter that can be used in changelist page\nto filter ``UUIDField`` or ``CharField``.\n\nCode example:\n\n.. code-block:: python\n\n from django.contrib import admin\n from immunity_utils.admin_theme.filters import InputFilter\n from my_app.models import MyModel\n\n @admin.register(MyModel)\n class MyModelAdmin(admin.ModelAdmin):\n list_filter = [\n ('my_field', InputFilter),\n 'other_field',\n # ...\n ]\n\nBy default ``InputFilter`` use exact lookup to filter items which matches to the value being\nsearched by the user. But this behavior can be changed by modifying ``InputFilter`` as following:\n\n.. code-block:: python\n\n from django.contrib import admin\n from immunity_utils.admin_theme.filters import InputFilter\n from my_app.models import MyModel\n\n class MyInputFilter(InputFilter):\n lookup = 'icontains'\n\n\n @admin.register(MyModel)\n class MyModelAdmin(admin.ModelAdmin):\n list_filter = [\n ('my_field', MyInputFilter),\n 'other_field',\n # ...\n ]\n\nTo know about other lookups that can be used please check\n`Django Lookup API Reference <https://docs.djangoproject.com/en/3.2/ref/models/lookups/#django.db.models.Lookup>`__\n\n``immunity_utils.admin_theme.filters.SimpleInputFilter``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA stripped down version of ``immunity_utils.admin_theme.filters.InputFilter`` that provides\nflexibility to customize filtering. It can be used to filter objects using indirectly\nrelated fields.\n\nThe derived filter class should define the ``queryset`` method as shown in following example:\n\n.. code-block:: python\n\n from django.contrib import admin\n from immunity_utils.admin_theme.filters import SimpleInputFilter\n from my_app.models import MyModel\n\n class MyInputFilter(SimpleInputFilter):\n parameter_name = 'shelf'\n title = _('Shelf')\n\n def queryset(self, request, queryset):\n if self.value() is not None:\n return queryset.filter(name__icontains=self.value())\n\n\n @admin.register(MyModel)\n class MyModelAdmin(admin.ModelAdmin):\n list_filter = [\n MyInputFilter,\n 'other_field',\n # ...\n ]\n\n``immunity_utils.admin_theme.filters.AutocompleteFilter``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``admin_theme`` sub app of this package provides an auto complete\nfilter that uses django autocomplete widget to load filter data asynchronously.\n\nThis filter can be helpful when the number of objects is too large\nto load all at once which may cause the slow loading of the page.\n\n.. code-block:: python\n\n from django.contrib import admin\n from immunity_utils.admin_theme.filters import AutocompleteFilter\n from my_app.models import MyModel, MyOtherModel\n\n class MyAutoCompleteFilter(AutocompleteFilter):\n field_name = 'field'\n parameter_name = 'field_id'\n title = _('My Field')\n\n @admin.register(MyModel)\n class MyModelAdmin(admin.ModelAdmin):\n list_filter = [\n MyAutoCompleteFilter,\n ...\n ]\n\n @admin.register(MyOtherModel)\n class MyOtherModelAdmin(admin.ModelAdmin):\n search_fields = ['id']\n\nTo customize or know more about it, please refer to the\n`django-admin-autocomplete-filter documentation\n<https://github.com/farhan0581/django-admin-autocomplete-filter#usage>`_.\n\nCode utilities\n--------------\n\n``immunity_utils.utils.get_random_key``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nGenerates an random string of 32 characters.\n\n``immunity_utils.utils.deep_merge_dicts``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nReturns a new ``dict`` which is the result of the merge of the two dictionaries,\nall elements are deep-copied to avoid modifying the original data structures.\n\nUsage:\n\n.. code-block:: python\n\n from immunity_utils.utils import deep_merge_dicts\n\n mergd_dict = deep_merge_dicts(dict1, dict2)\n\n``immunity_utils.utils.default_or_test``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf the program is being executed during automated tests the value supplied in\nthe ``test`` argument will be returned, otherwise the one supplied in the\n``value`` argument is returned.\n\n.. code-block:: python\n\n from immunity_utils.utils import default_or_test\n\n THROTTLE_RATE = getattr(\n settings,\n 'THROTTLE_RATE',\n default_or_test(value='20/day', test=None),\n )\n\n``immunity_utils.utils.print_color``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default colors**: ``['white_bold', 'green_bold', 'yellow_bold', 'red_bold']``\n\nIf you want to print a string in ``Red Bold``, you can do it as below.\n\n.. code-block:: python\n\n from immunity_utils.utils import print_color\n\n print_color('This is the printed in Red Bold', color_name='red_bold')\n\nYou may also provide the ``end`` arguement similar to built-in print method.\n\n``immunity_utils.utils.SorrtedOrderedDict``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nExtends ``collections.SortedDict`` and implements logic to sort inserted\nitems based on ``key`` value. Sorting is done at insert operation which\nincurs memory space overhead.\n\n``immunity_utils.tasks.ImmunityCeleryTask``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA custom celery task class that sets hard and soft time limits of celery tasks\nusing `IMMUNITY\n_CELERY_HARD_TIME_LIMIT <#immunity_celery_hard_time_limit>`_\nand `IMMUNITY\n_CELERY_SOFT_TIME_LIMIT <#immunity_celery_soft_time_limit>`_\nsettings respectively.\n\nUsage:\n\n.. code-block:: python\n\n from celery import shared_task\n\n from immunity_utils.tasks import ImmunityCeleryTask\n\n @shared_task(base=ImmunityCeleryTask)\n def your_celery_task():\n pass\n\n**Note:** This task class should be used for regular background tasks\nbut not for complex background tasks which can take a long time to execute\n(eg: firmware upgrades, network operations with retry mechanisms).\n\n``immunity_utils.utils.retryable_request``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA utility function for making HTTP requests with built-in retry logic.\nThis function is useful for handling transient errors encountered during HTTP\nrequests by automatically retrying failed requests with exponential backoff.\nIt provides flexibility in configuring various retry parameters to suit\ndifferent use cases.\n\nUsage:\n\n.. code-block:: python\n\n from immunity_utils.utils import retryable_request\n\n response = retryable_request(\n method='GET',\n url='https://immunity.org',\n timeout=(4, 8),\n max_retries=3,\n backoff_factor=1,\n backoff_jitter=0.0,\n status_forcelist=(429, 500, 502, 503, 504),\n allowed_methods=('HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST'),\n retry_kwargs=None,\n headers={'Authorization': 'Bearer token'}\n )\n\n**Paramters:**\n\n- ``method`` (str): The HTTP method to be used for the request in lower\n case (e.g., 'get', 'post', etc.).\n- ``timeout`` (tuple): A tuple containing two elements: connection timeout\n and read timeout in seconds (default: (4, 8)).\n- ``max_retries`` (int): The maximum number of retry attempts in case of\n request failure (default: 3).\n- ``backoff_factor`` (float): A factor by which the retry delay increases\n after each retry (default: 1).\n- ``backoff_jitter`` (float): A jitter to apply to the backoff factor to prevent\n retry storms (default: 0.0).\n- ``status_forcelist`` (tuple): A tuple of HTTP status codes for which retries\n should be attempted (default: (429, 500, 502, 503, 504)).\n- ``allowed_methods`` (tuple): A tuple of HTTP methods that are allowed for\n the request (default: ('HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST')).\n- ``retry_kwargs`` (dict): Additional keyword arguments to be passed to the\n retry mechanism (default: None).\n- ``**kwargs``: Additional keyword arguments to be passed to the underlying request\n method (e.g. 'headers', etc.).\n\nNote: This method will raise a requests.exceptions.RetryError if the request\nremains unsuccessful even after all retry attempts have been exhausted.\nThis exception indicates that the operation could not be completed successfully\ndespite the retry mechanism.\n\nStorage utilities\n-----------------\n\n``immunity_utils.storage.CompressStaticFilesStorage``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA static storage backend for compression inheriting from `django-compress-staticfiles's <https://pypi.org/project/django-compress-staticfiles/>`_ ``CompressStaticFilesStorage`` class.\n\nAdds support for excluding file types using `IMMUNITY\n_STATICFILES_VERSIONED_EXCLUDE <#immunity_staticfiles_versioned_exclude>`_ setting.\n\nTo use point ``STATICFILES_STORAGE`` to ``immunity_utils.storage.CompressStaticFilesStorage`` in ``settings.py``.\n\n.. code-block:: python\n\n STATICFILES_STORAGE = 'immunity_utils.storage.CompressStaticFilesStorage'\n\nAdmin Theme utilities\n---------------------\n\n``immunity_utils.admin_theme.email.send_email``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis function allows sending email in both plain text and HTML version (using the template\nand logo that can be customised using `IMMUNITY\n_EMAIL_TEMPLATE <#immunity_email_template>`_\nand `IMMUNITY\n_EMAIL_LOGO <#immunity_email_logo>`_ respectively).\n\nIn case the HTML version if not needed it may be disabled by\nsetting `IMMUNITY\n_HTML_EMAIL <#immunity_html_email>`_ to ``False``.\n\n**Syntax:**\n\n.. code-block:: python\n\n send_email(subject, body_text, body_html, recipients, **kwargs)\n\n+--------------------+--------------------------------------------------------------------------------------------+\n| **Parameter** | **Description** |\n+--------------------+--------------------------------------------------------------------------------------------+\n| ``subject`` | (``str``) The subject of the email template. |\n+--------------------+--------------------------------------------------------------------------------------------+\n| ``body_text`` | (``str``) The body of the text message to be emailed. |\n+--------------------+--------------------------------------------------------------------------------------------+\n| ``body_html`` | (``str``) The body of the html template to be emailed. |\n+--------------------+--------------------------------------------------------------------------------------------+\n| ``recipients`` | (``list``) The list of recipients to send the mail to. |\n+--------------------+--------------------------------------------------------------------------------------------+\n| ``extra_context`` | **optional** (``dict``) Extra context which is passed to the template. |\n| | The dictionary keys ``call_to_action_text`` and ``call_to_action_url`` |\n| | can be passed to show a call to action button. |\n| | Similarly, ``footer`` can be passed to add a footer. |\n+--------------------+--------------------------------------------------------------------------------------------+\n| ``**kwargs`` | Any additional keyword arguments (e.g. ``attachments``, ``headers``, etc.) |\n| | are passed directly to the `django.core.mail.EmailMultiAlternatives |\n| | <https://docs.djangoproject.com/en/4.1/topics/email/#sending-alternative-content-types>`_. |\n+--------------------+--------------------------------------------------------------------------------------------+\n\n\n**Note**: Data passed in body should be validated and user supplied data should not be sent directly to the function.\n\nREST API utilities\n------------------\n\n``immunity_utils.api.serializers.ValidatedModelSerializer``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nA model serializer which calls the model instance ``full_clean()``.\n\n``immunity_utils.api.apps.ApiAppConfig``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you're creating an Immunity module which provides a REST API built with Django REST Framework,\nchances is that you may need to define some default settings to control its throttling or other aspects.\n\nHere's how to easily do it:\n\n.. code-block:: python\n\n from django.conf import settings\n from django.utils.translation import ugettext_lazy as _\n from immunity_utils.api.apps import ApiAppConfig\n\n\n class MyModuleConfig(ApiAppConfig):\n name = 'my_immunity_module'\n label = 'my_module'\n verbose_name = _('My Immunity Module')\n\n # assumes API is enabled by default\n API_ENABLED = getattr(settings, 'MY_IMMUNITY\n_MODULE_API_ENABLED', True)\n # set throttling rates for your module here\n REST_FRAMEWORK_SETTINGS = {\n 'DEFAULT_THROTTLE_RATES': {'my_module': '400/hour'},\n }\n\nEvery immunity module which has an API should use this class to configure\nits own default settings, which will be merged with the settings of the other\nmodules.\n\nTest utilities\n--------------\n\n``immunity_utils.tests.catch_signal``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis method can be used to mock a signal call inorder to easily verify\nthat the signal has been called.\n\nUsage example as a context-manager:\n\n.. code-block:: python\n\n from immunity_utils.tests import catch_signal\n\n with catch_signal(immunity_signal) as handler:\n model_instance.trigger_signal()\n handler.assert_called_once_with(\n arg1='value1',\n arg2='value2',\n sender=ModelName,\n signal=immunity_signal,\n )\n\n``immunity_utils.tests.TimeLoggingTestRunner``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. figure:: https://raw.githubusercontent.com/immunity/immunity-utils/master/docs/TimeLoggingTestRunner.png\n :align: center\n\nThis class extends the `default test runner provided by Django <https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEST_RUNNER>`_\nand logs the time spent by each test, making it easier to spot slow tests by highlighting\ntime taken by it in yellow (time shall be highlighted in red if it crosses the second threshold).\n\nBy default tests are considered slow if they take more than 0.3 seconds but you can control\nthis with `IMMUNITY\n_SLOW_TEST_THRESHOLD <#immunity_slow_test_threshold>`_.\n\nIn order to switch to this test runner you have set the following in your `settings.py`:\n\n.. code-block:: python\n\n TEST_RUNNER = 'immunity_utils.tests.TimeLoggingTestRunner'\n\n``immunity_utils.tests.capture_stdout``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis decorator can be used to capture standard output produced by tests,\neither to silence it or to write assertions.\n\nExample usage:\n\n.. code-block:: python\n\n from immunity_utils.tests import capture_stdout\n\n @capture_stdout()\n def test_something(self):\n function_generating_output() # pseudo code\n\n @capture_stdout()\n def test_something_again(self, captured_ouput):\n # pseudo code\n function_generating_output()\n # now you can create assertions on the captured output\n self.assertIn('expected stdout', captured_ouput.getvalue())\n # if there are more than one assertions, clear the captured output first\n captured_error.truncate(0)\n captured_error.seek(0)\n # you can create new assertion now\n self.assertIn('another output', captured_ouput.getvalue())\n\n**Notes**:\n\n- If assertions need to be made on the captured output, an additional argument\n (in the example above is named ``captured_output``) can be passed as an argument\n to the decorated test method, alternatively it can be omitted.\n- A ``StingIO`` instance is used for capturing output by default but if needed\n it's possible to pass a custom ``StringIO`` instance to the decorator function.\n\n``immunity_utils.tests.capture_stderr``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nEquivalent to ``capture_stdout``, but for standard error.\n\nExample usage:\n\n.. code-block:: python\n\n from immunity_utils.tests import capture_stderr\n\n @capture_stderr()\n def test_error(self):\n function_generating_error() # pseudo code\n\n @capture_stderr()\n def test_error_again(self, captured_error):\n # pseudo code\n function_generating_error()\n # now you can create assertions on captured error\n self.assertIn('expected error', captured_error.getvalue())\n # if there are more than one assertions, clear the captured error first\n captured_error.truncate(0)\n captured_error.seek(0)\n # you can create new assertion now\n self.assertIn('another expected error', captured_error.getvalue())\n\n``immunity_utils.tests.capture_any_output``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nEquivalent to ``capture_stdout`` and ``capture_stderr``, but captures both types of\noutput (standard output and standard error).\n\nExample usage:\n\n.. code-block:: python\n\n from immunity_utils.tests import capture_any_output\n\n @capture_any_output()\n def test_something_out(self):\n function_generating_output() # pseudo code\n\n @capture_any_output()\n def test_out_again(self, captured_output, captured_error):\n # pseudo code\n function_generating_output_and_errors()\n # now you can create assertions on captured error\n self.assertIn('expected stdout', captured_output.getvalue())\n self.assertIn('expected stderr', captured_error.getvalue())\n\n``immunity_utils.tests.AssertNumQueriesSubTestMixin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis mixin overrides the\n`assertNumQueries <https://docs.djangoproject.com/en/dev/topics/testing/tools/#django.test.TransactionTestCase.assertNumQueries>`_\nassertion from the django test case to run in a ``subTest`` so that the\nquery check does not block the whole test if it fails.\n\nExample usage:\n\n.. code-block:: python\n\n from django.test import TestCase\n from immunity_utils.tests import AssertNumQueriesSubTestMixin\n\n\n class MyTest(AssertNumQueriesSubTestMixin, TestCase):\n def my_test(self):\n with self.assertNumQueries(2):\n MyModel.objects.count()\n\n # the assertion above will fail but this line will be executed\n print('This will be printed anyway.')\n\n``immunity_utils.test_selenium_mixins.SeleniumTestMixin``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis mixin provides basic setup for Selenium tests with method to\nopen URL and login and logout a user.\n\nDatabase backends\n-----------------\n\n``immunity_utils.db.backends.spatialite``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis backend extends ``django.contrib.gis.db.backends.spatialite``\ndatabase backend to implement a workaround for handling\n`issue with sqlite 3.36 and spatialite 5 <https://code.djangoproject.com/ticket/32935>`_.\n\nCollection of Usage Metrics\n---------------------------\n\nThe ``immunity-utils`` module includes an optional\nsub-app ``immunity_utils.metric_collection``,\nwhich allows us to collect of the following information\nfrom Immunity instances:\n\n- Immunity Version\n- List of enabled Immunity modules and their version\n- Operating System identifier, e.g.:\n Linux version, Kernel version, target platform (e.g. x86)\n- Installation method, if available, e.g. `ansible-immunity2\n <https://github.com/edge-servers/ansible-immunity2>`_\n or `docker-immunity <https://github.com/edge-servers/docker-immunity>`_\n\nThe data above is collected during the following events:\n\n- **Install**: when Immunity is installed the first time\n- **Upgrade**: when any Immunity module is upgraded\n- **Heartbeat**: once every 24 hours\n\nWe collect data on Immunity usage to gauge user engagement, satisfaction,\nand upgrade patterns. This informs our development decisions, ensuring\ncontinuous improvement aligned with user needs.\n\nTo enhance our understanding and management of this data, we have\nintegrated `Clean Insights <https://cleaninsights.org/>`_, a privacy-preserving\nanalytics tool. Clean Insights allows us to responsibly gather and analyze\nusage metrics without compromising user privacy. It provides us with the\nmeans to make data-driven decisions while respecting our users' rights and trust.\n\nWe have taken great care to ensure no\nsensitive or personal data is being tracked.\n\nOpting out from metric collection\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nYou can opt-out from sharing this data any time from the \"System Info\" page.\nAlternatively, you can also remove the ``immunity_utils.metric_collection``\napp from ``INSTALLED_APPS`` in one of the following ways:\n\n- If you are using the `ansible-immunity2\n <https://github.com/edge-servers/ansible-immunity2>`_ role, you can set the\n variable ``immunity2_usage_metric_collection`` to ``false`` in your playbook.\n\n- If you are using `docker-immunity\n <https://github.com/edge-servers/docker-immunity>`_, you can set set the\n environment variable ``METRIC_COLLECTION`` to ``False`` in the ``.env`` file.\n\nHowever, it would be very helpful to the project if you keep the\ncolection of these metrics enabled, because the feedback we get from\nthis data is useful to guide the project in the right direction.\n\nQuality Assurance Checks\n------------------------\n\nThis package contains some common QA checks that are used in the\nautomated builds of different Immunity modules.\n\n``immunity-qa-format``\n^^^^^^^^^^^^^^^^^^^^^^\n\nThis shell script automatically formats Python and CSS code according\nto the `Immunity coding style conventions <https://immunity.io/docs/developer/contributing.html#coding-style-conventions>`_.\n\nIt runs ``isort`` and ``black`` to format python code\n(these two dependencies are required and installed automatically when running\n``pip install immunity-utils[qa]``).\n\nThe ``stylelint`` and ``jshint`` programs are used to perform style checks on CSS and JS code respectively, but they are optional:\nif ``stylelint`` and/or ``jshint`` are not installed, the check(s) will be skipped.\n\n``immunity-qa-check``\n^^^^^^^^^^^^^^^^^^^^^\n\nShell script to run the following quality assurance checks:\n\n* `checkmigrations <#checkmigrations>`_\n* `checkcommit <#checkcommit>`_\n* `checkendline <#checkendline>`_\n* `checkpendingmigrations <#checkpendingmigrations>`_\n* `checkrst <#checkrst>`_\n* ``flake8`` - Python code linter\n* ``isort`` - Sorts python imports alphabetically, and seperated into sections\n* ``black`` - Formats python code using a common standard\n* ``csslinter`` - Formats and checks CSS code using stylelint common standard\n* ``jslinter`` - Checks Javascript code using jshint common standard\n\nIf a check requires a flag, it can be passed forward in the same way.\n\nUsage example::\n\n immunity-qa-check --migration-path <path> --message <commit-message>\n\nAny unneeded checks can be skipped by passing ``--skip-<check-name>``\n\nUsage example::\n\n immunity-qa-check --skip-isort\n\nFor backward compatibility ``csslinter`` and ``jslinter`` are skipped by default.\nTo run them in checks pass arguements in this way.\n\nUsage example::\n\n # To activate csslinter\n immunity-qa-check --csslinter\n\n # To activate jslinter\n immunity-qa-check --jslinter\n\nYou can do multiple ``checkmigrations`` by passing the arguments with space-delimited string.\n\nFor example, this multiple ``checkmigrations``::\n\n checkmigrations --migrations-to-ignore 3 \\\n --migration-path ./immunity_users/migrations/ || exit 1\n\n checkmigrations --migrations-to-ignore 2 \\\n --migration-path ./tests/testapp/migrations/ || exit 1\n\nCan be changed with::\n\n immunity-qa-check --migrations-to-ignore \"3 2\" \\\n --migration-path \"./immunity_users/migrations/ ./tests/testapp/migrations/\"\n\n``checkmigrations``\n^^^^^^^^^^^^^^^^^^^\n\nEnsures the latest migrations created have a human readable name.\n\nWe want to avoid having many migrations named like ``0003_auto_20150410_3242.py``.\n\nThis way we can reconstruct the evolution of our database schemas faster, with\nless efforts and hence less costs.\n\nUsage example::\n\n checkmigrations --migration-path ./django_freeradius/migrations/\n\n``checkcommit``\n^^^^^^^^^^^^^^^\n\nEnsures the last commit message follows our `commit message style guidelines\n<http://immunity.io/docs/developer/contributing.html#commit-message-style-guidelines>`_.\n\nWe want to keep the commit log readable, consistent and easy to scan in order\nto make it easy to analyze the history of our modules, which is also a very\nimportant activity when performing maintenance.\n\nUsage example::\n\n checkcommit --message \"$(git log --format=%B -n 1)\"\n\nIf, for some reason, you wish to skip this QA check for a specific commit message\nyou can add ``#noqa`` to the end of your commit message.\n\nUsage example::\n\n [qa] Improved #20\n\n Simulation of a special unplanned case\n #noqa\n\n``checkendline``\n^^^^^^^^^^^^^^^^\n\nEnsures that a blank line is kept at the end of each file.\n\n``checkpendingmigrations``\n^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nEnsures there django migrations are up to date and no new migrations need to\nbe created.\n\nIt accepts an optional ``--migration-module`` flag indicating the django app\nname that should be passed to ``./manage.py makemigrations``, eg:\n``./manage.py makemigrations $MIGRATION_MODULE``.\n\n``checkrst``\n^^^^^^^^^^^^^\n\nChecks the syntax of all ReStructuredText files to ensure they can be published on pypi or using python-sphinx.\n\nSettings\n--------\n\n``IMMUNITY\n_ADMIN_SITE_CLASS``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``immunity_utils.admin_theme.admin.ImmunityAdminSite``\n\nIf you need to use a customized admin site class, you can use this setting.\n\n``IMMUNITY\n_ADMIN_SITE_TITLE``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``Immunity Admin``\n\nTitle value used in the ``<title>`` HTML tag of the admin site.\n\n``IMMUNITY\n_ADMIN_SITE_HEADER``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``Immunity``\n\nHeading text used in the main ``<h1>`` HTML tag (the logo) of the admin site.\n\n``IMMUNITY\n_ADMIN_INDEX_TITLE``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``Network administration``\n\nTitle shown to users in the index page of the admin site.\n\n``IMMUNITY\n_ADMIN_DASHBOARD_ENABLED``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``True``\n\nWhen ``True``, enables the `Immunity Dashboard <#immunity-dashboard>`_.\nUpon login, the user will be greeted with the dashboard instead of the default\nDjango admin index page.\n\n``IMMUNITY\n_ADMIN_THEME_LINKS``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``[]``\n\n**Note**: this setting requires\n`the admin_theme_settings context processor <#supplying-custom-css-and-js-for-the-admin-theme>`_\nin order to work.\n\nAllows to override the default CSS and favicon, as well as add extra\n<link> HTML elements if needed.\n\nThis setting overrides the default theme, you can reuse the default CSS or replace it entirely.\n\nThe following example shows how to keep using the default CSS,\nsupply an additional CSS and replace the favicon.\n\nExample usage:\n\n.. code-block:: python\n\n IMMUNITY\n_ADMIN_THEME_LINKS = [\n {'type': 'text/css', 'href': '/static/admin/css/immunity.css', 'rel': 'stylesheet', 'media': 'all'},\n {'type': 'text/css', 'href': '/static/admin/css/custom-theme.css', 'rel': 'stylesheet', 'media': 'all'},\n {'type': 'image/x-icon', 'href': '/static/favicon.png', 'rel': 'icon'}\n ]\n\n``IMMUNITY\n_ADMIN_THEME_JS``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``[]``\n\nAllows to pass a list of strings representing URLs of custom JS files to load.\n\nExample usage:\n\n.. code-block:: python\n\n IMMUNITY\n_ADMIN_THEME_JS = [\n '/static/custom-admin-theme.js',\n ]\n\n``IMMUNITY\n_ADMIN_SHOW_USERLINKS_BLOCK``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``False``\n\nWhen True, enables Django user links on the admin site.\n\ni.e. (USER NAME/ VIEW SITE / CHANGE PASSWORD / LOG OUT).\n\nThese links are already shown in the main navigation menu and for this reason are hidden by default.\n\n``IMMUNITY\n_API_DOCS``\n^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``True``\n\nWhether the OpenAPI documentation is enabled.\n\nWhen enabled, you can view the available documentation using the\nSwagger endpoint at ``/api/v1/docs/``.\n\nYou also need to add the following url to your project urls.py:\n\n.. code-block:: python\n\n urlpatterns += [\n url(r'^api/v1/', include('immunity_utils.api.urls')),\n ]\n\n``IMMUNITY\n_API_INFO``\n^^^^^^^^^^^^^^^^^^^^^\n\n**default**:\n\n.. code-block:: python\n\n {\n 'title': 'Immunity API',\n 'default_version': 'v1',\n 'description': 'Immunity REST API',\n }\n\nDefine OpenAPI general information.\nNOTE: This setting requires ``IMMUNITY\n_API_DOCS = True`` to take effect.\n\nFor more information about optional parameters check the\n`drf-yasg documentation <https://drf-yasg.readthedocs.io/en/stable/readme.html#quickstart>`_.\n\n``IMMUNITY\n_SLOW_TEST_THRESHOLD``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``[0.3, 1]`` (seconds)\n\nIt can be used to change the thresholds used by `TimeLoggingTestRunner <#immunity_utilsteststimeloggingtestrunner>`_\nto detect slow tests (0.3s by default) and highlight the slowest ones (1s by default) amongst them.\n\n``IMMUNITY\n_STATICFILES_VERSIONED_EXCLUDE``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n**default**: ``['leaflet/*/*.png']``\n\nAllows to pass a list of **Unix shell-style wildcards** for files to be excluded by `CompressStaticFilesStorage <#immunity_utilsstorageCompressStaticFilesStorage>`_.\n\nBy default Leaflet PNGs have been excluded to avoid bugs like `immunity/ansible-immunity2#232 <https://github.com/edge-servers/ansible-immunity2/issues/232>`_.\n\nExample usage:\n\n.. code-block:: python\n\n IMMUNITY\n_STATICFILES_VERSIONED_EXCLUDE = [\n '*png',\n ]\n\n``IMMUNITY\n_HTML_EMAIL``\n^^^^^^^^^^^^^^^^^^^^^^^\n\n+---------+----------+\n| type | ``bool`` |\n+---------+----------+\n| default | ``True`` |\n+---------+----------+\n\nIf ``True``, an HTML themed version of the email can be sent using\nthe `send_email <#immunity_utilsadmin_themeemailsend_email>`_ function.\n\n``IMMUNITY\n_EMAIL_TEMPLATE``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n+---------+----------------------------------------+\n| type | ``str`` |\n+---------+----------------------------------------+\n| default | ``immunity_utils/email_template.html`` |\n+---------+----------------------------------------+\n\nThis setting allows to change the django template used for sending emails with\nthe `send_email <#immunity_utilsadmin_themeemailsend_email>`_ function.\nIt is recommended to extend the default email template as in the example below.\n\n.. code-block:: django\n\n {% extends 'immunity_utils/email_template.html' %}\n {% block styles %}\n {{ block.super }}\n <style>\n .background {\n height: 100%;\n background: linear-gradient(to bottom, #8ccbbe 50%, #3797a4 50%);\n background-repeat: no-repeat;\n background-attachment: fixed;\n padding: 50px;\n }\n\n .mail-header {\n background-color: #3797a4;\n color: white;\n }\n </style>\n {% endblock styles %}\n\nSimilarly, you can customize the HTML of the template by overriding the ``body`` block.\nSee `email_template.html <https://github.com/edge-servers/immunity-utils/blob/\nmaster/immunity_utils/admin_theme/templates/immunity_utils/email_template.html>`_\nfor reference implementation.\n\n``IMMUNITY\n_EMAIL_LOGO``\n^^^^^^^^^^^^^^^^^^^^^^^\n\n+---------+-------------------------------------------------------------------------------------+\n| type | ``str`` |\n+---------+-------------------------------------------------------------------------------------+\n| default | `Immunity logo <https://raw.githubusercontent.com/immunity/immunity-utils/master/ \\ |\n| | immunity_utils/static/immunity-utils/images/immunity-logo.png>`_ |\n+---------+-------------------------------------------------------------------------------------+\n\nThis setting allows to change the logo which is displayed in HTML version of the email.\n\n**Note**: Provide a URL which points to the logo on your own web server. Ensure that the URL provided is\npublicly accessible from the internet. Otherwise, the logo may not be displayed in the email.\nPlease also note that SVG images do not get processed by some email clients\nlike Gmail so it is recommended to use PNG images.\n\n``IMMUNITY\n_CELERY_SOFT_TIME_LIMIT``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n+---------+---------------------+\n| type | ``int`` |\n+---------+---------------------+\n| default | ``30`` (in seconds) |\n+---------+---------------------+\n\nSets the soft time limit for celery tasks using\n`ImmunityCeleryTask <#immunity_utilstasksimmunitycelerytask>`_.\n\n``IMMUNITY\n_CELERY_HARD_TIME_LIMIT``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n+---------+----------------------+\n| type | ``int`` |\n+---------+----------------------+\n| default | ``120`` (in seconds) |\n+---------+----------------------+\n\nSets the hard time limit for celery tasks using\n`ImmunityCeleryTask <#immunity_utilstasksimmunitycelerytask>`_.\n\n``IMMUNITY\n_AUTOCOMPLETE_FILTER_VIEW``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n+---------+-------------------------------------------------------------+\n| type | ``str`` |\n+---------+-------------------------------------------------------------+\n| default | ``'immunity_utils.admin_theme.views.AutocompleteJsonView'`` |\n+---------+-------------------------------------------------------------+\n\nDotted path to the ``AutocompleteJsonView`` used by the\n``immunity_utils.admin_theme.filters.AutocompleteFilter``.\n\nInstalling for development\n--------------------------\n\nInstall the system dependencies:\n\n.. code-block:: shell\n\n sudo apt-get install sqlite3 libsqlite3-dev\n\n # For running E2E Selenium tests\n sudo apt install chromium\n\nInstall your forked repo:\n\n.. code-block:: shell\n\n git clone git://github.com/<your_fork>/immunity-utils\n cd immunity-utils/\n pip install -e .[qa,rest]\n\nInstall test requirements:\n\n.. code-block:: shell\n\n pip install -r requirements-test.txt\n\nInstall node dependencies used for testing:\n\n.. code-block:: shell\n\n npm install -g stylelint jshint\n\nSet up the pre-push hook to run tests and QA checks automatically right before the git push action, so that if anything fails the push operation will be aborted:\n\n.. code-block:: shell\n\n immunity-pre-push-hook --install\n\nInstall WebDriver for Chromium for your browser version from `<https://chromedriver.chromium.org/home>`_\nand Extract ``chromedriver`` to one of directories from your ``$PATH`` (example: ``~/.local/bin/``).\n\nCreate database:\n\n.. code-block:: shell\n\n cd tests/\n ./manage.py migrate\n ./manage.py createsuperuser\n\nRun development server:\n\n.. code-block:: shell\n\n cd tests/\n ./manage.py runserver\n\nYou can access the admin interface of the test project at http://127.0.0.1:8000/admin/.\n\nRun tests with:\n\n.. code-block:: shell\n\n ./runtests.py --parallel\n\nContributing\n------------\n\nPlease refer to the `Immunity contributing guidelines <http://immunity.io/docs/developer/contributing.html>`_.\n\nSupport\n-------\n\nSee `Immunity Support Channels <http://immunity.org/support.html>`_.\n\nChangelog\n---------\n\nSee `CHANGES <https://github.com/edge-servers/immunity-utils/blob/master/CHANGES.rst>`_.\n\nLicense\n-------\n\nSee `LICENSE <https://github.com/edge-servers/immunity-utils/blob/master/LICENSE>`_.\n\nAttribution\n-----------\n\n- `Wireless icon <https://github.com/edge-servers/immunity-utils/blob/master/immunity_utils/admin_theme/static/ui/immunity/images/monitoring-wifi.svg>`_\n is licensed by Gregbaker, under `CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>`_ ,\n via `Wikimedia Commons <https://commons.wikimedia.org/wiki/File:Wireless-icon.svg>`_.\n- `Roboto webfont <https://www.google.com/fonts/specimen/Roboto>`_ is licensed\n under the `Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>`_.\n WOFF files extracted using `<https://github.com/majodev/google-webfonts-helper>`_.\n",
"bugtrack_url": null,
"license": "BSD-3-Clause",
"summary": "Immunity 2 Utilities",
"version": "1.1a0",
"project_urls": {
"Download": "https://github.com/edge-servers/immunity-utils/releases",
"Homepage": "http://immunity.org"
},
"split_keywords": [
"django",
" netjson",
" openwrt",
" networking",
" immunity"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "0b0f3301d0e67041bf2b270bcc584fb9e8b319aeccff4d58cae911f148b11b44",
"md5": "cb8a8c3e50bf17e3bb747a6d17f26435",
"sha256": "8121dac769051c592799decb6b15034c8dbcf491b97800711a4d1053816260a1"
},
"downloads": -1,
"filename": "immunity_utils-1.1a0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "cb8a8c3e50bf17e3bb747a6d17f26435",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 825365,
"upload_time": "2024-06-25T06:34:42",
"upload_time_iso_8601": "2024-06-25T06:34:42.204759Z",
"url": "https://files.pythonhosted.org/packages/0b/0f/3301d0e67041bf2b270bcc584fb9e8b319aeccff4d58cae911f148b11b44/immunity_utils-1.1a0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "f627f387442ccb4f14d76c909a9e0cf698d2211ebf839816df7433321d1da6b0",
"md5": "57f47906d8420bbb8bf57a5f6bc3295d",
"sha256": "c57b5e6a534de80d3e20fc41c11841d9a523f50a3b8f22ee46ee2c336e8f890f"
},
"downloads": -1,
"filename": "immunity_utils-1.1a0.tar.gz",
"has_sig": false,
"md5_digest": "57f47906d8420bbb8bf57a5f6bc3295d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 828688,
"upload_time": "2024-06-25T06:34:46",
"upload_time_iso_8601": "2024-06-25T06:34:46.712904Z",
"url": "https://files.pythonhosted.org/packages/f6/27/f387442ccb4f14d76c909a9e0cf698d2211ebf839816df7433321d1da6b0/immunity_utils-1.1a0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-25 06:34:46",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "edge-servers",
"github_project": "immunity-utils",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "immunity-utils"
}