Repository: joke2k/django-environ

Issue:
`cache_url` doesn't handle backend properly.
I have no idea if it's bug or a feature:

let's say I have 2 env var:
```
DEFAULT_COMPONENT_URL=dummycache://
DEFAULT_COMPONENT_BACKEND=dumy
```

in `settings.py`

```python
import environ
from pathlib import Path

BASE_DIR = Path(environ.Path())
env = environ.FileAwareEnv(
    interpolate=True,
)
environ.Env.read_env(BASE_DIR / ".env")
CACHES = {
    "default": env.cache_url(
        "DEFAULT_CACHE_URL",
        f"redis://redis:6379/1",
        backend=env.str("DEFAULT_CACHE_BACKEND", "django_redis.cache.RedisCache"),
    )
}
```

 the backend is set here https://github.com/joke2k/django-environ/blob/8874288ac11bc70fa491439d52a4c307f9388aea/environ/environ.py#L629 to `django.core.cache.backends.dummy.DummyCache`.

But the `backend`key is reset to the default value 44 lines later here https://github.com/joke2k/django-environ/blob/8874288ac11bc70fa491439d52a4c307f9388aea/environ/environ.py#L673-L674

no idea if this is intentional or not. I the fix is only removing the `if backend` line I'll be happy to submit a PR.

Discussion:
@sergeyklay: Hi @RemiDesgrange,

Thank you for bringing this issue to our attention. To better understand the problem and evaluate your proposed solution, could you please provide a test case that reproduces the issue? A test that highlights the current problems and needs would be particularly helpful.@RemiDesgrange: Ok, in what form this solution should be provided ? The `settings.py` I gave can be executed in a shell (without even django). Do you need a test case with pytest etc... ?

Thx.@sergeyklay: > Ok, in what form this solution should be provided ? The `settings.py` I gave can be executed in a shell (without even django). Do you need a test case with pytest etc... ?
> 
> Thx.

Providing a unit test would be ideal@fdemmer: @RemiDesgrange 

in your example you mention `DEFAULT_COMPONENT_URL`. is that a typo? should it mean `DEFAULT_CACHE_URL`?

> no idea if this is intentional or not. I the fix is only removing the if backend line I'll be happy to submit a PR.

i am guessing it _is intentional_, although there is no documentation ~~or [testcase](https://github.com/joke2k/django-environ/blob/main/tests/test_cache.py)~~ for it.

the `backend` kwarg _should_ override the backend determined via the "scheme" and can be used to set a specific cache backend implementation, e.g.:

```
# use 3rd party django-redis backend instead of built-in
env.cache_url("redis://redis:6379/1", backend="django_redis.cache.RedisCache")
```

A few other shortcuts support the same mechanism, like `db(engine=...)`, `email(backend=...)` or `search_url(engine=...)`.

**edit: correction**, here's a test: https://github.com/joke2k/django-environ/blob/df301b607f287df049b4efeda14717fcdbe123b1/tests/test_cache.py#L173

/cc @sergeyklay looks like this is properly working and tested; may need docs.@RemiDesgrange: Sorry `DEFAULT_CACHE_URL` it is. `DEFAULT_COMPONENT_URL` is indeed a typo.

Thanks for the clarification.

Solution patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4e28fa5a..342d5ce8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,12 +24,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install dependencies
         run: |
@@ -44,7 +44,7 @@ jobs:
 
       - name: Archive build artifacts
         if: success()
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           # To ensure that jobs don't overwrite existing artifacts,
           # use a different name per job.
@@ -64,12 +64,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install in dev mode
         run: python -m pip install -e .
diff --git a/.github/workflows/change-pr-target.yml b/.github/workflows/change-pr-target.yml
index eb12a30c..af419d11 100644
--- a/.github/workflows/change-pr-target.yml
+++ b/.github/workflows/change-pr-target.yml
@@ -8,7 +8,7 @@ jobs:
   check-branch:
     runs-on: ubuntu-latest
     steps:
-      - uses: Vankka/pr-target-branch-action@v2
+      - uses: Vankka/pr-target-branch-action@v3
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1fe3f1d6..0d80bfe7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,32 +47,22 @@ jobs:
 
       matrix:
         python:
-          - '3.8'
           - '3.9'
           - '3.10'
           - '3.11'
           - '3.12'
-          - 'pypy-3.7'
+          - '3.13'
+          - 'pypy-3.10'
         os: [ ubuntu-latest, macos-latest, windows-latest ]
 
-        # These versions are no longer supported by Python team, and may
-        # eventually be dropped from GitHub Actions.  The support of these
-        # versions by django-environ will continue for as long as possible,
-        # and may be discontinued at any time.
-        include:
-          - python: '3.6'
-            os: ubuntu-20.04
-          - python: '3.7'
-            os: ubuntu-20.04
-
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
         with:
           fetch-depth: 5
 
       - name: Set up Python ${{ matrix.python }}
-        uses: actions/setup-python@v4.7.1
+        uses: actions/setup-python@v5.3.0
         with:
           python-version: ${{ matrix.python }}
 
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 460c1e33..d8236f32 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -45,16 +45,16 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v4.1.1
+      uses: actions/checkout@v4.2.2
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v2
+      uses: github/codeql-action/init@v3
       with:
         languages: ${{ matrix.language }}
         
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v2
+      uses: github/codeql-action/autobuild@v3
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v2
+      uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/cs.yml b/.github/workflows/cs.yml
index 4daad7d5..30d41b99 100644
--- a/.github/workflows/cs.yml
+++ b/.github/workflows/cs.yml
@@ -25,12 +25,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install dependencies
         run: |
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index efef795c..0ae19c36 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -26,12 +26,12 @@ jobs:
 
     steps:
       - name: Checkout code
-        uses: actions/checkout@v4.1.1
+        uses: actions/checkout@v4.2.2
 
-      - name: Set up Python 3.10
-        uses: actions/setup-python@v4.7.1
+      - name: Set up Python 3.12
+        uses: actions/setup-python@v5.3.0
         with:
-          python-version: '3.10'
+          python-version: '3.12'
 
       - name: Install dependencies
         run: |
@@ -46,7 +46,7 @@ jobs:
 
       - name: Archive docs artifacts
         if: always()
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: docs
           path: docs
diff --git a/.gitignore b/.gitignore
index 0b4fa311..ec6473a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 27f93688..191e9990 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -17,7 +17,7 @@ build:
   tools:
     # Keep version in sync with tox.ini (testenv:docs) and
     # docs.yml (GitHub Action Workflow).
-    python: '3.10'
+    python: '3.12'
 
 python:
   install:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6051aa4a..033cae2e 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,16 +5,38 @@ All notable changes to this project will be documented in this file.
 The format is inspired by `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_
 and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
 
-`v0.11.3`_ - 0-Undefined-2023
+`v0.12.0`_ - 0-Undefined-2024
 -----------------------------
+Fixed
++++++
+- Include prefix in the ``ImproperlyConfigured`` error message
+  `#513 <https://github.com/joke2k/django-environ/issues/513>`_.
+
+Added
++++++
+- Add support for Python 3.12 and 3.13
+  `#538 <https://github.com/joke2k/django-environ/issues/538>`_.
+- Add support for Django 5.1
+  `#535 <https://github.com/joke2k/django-environ/issues/535>`_.
+- Add support for Django CockroachDB driver
+  `#509 <https://github.com/joke2k/django-environ/issues/509>`_.
+- Add support for Django Channels
+  `#266 <https://github.com/joke2k/django-environ/issues/266>`_.
+
 Changed
 +++++++
-- Formally support Python 3.12.
 - Disabled inline comments handling by default due to potential side effects.
   While the feature itself is useful, the project's philosophy dictates that
   it should not be enabled by default for all users
   `#499 <https://github.com/joke2k/django-environ/issues/499>`_.
 
+Removed
++++++++
+- Removed support of Python 3.6, 3.7 and 3.8
+  `#538 <https://github.com/joke2k/django-environ/issues/538>`_.
+- Removed support of Django 1.x.
+  `#538 <https://github.com/joke2k/django-environ/issues/538>`_.
+
 
 `v0.11.2`_ - 1-September-2023
 -----------------------------
@@ -399,7 +421,7 @@ Added
 - Initial release.
 
 
-.. _v0.11.3: https://github.com/joke2k/django-environ/compare/v0.11.2...v0.11.3
+.. _v0.12.0: https://github.com/joke2k/django-environ/compare/v0.11.2...v0.12.0
 .. _v0.11.2: https://github.com/joke2k/django-environ/compare/v0.11.1...v0.11.2
 .. _v0.11.1: https://github.com/joke2k/django-environ/compare/v0.11.0...v0.11.1
 .. _v0.11.0: https://github.com/joke2k/django-environ/compare/v0.10.0...v0.11.0
@@ -417,4 +439,4 @@ Added
 .. _v0.4.1: https://github.com/joke2k/django-environ/compare/v0.4...v0.4.1
 .. _v0.4: https://github.com/joke2k/django-environ/compare/v0.3.1...v0.4
 .. _v0.3.1: https://github.com/joke2k/django-environ/compare/v0.3...v0.3.1
-.. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3
\ No newline at end of file
+.. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3
diff --git a/LICENSE.txt b/LICENSE.txt
index 8737f752..97cfd65e 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/MANIFEST.in b/MANIFEST.in
index 9c2f064c..694e938d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/README.rst b/README.rst
index dbb572ba..ff8e0125 100644
--- a/README.rst
+++ b/README.rst
@@ -97,7 +97,8 @@ approach, some connection strings are expressed as url, so this package can pars
 it and return a ``urllib.parse.ParseResult``. These strings from ``os.environ``
 are loaded from a ``.env`` file and filled in ``os.environ`` with ``setdefault``
 method, to avoid to overwrite the real environ.
-A similar approach is used in `Two Scoops of Django <https://www.feldroy.com/books/two-scoops-of-django-3-x>`_
+A similar approach is used in
+`Two Scoops of Django <https://web.archive.org/web/20240121133956/https://www.feldroy.com/books/two-scoops-of-django-3-x>`_
 book and explained in `12factor-django <https://wellfire.co/learn/easier-12-factor-django>`_
 article.
 
@@ -126,8 +127,8 @@ its documentation lives at `Read the Docs <https://django-environ.readthedocs.io
 the code on `GitHub <https://github.com/joke2k/django-environ>`_,
 and the latest release on `PyPI <https://pypi.org/project/django-environ/>`_.
 
-It’s rigorously tested on Python 3.6+, and officially supports
-Django 1.11, 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, and 5.0.
+It’s rigorously tested on Python 3.9+, and officially supports
+Django 2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2, 5.0, and 5.1.
 
 If you'd like to contribute to ``django-environ`` you're most welcome!
 
diff --git a/SECURITY.rst b/SECURITY.rst
index 16ffdceb..f3ea149f 100644
--- a/SECURITY.rst
+++ b/SECURITY.rst
@@ -6,5 +6,5 @@ Reporting a Vulnerability
 -------------------------
 
 If you discover a security vulnerability within ``django-environ``, please
-send an e-mail to Serghei Iakovlev via egrep@protonmail.ch. All security
+send an e-mail to Serghei Iakovlev via oss@serghei.pl. All security
 vulnerabilities will be promptly addressed.
diff --git a/docs/Makefile b/docs/Makefile
index 887c0bba..343425b6 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/docs/conf.py b/docs/conf.py
index 8beac1f4..0e91d43c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/docs/docutils.conf b/docs/docutils.conf
index 4ed0bf5a..d624b452 100644
--- a/docs/docutils.conf
+++ b/docs/docutils.conf
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/docs/install.rst b/docs/install.rst
index 3dacca9e..b1a73c18 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -6,8 +6,8 @@ Installation
 Requirements
 ============
 
-* `Django <https://www.djangoproject.com/>`_ >= 1.11
-* `Python <https://www.python.org/>`_ >= 3.5
+* `Django <https://www.djangoproject.com/>`_ >= 2.2
+* `Python <https://www.python.org/>`_ >= 3.9
 
 Installing django-environ
 =========================
diff --git a/environ/__init__.py b/environ/__init__.py
index ddf05f92..8d29d2f1 100644
--- a/environ/__init__.py
+++ b/environ/__init__.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -21,7 +21,7 @@
 __copyright__ = 'Copyright (C) 2013-2023 Daniele Faraglia'
 """The copyright notice of the package."""
 
-__version__ = '0.11.3'
+__version__ = '0.12.0'
 """The version of the package."""
 
 __license__ = 'MIT'
@@ -36,7 +36,7 @@
 __maintainer__ = 'Serghei Iakovlev'
 """The maintainer of the package."""
 
-__maintainer_email__ = 'egrep@protonmail.ch'
+__maintainer_email__ = 'oss@serghei.pl'
 """The email of the maintainer of the package."""
 
 __url__ = 'https://django-environ.readthedocs.org'
diff --git a/environ/compat.py b/environ/compat.py
index 49b5b480..55953fe5 100644
--- a/environ/compat.py
+++ b/environ/compat.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/environ/environ.py b/environ/environ.py
index a3d64f2d..d83157a0 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -115,6 +115,7 @@ class Env:
         'psql': DJANGO_POSTGRES,
         'pgsql': DJANGO_POSTGRES,
         'postgis': 'django.contrib.gis.db.backends.postgis',
+        'cockroachdb': 'django_cockroachdb',
         'mysql': 'django.db.backends.mysql',
         'mysql2': 'django.db.backends.mysql',
         'mysql-connector': 'mysql.connector.django',
@@ -189,6 +190,13 @@ class Env:
                             for s in ('', 's')]
     CLOUDSQL = 'cloudsql'
 
+    DEFAULT_CHANNELS_ENV = "CHANNELS_URL"
+    CHANNELS_SCHEMES =  {
+        "inmemory": "channels.layers.InMemoryChannelLayer",
+        "redis": "channels_redis.core.RedisChannelLayer",
+        "redis+pubsub": "channels_redis.pubsub.RedisPubSubChannelLayer"
+    }
+
     def __init__(self, **scheme):
         self.smart_cast = True
         self.escape_proxy = False
@@ -337,6 +345,18 @@ def search_url(self, var=DEFAULT_SEARCH_ENV, default=NOTSET, engine=None):
             engine=engine
         )
 
+    def channels_url(self, var=DEFAULT_CHANNELS_ENV, default=NOTSET, backend=None):
+        """Returns a config dictionary, defaulting to CHANNELS_URL.
+
+        :rtype: dict
+        """
+        return self.channels_url_config(
+            self.url(var, default=default),
+            backend=backend
+        )
+
+    channels = channels_url
+
     def path(self, var, default=NOTSET, **kwargs):
         """
         :rtype: Path
@@ -388,7 +408,7 @@ def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
             value = self.ENVIRON[var_name]
         except KeyError as exc:
             if default is self.NOTSET:
-                error_msg = f'Set the {var} environment variable'
+                error_msg = f'Set the {var_name} environment variable'
                 raise ImproperlyConfigured(error_msg) from exc
 
             value = default
@@ -735,6 +755,33 @@ def email_url_config(cls, url, backend=None):
             config['OPTIONS'] = config_options
 
         return config
+    
+    @classmethod
+    def channels_url_config(cls, url, backend=None):
+        """Parse an arbitrary channels URL.
+
+        :param urllib.parse.ParseResult or str url:
+            Email URL to parse.
+        :param str or None backend:
+            If None, the backend is evaluates from the ``url``.
+        :return: Parsed channels URL.
+        :rtype: dict
+        """
+        config = {}
+        url = urlparse(url) if not isinstance(url, cls.URL_CLASS) else url
+        
+        if backend:
+            config["BACKEND"] = backend
+        elif url.scheme not in cls.CHANNELS_SCHEMES:
+            raise ImproperlyConfigured(f"Invalid channels schema {url.scheme}")
+        else:
+            config["BACKEND"] = cls.CHANNELS_SCHEMES[url.scheme]
+            if url.scheme in ("redis", "redis+pubsub"):
+                config["CONFIG"] = {
+                    "hosts": [url._replace(scheme="redis").geturl()]
+                }
+
+        return config
 
     @classmethod
     def _parse_common_search_params(cls, url):
diff --git a/environ/fileaware_mapping.py b/environ/fileaware_mapping.py
index 0578945b..9d59e0ac 100644
--- a/environ/fileaware_mapping.py
+++ b/environ/fileaware_mapping.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/setup.py b/setup.py
index fd3cd811..e9272177 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@
 #
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -135,9 +135,6 @@ def get_version_string():
     'Development Status :: 5 - Production/Stable',
 
     'Framework :: Django',
-    'Framework :: Django :: 1.11',
-    'Framework :: Django :: 2.0',
-    'Framework :: Django :: 2.1',
     'Framework :: Django :: 2.2',
     'Framework :: Django :: 3.0',
     'Framework :: Django :: 3.1',
@@ -146,6 +143,7 @@ def get_version_string():
     'Framework :: Django :: 4.1',
     'Framework :: Django :: 4.2',
     'Framework :: Django :: 5.0',
+    'Framework :: Django :: 5.1',
 
     'Operating System :: OS Independent',
 
@@ -154,13 +152,11 @@ def get_version_string():
 
     'Programming Language :: Python',
     'Programming Language :: Python :: 3',
-    'Programming Language :: Python :: 3.6',
-    'Programming Language :: Python :: 3.7',
-    'Programming Language :: Python :: 3.8',
     'Programming Language :: Python :: 3.9',
     'Programming Language :: Python :: 3.10',
     'Programming Language :: Python :: 3.11',
     'Programming Language :: Python :: 3.12',
+    'Programming Language :: Python :: 3.13',
     'Programming Language :: Python :: Implementation :: CPython',
     'Programming Language :: Python :: Implementation :: PyPy',
 
@@ -185,11 +181,12 @@ def get_version_string():
     'testing': [
         'coverage[toml]>=5.0a4',  # Code coverage measurement for Python
         'pytest>=4.6.11',  # Our tests framework
+        'setuptools>=71.0.0',  # Needed as a dependency for some tests
     ],
     # Dependencies that are required to build documentation
     'docs': [
-        'furo>=2021.8.17b43,==2021.8.*',  # Sphinx documentation theme
-        'sphinx>=3.5.0',  # Python documentation generator
+        'furo>=2024.8.6',  # Sphinx documentation theme
+        'sphinx>=5.0',  # Python documentation generator
         'sphinx-notfound-page',  # Create a custom 404 page
     ],
 }
@@ -232,7 +229,7 @@ def get_version_string():
         platforms=['any'],
         include_package_data=True,
         zip_safe=False,
-        python_requires='>=3.6,<4',
+        python_requires='>=3.9,<4',
         install_requires=INSTALL_REQUIRES,
         dependency_links=DEPENDENCY_LINKS,
         extras_require=EXTRAS_REQUIRE,
diff --git a/tox.ini b/tox.ini
index fa3da3df..a0b8dc28 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2023, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -18,28 +18,23 @@ envlist =
     docs
     lint
     manifest
-    py{36,37,38,39,310,311}-django{111,22}
-    py{36,37,38,39,310,311}-django{30,31,32}
-    py{38,39,310,311}-django{40,41,42}
-    py{310,311}-django{50}
-    pypy-django{111,22,30,31,32}
+    py{39,310,311,312,313}-django{22,30,31,32,40,41,42}
+    py{310,311,312,313}-django{50,51}
+    pypy-django{22,30,31,32}
 
 [gh-actions]
 python =
-    3.6: py36
-    3.7: py37
-    3.8: py38
     3.9: py39
     3.10: py310
     3.11: py311
     3.12: py312
-    pypy-3.7: pypy
+    3.13: py313
+    pypy-3.10: pypy
 
 [testenv]
 description = Unit tests
 extras = testing
 deps =
-    django111: Django>=1.11,<2
     django22: Django>=2.2,<3.0
     django30: Django>=3.0,<3.1
     django31: Django>=3.1,<3.2
@@ -93,7 +88,7 @@ commands =
 description = Check external links in the package documentation
 # Keep basepython in sync with .readthedocs.yml and docs.yml
 # (GitHub Action Workflow).
-basepython = python3.10
+basepython = python3.12
 extras = docs
 commands =
     {envpython} -m sphinx \
@@ -111,7 +106,7 @@ isolated_build = true
 description = Build package documentation (HTML)
 # Keep basepython in sync with .readthedocs.yml and docs.yml
 # (GitHub Action Workflow).
-basepython = python3.10
+basepython = python3.12
 extras = docs
 commands =
     {envpython} -m sphinx \


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
index 19f3cf24..c5fd8236 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/asserts.py b/tests/asserts.py
index 3389e526..00d94b28 100644
--- a/tests/asserts.py
+++ b/tests/asserts.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/conftest.py b/tests/conftest.py
index 38550f73..ffdbf292 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 69e5e90f..dc9fbd42 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/test_cache.py b/tests/test_cache.py
index a8aff161..58e57e0b 100644
--- a/tests/test_cache.py
+++ b/tests/test_cache.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/test_channels.py b/tests/test_channels.py
new file mode 100644
index 00000000..64485a6f
--- /dev/null
+++ b/tests/test_channels.py
@@ -0,0 +1,25 @@
+# This file is part of the django-environ.
+#
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
+# Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
+#
+# For the full copyright and license information, please view
+# the LICENSE.txt file that was distributed with this source code.
+
+from environ import Env
+
+
+def test_channels_parsing():
+    url = "inmemory://"
+    result = Env.channels_url_config(url)
+    assert result["BACKEND"] == "channels.layers.InMemoryChannelLayer"
+
+    url = "redis://user:password@localhost:6379/0"
+    result = Env.channels_url_config(url)
+    assert result["BACKEND"] == "channels_redis.core.RedisChannelLayer"
+    assert result["CONFIG"]["hosts"][0] == "redis://user:password@localhost:6379/0"
+
+    url = "redis+pubsub://user:password@localhost:6379/0"
+    result = Env.channels_url_config(url)
+    assert result["BACKEND"] == "channels_redis.pubsub.RedisPubSubChannelLayer"
+    assert result["CONFIG"]["hosts"][0] == "redis://user:password@localhost:6379/0"
diff --git a/tests/test_db.py b/tests/test_db.py
index 8101a4a7..e26c5357 100644
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -59,6 +59,14 @@
          '',
          ''
          ),
+        # cockroachdb://username:secret@test.example.com:26258/dbname
+        ('cockroachdb://username:secret@test.example.com:26258/dbname',
+         'django_cockroachdb',
+         'dbname',
+         'test.example.com',
+         'username',
+         'secret',
+         26258),
         # mysqlgis://user:password@host:port/dbname
         ('mysqlgis://enigma:secret@example.com:5431/dbname',
          'django.contrib.gis.db.backends.mysql',
@@ -156,6 +164,7 @@
         'postgis',
         'postgres_cluster',
         'postgres_no_ports',
+        'cockroachdb',
         'mysqlgis',
         'cleardb',
         'mysql_no_password',
@@ -188,6 +197,16 @@ def test_db_parsing(url, engine, name, host, user, passwd, port):
         assert config['OPTIONS'] == {'reconnect': 'true'}
 
 
+def test_custom_db_engine():
+    """Override ENGINE determined from schema."""
+    env_url = 'postgres://enigma:secret@example.com:5431/dbname'
+
+    engine = 'mypackage.backends.whatever'
+    url = Env.db_url_config(env_url, engine=engine)
+
+    assert url['ENGINE'] == engine
+
+
 def test_postgres_complex_db_name_parsing():
     """Make sure we can use complex postgres host."""
     env_url = (
diff --git a/tests/test_email.py b/tests/test_email.py
index 30d38139..1c9a8754 100644
--- a/tests/test_email.py
+++ b/tests/test_email.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -22,3 +22,13 @@ def test_smtp_parsing():
     assert url['EMAIL_PORT'] == 587
     assert url['EMAIL_USE_TLS'] is True
     assert url['EMAIL_FILE_PATH'] == ''
+
+
+def test_custom_email_backend():
+    """Override EMAIL_BACKEND determined from schema."""
+    url = 'smtps://user@domain.com:password@smtp.example.com:587'
+
+    backend = 'mypackage.backends.whatever'
+    url = Env.email_url_config(url, backend=backend)
+
+    assert url['EMAIL_BACKEND'] == backend
diff --git a/tests/test_env.py b/tests/test_env.py
index 85e0499f..5f0f4c9b 100644
--- a/tests/test_env.py
+++ b/tests/test_env.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -407,6 +407,13 @@ def test_prefix(self):
         self.env.prefix = 'PREFIX_'
         assert self.env('TEST') == 'foo'
 
+    def test_prefix_and_not_present_without_default(self):
+        self.env.prefix = 'PREFIX_'
+        with pytest.raises(ImproperlyConfigured) as excinfo:
+            self.env('not_present')
+        assert str(excinfo.value) == 'Set the PREFIX_not_present environment variable'
+        assert excinfo.value.__cause__ is not None
+
 
 class TestFileEnv(TestEnv):
     def setup_method(self, method):
diff --git a/tests/test_fileaware.py b/tests/test_fileaware.py
index f411b7de..00442ae6 100644
--- a/tests/test_fileaware.py
+++ b/tests/test_fileaware.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/test_path.py b/tests/test_path.py
index b97f4caf..5d868d4d 100644
--- a/tests/test_path.py
+++ b/tests/test_path.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/test_schema.py b/tests/test_schema.py
index 7a7f62ec..45f0e983 100644
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
diff --git a/tests/test_search.py b/tests/test_search.py
index a6d8f061..38f11f73 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view
@@ -86,6 +86,16 @@ def test_elasticsearch_parsing(url, engine, scheme):
     assert url["URL"].startswith(scheme + ":")
 
 
+def test_custom_search_engine():
+    """Override ENGINE determined from schema."""
+    env_url = 'elasticsearch://127.0.0.1:9200/index'
+
+    engine = 'mypackage.backends.whatever'
+    url = Env.db_url_config(env_url, engine=engine)
+
+    assert url['ENGINE'] == engine
+
+
 @pytest.mark.parametrize('storage', ['file', 'ram'])
 def test_whoosh_parsing(whoosh_url, storage):
     post_limit = 128 * 1024 * 1024
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 2f3d32ee..02007179 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,6 +1,6 @@
 # This file is part of the django-environ.
 #
-# Copyright (c) 2021-2022, Serghei Iakovlev <egrep@protonmail.ch>
+# Copyright (c) 2021-2024, Serghei Iakovlev <oss@serghei.pl>
 # Copyright (c) 2013-2021, Daniele Faraglia <daniele.faraglia@gmail.com>
 #
 # For the full copyright and license information, please view