ftw-django-utils


Nameftw-django-utils JSON
Version 2023.4.0 PyPI version JSON
download
home_page
SummaryA collection of utils used in our Django based web applications.
upload_time2023-10-31 16:57:23
maintainer
docs_urlNone
author4teamwork AG
requires_python>=3.9,<4
license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # django-utils
A collection of utils used in our Django based web applications

[Changelog](CHANGELOG.md)

## Development

Installing dependencies, assuming you have poetry installed:

``` bash
poetry install
```

## Release

This package uses towncrier to manage the changelog, and to introduce new changes, a file with a concise title and a brief explanation of what the change accomplishes should be created in the `changes` directory, with a suffix indicating whether the change is a feature, bugfix, or other.

To make a release and publish it to PyPI, the following command can be executed:

``` bash
./bin/release
```

This script utilizes zest.releaser and towncrier to create the release, build the wheel, and publish it to PyPI.

Before running the release command, it is necessary to configure poetry with an access token for PyPI by executing the following command and inserting the token stored in 1password:

``` bash
poetry config pypi-token.pypi <token>
```

## Settings

### LogMixin

A mixin containing the logging configuration with default values suited for production.

Available environment variables:

* `LOGGING_LEVEL` (default: `"WARNING"`)
* `LOGGING_HANDLERS` (default: `["stream"]`)
* `LOGGING_FILENAME` (default: `<settings.BASE_DIR>/log/django.log`)

Usage:

````python
# myproject/settings/production.py

from django_utils.settings.logging import LogMixin

from myproject.settings.base import Base

class Production(LogMixin, Base):
    pass
````

### SentryMixin

A mixin to report errors to Sentry.

Available environment variables:

* `SENTRY_DSN` (required)
* `SENTRY_ENVIRONMENT` (default: `"Production"`)
* `SENTRY_CACERTS` (default: `None`)
* `SENTRY_TAGS` (default: `{}`)

Usage:

````python
# myproject/settings/production.py

from django_utils.settings.sentry import SentryMixin

from myproject.settings.base import Base

class Production(SentryMixin, Base):
    pass
````


## Management Commands

ℹ️ In order to use the management commands, you need to add `django_utils` to the `INSTALLED_APPS` setting.

### sentry_test

Use this command to test the connection to Sentry. The command raises an exception on purpose. This should be reported in Sentry.

Usage:

```bash
python manage.py sentry_test
```


## Model Fields

### TextField

A TextField (without max_length) which displays its form element as a single-line 'CharField' with extended width in the Django admin.

Usage:

```python
# models.py

from django.db import models
from django_utils.db.fields import TextField


class MyModel(models.Model):
    TextField(verbose_name="My TextField")
```


## Testing

### Views

This package provides views which can be used in E2E tests for test setup and teardown and to login or create test data.

ℹ️ These views rely on the availability of a management command executable as `python manage.py load_e2e_data --datasets initial` to load initial fixture data for the tests.

⚠️ Important: Never add these views (through `django_utils.testing.urls`) in production mode! They should only be used for testing purposes.

Usage:

```python
# myproject/settings/testing.py

from django_utils.settings.logging import LogMixin

from myproject.settings.base import Base

class TestingE2E(LogMixin, Base):
    LOGGING_LEVEL = "INFO"
    LOGGING_HANDLERS = ["file"]

    @property
    def LOGGING_FILENAME(self):
        return super().BASE_DIR / "log" / "e2e.log"


# myproject/urls.py

from django.conf import settings
from django.urls import include
from django.urls import path

urlpatterns = [
    # ...
]

if settings.CONFIGURATION == "settings.TestingE2E":
    urlpatterns += [
        path("e2e/", include("django_utils.testing.urls")),
    ]
```

#### E2ETestSetupView (`/setup`)

Creates a snapshot of the media root folder, resets the database, restores permissions and loads initial data (calls `python manage.py load_e2e_data --datasets initial`).

#### E2ETestTearDownView (`/teardown`)

Cleans up the media root folder.

#### TestingLoginView (`/login`)

Authenticates a user when posting a JSON request body such as `{"username": "username", "password": "password"}`.

#### E2ETestLoadDataView (`/load_data`)

Loads given datasets using `python manage.py load_e2e_data --datasets [datasets]` when posting a JSON request body such as `{"datasets": ["initial"]}`.

#### PingView (`/ping`)

Responds an HTTP response with status code 200.


## Protected File View

Django Utils offers a way to protect your files from anonymous access.

### Requirements

The file view relies on `django-sendfile2` and `djangorestframework`.

```bash
pip install django-sendfile2
pip install djangorestframework
```

### View

Django Utils offers a file getter view under `django_utils.views.FileGetterView`.
This view has to be used for a protected access to files.
The view will only deliver the files requested, when the user is logged in by default.
In order to override the behavior, subclass the `FileGetterView` and implement `get_object`.
The view will only permit access, when the `get_object` method evaluates a model instance. Simply return `None` to block the access.

```python
from django_utils.views import FileGetterView

class MyFileView(FileGetterView):
    def get_object(self, model_class, id, request, **kwargs):
        return model_class._default_manager.get(user=request.user, id=id)
```

### URL

To relay all the requests for assets to the file getter view, append your urls with a pattern like this:

```python
urlpatterns = [
    re_path(r"media/(?P<file_info>.*)", FileGetterView.as_view(), name="media"),
]
```

Your pattern has to end with `(?P<file_info>.*)` and register a file getter view.

### Serializer

In order for the file getter view to resolve certain model fields, the field must be a `ProtectedImageField` in the serializer:

```python
from django_utils.serializer.fields import ProtectedImageField

class MySerializer(Serializer):
    image = ProtectedImageField()

    class Meta:
        fields = (
            "image",
        )
```

### Settings

Configure your settings as follows:

```python
SENDFILE_BACKEND = "django_sendfile.backends.simple"
SENDFILE_ROOT = self.BASE_DIR / "media"
MEDIA_URL = "/media/"
```

The `SENDFILE_ROOT` has to point to the directory where your files will be stored.
The `MEDIA_URL` has to match the url pattern.

If you use uwsgi in production, apply the following settings to your `uwsgi.ini`:

```ini
plugins = router_static
static-safe = %(base_path)/media
collect-header = X-Sendfile X_SENDFILE
response-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE}
```

Make sure the `static-safe` matches your `MEDIA_URL` setting.

### Caching

The object the file is attached to, must have a field named `modified`, that changes every time the object is updated, in order to create a new URL when the file is updated.
The field is not mandatory but required for the caching to work properly.


## Authentication

### SessionAuthentication

Based on ``SessionAuthentication`` of Django REST Framework, this class allows distinguishing status codes 401 and 403 in API responses.
It returns a response with status code 401 for unauthenticated requests (user is not logged in) and returns a response with status code 403 if the user is logged in but does not have sufficient permissions.

Requires ``djangorestframework``.

Usage:

```python
# myproject/settings/base.py

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["django_utils.authentication.SessionAuthentication"],
}
```

## Delete hooks

Ianus is capable of requesting user deletion and notifying when users have been deleted. To enable this feature, Ianus requires certain views to be present in the service. Django Utils provides a default implementation for these views, which handle token authentication and validation of the request made by Ianus. To register these views, add the urlpatterns in your service being notified by Ianus, as shown below:

``` python
# urls.py
from django.urls import include

urlpatterns = [
    path("", include("django_utils.webhooks.delete_user.urls")),
]
```

When Ianus requests an inquiry to delete a user, a hook is called to determine if the user can be deleted or not. The default hook will always accept the inquiry. However, this behavior can be overridden by providing a module with an `inquire_delete` method. To do this, configure the `IANUS_USER_DELETE_HOOKS` setting and point it to a module that implements the `inquire_delete` method, as shown below:

``` python
# settings.py
IANUS_USER_DELETE_HOOKS = "backend.authentication.delete_hooks"
```

The `inquire_delete` method will receive the user that Ianus is inquiring to delete as a parameter. The `inquire_delete` method must return a tuple, where the first component is a boolean indicating whether the inquiry has passed or not, and the second component is a reason as a string, which tells Ianus the reason behind a failing inquiry. When the inquiry is successful, the reason can be set to `None`, as shown below:

``` python
# delete_hooks.py
def inquire_delete(user):
    return (True, None)
```

Alternatively, you can return a failure message to Ianus, indicating why the inquiry failed, as shown below:

``` python
# delete_hooks.py
def inquire_delete(user):
    return (False, "User cannot be deleted due to specific reasons.")
```

The same concept applies to user delete notification. A method with the name `subscribe_delete` in the same module has to be provided to subscribe to user deletion from Ianus. The `subscribe_delete` method will receive the user that was deleted in Ianus as an argument. The default implementation will always delete the user. You can provide your own implementation in this method, as shown below:

``` python
# delete_hooks.py
def subscribe_delete(user):
    user.delete()
```

To configure the authentication token for the delete hook API, use the `IANUS_DELETE_HOOK_API_TOKEN` setting. The authentication token that is configured in Ianus must match this token for the authentication to work correctly.

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "ftw-django-utils",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.9,<4",
    "maintainer_email": "",
    "keywords": "",
    "author": "4teamwork AG",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/bb/8b/3cc32b8726075d230a94b16eb5663ab2ea357420d16235cfa476a501a498/ftw_django_utils-2023.4.0.tar.gz",
    "platform": null,
    "description": "# django-utils\nA collection of utils used in our Django based web applications\n\n[Changelog](CHANGELOG.md)\n\n## Development\n\nInstalling dependencies, assuming you have poetry installed:\n\n``` bash\npoetry install\n```\n\n## Release\n\nThis package uses towncrier to manage the changelog, and to introduce new changes, a file with a concise title and a brief explanation of what the change accomplishes should be created in the `changes` directory, with a suffix indicating whether the change is a feature, bugfix, or other.\n\nTo make a release and publish it to PyPI, the following command can be executed:\n\n``` bash\n./bin/release\n```\n\nThis script utilizes zest.releaser and towncrier to create the release, build the wheel, and publish it to PyPI.\n\nBefore running the release command, it is necessary to configure poetry with an access token for PyPI by executing the following command and inserting the token stored in 1password:\n\n``` bash\npoetry config pypi-token.pypi <token>\n```\n\n## Settings\n\n### LogMixin\n\nA mixin containing the logging configuration with default values suited for production.\n\nAvailable environment variables:\n\n* `LOGGING_LEVEL` (default: `\"WARNING\"`)\n* `LOGGING_HANDLERS` (default: `[\"stream\"]`)\n* `LOGGING_FILENAME` (default: `<settings.BASE_DIR>/log/django.log`)\n\nUsage:\n\n````python\n# myproject/settings/production.py\n\nfrom django_utils.settings.logging import LogMixin\n\nfrom myproject.settings.base import Base\n\nclass Production(LogMixin, Base):\n    pass\n````\n\n### SentryMixin\n\nA mixin to report errors to Sentry.\n\nAvailable environment variables:\n\n* `SENTRY_DSN` (required)\n* `SENTRY_ENVIRONMENT` (default: `\"Production\"`)\n* `SENTRY_CACERTS` (default: `None`)\n* `SENTRY_TAGS` (default: `{}`)\n\nUsage:\n\n````python\n# myproject/settings/production.py\n\nfrom django_utils.settings.sentry import SentryMixin\n\nfrom myproject.settings.base import Base\n\nclass Production(SentryMixin, Base):\n    pass\n````\n\n\n## Management Commands\n\n\u2139\ufe0f In order to use the management commands, you need to add `django_utils` to the `INSTALLED_APPS` setting.\n\n### sentry_test\n\nUse this command to test the connection to Sentry. The command raises an exception on purpose. This should be reported in Sentry.\n\nUsage:\n\n```bash\npython manage.py sentry_test\n```\n\n\n## Model Fields\n\n### TextField\n\nA TextField (without max_length) which displays its form element as a single-line 'CharField' with extended width in the Django admin.\n\nUsage:\n\n```python\n# models.py\n\nfrom django.db import models\nfrom django_utils.db.fields import TextField\n\n\nclass MyModel(models.Model):\n    TextField(verbose_name=\"My TextField\")\n```\n\n\n## Testing\n\n### Views\n\nThis package provides views which can be used in E2E tests for test setup and teardown and to login or create test data.\n\n\u2139\ufe0f These views rely on the availability of a management command executable as `python manage.py load_e2e_data --datasets initial` to load initial fixture data for the tests.\n\n\u26a0\ufe0f Important: Never add these views (through `django_utils.testing.urls`) in production mode! They should only be used for testing purposes.\n\nUsage:\n\n```python\n# myproject/settings/testing.py\n\nfrom django_utils.settings.logging import LogMixin\n\nfrom myproject.settings.base import Base\n\nclass TestingE2E(LogMixin, Base):\n    LOGGING_LEVEL = \"INFO\"\n    LOGGING_HANDLERS = [\"file\"]\n\n    @property\n    def LOGGING_FILENAME(self):\n        return super().BASE_DIR / \"log\" / \"e2e.log\"\n\n\n# myproject/urls.py\n\nfrom django.conf import settings\nfrom django.urls import include\nfrom django.urls import path\n\nurlpatterns = [\n    # ...\n]\n\nif settings.CONFIGURATION == \"settings.TestingE2E\":\n    urlpatterns += [\n        path(\"e2e/\", include(\"django_utils.testing.urls\")),\n    ]\n```\n\n#### E2ETestSetupView (`/setup`)\n\nCreates a snapshot of the media root folder, resets the database, restores permissions and loads initial data (calls `python manage.py load_e2e_data --datasets initial`).\n\n#### E2ETestTearDownView (`/teardown`)\n\nCleans up the media root folder.\n\n#### TestingLoginView (`/login`)\n\nAuthenticates a user when posting a JSON request body such as `{\"username\": \"username\", \"password\": \"password\"}`.\n\n#### E2ETestLoadDataView (`/load_data`)\n\nLoads given datasets using `python manage.py load_e2e_data --datasets [datasets]` when posting a JSON request body such as `{\"datasets\": [\"initial\"]}`.\n\n#### PingView (`/ping`)\n\nResponds an HTTP response with status code 200.\n\n\n## Protected File View\n\nDjango Utils offers a way to protect your files from anonymous access.\n\n### Requirements\n\nThe file view relies on `django-sendfile2` and `djangorestframework`.\n\n```bash\npip install django-sendfile2\npip install djangorestframework\n```\n\n### View\n\nDjango Utils offers a file getter view under `django_utils.views.FileGetterView`.\nThis view has to be used for a protected access to files.\nThe view will only deliver the files requested, when the user is logged in by default.\nIn order to override the behavior, subclass the `FileGetterView` and implement `get_object`.\nThe view will only permit access, when the `get_object` method evaluates a model instance. Simply return `None` to block the access.\n\n```python\nfrom django_utils.views import FileGetterView\n\nclass MyFileView(FileGetterView):\n    def get_object(self, model_class, id, request, **kwargs):\n        return model_class._default_manager.get(user=request.user, id=id)\n```\n\n### URL\n\nTo relay all the requests for assets to the file getter view, append your urls with a pattern like this:\n\n```python\nurlpatterns = [\n    re_path(r\"media/(?P<file_info>.*)\", FileGetterView.as_view(), name=\"media\"),\n]\n```\n\nYour pattern has to end with `(?P<file_info>.*)` and register a file getter view.\n\n### Serializer\n\nIn order for the file getter view to resolve certain model fields, the field must be a `ProtectedImageField` in the serializer:\n\n```python\nfrom django_utils.serializer.fields import ProtectedImageField\n\nclass MySerializer(Serializer):\n    image = ProtectedImageField()\n\n    class Meta:\n        fields = (\n            \"image\",\n        )\n```\n\n### Settings\n\nConfigure your settings as follows:\n\n```python\nSENDFILE_BACKEND = \"django_sendfile.backends.simple\"\nSENDFILE_ROOT = self.BASE_DIR / \"media\"\nMEDIA_URL = \"/media/\"\n```\n\nThe `SENDFILE_ROOT` has to point to the directory where your files will be stored.\nThe `MEDIA_URL` has to match the url pattern.\n\nIf you use uwsgi in production, apply the following settings to your `uwsgi.ini`:\n\n```ini\nplugins = router_static\nstatic-safe = %(base_path)/media\ncollect-header = X-Sendfile X_SENDFILE\nresponse-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE}\n```\n\nMake sure the `static-safe` matches your `MEDIA_URL` setting.\n\n### Caching\n\nThe object the file is attached to, must have a field named `modified`, that changes every time the object is updated, in order to create a new URL when the file is updated.\nThe field is not mandatory but required for the caching to work properly.\n\n\n## Authentication\n\n### SessionAuthentication\n\nBased on ``SessionAuthentication`` of Django REST Framework, this class allows distinguishing status codes 401 and 403 in API responses.\nIt returns a response with status code 401 for unauthenticated requests (user is not logged in) and returns a response with status code 403 if the user is logged in but does not have sufficient permissions.\n\nRequires ``djangorestframework``.\n\nUsage:\n\n```python\n# myproject/settings/base.py\n\nREST_FRAMEWORK = {\n    \"DEFAULT_AUTHENTICATION_CLASSES\": [\"django_utils.authentication.SessionAuthentication\"],\n}\n```\n\n## Delete hooks\n\nIanus is capable of requesting user deletion and notifying when users have been deleted. To enable this feature, Ianus requires certain views to be present in the service. Django Utils provides a default implementation for these views, which handle token authentication and validation of the request made by Ianus. To register these views, add the urlpatterns in your service being notified by Ianus, as shown below:\n\n``` python\n# urls.py\nfrom django.urls import include\n\nurlpatterns = [\n    path(\"\", include(\"django_utils.webhooks.delete_user.urls\")),\n]\n```\n\nWhen Ianus requests an inquiry to delete a user, a hook is called to determine if the user can be deleted or not. The default hook will always accept the inquiry. However, this behavior can be overridden by providing a module with an `inquire_delete` method. To do this, configure the `IANUS_USER_DELETE_HOOKS` setting and point it to a module that implements the `inquire_delete` method, as shown below:\n\n``` python\n# settings.py\nIANUS_USER_DELETE_HOOKS = \"backend.authentication.delete_hooks\"\n```\n\nThe `inquire_delete` method will receive the user that Ianus is inquiring to delete as a parameter. The `inquire_delete` method must return a tuple, where the first component is a boolean indicating whether the inquiry has passed or not, and the second component is a reason as a string, which tells Ianus the reason behind a failing inquiry. When the inquiry is successful, the reason can be set to `None`, as shown below:\n\n``` python\n# delete_hooks.py\ndef inquire_delete(user):\n    return (True, None)\n```\n\nAlternatively, you can return a failure message to Ianus, indicating why the inquiry failed, as shown below:\n\n``` python\n# delete_hooks.py\ndef inquire_delete(user):\n    return (False, \"User cannot be deleted due to specific reasons.\")\n```\n\nThe same concept applies to user delete notification. A method with the name `subscribe_delete` in the same module has to be provided to subscribe to user deletion from Ianus. The `subscribe_delete` method will receive the user that was deleted in Ianus as an argument. The default implementation will always delete the user. You can provide your own implementation in this method, as shown below:\n\n``` python\n# delete_hooks.py\ndef subscribe_delete(user):\n    user.delete()\n```\n\nTo configure the authentication token for the delete hook API, use the `IANUS_DELETE_HOOK_API_TOKEN` setting. The authentication token that is configured in Ianus must match this token for the authentication to work correctly.\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "A collection of utils used in our Django based web applications.",
    "version": "2023.4.0",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "70c26fafa96644811659d9b378f417113943aeae61429fd3dc0bf488b353120f",
                "md5": "943c7275a917edaf0ff1798aec50077b",
                "sha256": "5db4678c1a9a974642453049f652a0ba75704a095862ae21297b7b2dc6c60d41"
            },
            "downloads": -1,
            "filename": "ftw_django_utils-2023.4.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "943c7275a917edaf0ff1798aec50077b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9,<4",
            "size": 17590,
            "upload_time": "2023-10-31T16:57:21",
            "upload_time_iso_8601": "2023-10-31T16:57:21.403217Z",
            "url": "https://files.pythonhosted.org/packages/70/c2/6fafa96644811659d9b378f417113943aeae61429fd3dc0bf488b353120f/ftw_django_utils-2023.4.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bb8b3cc32b8726075d230a94b16eb5663ab2ea357420d16235cfa476a501a498",
                "md5": "8696f7461715f0a3c2d1b180de42995e",
                "sha256": "27132ef19e25b37956306a78afb184aadb43e5738b9709d37b37a84c42103e95"
            },
            "downloads": -1,
            "filename": "ftw_django_utils-2023.4.0.tar.gz",
            "has_sig": false,
            "md5_digest": "8696f7461715f0a3c2d1b180de42995e",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9,<4",
            "size": 15276,
            "upload_time": "2023-10-31T16:57:23",
            "upload_time_iso_8601": "2023-10-31T16:57:23.075480Z",
            "url": "https://files.pythonhosted.org/packages/bb/8b/3cc32b8726075d230a94b16eb5663ab2ea357420d16235cfa476a501a498/ftw_django_utils-2023.4.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-10-31 16:57:23",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "ftw-django-utils"
}
        
Elapsed time: 0.13583s