New Upstream Release - django-maintenance-mode

Ready changes

Summary

Merged new upstream version: 0.18.0 (was: 0.16.1).

Resulting package

Built on 2022-12-31T01:11 (took 5m32s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-django-maintenance-mode

Lintian Result

Diff

diff --git a/LICENSE.txt b/LICENSE.txt
index 08c3cb0..263e288 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2016 Fabio Caccamo - fabio.caccamo@gmail.com
+Copyright (c) 2016-present Fabio Caccamo - fabio.caccamo@gmail.com
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/PKG-INFO b/PKG-INFO
index 17a5033..a785de3 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,350 +1,360 @@
 Metadata-Version: 2.1
 Name: django-maintenance-mode
-Version: 0.16.1
+Version: 0.18.0
 Summary: django-maintenance-mode shows a 503 error page when maintenance-mode is on.
 Home-page: https://github.com/fabiocaccamo/django-maintenance-mode
+Download-URL: https://github.com/fabiocaccamo/django-maintenance-mode/archive/0.18.0.tar.gz
 Author: Fabio Caccamo
 Author-email: fabio.caccamo@gmail.com
 License: MIT
-Download-URL: https://github.com/fabiocaccamo/django-maintenance-mode/archive/0.16.1.tar.gz
-Description: [![](https://img.shields.io/pypi/pyversions/django-maintenance-mode.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
-        [![](https://img.shields.io/pypi/djversions/django-maintenance-mode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
-        
-        [![](https://img.shields.io/pypi/v/django-maintenance-mode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-maintenance-mode/)
-        [![](https://pepy.tech/badge/django-maintenance-mode)](https://pepy.tech/project/django-maintenance-mode)
-        [![](https://img.shields.io/github/stars/fabiocaccamo/django-maintenance-mode?logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode/)
-        [![](https://badges.pufler.dev/visits/fabiocaccamo/django-maintenance-mode?label=visitors&color=blue)](https://badges.pufler.dev)
-        [![](https://img.shields.io/pypi/l/django-maintenance-mode.svg?color=blue)](https://github.com/fabiocaccamo/django-maintenance-mode/blob/master/LICENSE.txt)
-        
-        [![](https://img.shields.io/travis/fabiocaccamo/django-maintenance-mode?logo=travis&label=build)](https://travis-ci.org/fabiocaccamo/django-maintenance-mode)
-        [![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-maintenance-mode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-maintenance-mode)
-        [![](https://img.shields.io/codacy/grade/918668ac85e74206a4d8d95923548d79?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-maintenance-mode)
-        [![](https://img.shields.io/codeclimate/maintainability/fabiocaccamo/django-maintenance-mode?logo=code-climate)](https://codeclimate.com/github/fabiocaccamo/django-maintenance-mode/)
-        [![](https://requires.io/github/fabiocaccamo/django-maintenance-mode/requirements.svg?branch=master)](https://requires.io/github/fabiocaccamo/django-maintenance-mode/requirements/?branch=master)
-        
-        # django-maintenance-mode
-        django-maintenance-mode shows a 503 error page when **maintenance-mode** is **on**.
-        
-        It works at application level, so your django instance should be up.
-        
-        It doesn't use database and doesn't prevent database access.
-        
-        ## Installation
-        
-        1. Run ``pip install django-maintenance-mode`` or [download django-maintenance-mode](http://pypi.python.org/pypi/django-maintenance-mode) and add the **maintenance_mode** package to your project
-        2. Add ``'maintenance_mode'`` to ``settings.INSTALLED_APPS`` before custom applications
-        3. Add ``'maintenance_mode.middleware.MaintenanceModeMiddleware'`` to ``settings.MIDDLEWARE_CLASSES``/``settings.MIDDLEWARE`` as last middleware
-        4. Add your custom ``templates/503.html`` file
-        5. Restart your application server
-        
-        ## Configuration (optional)
-        
-        ### Settings
-        All these settings are optional, if not defined in ``settings.py`` the default values (listed below) will be used.
-        
-        ```python
-        # if True the maintenance-mode will be activated
-        MAINTENANCE_MODE = None
-        ```
-        
-        ```python
-        # by default, to get/set the state value a local file backend is used
-        # if you want to use the db or cache, you can create a custom backend
-        # custom backends must extend 'maintenance_mode.backends.AbstractStateBackend' class
-        # and implement get_value(self) and set_value(self, val) methods
-        MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.LocalFileBackend'
-        
-        # alternatively it is possible to use the default storage backend
-        MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.DefaultStorageBackend'
-        ```
-        
-        ```python
-        # by default, a file named "maintenance_mode_state.txt" will be created in the settings.py directory
-        # you can customize the state file path in case the default one is not writable
-        MAINTENANCE_MODE_STATE_FILE_PATH = 'maintenance_mode_state.txt'
-        ```
-        
-        ```python
-        # if True admin site will not be affected by the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_ADMIN_SITE = False
-        ```
-        
-        ```python
-        # if True anonymous users will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = False
-        ```
-        
-        ```python
-        # if True authenticated users will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER = False
-        ```
-        
-        ```python
-        # if True the staff will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_STAFF = False
-        ```
-        
-        ```python
-        # if True the superuser will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_SUPERUSER = False
-        ```
-        
-        ```python
-        # list of ip-addresses that will not be affected by the maintenance-mode
-        # ip-addresses will be used to compile regular expressions objects
-        MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = ()
-        ```
-        
-        ```python
-        # the path of the function that will return the client IP address given the request object -> 'myapp.mymodule.myfunction'
-        # the default function ('maintenance_mode.utils.get_client_ip_address') returns request.META['REMOTE_ADDR']
-        # in some cases the default function returns None, to avoid this scenario just use 'django-ipware'
-        MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None
-        ```
-        Retrieve user's real IP address using [`django-ipware`](https://github.com/un33k/django-ipware):
-        ```python
-        MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip'
-        ```
-        
-        ```python
-        # list of urls that will not be affected by the maintenance-mode
-        # urls will be used to compile regular expressions objects
-        MAINTENANCE_MODE_IGNORE_URLS = ()
-        ```
-        
-        ```python
-        # if True the maintenance mode will not return 503 response while running tests
-        # useful for running tests while maintenance mode is on, before opening the site to public use
-        MAINTENANCE_MODE_IGNORE_TESTS = False
-        ```
-        
-        ```python
-        # the absolute url where users will be redirected to during maintenance-mode
-        MAINTENANCE_MODE_REDIRECT_URL = None
-        ```
-        
-        ```python
-        # the template that will be shown by the maintenance-mode page
-        MAINTENANCE_MODE_TEMPLATE = '503.html'
-        ```
-        
-        ```python
-        # the path of the function that will return the template context -> 'myapp.mymodule.myfunction'
-        MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT = None
-        ```
-        
-        ```python
-        # the HTTP status code to send
-        MAINTENANCE_MODE_STATUS_CODE = 503
-        ```
-        
-        ```python
-        # the value in seconds of the Retry-After header during maintenance-mode
-        MAINTENANCE_MODE_RETRY_AFTER = 3600 # 1 hour
-        ```
-        
-        #### Context Processors
-        Add **maintenance_mode.context_processors.maintenance_mode** to your context_processors list in ``settings.py`` if you want to access the maintenance_mode status in your templates.
-        
-        ```python
-        TEMPLATES = [
-            {
-                # ...
-                'OPTIONS': {
-                    'context_processors': [
-                        # ...
-                        'maintenance_mode.context_processors.maintenance_mode',
-                        # ...
-                    ],
-                },
-                # ...
-            },
-        ]
-        ```
-        
-        #### Logging
-        You can disable emailing 503 errors to admins while maintenance mode is enabled:
-        
-        ```python
-        LOGGING = {
-            'filters': {
-                'require_not_maintenance_mode_503': {
-                    '()': 'maintenance_mode.logging.RequireNotMaintenanceMode503',
-                },
-                ...
-            },
-            'handlers': {
-                ...
-            },
-            ...
-        }
-        ```
-        
-        ### Context Managers
-        You can force a block of code execution to run under maintenance mode or not using context managers:
-        
-        ```python
-        from maintenance_mode.core import maintenance_mode_off, maintenance_mode_on
-        
-        with maintenance_mode_on():
-            # do stuff
-            pass
-        
-        with maintenance_mode_off():
-            # do stuff
-            pass
-        ```
-        
-        ### URLs
-        Add **maintenance_mode.urls** to ``urls.py`` if you want superusers able to set maintenance_mode using urls.
-        
-        ```python
-        urlpatterns = [
-            # ...
-            url(r'^maintenance-mode/', include('maintenance_mode.urls')),
-            # ...
-        ]
-        ```
-        
-        ### Views
-        You can force maintenance mode on/off at view level using view decorators:
-        
-        ```python
-        from maintenance_mode.decorators import force_maintenance_mode_off, force_maintenance_mode_on
-        
-        @force_maintenance_mode_off
-        def my_view_a(request):
-            # never return 503 response
-            pass
-        
-        @force_maintenance_mode_on
-        def my_view_b(request):
-            # always return 503 response
-            pass
-        ```
-        
-        ## Usage
-        
-        ### Python
-        ```python
-        from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
-        
-        set_maintenance_mode(True)
-        
-        if get_maintenance_mode():
-            set_maintenance_mode(False)
-        ```
-        or
-        ```python
-        from django.core.management import call_command
-        from django.core.management.base import BaseCommand
-        
-        
-        class Command(BaseCommand):
-        
-            def handle(self, *args, **options):
-        
-                call_command('maintenance_mode', 'on')
-        
-                # call your command(s)
-        
-                call_command('maintenance_mode', 'off')
-        
-        
-        
-        ```
-        
-        ### Templates
-        ```html
-        {% if maintenance_mode %}
-        <!-- html -->
-        {% endif %}
-        ```
-        
-        ### Terminal
-        
-        Run ``python manage.py maintenance_mode <on|off>``
-        
-        *(**This is not Heroku-friendly because** any execution of heroku run* `manage.py` *will be run on a separate worker dyno, not the web one. Therefore **the state-file is set but on the wrong machine. You should use a custom*** `MAINTENANCE_MODE_STATE_BACKEND`*.)*
-        
-        ### URLs
-        Superusers can change maintenance-mode using the following urls:
-        
-        ``/maintenance-mode/off/``
-        
-        ``/maintenance-mode/on/``
-        
-        ## Testing
-        ```bash
-        # create python virtual environment
-        virtualenv testing_django_maintenance_mode
-        
-        # activate virtualenv
-        cd testing_django_maintenance_mode && . bin/activate
-        
-        # clone repo
-        git clone https://github.com/fabiocaccamo/django-maintenance-mode.git src && cd src
-        
-        # run tests
-        tox
-        # or
-        python setup.py test
-        # or
-        python -m django test --settings "tests.settings"
-        ```
-        
-        ## License
-        Released under [MIT License](LICENSE.txt).
-        
-        ---
-        
-        ## See also
-        
-        - [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก
-        
-        - [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. ๐ŸŽจ
-        
-        - [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. โš™๏ธ
-        
-        - [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. โ†ช๏ธ
-        
-        - [`django-treenode`](https://github.com/fabiocaccamo/django-treenode) - probably the best abstract model / admin for your tree based stuff. ๐ŸŒณ
-        
-        - [`python-benedict`](https://github.com/fabiocaccamo/python-benedict) - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. ๐Ÿ“˜
-        
-        - [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ
-        
-        - [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ
-        
+Project-URL: Documentation, https://github.com/fabiocaccamo/django-maintenance-mode#readme
+Project-URL: Issues, https://github.com/fabiocaccamo/django-maintenance-mode/issues
+Project-URL: Funding, https://github.com/sponsors/fabiocaccamo/
+Project-URL: Twitter, https://twitter.com/fabiocaccamo
 Keywords: django,maintenance,mode,offline,under,503,service,temporarily,unavailable
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Web Environment
 Classifier: Framework :: Django
-Classifier: Framework :: Django :: 1.7
-Classifier: Framework :: Django :: 1.8
-Classifier: Framework :: Django :: 1.9
-Classifier: Framework :: Django :: 1.10
-Classifier: Framework :: Django :: 1.11
-Classifier: Framework :: Django :: 2.0
-Classifier: Framework :: Django :: 2.1
 Classifier: Framework :: Django :: 2.2
 Classifier: Framework :: Django :: 3.0
 Classifier: Framework :: Django :: 3.1
 Classifier: Framework :: Django :: 3.2
+Classifier: Framework :: Django :: 4.0
+Classifier: Framework :: Django :: 4.1
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: Software Development :: Build Tools
-Requires: django(>=1.7)
+Requires: django (>= 2.2)
 Description-Content-Type: text/markdown
+License-File: LICENSE.txt
+
+[![](https://img.shields.io/pypi/pyversions/django-maintenance-mode.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
+[![](https://img.shields.io/pypi/djversions/django-maintenance-mode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
+
+[![](https://img.shields.io/pypi/v/django-maintenance-mode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-maintenance-mode/)
+[![](https://pepy.tech/badge/django-maintenance-mode/month)](https://pepy.tech/project/django-maintenance-mode)
+[![](https://img.shields.io/github/stars/fabiocaccamo/django-maintenance-mode?logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode/)
+[![](https://badges.pufler.dev/visits/fabiocaccamo/django-maintenance-mode?label=visitors&color=blue)](https://badges.pufler.dev)
+[![](https://img.shields.io/pypi/l/django-maintenance-mode.svg?color=blue)](https://github.com/fabiocaccamo/django-maintenance-mode/blob/master/LICENSE.txt)
+
+[![](https://results.pre-commit.ci/badge/github/fabiocaccamo/django-maintenance-mode/master.svg)](https://results.pre-commit.ci/latest/github/fabiocaccamo/django-maintenance-mode/master)
+[![](https://img.shields.io/github/workflow/status/fabiocaccamo/django-maintenance-mode/Test%20package?label=build&logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode)
+[![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-maintenance-mode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-maintenance-mode)
+[![](https://img.shields.io/codacy/grade/918668ac85e74206a4d8d95923548d79?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-maintenance-mode)
+[![](https://img.shields.io/codeclimate/maintainability/fabiocaccamo/django-maintenance-mode?logo=code-climate)](https://codeclimate.com/github/fabiocaccamo/django-maintenance-mode/)
+[![](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+
+# django-maintenance-mode
+django-maintenance-mode shows a 503 error page when **maintenance-mode** is **on**.
+
+It works at application level, so your django instance should be up.
+
+It doesn't use database and doesn't prevent database access.
+
+## Installation
+
+1. Run `pip install django-maintenance-mode` or [download django-maintenance-mode](http://pypi.python.org/pypi/django-maintenance-mode) and add the **maintenance_mode** package to your project
+2. Add `maintenance_mode` to `settings.INSTALLED_APPS` before custom applications
+3. Add `maintenance_mode.middleware.MaintenanceModeMiddleware` to `settings.MIDDLEWARE` as last middleware
+4. Add your custom `templates/503.html` file
+5. Restart your application server
+
+## Configuration (optional)
+
+### Settings
+All these settings are optional, if not defined in `settings.py` the default values (listed below) will be used.
+
+```python
+# if True the maintenance-mode will be activated
+MAINTENANCE_MODE = None
+```
+
+```python
+# by default, to get/set the state value a local file backend is used
+# if you want to use the db or cache, you can create a custom backend
+# custom backends must extend 'maintenance_mode.backends.AbstractStateBackend' class
+# and implement get_value(self) and set_value(self, val) methods
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.LocalFileBackend"
+
+# alternatively it is possible to use the default storage backend
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.DefaultStorageBackend"
+
+# alternatively it is possible to use the static storage backend
+# make sure that STATIC_ROOT and STATIC_URL are also set
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.StaticStorageBackend"
+```
+
+```python
+# by default, a file named "maintenance_mode_state.txt" will be created in the settings.py directory
+# you can customize the state file path in case the default one is not writable
+MAINTENANCE_MODE_STATE_FILE_PATH = "maintenance_mode_state.txt"
+```
+
+```python
+# if True admin site will not be affected by the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_ADMIN_SITE = False
+```
+
+```python
+# if True anonymous users will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = False
+```
+
+```python
+# if True authenticated users will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER = False
+```
+
+```python
+# if True the staff will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_STAFF = False
+```
+
+```python
+# if True the superuser will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_SUPERUSER = False
+```
+
+```python
+# list of ip-addresses that will not be affected by the maintenance-mode
+# ip-addresses will be used to compile regular expressions objects
+MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = ()
+```
+
+```python
+# the path of the function that will return the client IP address given the request object -> 'myapp.mymodule.myfunction'
+# the default function ('maintenance_mode.utils.get_client_ip_address') returns request.META['REMOTE_ADDR']
+# in some cases the default function returns None, to avoid this scenario just use 'django-ipware'
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None
+```
+Retrieve user's real IP address using [`django-ipware`](https://github.com/un33k/django-ipware):
+```python
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = "ipware.ip.get_ip"
+```
+
+```python
+# list of urls that will not be affected by the maintenance-mode
+# urls will be used to compile regular expressions objects
+MAINTENANCE_MODE_IGNORE_URLS = ()
+```
+
+```python
+# if True the maintenance mode will not return 503 response while running tests
+# useful for running tests while maintenance mode is on, before opening the site to public use
+MAINTENANCE_MODE_IGNORE_TESTS = False
+```
+
+```python
+# the absolute url where users will be redirected to during maintenance-mode
+MAINTENANCE_MODE_REDIRECT_URL = None
+```
+
+```python
+# the template that will be shown by the maintenance-mode page
+MAINTENANCE_MODE_TEMPLATE = "503.html"
+```
+
+```python
+# the path of the function that will return the template context -> 'myapp.mymodule.myfunction'
+MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT = None
+```
+
+```python
+# the HTTP status code to send
+MAINTENANCE_MODE_STATUS_CODE = 503
+```
+
+```python
+# the value in seconds of the Retry-After header during maintenance-mode
+MAINTENANCE_MODE_RETRY_AFTER = 3600 # 1 hour
+```
+
+#### Context Processors
+Add **maintenance_mode.context_processors.maintenance_mode** to your context_processors list in `settings.py` if you want to access the maintenance_mode status in your templates.
+
+```python
+TEMPLATES = [
+    {
+        # ...
+        "OPTIONS": {
+            "context_processors": [
+                # ...
+                "maintenance_mode.context_processors.maintenance_mode",
+                # ...
+            ],
+        },
+        # ...
+    },
+]
+```
+
+#### Logging
+You can disable emailing 503 errors to admins while maintenance mode is enabled:
+
+```python
+LOGGING = {
+    "filters": {
+        "require_not_maintenance_mode_503": {
+            "()": "maintenance_mode.logging.RequireNotMaintenanceMode503",
+        },
+        ...
+    },
+    "handlers": {
+        ...
+    },
+    ...
+}
+```
+
+### Context Managers
+You can force a block of code execution to run under maintenance mode or not using context managers:
+
+```python
+from maintenance_mode.core import maintenance_mode_off, maintenance_mode_on
+
+with maintenance_mode_on():
+    # do stuff
+    pass
+
+with maintenance_mode_off():
+    # do stuff
+    pass
+```
+
+### URLs
+Add **maintenance_mode.urls** to `urls.py` if you want superusers able to set maintenance_mode using urls.
+
+```python
+urlpatterns = [
+    # ...
+    re_path(r"^maintenance-mode/", include("maintenance_mode.urls")),
+    # ...
+]
+```
+
+### Views
+You can force maintenance mode on/off at view level using view decorators:
+
+```python
+from maintenance_mode.decorators import force_maintenance_mode_off, force_maintenance_mode_on
+
+@force_maintenance_mode_off
+def my_view_a(request):
+    # never return 503 response
+    pass
+
+@force_maintenance_mode_on
+def my_view_b(request):
+    # always return 503 response
+    pass
+```
+
+## Usage
+
+### Python
+```python
+from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
+
+set_maintenance_mode(True)
+
+if get_maintenance_mode():
+    set_maintenance_mode(False)
+```
+or
+```python
+from django.core.management import call_command
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+
+    def handle(self, *args, **options):
+
+        call_command("maintenance_mode", "on")
+
+        # call your command(s)
+
+        call_command("maintenance_mode", "off")
+
+```
+
+### Templates
+```html
+{% if maintenance_mode %}
+<!-- html -->
+{% endif %}
+```
+
+### Terminal
+
+Run ``python manage.py maintenance_mode <on|off>``
+
+*(**This is not Heroku-friendly because** any execution of heroku run* `manage.py` *will be run on a separate worker dyno, not the web one. Therefore **the state-file is set but on the wrong machine. You should use a custom*** `MAINTENANCE_MODE_STATE_BACKEND`*.)*
+
+### URLs
+Superusers can change maintenance-mode using the following urls:
+
+`/maintenance-mode/off/`
+
+`/maintenance-mode/on/`
+
+## Testing
+```bash
+# clone repository
+git clone https://github.com/fabiocaccamo/django-maintenance-mode.git && cd django-maintenance-mode
+
+# create virtualenv and activate it
+python -m venv venv && . venv/bin/activate
+
+# upgrade pip
+python -m pip install --upgrade pip
+
+# install requirements
+pip install -r requirements.txt -r requirements-test.txt
+
+# run tests
+tox
+# or
+python runtests.py
+# or
+python -m django test --settings "tests.settings"
+```
+
+## License
+Released under [MIT License](LICENSE.txt).
+
+---
+
+## Supporting
+
+- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/django-maintenance-mode)
+- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)
+- :blue_heart: Follow me on [Twitter](https://twitter.com/fabiocaccamo)
+- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)
+
+## See also
+
+- [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก
+
+- [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. ๐ŸŽจ
+
+- [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. โš™๏ธ
+
+- [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. โ†ช๏ธ
+
+- [`django-treenode`](https://github.com/fabiocaccamo/django-treenode) - probably the best abstract model / admin for your tree based stuff. ๐ŸŒณ
+
+- [`python-benedict`](https://github.com/fabiocaccamo/python-benedict) - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. ๐Ÿ“˜
+
+- [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ
+
+- [`python-fontbro`](https://github.com/fabiocaccamo/python-fontbro) - friendly font operations. ๐Ÿงข
+
+- [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ
diff --git a/README.md b/README.md
index be8c9a2..41d91e0 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,17 @@
 [![](https://img.shields.io/pypi/djversions/django-maintenance-mode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
 
 [![](https://img.shields.io/pypi/v/django-maintenance-mode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-maintenance-mode/)
-[![](https://pepy.tech/badge/django-maintenance-mode)](https://pepy.tech/project/django-maintenance-mode)
+[![](https://pepy.tech/badge/django-maintenance-mode/month)](https://pepy.tech/project/django-maintenance-mode)
 [![](https://img.shields.io/github/stars/fabiocaccamo/django-maintenance-mode?logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode/)
 [![](https://badges.pufler.dev/visits/fabiocaccamo/django-maintenance-mode?label=visitors&color=blue)](https://badges.pufler.dev)
 [![](https://img.shields.io/pypi/l/django-maintenance-mode.svg?color=blue)](https://github.com/fabiocaccamo/django-maintenance-mode/blob/master/LICENSE.txt)
 
-[![](https://img.shields.io/travis/fabiocaccamo/django-maintenance-mode?logo=travis&label=build)](https://travis-ci.org/fabiocaccamo/django-maintenance-mode)
+[![](https://results.pre-commit.ci/badge/github/fabiocaccamo/django-maintenance-mode/master.svg)](https://results.pre-commit.ci/latest/github/fabiocaccamo/django-maintenance-mode/master)
+[![](https://img.shields.io/github/workflow/status/fabiocaccamo/django-maintenance-mode/Test%20package?label=build&logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode)
 [![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-maintenance-mode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-maintenance-mode)
 [![](https://img.shields.io/codacy/grade/918668ac85e74206a4d8d95923548d79?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-maintenance-mode)
 [![](https://img.shields.io/codeclimate/maintainability/fabiocaccamo/django-maintenance-mode?logo=code-climate)](https://codeclimate.com/github/fabiocaccamo/django-maintenance-mode/)
-[![](https://requires.io/github/fabiocaccamo/django-maintenance-mode/requirements.svg?branch=master)](https://requires.io/github/fabiocaccamo/django-maintenance-mode/requirements/?branch=master)
+[![](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 
 # django-maintenance-mode
 django-maintenance-mode shows a 503 error page when **maintenance-mode** is **on**.
@@ -22,16 +23,16 @@ It doesn't use database and doesn't prevent database access.
 
 ## Installation
 
-1. Run ``pip install django-maintenance-mode`` or [download django-maintenance-mode](http://pypi.python.org/pypi/django-maintenance-mode) and add the **maintenance_mode** package to your project
-2. Add ``'maintenance_mode'`` to ``settings.INSTALLED_APPS`` before custom applications
-3. Add ``'maintenance_mode.middleware.MaintenanceModeMiddleware'`` to ``settings.MIDDLEWARE_CLASSES``/``settings.MIDDLEWARE`` as last middleware
-4. Add your custom ``templates/503.html`` file
+1. Run `pip install django-maintenance-mode` or [download django-maintenance-mode](http://pypi.python.org/pypi/django-maintenance-mode) and add the **maintenance_mode** package to your project
+2. Add `maintenance_mode` to `settings.INSTALLED_APPS` before custom applications
+3. Add `maintenance_mode.middleware.MaintenanceModeMiddleware` to `settings.MIDDLEWARE` as last middleware
+4. Add your custom `templates/503.html` file
 5. Restart your application server
 
 ## Configuration (optional)
 
 ### Settings
-All these settings are optional, if not defined in ``settings.py`` the default values (listed below) will be used.
+All these settings are optional, if not defined in `settings.py` the default values (listed below) will be used.
 
 ```python
 # if True the maintenance-mode will be activated
@@ -43,16 +44,20 @@ MAINTENANCE_MODE = None
 # if you want to use the db or cache, you can create a custom backend
 # custom backends must extend 'maintenance_mode.backends.AbstractStateBackend' class
 # and implement get_value(self) and set_value(self, val) methods
-MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.LocalFileBackend'
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.LocalFileBackend"
 
 # alternatively it is possible to use the default storage backend
-MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.DefaultStorageBackend'
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.DefaultStorageBackend"
+
+# alternatively it is possible to use the static storage backend
+# make sure that STATIC_ROOT and STATIC_URL are also set
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.StaticStorageBackend"
 ```
 
 ```python
 # by default, a file named "maintenance_mode_state.txt" will be created in the settings.py directory
 # you can customize the state file path in case the default one is not writable
-MAINTENANCE_MODE_STATE_FILE_PATH = 'maintenance_mode_state.txt'
+MAINTENANCE_MODE_STATE_FILE_PATH = "maintenance_mode_state.txt"
 ```
 
 ```python
@@ -94,7 +99,7 @@ MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None
 ```
 Retrieve user's real IP address using [`django-ipware`](https://github.com/un33k/django-ipware):
 ```python
-MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip'
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = "ipware.ip.get_ip"
 ```
 
 ```python
@@ -116,7 +121,7 @@ MAINTENANCE_MODE_REDIRECT_URL = None
 
 ```python
 # the template that will be shown by the maintenance-mode page
-MAINTENANCE_MODE_TEMPLATE = '503.html'
+MAINTENANCE_MODE_TEMPLATE = "503.html"
 ```
 
 ```python
@@ -135,16 +140,16 @@ MAINTENANCE_MODE_RETRY_AFTER = 3600 # 1 hour
 ```
 
 #### Context Processors
-Add **maintenance_mode.context_processors.maintenance_mode** to your context_processors list in ``settings.py`` if you want to access the maintenance_mode status in your templates.
+Add **maintenance_mode.context_processors.maintenance_mode** to your context_processors list in `settings.py` if you want to access the maintenance_mode status in your templates.
 
 ```python
 TEMPLATES = [
     {
         # ...
-        'OPTIONS': {
-            'context_processors': [
+        "OPTIONS": {
+            "context_processors": [
                 # ...
-                'maintenance_mode.context_processors.maintenance_mode',
+                "maintenance_mode.context_processors.maintenance_mode",
                 # ...
             ],
         },
@@ -158,13 +163,13 @@ You can disable emailing 503 errors to admins while maintenance mode is enabled:
 
 ```python
 LOGGING = {
-    'filters': {
-        'require_not_maintenance_mode_503': {
-            '()': 'maintenance_mode.logging.RequireNotMaintenanceMode503',
+    "filters": {
+        "require_not_maintenance_mode_503": {
+            "()": "maintenance_mode.logging.RequireNotMaintenanceMode503",
         },
         ...
     },
-    'handlers': {
+    "handlers": {
         ...
     },
     ...
@@ -187,12 +192,12 @@ with maintenance_mode_off():
 ```
 
 ### URLs
-Add **maintenance_mode.urls** to ``urls.py`` if you want superusers able to set maintenance_mode using urls.
+Add **maintenance_mode.urls** to `urls.py` if you want superusers able to set maintenance_mode using urls.
 
 ```python
 urlpatterns = [
     # ...
-    url(r'^maintenance-mode/', include('maintenance_mode.urls')),
+    re_path(r"^maintenance-mode/", include("maintenance_mode.urls")),
     # ...
 ]
 ```
@@ -235,13 +240,11 @@ class Command(BaseCommand):
 
     def handle(self, *args, **options):
 
-        call_command('maintenance_mode', 'on')
+        call_command("maintenance_mode", "on")
 
         # call your command(s)
 
-        call_command('maintenance_mode', 'off')
-
-
+        call_command("maintenance_mode", "off")
 
 ```
 
@@ -261,25 +264,28 @@ Run ``python manage.py maintenance_mode <on|off>``
 ### URLs
 Superusers can change maintenance-mode using the following urls:
 
-``/maintenance-mode/off/``
+`/maintenance-mode/off/`
 
-``/maintenance-mode/on/``
+`/maintenance-mode/on/`
 
 ## Testing
 ```bash
-# create python virtual environment
-virtualenv testing_django_maintenance_mode
+# clone repository
+git clone https://github.com/fabiocaccamo/django-maintenance-mode.git && cd django-maintenance-mode
+
+# create virtualenv and activate it
+python -m venv venv && . venv/bin/activate
 
-# activate virtualenv
-cd testing_django_maintenance_mode && . bin/activate
+# upgrade pip
+python -m pip install --upgrade pip
 
-# clone repo
-git clone https://github.com/fabiocaccamo/django-maintenance-mode.git src && cd src
+# install requirements
+pip install -r requirements.txt -r requirements-test.txt
 
 # run tests
 tox
 # or
-python setup.py test
+python runtests.py
 # or
 python -m django test --settings "tests.settings"
 ```
@@ -289,6 +295,13 @@ Released under [MIT License](LICENSE.txt).
 
 ---
 
+## Supporting
+
+- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/django-maintenance-mode)
+- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)
+- :blue_heart: Follow me on [Twitter](https://twitter.com/fabiocaccamo)
+- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)
+
 ## See also
 
 - [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก
@@ -305,4 +318,6 @@ Released under [MIT License](LICENSE.txt).
 
 - [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ
 
+- [`python-fontbro`](https://github.com/fabiocaccamo/python-fontbro) - friendly font operations. ๐Ÿงข
+
 - [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ
diff --git a/debian/changelog b/debian/changelog
index 74e0910..123131e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+django-maintenance-mode (0.18.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 31 Dec 2022 01:06:48 -0000
+
 django-maintenance-mode (0.16.1-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/django_maintenance_mode.egg-info/PKG-INFO b/django_maintenance_mode.egg-info/PKG-INFO
index 17a5033..a785de3 100644
--- a/django_maintenance_mode.egg-info/PKG-INFO
+++ b/django_maintenance_mode.egg-info/PKG-INFO
@@ -1,350 +1,360 @@
 Metadata-Version: 2.1
 Name: django-maintenance-mode
-Version: 0.16.1
+Version: 0.18.0
 Summary: django-maintenance-mode shows a 503 error page when maintenance-mode is on.
 Home-page: https://github.com/fabiocaccamo/django-maintenance-mode
+Download-URL: https://github.com/fabiocaccamo/django-maintenance-mode/archive/0.18.0.tar.gz
 Author: Fabio Caccamo
 Author-email: fabio.caccamo@gmail.com
 License: MIT
-Download-URL: https://github.com/fabiocaccamo/django-maintenance-mode/archive/0.16.1.tar.gz
-Description: [![](https://img.shields.io/pypi/pyversions/django-maintenance-mode.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
-        [![](https://img.shields.io/pypi/djversions/django-maintenance-mode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
-        
-        [![](https://img.shields.io/pypi/v/django-maintenance-mode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-maintenance-mode/)
-        [![](https://pepy.tech/badge/django-maintenance-mode)](https://pepy.tech/project/django-maintenance-mode)
-        [![](https://img.shields.io/github/stars/fabiocaccamo/django-maintenance-mode?logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode/)
-        [![](https://badges.pufler.dev/visits/fabiocaccamo/django-maintenance-mode?label=visitors&color=blue)](https://badges.pufler.dev)
-        [![](https://img.shields.io/pypi/l/django-maintenance-mode.svg?color=blue)](https://github.com/fabiocaccamo/django-maintenance-mode/blob/master/LICENSE.txt)
-        
-        [![](https://img.shields.io/travis/fabiocaccamo/django-maintenance-mode?logo=travis&label=build)](https://travis-ci.org/fabiocaccamo/django-maintenance-mode)
-        [![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-maintenance-mode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-maintenance-mode)
-        [![](https://img.shields.io/codacy/grade/918668ac85e74206a4d8d95923548d79?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-maintenance-mode)
-        [![](https://img.shields.io/codeclimate/maintainability/fabiocaccamo/django-maintenance-mode?logo=code-climate)](https://codeclimate.com/github/fabiocaccamo/django-maintenance-mode/)
-        [![](https://requires.io/github/fabiocaccamo/django-maintenance-mode/requirements.svg?branch=master)](https://requires.io/github/fabiocaccamo/django-maintenance-mode/requirements/?branch=master)
-        
-        # django-maintenance-mode
-        django-maintenance-mode shows a 503 error page when **maintenance-mode** is **on**.
-        
-        It works at application level, so your django instance should be up.
-        
-        It doesn't use database and doesn't prevent database access.
-        
-        ## Installation
-        
-        1. Run ``pip install django-maintenance-mode`` or [download django-maintenance-mode](http://pypi.python.org/pypi/django-maintenance-mode) and add the **maintenance_mode** package to your project
-        2. Add ``'maintenance_mode'`` to ``settings.INSTALLED_APPS`` before custom applications
-        3. Add ``'maintenance_mode.middleware.MaintenanceModeMiddleware'`` to ``settings.MIDDLEWARE_CLASSES``/``settings.MIDDLEWARE`` as last middleware
-        4. Add your custom ``templates/503.html`` file
-        5. Restart your application server
-        
-        ## Configuration (optional)
-        
-        ### Settings
-        All these settings are optional, if not defined in ``settings.py`` the default values (listed below) will be used.
-        
-        ```python
-        # if True the maintenance-mode will be activated
-        MAINTENANCE_MODE = None
-        ```
-        
-        ```python
-        # by default, to get/set the state value a local file backend is used
-        # if you want to use the db or cache, you can create a custom backend
-        # custom backends must extend 'maintenance_mode.backends.AbstractStateBackend' class
-        # and implement get_value(self) and set_value(self, val) methods
-        MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.LocalFileBackend'
-        
-        # alternatively it is possible to use the default storage backend
-        MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.DefaultStorageBackend'
-        ```
-        
-        ```python
-        # by default, a file named "maintenance_mode_state.txt" will be created in the settings.py directory
-        # you can customize the state file path in case the default one is not writable
-        MAINTENANCE_MODE_STATE_FILE_PATH = 'maintenance_mode_state.txt'
-        ```
-        
-        ```python
-        # if True admin site will not be affected by the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_ADMIN_SITE = False
-        ```
-        
-        ```python
-        # if True anonymous users will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = False
-        ```
-        
-        ```python
-        # if True authenticated users will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER = False
-        ```
-        
-        ```python
-        # if True the staff will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_STAFF = False
-        ```
-        
-        ```python
-        # if True the superuser will not see the maintenance-mode page
-        MAINTENANCE_MODE_IGNORE_SUPERUSER = False
-        ```
-        
-        ```python
-        # list of ip-addresses that will not be affected by the maintenance-mode
-        # ip-addresses will be used to compile regular expressions objects
-        MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = ()
-        ```
-        
-        ```python
-        # the path of the function that will return the client IP address given the request object -> 'myapp.mymodule.myfunction'
-        # the default function ('maintenance_mode.utils.get_client_ip_address') returns request.META['REMOTE_ADDR']
-        # in some cases the default function returns None, to avoid this scenario just use 'django-ipware'
-        MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None
-        ```
-        Retrieve user's real IP address using [`django-ipware`](https://github.com/un33k/django-ipware):
-        ```python
-        MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip'
-        ```
-        
-        ```python
-        # list of urls that will not be affected by the maintenance-mode
-        # urls will be used to compile regular expressions objects
-        MAINTENANCE_MODE_IGNORE_URLS = ()
-        ```
-        
-        ```python
-        # if True the maintenance mode will not return 503 response while running tests
-        # useful for running tests while maintenance mode is on, before opening the site to public use
-        MAINTENANCE_MODE_IGNORE_TESTS = False
-        ```
-        
-        ```python
-        # the absolute url where users will be redirected to during maintenance-mode
-        MAINTENANCE_MODE_REDIRECT_URL = None
-        ```
-        
-        ```python
-        # the template that will be shown by the maintenance-mode page
-        MAINTENANCE_MODE_TEMPLATE = '503.html'
-        ```
-        
-        ```python
-        # the path of the function that will return the template context -> 'myapp.mymodule.myfunction'
-        MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT = None
-        ```
-        
-        ```python
-        # the HTTP status code to send
-        MAINTENANCE_MODE_STATUS_CODE = 503
-        ```
-        
-        ```python
-        # the value in seconds of the Retry-After header during maintenance-mode
-        MAINTENANCE_MODE_RETRY_AFTER = 3600 # 1 hour
-        ```
-        
-        #### Context Processors
-        Add **maintenance_mode.context_processors.maintenance_mode** to your context_processors list in ``settings.py`` if you want to access the maintenance_mode status in your templates.
-        
-        ```python
-        TEMPLATES = [
-            {
-                # ...
-                'OPTIONS': {
-                    'context_processors': [
-                        # ...
-                        'maintenance_mode.context_processors.maintenance_mode',
-                        # ...
-                    ],
-                },
-                # ...
-            },
-        ]
-        ```
-        
-        #### Logging
-        You can disable emailing 503 errors to admins while maintenance mode is enabled:
-        
-        ```python
-        LOGGING = {
-            'filters': {
-                'require_not_maintenance_mode_503': {
-                    '()': 'maintenance_mode.logging.RequireNotMaintenanceMode503',
-                },
-                ...
-            },
-            'handlers': {
-                ...
-            },
-            ...
-        }
-        ```
-        
-        ### Context Managers
-        You can force a block of code execution to run under maintenance mode or not using context managers:
-        
-        ```python
-        from maintenance_mode.core import maintenance_mode_off, maintenance_mode_on
-        
-        with maintenance_mode_on():
-            # do stuff
-            pass
-        
-        with maintenance_mode_off():
-            # do stuff
-            pass
-        ```
-        
-        ### URLs
-        Add **maintenance_mode.urls** to ``urls.py`` if you want superusers able to set maintenance_mode using urls.
-        
-        ```python
-        urlpatterns = [
-            # ...
-            url(r'^maintenance-mode/', include('maintenance_mode.urls')),
-            # ...
-        ]
-        ```
-        
-        ### Views
-        You can force maintenance mode on/off at view level using view decorators:
-        
-        ```python
-        from maintenance_mode.decorators import force_maintenance_mode_off, force_maintenance_mode_on
-        
-        @force_maintenance_mode_off
-        def my_view_a(request):
-            # never return 503 response
-            pass
-        
-        @force_maintenance_mode_on
-        def my_view_b(request):
-            # always return 503 response
-            pass
-        ```
-        
-        ## Usage
-        
-        ### Python
-        ```python
-        from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
-        
-        set_maintenance_mode(True)
-        
-        if get_maintenance_mode():
-            set_maintenance_mode(False)
-        ```
-        or
-        ```python
-        from django.core.management import call_command
-        from django.core.management.base import BaseCommand
-        
-        
-        class Command(BaseCommand):
-        
-            def handle(self, *args, **options):
-        
-                call_command('maintenance_mode', 'on')
-        
-                # call your command(s)
-        
-                call_command('maintenance_mode', 'off')
-        
-        
-        
-        ```
-        
-        ### Templates
-        ```html
-        {% if maintenance_mode %}
-        <!-- html -->
-        {% endif %}
-        ```
-        
-        ### Terminal
-        
-        Run ``python manage.py maintenance_mode <on|off>``
-        
-        *(**This is not Heroku-friendly because** any execution of heroku run* `manage.py` *will be run on a separate worker dyno, not the web one. Therefore **the state-file is set but on the wrong machine. You should use a custom*** `MAINTENANCE_MODE_STATE_BACKEND`*.)*
-        
-        ### URLs
-        Superusers can change maintenance-mode using the following urls:
-        
-        ``/maintenance-mode/off/``
-        
-        ``/maintenance-mode/on/``
-        
-        ## Testing
-        ```bash
-        # create python virtual environment
-        virtualenv testing_django_maintenance_mode
-        
-        # activate virtualenv
-        cd testing_django_maintenance_mode && . bin/activate
-        
-        # clone repo
-        git clone https://github.com/fabiocaccamo/django-maintenance-mode.git src && cd src
-        
-        # run tests
-        tox
-        # or
-        python setup.py test
-        # or
-        python -m django test --settings "tests.settings"
-        ```
-        
-        ## License
-        Released under [MIT License](LICENSE.txt).
-        
-        ---
-        
-        ## See also
-        
-        - [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก
-        
-        - [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. ๐ŸŽจ
-        
-        - [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. โš™๏ธ
-        
-        - [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. โ†ช๏ธ
-        
-        - [`django-treenode`](https://github.com/fabiocaccamo/django-treenode) - probably the best abstract model / admin for your tree based stuff. ๐ŸŒณ
-        
-        - [`python-benedict`](https://github.com/fabiocaccamo/python-benedict) - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. ๐Ÿ“˜
-        
-        - [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ
-        
-        - [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ
-        
+Project-URL: Documentation, https://github.com/fabiocaccamo/django-maintenance-mode#readme
+Project-URL: Issues, https://github.com/fabiocaccamo/django-maintenance-mode/issues
+Project-URL: Funding, https://github.com/sponsors/fabiocaccamo/
+Project-URL: Twitter, https://twitter.com/fabiocaccamo
 Keywords: django,maintenance,mode,offline,under,503,service,temporarily,unavailable
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Web Environment
 Classifier: Framework :: Django
-Classifier: Framework :: Django :: 1.7
-Classifier: Framework :: Django :: 1.8
-Classifier: Framework :: Django :: 1.9
-Classifier: Framework :: Django :: 1.10
-Classifier: Framework :: Django :: 1.11
-Classifier: Framework :: Django :: 2.0
-Classifier: Framework :: Django :: 2.1
 Classifier: Framework :: Django :: 2.2
 Classifier: Framework :: Django :: 3.0
 Classifier: Framework :: Django :: 3.1
 Classifier: Framework :: Django :: 3.2
+Classifier: Framework :: Django :: 4.0
+Classifier: Framework :: Django :: 4.1
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.4
-Classifier: Programming Language :: Python :: 3.5
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Topic :: Software Development :: Build Tools
-Requires: django(>=1.7)
+Requires: django (>= 2.2)
 Description-Content-Type: text/markdown
+License-File: LICENSE.txt
+
+[![](https://img.shields.io/pypi/pyversions/django-maintenance-mode.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
+[![](https://img.shields.io/pypi/djversions/django-maintenance-mode?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
+
+[![](https://img.shields.io/pypi/v/django-maintenance-mode.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-maintenance-mode/)
+[![](https://pepy.tech/badge/django-maintenance-mode/month)](https://pepy.tech/project/django-maintenance-mode)
+[![](https://img.shields.io/github/stars/fabiocaccamo/django-maintenance-mode?logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode/)
+[![](https://badges.pufler.dev/visits/fabiocaccamo/django-maintenance-mode?label=visitors&color=blue)](https://badges.pufler.dev)
+[![](https://img.shields.io/pypi/l/django-maintenance-mode.svg?color=blue)](https://github.com/fabiocaccamo/django-maintenance-mode/blob/master/LICENSE.txt)
+
+[![](https://results.pre-commit.ci/badge/github/fabiocaccamo/django-maintenance-mode/master.svg)](https://results.pre-commit.ci/latest/github/fabiocaccamo/django-maintenance-mode/master)
+[![](https://img.shields.io/github/workflow/status/fabiocaccamo/django-maintenance-mode/Test%20package?label=build&logo=github)](https://github.com/fabiocaccamo/django-maintenance-mode)
+[![](https://img.shields.io/codecov/c/gh/fabiocaccamo/django-maintenance-mode?logo=codecov)](https://codecov.io/gh/fabiocaccamo/django-maintenance-mode)
+[![](https://img.shields.io/codacy/grade/918668ac85e74206a4d8d95923548d79?logo=codacy)](https://www.codacy.com/app/fabiocaccamo/django-maintenance-mode)
+[![](https://img.shields.io/codeclimate/maintainability/fabiocaccamo/django-maintenance-mode?logo=code-climate)](https://codeclimate.com/github/fabiocaccamo/django-maintenance-mode/)
+[![](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+
+# django-maintenance-mode
+django-maintenance-mode shows a 503 error page when **maintenance-mode** is **on**.
+
+It works at application level, so your django instance should be up.
+
+It doesn't use database and doesn't prevent database access.
+
+## Installation
+
+1. Run `pip install django-maintenance-mode` or [download django-maintenance-mode](http://pypi.python.org/pypi/django-maintenance-mode) and add the **maintenance_mode** package to your project
+2. Add `maintenance_mode` to `settings.INSTALLED_APPS` before custom applications
+3. Add `maintenance_mode.middleware.MaintenanceModeMiddleware` to `settings.MIDDLEWARE` as last middleware
+4. Add your custom `templates/503.html` file
+5. Restart your application server
+
+## Configuration (optional)
+
+### Settings
+All these settings are optional, if not defined in `settings.py` the default values (listed below) will be used.
+
+```python
+# if True the maintenance-mode will be activated
+MAINTENANCE_MODE = None
+```
+
+```python
+# by default, to get/set the state value a local file backend is used
+# if you want to use the db or cache, you can create a custom backend
+# custom backends must extend 'maintenance_mode.backends.AbstractStateBackend' class
+# and implement get_value(self) and set_value(self, val) methods
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.LocalFileBackend"
+
+# alternatively it is possible to use the default storage backend
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.DefaultStorageBackend"
+
+# alternatively it is possible to use the static storage backend
+# make sure that STATIC_ROOT and STATIC_URL are also set
+MAINTENANCE_MODE_STATE_BACKEND = "maintenance_mode.backends.StaticStorageBackend"
+```
+
+```python
+# by default, a file named "maintenance_mode_state.txt" will be created in the settings.py directory
+# you can customize the state file path in case the default one is not writable
+MAINTENANCE_MODE_STATE_FILE_PATH = "maintenance_mode_state.txt"
+```
+
+```python
+# if True admin site will not be affected by the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_ADMIN_SITE = False
+```
+
+```python
+# if True anonymous users will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = False
+```
+
+```python
+# if True authenticated users will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER = False
+```
+
+```python
+# if True the staff will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_STAFF = False
+```
+
+```python
+# if True the superuser will not see the maintenance-mode page
+MAINTENANCE_MODE_IGNORE_SUPERUSER = False
+```
+
+```python
+# list of ip-addresses that will not be affected by the maintenance-mode
+# ip-addresses will be used to compile regular expressions objects
+MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = ()
+```
+
+```python
+# the path of the function that will return the client IP address given the request object -> 'myapp.mymodule.myfunction'
+# the default function ('maintenance_mode.utils.get_client_ip_address') returns request.META['REMOTE_ADDR']
+# in some cases the default function returns None, to avoid this scenario just use 'django-ipware'
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None
+```
+Retrieve user's real IP address using [`django-ipware`](https://github.com/un33k/django-ipware):
+```python
+MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = "ipware.ip.get_ip"
+```
+
+```python
+# list of urls that will not be affected by the maintenance-mode
+# urls will be used to compile regular expressions objects
+MAINTENANCE_MODE_IGNORE_URLS = ()
+```
+
+```python
+# if True the maintenance mode will not return 503 response while running tests
+# useful for running tests while maintenance mode is on, before opening the site to public use
+MAINTENANCE_MODE_IGNORE_TESTS = False
+```
+
+```python
+# the absolute url where users will be redirected to during maintenance-mode
+MAINTENANCE_MODE_REDIRECT_URL = None
+```
+
+```python
+# the template that will be shown by the maintenance-mode page
+MAINTENANCE_MODE_TEMPLATE = "503.html"
+```
+
+```python
+# the path of the function that will return the template context -> 'myapp.mymodule.myfunction'
+MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT = None
+```
+
+```python
+# the HTTP status code to send
+MAINTENANCE_MODE_STATUS_CODE = 503
+```
+
+```python
+# the value in seconds of the Retry-After header during maintenance-mode
+MAINTENANCE_MODE_RETRY_AFTER = 3600 # 1 hour
+```
+
+#### Context Processors
+Add **maintenance_mode.context_processors.maintenance_mode** to your context_processors list in `settings.py` if you want to access the maintenance_mode status in your templates.
+
+```python
+TEMPLATES = [
+    {
+        # ...
+        "OPTIONS": {
+            "context_processors": [
+                # ...
+                "maintenance_mode.context_processors.maintenance_mode",
+                # ...
+            ],
+        },
+        # ...
+    },
+]
+```
+
+#### Logging
+You can disable emailing 503 errors to admins while maintenance mode is enabled:
+
+```python
+LOGGING = {
+    "filters": {
+        "require_not_maintenance_mode_503": {
+            "()": "maintenance_mode.logging.RequireNotMaintenanceMode503",
+        },
+        ...
+    },
+    "handlers": {
+        ...
+    },
+    ...
+}
+```
+
+### Context Managers
+You can force a block of code execution to run under maintenance mode or not using context managers:
+
+```python
+from maintenance_mode.core import maintenance_mode_off, maintenance_mode_on
+
+with maintenance_mode_on():
+    # do stuff
+    pass
+
+with maintenance_mode_off():
+    # do stuff
+    pass
+```
+
+### URLs
+Add **maintenance_mode.urls** to `urls.py` if you want superusers able to set maintenance_mode using urls.
+
+```python
+urlpatterns = [
+    # ...
+    re_path(r"^maintenance-mode/", include("maintenance_mode.urls")),
+    # ...
+]
+```
+
+### Views
+You can force maintenance mode on/off at view level using view decorators:
+
+```python
+from maintenance_mode.decorators import force_maintenance_mode_off, force_maintenance_mode_on
+
+@force_maintenance_mode_off
+def my_view_a(request):
+    # never return 503 response
+    pass
+
+@force_maintenance_mode_on
+def my_view_b(request):
+    # always return 503 response
+    pass
+```
+
+## Usage
+
+### Python
+```python
+from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
+
+set_maintenance_mode(True)
+
+if get_maintenance_mode():
+    set_maintenance_mode(False)
+```
+or
+```python
+from django.core.management import call_command
+from django.core.management.base import BaseCommand
+
+
+class Command(BaseCommand):
+
+    def handle(self, *args, **options):
+
+        call_command("maintenance_mode", "on")
+
+        # call your command(s)
+
+        call_command("maintenance_mode", "off")
+
+```
+
+### Templates
+```html
+{% if maintenance_mode %}
+<!-- html -->
+{% endif %}
+```
+
+### Terminal
+
+Run ``python manage.py maintenance_mode <on|off>``
+
+*(**This is not Heroku-friendly because** any execution of heroku run* `manage.py` *will be run on a separate worker dyno, not the web one. Therefore **the state-file is set but on the wrong machine. You should use a custom*** `MAINTENANCE_MODE_STATE_BACKEND`*.)*
+
+### URLs
+Superusers can change maintenance-mode using the following urls:
+
+`/maintenance-mode/off/`
+
+`/maintenance-mode/on/`
+
+## Testing
+```bash
+# clone repository
+git clone https://github.com/fabiocaccamo/django-maintenance-mode.git && cd django-maintenance-mode
+
+# create virtualenv and activate it
+python -m venv venv && . venv/bin/activate
+
+# upgrade pip
+python -m pip install --upgrade pip
+
+# install requirements
+pip install -r requirements.txt -r requirements-test.txt
+
+# run tests
+tox
+# or
+python runtests.py
+# or
+python -m django test --settings "tests.settings"
+```
+
+## License
+Released under [MIT License](LICENSE.txt).
+
+---
+
+## Supporting
+
+- :star: Star this project on [GitHub](https://github.com/fabiocaccamo/django-maintenance-mode)
+- :octocat: Follow me on [GitHub](https://github.com/fabiocaccamo)
+- :blue_heart: Follow me on [Twitter](https://twitter.com/fabiocaccamo)
+- :moneybag: Sponsor me on [Github](https://github.com/sponsors/fabiocaccamo)
+
+## See also
+
+- [`django-admin-interface`](https://github.com/fabiocaccamo/django-admin-interface) - the default admin interface made customizable by the admin itself. popup windows replaced by modals. ๐Ÿง™ โšก
+
+- [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. ๐ŸŽจ
+
+- [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. โš™๏ธ
+
+- [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. โ†ช๏ธ
+
+- [`django-treenode`](https://github.com/fabiocaccamo/django-treenode) - probably the best abstract model / admin for your tree based stuff. ๐ŸŒณ
+
+- [`python-benedict`](https://github.com/fabiocaccamo/python-benedict) - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. ๐Ÿ“˜
+
+- [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ’ณ
+
+- [`python-fontbro`](https://github.com/fabiocaccamo/python-fontbro) - friendly font operations. ๐Ÿงข
+
+- [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. ๐ŸงŸโ€โ™‚๏ธ
diff --git a/django_maintenance_mode.egg-info/SOURCES.txt b/django_maintenance_mode.egg-info/SOURCES.txt
index ee86e98..7a60425 100644
--- a/django_maintenance_mode.egg-info/SOURCES.txt
+++ b/django_maintenance_mode.egg-info/SOURCES.txt
@@ -1,11 +1,13 @@
 LICENSE.txt
 MANIFEST.in
 README.md
+pyproject.toml
 setup.cfg
 setup.py
 django_maintenance_mode.egg-info/PKG-INFO
 django_maintenance_mode.egg-info/SOURCES.txt
 django_maintenance_mode.egg-info/dependency_links.txt
+django_maintenance_mode.egg-info/requires.txt
 django_maintenance_mode.egg-info/top_level.txt
 maintenance_mode/__init__.py
 maintenance_mode/backends.py
diff --git a/django_maintenance_mode.egg-info/requires.txt b/django_maintenance_mode.egg-info/requires.txt
new file mode 100644
index 0000000..07a7b35
--- /dev/null
+++ b/django_maintenance_mode.egg-info/requires.txt
@@ -0,0 +1 @@
+python-fsutil>=0.7.0
diff --git a/maintenance_mode/__init__.py b/maintenance_mode/__init__.py
index 735d11c..9105a42 100644
--- a/maintenance_mode/__init__.py
+++ b/maintenance_mode/__init__.py
@@ -1,3 +1 @@
-# -*- coding: utf-8 -*-
-
 from maintenance_mode import settings
diff --git a/maintenance_mode/backends.py b/maintenance_mode/backends.py
index bd1e173..a67f6cf 100644
--- a/maintenance_mode/backends.py
+++ b/maintenance_mode/backends.py
@@ -1,6 +1,5 @@
-# -*- coding: utf-8 -*-
-
 from django.conf import settings
+from django.contrib.staticfiles.storage import staticfiles_storage
 from django.core.files.base import ContentFile
 from django.core.files.storage import default_storage
 
@@ -8,19 +7,18 @@ from maintenance_mode.io import read_file, write_file
 
 
 class AbstractStateBackend(object):
-
     @staticmethod
     def from_bool_to_str_value(value):
         value = str(int(value))
-        if value not in ['0', '1']:
-            raise ValueError('state value is not 0|1')
+        if value not in ["0", "1"]:
+            raise ValueError("state value is not 0|1")
         return value
 
     @staticmethod
     def from_str_to_bool_value(value):
         value = value.strip()
-        if value not in ['0', '1']:
-            raise ValueError('state value is not 0|1')
+        if value not in ["0", "1"]:
+            raise ValueError("state value is not 0|1")
         value = bool(int(value))
         return value
 
@@ -36,31 +34,59 @@ class DefaultStorageBackend(AbstractStateBackend):
     django-maintenance-mode backend which uses the default storage.
     Kindly provided by Dominik George https://github.com/Natureshadow
     """
+
+    def _get_filename(self):
+        return settings.MAINTENANCE_MODE_STATE_FILE_NAME
+
     def get_value(self):
-        filename = settings.MAINTENANCE_MODE_STATE_FILE_NAME
+        filename = self._get_filename()
         try:
-            with default_storage.open(filename, 'r') as statefile:
+            with default_storage.open(filename, "r") as statefile:
                 return self.from_str_to_bool_value(statefile.read())
         except IOError:
             return False
 
     def set_value(self, value):
-        filename = settings.MAINTENANCE_MODE_STATE_FILE_NAME
+        filename = self._get_filename()
         if default_storage.exists(filename):
             default_storage.delete(filename)
-        content = ContentFile(self.from_bool_to_str_value(value))
+        content = ContentFile(self.from_bool_to_str_value(value).encode())
         default_storage.save(filename, content)
 
 
+class StaticStorageBackend(AbstractStateBackend):
+    """
+    django-maintenance-mode backend which uses the staticfiles storage.
+    """
+
+    def get_value(self):
+        filename = settings.MAINTENANCE_MODE_STATE_FILE_NAME
+        if staticfiles_storage.exists(filename):
+            with staticfiles_storage.open(filename, "r") as statefile:
+                return self.from_str_to_bool_value(statefile.read())
+        return False
+
+    def set_value(self, value):
+        filename = settings.MAINTENANCE_MODE_STATE_FILE_NAME
+        if staticfiles_storage.exists(filename):
+            staticfiles_storage.delete(filename)
+        content = ContentFile(self.from_bool_to_str_value(value).encode())
+        staticfiles_storage.save(filename, content)
+
+
 class LocalFileBackend(AbstractStateBackend):
     """
     django-maintenance-mode backend which uses the local file-sistem.
     """
+
+    def _get_filepath(self):
+        return f"{settings.MAINTENANCE_MODE_STATE_FILE_PATH}"
+
     def get_value(self):
-        value = read_file(settings.MAINTENANCE_MODE_STATE_FILE_PATH, '0')
+        value = read_file(self._get_filepath(), "0")
         value = self.from_str_to_bool_value(value)
         return value
 
     def set_value(self, value):
         value = self.from_bool_to_str_value(value)
-        write_file(settings.MAINTENANCE_MODE_STATE_FILE_PATH, value)
+        write_file(self._get_filepath(), value)
diff --git a/maintenance_mode/context_processors.py b/maintenance_mode/context_processors.py
index feb99c2..87ac670 100644
--- a/maintenance_mode/context_processors.py
+++ b/maintenance_mode/context_processors.py
@@ -1,7 +1,5 @@
-# -*- coding: utf-8 -*-
-
 from maintenance_mode.core import get_maintenance_mode
 
 
 def maintenance_mode(request):
-    return { 'maintenance_mode':get_maintenance_mode() }
+    return {"maintenance_mode": get_maintenance_mode()}
diff --git a/maintenance_mode/core.py b/maintenance_mode/core.py
index cafc122..756aa3a 100644
--- a/maintenance_mode/core.py
+++ b/maintenance_mode/core.py
@@ -1,47 +1,32 @@
-# -*- coding: utf-8 -*-
+from contextlib import ContextDecorator
+from functools import wraps
 
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
-
-try:
-    from contextlib import ContextDecorator
-except ImportError:
-    # ContextDecorator was introduced in Django 1.8
-    from django.utils.decorators import available_attrs
-
-    class ContextDecorator(object):
-        """
-        A base class that enables a context manager to also be used as a decorator.
-        """
-        def __call__(self, func):
-            @wraps(func, assigned=available_attrs(func))
-            def inner(*args, **kwargs):
-                with self:
-                    return func(*args, **kwargs)
-            return inner
-
 from django.utils.module_loading import import_string
 
-from functools import wraps
-
 from maintenance_mode.backends import AbstractStateBackend
 
 
 def get_maintenance_mode_backend():
     try:
         backend_class = import_string(settings.MAINTENANCE_MODE_STATE_BACKEND)
-        if issubclass(backend_class, AbstractStateBackend) and \
-                backend_class != AbstractStateBackend:
+        if (
+            issubclass(backend_class, AbstractStateBackend)
+            and backend_class != AbstractStateBackend
+        ):
             backend = backend_class()
             return backend
         else:
             raise ImproperlyConfigured(
-                'backend doesn\'t extend '
-                '\'maintenance_mode.backends.AbstractStateBackend\' class.')
+                "backend doesn't extend "
+                "'maintenance_mode.backends.AbstractStateBackend' class."
+            )
     except ImportError:
         raise ImproperlyConfigured(
-            'backend not found, check '
-            '\'settings.MAINTENANCE_MODE_STATE_BACKEND\' path.')
+            "backend not found, check "
+            "'settings.MAINTENANCE_MODE_STATE_BACKEND' path."
+        )
 
 
 def get_maintenance_mode():
@@ -65,11 +50,11 @@ def set_maintenance_mode(value):
     # If maintenance mode is defined in settings, it can't be changed.
     if settings.MAINTENANCE_MODE is not None:
         raise ImproperlyConfigured(
-            'Maintenance mode cannot be set dynamically '
-            'if defined in settings.')
+            "Maintenance mode cannot be set dynamically " "if defined in settings."
+        )
 
     if not isinstance(value, bool):
-        raise TypeError('value argument type is not boolean')
+        raise TypeError("value argument type is not boolean")
 
     backend = get_maintenance_mode_backend()
     backend.set_value(value)
@@ -82,6 +67,7 @@ class override_maintenance_mode(ContextDecorator):
     @ivar value: Overriden value of maintenance mode
     @ivar old_value: Original value of maintenance mode
     """
+
     def __init__(self, value):
         self.value = value
         self.old_value = None
@@ -98,6 +84,7 @@ class maintenance_mode_on(override_maintenance_mode):
     """
     Decorator/context manager to locally set maintenance mode to True.
     """
+
     def __init__(self):
         super(maintenance_mode_on, self).__init__(True)
 
@@ -106,5 +93,6 @@ class maintenance_mode_off(override_maintenance_mode):
     """
     Decorator/context manager to locally set maintenance mode to False.
     """
+
     def __init__(self):
         super(maintenance_mode_off, self).__init__(False)
diff --git a/maintenance_mode/decorators.py b/maintenance_mode/decorators.py
index 58d8816..a029bc9 100644
--- a/maintenance_mode/decorators.py
+++ b/maintenance_mode/decorators.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
 from functools import wraps
 
 from maintenance_mode.http import get_maintenance_response
@@ -9,7 +7,8 @@ def force_maintenance_mode_off(view_func):
     @wraps(view_func)
     def wrapper(request, *args, **kwargs):
         return view_func(request, *args, **kwargs)
-    wrapper.__dict__['force_maintenance_mode_off'] = True
+
+    wrapper.__dict__["force_maintenance_mode_off"] = True
     return wrapper
 
 
@@ -17,5 +16,6 @@ def force_maintenance_mode_on(view_func):
     @wraps(view_func)
     def wrapper(request, *args, **kwargs):
         return get_maintenance_response(request)
-    wrapper.__dict__['force_maintenance_mode_on'] = True
+
+    wrapper.__dict__["force_maintenance_mode_on"] = True
     return wrapper
diff --git a/maintenance_mode/http.py b/maintenance_mode/http.py
index c87d87f..dc10b16 100644
--- a/maintenance_mode/http.py
+++ b/maintenance_mode/http.py
@@ -1,35 +1,17 @@
-# -*- coding: utf-8 -*-
+import re
+import sys
 
-import django
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured
-
-if django.VERSION < (2, 0):
-    from django.core.urlresolvers import (
-        NoReverseMatch, resolve, Resolver404, reverse, )
-else:
-    from django.urls import (
-        NoReverseMatch, resolve, Resolver404, reverse, )
-
-from django.shortcuts import render, redirect
+from django.shortcuts import redirect, render
 from django.template import RequestContext
+from django.urls import NoReverseMatch, Resolver404, resolve, reverse
 from django.utils.cache import add_never_cache_headers
 from django.utils.module_loading import import_string
 
 from maintenance_mode.core import get_maintenance_mode
 from maintenance_mode.utils import get_client_ip_address
 
-import re
-
-try:
-    # since python 3.7
-    pattern_class = re.Pattern
-except AttributeError:
-    # before python 3.7
-    pattern_class = re._pattern_type
-
-import sys
-
 
 def get_maintenance_response(request):
     """
@@ -43,45 +25,44 @@ def get_maintenance_response(request):
     if settings.MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT:
         try:
             get_request_context_func = import_string(
-                settings.MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT)
+                settings.MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT
+            )
         except ImportError:
             raise ImproperlyConfigured(
-                'settings.MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT '
-                'is not a valid function path.'
+                "settings.MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT "
+                "is not a valid function path."
             )
 
         context = get_request_context_func(request=request)
 
-    kwargs = {'context': context}
-    if django.VERSION < (1, 8):
-        kwargs = {'context_instance': RequestContext(request, context)}
-
-    response = render(request, settings.MAINTENANCE_MODE_TEMPLATE,
-                      status=settings.MAINTENANCE_MODE_STATUS_CODE,
-                      **kwargs)
-    response['Retry-After'] = settings.MAINTENANCE_MODE_RETRY_AFTER
+    kwargs = {"context": context}
+    response = render(
+        request,
+        settings.MAINTENANCE_MODE_TEMPLATE,
+        status=settings.MAINTENANCE_MODE_STATUS_CODE,
+        **kwargs
+    )
+    response["Retry-After"] = settings.MAINTENANCE_MODE_RETRY_AFTER
     add_never_cache_headers(response)
     return response
 
 
-def need_maintenance_response(request):
-    """
-    Tells if the given request needs a maintenance response or not.
-    """
-
+def _need_maintenance_from_view(request):
     try:
         view_match = resolve(request.path)
         view_func = view_match[0]
         view_dict = view_func.__dict__
 
         view_force_maintenance_mode_off = view_dict.get(
-            'force_maintenance_mode_off', False)
+            "force_maintenance_mode_off", False
+        )
         if view_force_maintenance_mode_off:
             # view has 'force_maintenance_mode_off' decorator
             return False
 
         view_force_maintenance_mode_on = view_dict.get(
-            'force_maintenance_mode_on', False)
+            "force_maintenance_mode_on", False
+        )
         if view_force_maintenance_mode_on:
             # view has 'force_maintenance_mode_on' decorator
             return True
@@ -89,115 +70,157 @@ def need_maintenance_response(request):
     except Resolver404:
         pass
 
-    if not get_maintenance_mode():
-        return False
 
+def _need_maintenance_from_url(request):
     try:
-        url_off = reverse('maintenance_mode_off')
-
+        url_off = reverse("maintenance_mode_off")
         resolve(url_off)
-
         if url_off == request.path_info:
             return False
-
     except NoReverseMatch:
         # maintenance_mode.urls not added
         pass
 
-    if hasattr(request, 'user'):
 
-        if django.VERSION < (1, 10):
-            if settings.MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER \
-                    and request.user.is_anonymous():
-                return False
+def _need_maintenance_ignore_users(request):
+    if not hasattr(request, "user"):
+        return
 
-            if settings.MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER \
-                    and request.user.is_authenticated():
-                return False
-        else:
-            if settings.MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER \
-                    and request.user.is_anonymous:
-                return False
+    user = request.user
 
-            if settings.MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER \
-                    and request.user.is_authenticated:
-                return False
+    if settings.MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER and user.is_anonymous:
+        return False
 
-        if settings.MAINTENANCE_MODE_IGNORE_STAFF \
-                and request.user.is_staff:
-            return False
+    if settings.MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER and user.is_authenticated:
+        return False
+
+    if settings.MAINTENANCE_MODE_IGNORE_STAFF and user.is_staff:
+        return False
+
+    if settings.MAINTENANCE_MODE_IGNORE_SUPERUSER and user.is_superuser:
+        return False
+
+
+def _need_maintenance_ignore_admin_site(request):
+    if not settings.MAINTENANCE_MODE_IGNORE_ADMIN_SITE:
+        return
 
-        if settings.MAINTENANCE_MODE_IGNORE_SUPERUSER \
-                and request.user.is_superuser:
+    try:
+        request_path = request.path if request.path else ""
+        if not request_path.endswith("/"):
+            request_path += "/"
+
+        admin_url = reverse("admin:index")
+        if request_path.startswith(admin_url):
             return False
+    except NoReverseMatch:
+        # admin.urls not added
+        pass
 
-    if settings.MAINTENANCE_MODE_IGNORE_ADMIN_SITE:
 
-        try:
-            request_path = request.path if request.path else ''
-            if not request_path.endswith('/'):
-                request_path += '/'
+def _need_maintenance_ignore_tests(request):
+    if not settings.MAINTENANCE_MODE_IGNORE_TESTS:
+        return
 
-            admin_url = reverse('admin:index')
-            if request_path.startswith(admin_url):
-                return False
+    is_testing = False
 
-        except NoReverseMatch:
-            # admin.urls not added
-            pass
+    if (len(sys.argv) > 0 and "runtests" in sys.argv[0]) or (
+        len(sys.argv) > 1 and sys.argv[1] == "test"
+    ):
+        # python runtests.py | python manage.py test | python
+        # setup.py test | django-admin.py test
+        is_testing = True
 
-    if settings.MAINTENANCE_MODE_IGNORE_TESTS:
+    if is_testing:
+        return False
 
-        is_testing = False
 
-        if (len(sys.argv) > 0 and 'runtests' in sys.argv[0]) \
-                or (len(sys.argv) > 1 and sys.argv[1] == 'test'):
-            # python runtests.py | python manage.py test | python
-            # setup.py test | django-admin.py test
-            is_testing = True
+def _need_maintenance_ignore_ip_addresses(request):
+    if not settings.MAINTENANCE_MODE_IGNORE_IP_ADDRESSES:
+        return
 
-        if is_testing:
+    if settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS:
+        try:
+            get_client_ip_address_func = import_string(
+                settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS
+            )
+        except ImportError:
+            raise ImproperlyConfigured(
+                "settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS "
+                "is not a valid function path."
+            )
+        else:
+            client_ip_address = get_client_ip_address_func(request)
+    else:
+        client_ip_address = get_client_ip_address(request)
+
+    for ip_address in settings.MAINTENANCE_MODE_IGNORE_IP_ADDRESSES:
+        ip_address_re = re.compile(ip_address)
+        if ip_address_re.match(client_ip_address):
             return False
 
-    if settings.MAINTENANCE_MODE_IGNORE_IP_ADDRESSES:
-
-        if settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS:
-            try:
-                get_client_ip_address_func = import_string(
-                    settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS)
-            except ImportError:
-                raise ImproperlyConfigured(
-                    'settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS '
-                    'is not a valid function path.')
-            else:
-                client_ip_address = get_client_ip_address_func(request)
-        else:
-            client_ip_address = get_client_ip_address(request)
 
-        for ip_address in settings.MAINTENANCE_MODE_IGNORE_IP_ADDRESSES:
+def _need_maintenance_ignore_urls(request):
+    if not settings.MAINTENANCE_MODE_IGNORE_URLS:
+        return
 
-            ip_address_re = re.compile(ip_address)
+    for url in settings.MAINTENANCE_MODE_IGNORE_URLS:
+        if not isinstance(url, re.Pattern):
+            url = str(url)
+        url_re = re.compile(url)
+        if url_re.match(request.path_info):
+            return False
 
-            if ip_address_re.match(client_ip_address):
-                return False
 
-    if settings.MAINTENANCE_MODE_IGNORE_URLS:
+def _need_maintenance_redirects(request):
+    if not settings.MAINTENANCE_MODE_REDIRECT_URL:
+        return
 
-        for url in settings.MAINTENANCE_MODE_IGNORE_URLS:
+    redirect_url_re = re.compile(settings.MAINTENANCE_MODE_REDIRECT_URL)
 
-            if not isinstance(url, pattern_class):
-                url = str(url)
-            url_re = re.compile(url)
+    if redirect_url_re.match(request.path_info):
+        return False
 
-            if url_re.match(request.path_info):
-                return False
 
-    if settings.MAINTENANCE_MODE_REDIRECT_URL:
+def need_maintenance_response(request):
+    """
+    Tells if the given request needs a maintenance response or not.
+    """
 
-        redirect_url_re = re.compile(
-            settings.MAINTENANCE_MODE_REDIRECT_URL)
+    value = _need_maintenance_from_view(request)
+    if isinstance(value, bool):
+        return value
 
-        if redirect_url_re.match(request.path_info):
-            return False
+    value = get_maintenance_mode()
+    if not value:
+        return value
+
+    value = _need_maintenance_from_url(request)
+    if isinstance(value, bool):
+        return value
+
+    value = _need_maintenance_ignore_users(request)
+    if isinstance(value, bool):
+        return value
+
+    value = _need_maintenance_ignore_admin_site(request)
+    if isinstance(value, bool):
+        return value
+
+    value = _need_maintenance_ignore_tests(request)
+    if isinstance(value, bool):
+        return value
+
+    value = _need_maintenance_ignore_ip_addresses(request)
+    if isinstance(value, bool):
+        return value
+
+    value = _need_maintenance_ignore_urls(request)
+    if isinstance(value, bool):
+        return value
+
+    value = _need_maintenance_redirects(request)
+    if isinstance(value, bool):
+        return value
 
     return True
diff --git a/maintenance_mode/io.py b/maintenance_mode/io.py
index fd23745..cd825dc 100644
--- a/maintenance_mode/io.py
+++ b/maintenance_mode/io.py
@@ -1,21 +1,17 @@
-# -*- coding: utf-8 -*-
-
 import os
 
+import fsutil
+
 
-def read_file(file_path, default_content=''):
+def read_file(file_path, default_content=""):
     """
     Read file at the specified path.
     If file doesn't exist, it will be created with default-content.
     Returns the file content.
     """
-    if not os.path.exists(file_path):
-        write_file(file_path, default_content)
-
-    handler = open(file_path, 'r')
-    content = handler.read()
-    handler.close()
-    return content or default_content
+    if not fsutil.exists(file_path):
+        fsutil.write_file(file_path, default_content)
+    return fsutil.read_file(file_path) or default_content
 
 
 def write_file(file_path, content):
@@ -23,6 +19,4 @@ def write_file(file_path, content):
     Write file at the specified path with content.
     If file exists, it will be overwritten.
     """
-    handler = open(file_path, 'w+')
-    handler.write(content)
-    handler.close()
+    fsutil.write_file(file_path, content)
diff --git a/maintenance_mode/logging.py b/maintenance_mode/logging.py
index cb49c55..0e6d428 100644
--- a/maintenance_mode/logging.py
+++ b/maintenance_mode/logging.py
@@ -1,13 +1,9 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
+import logging
 
 from django.conf import settings
 
 from maintenance_mode.core import get_maintenance_mode
 
-import logging
-
 
 class RequireNotMaintenanceMode503(logging.Filter):
     """
@@ -19,7 +15,7 @@ class RequireNotMaintenanceMode503(logging.Filter):
         Return False if maintenance mode is on and
         the given record has a status code of 503.
         """
-        status_code = getattr(record, 'status_code', None)
+        status_code = getattr(record, "status_code", None)
         if get_maintenance_mode() and status_code == 503:
             return False
         return True
diff --git a/maintenance_mode/management/commands/maintenance_mode.py b/maintenance_mode/management/commands/maintenance_mode.py
index 9063342..d3b3e8d 100644
--- a/maintenance_mode/management/commands/maintenance_mode.py
+++ b/maintenance_mode/management/commands/maintenance_mode.py
@@ -1,9 +1,3 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import absolute_import
-
-import django
-
 from django.conf import settings
 from django.core.management.base import BaseCommand, CommandError
 
@@ -12,13 +6,15 @@ from maintenance_mode import core
 
 class Command(BaseCommand):
 
-    args = '<on|off>'
-    help = 'run python manage.py maintenance_mode %s '\
-           'to change maintenance-mode state' % args
+    args = "<on|off>"
+    help = (
+        "run python manage.py maintenance_mode %s "
+        "to change maintenance-mode state" % args
+    )
 
     def add_arguments(self, parser):
-        parser.add_argument('state')
-        parser.add_argument('--interactive', dest='interactive', action='store_true')
+        parser.add_argument("state")
+        parser.add_argument("--interactive", dest="interactive", action="store_true")
 
     def get_maintenance_mode(self):
         try:
@@ -26,16 +22,18 @@ class Command(BaseCommand):
             return value
         except IOError:
             raise CommandError(
-                'Unable to read state file at: %s' % (
-                    settings.MAINTENANCE_MODE_STATE_FILE_NAME, ))
+                "Unable to read state file at: %s"
+                % (settings.MAINTENANCE_MODE_STATE_FILE_NAME,)
+            )
 
     def set_maintenance_mode(self, value):
         try:
             core.set_maintenance_mode(value)
         except IOError:
             raise CommandError(
-                'Unable to write state file at: %s' % (
-                    settings.MAINTENANCE_MODE_STATE_FILE_NAME, ))
+                "Unable to write state file at: %s"
+                % (settings.MAINTENANCE_MODE_STATE_FILE_NAME,)
+            )
 
     def set_maintenance_mode_with_confirm(self, value, confirm_message, interactive):
         if interactive:
@@ -45,61 +43,56 @@ class Command(BaseCommand):
             self.set_maintenance_mode(value)
 
     def confirm(self, message):
-        # Fix for Python 2.x.
-        try:
-            input_func = raw_input
-        except NameError:
-            input_func = input
-
+        input_func = input
         answer = input_func(message)
         answer = answer.lower()
-        return answer.find('y') == 0
+        return answer.find("y") == 0
 
     def handle(self, *args, **options):
 
-        verbosity = int(options['verbosity'])
+        verbosity = int(options["verbosity"])
         verbose = True if verbosity == 3 else False
-        interactive = options.get('interactive', False)
-
-        if django.VERSION < (1, 8):
-            if len(args) != 1:
-                raise CommandError(
-                    'Expected 1 argument: %s' % (self.args, ))
-
-            state = args[0]
-        else:
-            state = options['state']
-
+        interactive = options.get("interactive", False)
+        state = options["state"]
         state = state.lower()
         value = self.get_maintenance_mode()
 
-        if state in ['on', 'yes', 'true', '1']:
+        if state in ["on", "yes", "true", "1"]:
 
             if value:
                 if verbose:
-                    self.stdout.write('maintenance mode is already on')
+                    self.stdout.write("maintenance mode is already on")
                 return
 
             self.set_maintenance_mode_with_confirm(
-                True, 'maintenance mode on? (y/N) ', interactive)
+                True, "maintenance mode on? (y/N) ", interactive
+            )
 
-        elif state in ['off', 'no', 'false', '0']:
+        elif state in ["off", "no", "false", "0"]:
 
             if not value:
                 if verbose:
-                    self.stdout.write('maintenance mode is already off')
+                    self.stdout.write("maintenance mode is already off")
                 return
 
             self.set_maintenance_mode_with_confirm(
-                False, 'maintenance mode off? (y/N) ', interactive)
+                False, "maintenance mode off? (y/N) ", interactive
+            )
 
         else:
-            raise CommandError('Invalid argument: \'%s\' '
-                               'expected %s' % (state, self.args, ))
+            raise CommandError(
+                "Invalid argument: '%s' "
+                "expected %s"
+                % (
+                    state,
+                    self.args,
+                )
+            )
 
         if verbose:
-            output = 'maintenance mode: %s' % (
-                'on' if self.get_maintenance_mode() else 'off', )
+            output = "maintenance mode: %s" % (
+                "on" if self.get_maintenance_mode() else "off",
+            )
             self.stdout.write(output)
 
         return
diff --git a/maintenance_mode/middleware.py b/maintenance_mode/middleware.py
index b44577e..44ffe4e 100644
--- a/maintenance_mode/middleware.py
+++ b/maintenance_mode/middleware.py
@@ -1,22 +1,17 @@
-# -*- coding: utf-8 -*-
+from maintenance_mode.http import get_maintenance_response, need_maintenance_response
 
-import django
 
-if django.VERSION < (1, 10):
-    __MaintenanceModeMiddlewareBaseClass = object
-else:
-    # https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware
-    from django.utils.deprecation import MiddlewareMixin
-    __MaintenanceModeMiddlewareBaseClass = MiddlewareMixin
+class MaintenanceModeMiddleware(object):
+    def __init__(self, get_response=None):
+        self.get_response = get_response
 
-from maintenance_mode.http import (
-    get_maintenance_response, need_maintenance_response, )
-
-
-class MaintenanceModeMiddleware(__MaintenanceModeMiddlewareBaseClass):
+    def __call__(self, request):
+        response = self.process_request(request)
+        if response is None and callable(self.get_response):
+            response = self.get_response(request)
+        return response
 
     def process_request(self, request):
         if need_maintenance_response(request):
             return get_maintenance_response(request)
-        else:
-            return None
+        return None
diff --git a/maintenance_mode/settings.py b/maintenance_mode/settings.py
index 5da1517..5a51e91 100644
--- a/maintenance_mode/settings.py
+++ b/maintenance_mode/settings.py
@@ -1,66 +1,64 @@
-# -*- coding: utf-8 -*-
+import os
 
+import fsutil
 from django.conf import settings
 from django.utils.module_loading import import_module
 
-import os
-
-
-if not hasattr(settings, 'MAINTENANCE_MODE'):
+if not hasattr(settings, "MAINTENANCE_MODE"):
     settings.MAINTENANCE_MODE = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS'):
+if not hasattr(settings, "MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS"):
     settings.MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT'):
+if not hasattr(settings, "MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT"):
     settings.MAINTENANCE_MODE_GET_TEMPLATE_CONTEXT = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_ADMIN_SITE'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_ADMIN_SITE"):
     settings.MAINTENANCE_MODE_IGNORE_ADMIN_SITE = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER"):
     settings.MAINTENANCE_MODE_IGNORE_ANONYMOUS_USER = False
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER"):
     settings.MAINTENANCE_MODE_IGNORE_AUTHENTICATED_USER = False
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_IP_ADDRESSES'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_IP_ADDRESSES"):
     settings.MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_STAFF'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_STAFF"):
     settings.MAINTENANCE_MODE_IGNORE_STAFF = False
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_SUPERUSER'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_SUPERUSER"):
     settings.MAINTENANCE_MODE_IGNORE_SUPERUSER = False
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_TESTS'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_TESTS"):
     settings.MAINTENANCE_MODE_IGNORE_TESTS = False
 
-if not hasattr(settings, 'MAINTENANCE_MODE_IGNORE_URLS'):
+if not hasattr(settings, "MAINTENANCE_MODE_IGNORE_URLS"):
     settings.MAINTENANCE_MODE_IGNORE_URLS = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_REDIRECT_URL'):
+if not hasattr(settings, "MAINTENANCE_MODE_REDIRECT_URL"):
     settings.MAINTENANCE_MODE_REDIRECT_URL = None
 
-if not hasattr(settings, 'MAINTENANCE_MODE_STATE_BACKEND'):
-    settings.MAINTENANCE_MODE_STATE_BACKEND = 'maintenance_mode.backends.LocalFileBackend'
+if not hasattr(settings, "MAINTENANCE_MODE_STATE_BACKEND"):
+    settings.MAINTENANCE_MODE_STATE_BACKEND = (
+        "maintenance_mode.backends.LocalFileBackend"
+    )
 
-if not hasattr(settings, 'MAINTENANCE_MODE_STATE_FILE_NAME'):
-    settings.MAINTENANCE_MODE_STATE_FILE_NAME = 'maintenance_mode_state.txt'
+if not hasattr(settings, "MAINTENANCE_MODE_STATE_FILE_NAME"):
+    settings.MAINTENANCE_MODE_STATE_FILE_NAME = "maintenance_mode_state.txt"
 
-if not hasattr(settings, 'MAINTENANCE_MODE_STATE_FILE_PATH'):
-    settings_module = import_module(os.environ['DJANGO_SETTINGS_MODULE'])
-    settings_path = settings_module.__file__
-    settings_dir = os.path.dirname(settings_path)
-    settings.MAINTENANCE_MODE_STATE_FILE_PATH = os.path.abspath(
-        os.path.join(settings_dir,
-            settings.MAINTENANCE_MODE_STATE_FILE_NAME))
+if not hasattr(settings, "MAINTENANCE_MODE_STATE_FILE_PATH"):
+    settings_module = import_module(os.environ["DJANGO_SETTINGS_MODULE"])
+    settings.MAINTENANCE_MODE_STATE_FILE_PATH = fsutil.join_path(
+        settings_module.__file__, settings.MAINTENANCE_MODE_STATE_FILE_NAME
+    )
 
-if not hasattr(settings, 'MAINTENANCE_MODE_TEMPLATE'):
-    settings.MAINTENANCE_MODE_TEMPLATE = '503.html'
+if not hasattr(settings, "MAINTENANCE_MODE_TEMPLATE"):
+    settings.MAINTENANCE_MODE_TEMPLATE = "503.html"
 
-if not hasattr(settings, 'MAINTENANCE_MODE_STATUS_CODE'):
+if not hasattr(settings, "MAINTENANCE_MODE_STATUS_CODE"):
     settings.MAINTENANCE_MODE_STATUS_CODE = 503
 
-if not hasattr(settings, 'MAINTENANCE_MODE_RETRY_AFTER'):
+if not hasattr(settings, "MAINTENANCE_MODE_RETRY_AFTER"):
     settings.MAINTENANCE_MODE_RETRY_AFTER = 3600
diff --git a/maintenance_mode/templates/503.html b/maintenance_mode/templates/503.html
index d813e12..c1d41df 100644
--- a/maintenance_mode/templates/503.html
+++ b/maintenance_mode/templates/503.html
@@ -1 +1 @@
-django-maintenance-mode
\ No newline at end of file
+django-maintenance-mode
diff --git a/maintenance_mode/urls.py b/maintenance_mode/urls.py
index 173a262..0fad943 100644
--- a/maintenance_mode/urls.py
+++ b/maintenance_mode/urls.py
@@ -1,16 +1,8 @@
-# -*- coding: utf-8 -*-
-
-import django
-
-if django.VERSION < (2, 0):
-    from django.conf.urls import url as re_path
-else:
-    from django.urls import re_path
+from django.urls import re_path
 
 from maintenance_mode.views import maintenance_mode_off, maintenance_mode_on
 
-
 urlpatterns = [
-    re_path(r'^off/$', maintenance_mode_off, name='maintenance_mode_off'),
-    re_path(r'^on/$', maintenance_mode_on, name='maintenance_mode_on'),
+    re_path(r"^off/$", maintenance_mode_off, name="maintenance_mode_off"),
+    re_path(r"^on/$", maintenance_mode_on, name="maintenance_mode_on"),
 ]
diff --git a/maintenance_mode/utils.py b/maintenance_mode/utils.py
index 27830a5..9982005 100644
--- a/maintenance_mode/utils.py
+++ b/maintenance_mode/utils.py
@@ -1,7 +1,5 @@
-# -*- coding: utf-8 -*-
-
 def get_client_ip_address(request):
     """
     Get the client IP Address.
     """
-    return request.META['REMOTE_ADDR']
+    return request.META["REMOTE_ADDR"]
diff --git a/maintenance_mode/version.py b/maintenance_mode/version.py
index 8ea7850..1317d75 100644
--- a/maintenance_mode/version.py
+++ b/maintenance_mode/version.py
@@ -1,3 +1 @@
-# -*- coding: utf-8 -*-
-
-__version__ = '0.16.1'
+__version__ = "0.18.0"
diff --git a/maintenance_mode/views.py b/maintenance_mode/views.py
index b98d324..a916c92 100644
--- a/maintenance_mode/views.py
+++ b/maintenance_mode/views.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
 from django.http import HttpResponseRedirect
 
 from maintenance_mode.core import set_maintenance_mode
@@ -13,7 +11,7 @@ def maintenance_mode_off(request):
     if request.user.is_superuser:
         set_maintenance_mode(False)
 
-    return HttpResponseRedirect('/')
+    return HttpResponseRedirect("/")
 
 
 def maintenance_mode_on(request):
@@ -24,4 +22,4 @@ def maintenance_mode_on(request):
     if request.user.is_superuser:
         set_maintenance_mode(True)
 
-    return HttpResponseRedirect('/')
+    return HttpResponseRedirect("/")
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..9e35679
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,16 @@
+[tool.black]
+include = '\.pyi?$'
+exclude = '''
+/(
+    \.git
+  | \.hg
+  | \.mypy_cache
+  | \.tox
+  | \.venv
+  | _build
+  | buck-out
+  | build
+  | dist
+  | venv
+)/
+'''
diff --git a/setup.cfg b/setup.cfg
index 9f88734..da016b5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [metadata]
-description-file = README.md
+description_file = README.md
 
 [egg_info]
 tag_build = 
diff --git a/setup.py b/setup.py
index e871ca8..5d89d39 100644
--- a/setup.py
+++ b/setup.py
@@ -1,69 +1,84 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
 
-from setuptools import setup, find_packages
+import os
+import sys
 
-import os, sys
+from setuptools import find_packages, setup
 
-exec(open('maintenance_mode/version.py').read())
+exec(open("maintenance_mode/version.py").read())
 
-github_url = 'https://github.com/fabiocaccamo'
-package_name = 'django-maintenance-mode'
-package_url = '{}/{}'.format(github_url, package_name)
+package_name = "django-maintenance-mode"
+package_url = f"https://github.com/fabiocaccamo/{package_name}"
 package_path = os.path.abspath(os.path.dirname(__file__))
-long_description_file_path = os.path.join(package_path, 'README.md')
-long_description_content_type = 'text/markdown'
-long_description = ''
+download_url = f"{package_url}/archive/{__version__}.tar.gz"
+documentation_url = f"{package_url}#readme"
+issues_url = f"{package_url}/issues"
+sponsor_url = "https://github.com/sponsors/fabiocaccamo/"
+twitter_url = "https://twitter.com/fabiocaccamo"
+
+long_description_file_path = os.path.join(package_path, "README.md")
+long_description_content_type = "text/markdown"
+long_description = ""
 try:
-    long_description_file_options = {} if sys.version_info[0] < 3 else { 'encoding':'utf-8' }
-    with open(long_description_file_path, 'r', **long_description_file_options) as f:
+    with open(long_description_file_path, "r", encoding="utf-8") as f:
         long_description = f.read()
 except IOError:
     pass
 
 setup(
     name=package_name,
-    packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
+    packages=find_packages(exclude=["contrib", "docs", "tests*"]),
     version=__version__,
-    description='django-maintenance-mode shows a 503 error page when maintenance-mode is on.',
+    description="django-maintenance-mode shows a 503 error page when maintenance-mode is on.",
     long_description=long_description,
     long_description_content_type=long_description_content_type,
-    author='Fabio Caccamo',
-    author_email='fabio.caccamo@gmail.com',
+    author="Fabio Caccamo",
+    author_email="fabio.caccamo@gmail.com",
     url=package_url,
-    download_url='{}/archive/{}.tar.gz'.format(package_url, __version__),
-    keywords=['django', 'maintenance', 'mode', 'offline', 'under', '503', 'service', 'temporarily', 'unavailable'],
-    requires=['django(>=1.7)'],
+    download_url=download_url,
+    project_urls={
+        "Documentation": documentation_url,
+        "Issues": issues_url,
+        "Funding": sponsor_url,
+        "Twitter": twitter_url,
+    },
+    keywords=[
+        "django",
+        "maintenance",
+        "mode",
+        "offline",
+        "under",
+        "503",
+        "service",
+        "temporarily",
+        "unavailable",
+    ],
+    requires=[
+        "django (>= 2.2)",
+    ],
+    install_requires=[
+        "python-fsutil >= 0.7.0",
+    ],
     classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Environment :: Web Environment',
-        'Framework :: Django',
-        'Framework :: Django :: 1.7',
-        'Framework :: Django :: 1.8',
-        'Framework :: Django :: 1.9',
-        'Framework :: Django :: 1.10',
-        'Framework :: Django :: 1.11',
-        'Framework :: Django :: 2.0',
-        'Framework :: Django :: 2.1',
-        'Framework :: Django :: 2.2',
-        'Framework :: Django :: 3.0',
-        'Framework :: Django :: 3.1',
-        'Framework :: Django :: 3.2',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: MIT License',
-        'Natural Language :: English',
-        'Operating System :: OS Independent',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Topic :: Software Development :: Build Tools',
+        "Development Status :: 5 - Production/Stable",
+        "Environment :: Web Environment",
+        "Framework :: Django",
+        "Framework :: Django :: 2.2",
+        "Framework :: Django :: 3.0",
+        "Framework :: Django :: 3.1",
+        "Framework :: Django :: 3.2",
+        "Framework :: Django :: 4.0",
+        "Framework :: Django :: 4.1",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: MIT License",
+        "Natural Language :: English",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
+        "Topic :: Software Development :: Build Tools",
     ],
-    license='MIT',
-    test_suite='runtests.runtests'
+    license="MIT",
 )

More details

Full run details