diff --git a/INSTALL.rst b/INSTALL.rst index c628b58..3678c1e 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -18,7 +18,7 @@ Prerequisites You need the following libraries and/or programs: -* `Python`_ - check the ``Dockerfile`` for the required version. +* `Python`_ - 3.11 * Python `Virtualenv`_ and `Pip`_ * `PostgreSQL`_ * `Node.js`_ @@ -44,13 +44,10 @@ development machine. .. code-block:: bash - $ git clone git@bitbucket.org:maykinmedia/referentielijsten.git + $ git clone https://github.com/maykinmedia/referentielijsten.git $ cd referentielijsten -3. Install all required (backend) libraries. - **Tip:** You can use the ``bootstrap.py`` script to install the requirements - and set the proper settings in ``manage.py``. Or, perform the steps - manually: +3. Install all required (backend) libraries: .. code-block:: bash @@ -69,6 +66,7 @@ development machine. .. code-block:: bash + $ source env/bin/activate $ python src/manage.py collectstatic --link $ python src/manage.py migrate @@ -148,10 +146,6 @@ file or as part of the ``(post)activate`` of your virtualenv. * ``DB_HOST``: database host. Defaults to ``localhost`` * ``DB_PORT``: database port. Defaults to ``5432``. -* ``SENTRY_DSN``: the DSN of the project in Sentry. If set, enabled Sentry SDK as - logger and will send errors/logging to Sentry. If unset, Sentry SDK will be - disabled. - Docker ====== @@ -162,7 +156,7 @@ The easiest way to get the project started is by using `Docker Compose`_. .. code-block:: bash - $ git clone git@bitbucket.org:maykinmedia/referentielijsten.git + $ git clone https://github.com/maykinmedia/referentielijsten.git Cloning into 'referentielijsten'... ... @@ -194,8 +188,8 @@ The easiest way to get the project started is by using `Docker Compose`_. ... Superuser created successfully. - $ docker exec -it referentielijsten_web_1 /app/src/manage.py loaddata admin_index groups - Installed 5 object(s) from 2 fixture(s) + $ docker exec -it referentielijsten_web_1 /app/src/manage.py loaddata default_admin_index.json + Installed 4 object(s) from 1 fixture(s) 4. Point your browser to ``http://localhost:8000/`` to access the project's management interface with the credentials used in step 3. @@ -240,60 +234,6 @@ all settings. $ docker exec -it referentielijsten /app/src/manage.py createsuperuser -Building and publishing the image ---------------------------------- - -Using ``bin/release-docker-image``, you can easily build and tag the image. - -The script is based on git branches and tags - if you're on the ``master`` -branch and the current ``HEAD`` is tagged, the tag will be used as -``RELEASE_TAG`` and the image will be pushed. If you want to push the image -without a git tag, you can use the ``RELEASE_TAG`` envvar. - -The image will only be pushed if the ``JOB_NAME`` envvar is set. The image -will always be built, even if no envvar is set. The default release tag is -``latest``. - -Example usage: - -.. code-block:: bash - - JOB_NAME=publish RELEASE_TAG=dev ./bin/release-docker-image.sh - - -Staging and production -====================== - -Ansible is used to deploy test, staging and production servers. It is assumed -the target machine has a clean `Debian`_ installation. - -1. Make sure you have `Ansible`_ installed (globally or in the virtual - environment): - - .. code-block:: bash - - $ pip install ansible - -2. Navigate to the project directory, and install the Maykin deployment - submodule if you haven't already: - - .. code-block:: bash - - $ git submodule update --init - -3. Run the Ansible playbook to provision a clean Debian machine: - - .. code-block:: bash - - $ cd deployment - $ ansible-playbook .yml - -For more information, see the ``README`` file in the deployment directory. - -.. _Debian: https://www.debian.org/ -.. _Ansible: https://pypi.org/project/ansible/ - - Settings ======== diff --git a/README.rst b/README.rst index ee0d0d2..6cdde01 100644 --- a/README.rst +++ b/README.rst @@ -3,15 +3,15 @@ referentielijsten ================== :Version: 0.1.0 -:Source: https://bitbucket.org/maykinmedia/referentielijsten -:Keywords: ```` +:Source: https://github.com/maykinmedia/referentielijsten +:Keywords: referentielijsten, stambomen :PythonVersion: 3.11 -|build-status| |requirements| +|docker| ```` -Developed by `Maykin Media B.V.`_ for ```` +Developed by `Maykin Media B.V.`_ Introduction @@ -19,12 +19,50 @@ Introduction ```` +API specificatie +================ -Documentation -============= +Hieronder staat de versie van Open Klant en welke versie van de +API-specificatie wordt aangeboden. -See ``INSTALL.rst`` for installation instructions, available settings and -commands. +========================== ============== ============= ================ +Referentielijsten versie API versie Release datum API specificatie +========================== ============== ============= ================ +master/latest n/a n/a `ReDoc `_, + `Swagger `_ + + +Ready-to-go implementatie +========================= + +|build-status| |coverage| |code-style| |codeql| |black| |python-versions| + +Deze implementatie is bedoeld als referentie implementatie van de API +specificaties maar tevens een productiewaardig component dat ingezet kan worden +in het ICT landschap van de overheid. + +Quickstart +---------- + +1. Download en start Open Klant: + + .. code:: bash + + $ wget https://raw.githubusercontent.com/maykinmedia/referentielijsten/master/docker-compose.yml + $ docker-compose up -d --no-build + $ docker-compose exec web src/manage.py createsuperuser + +2. In de browser, navigeer naar ``http://localhost:8000/`` om de beheerinterface + en de API te benaderen. + + +Links +===== + +* `Docker image `_ +* `Issues `_ +* `Code `_ +* `Community `_ References @@ -34,13 +72,35 @@ References * `Code `_ -.. |build-status| image:: https://github.com/maykinmedia/referentielijsten/workflows/ci/badge.svg?branch=master +.. _`Maykin B.V.`: https://www.maykinmedia.nl + +.. |build-status| image:: https://github.com/maykinmedia/referentielijsten/workflows/ci.yml/badge.svg?branch=master :alt: Build status :target: https://github.com/maykinmedia/referentielijsten/actions?query=workflow%3Aci -.. |requirements| image:: https://requires.io/bitbucket/maykinmedia/referentielijsten/requirements.svg?branch=master - :target: https://requires.io/bitbucket/maykinmedia/referentielijsten/requirements/?branch=master - :alt: Requirements status +.. |coverage| image:: https://codecov.io/github/maykinmedia/referentielijsten/branch/master/graphs/badge.svg?branch=master + :alt: Coverage + :target: https://codecov.io/gh/maykinmedia/referentielijsten + +.. |code-style| image:: https://github.com/maykinmedia/referentielijsten/actions/workflows/code-style.yml/badge.svg?branch=master + :alt: Code style + :target: https://github.com/maykinmedia/referentielijsten/actions/workflows/code-style.yml + +.. |codeql| image:: https://github.com/maykinmedia/referentielijsten/actions/workflows/codeql.yml/badge.svg?branch=master + :alt: CodeQL scan + :target: https://github.com/maykinmedia/referentielijsten/actions/workflows/codeql.yml + +.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :alt: Code style + :target: https://github.com/psf/black + +.. |docker| image:: https://img.shields.io/docker/v/maykinmedia/referentielijsten?sort=semver + :alt: Docker image + :target: https://hub.docker.com/r/maykinmedia/referentielijsten +.. |python-versions| image:: https://img.shields.io/badge/python-3.11%2B-blue.svg + :alt: Supported Python version -.. _Maykin Media B.V.: https://www.maykinmedia.nl +.. |lint-oas| image:: https://github.com/maykinmedia/referentielijsten/workflows/actions/lint-oas/badge.svg + :alt: Lint OAS + :target: https://github.com/maykinmedia/referentielijsten/actions?query=workflow%3Alint-oas diff --git a/bin/celery_beat.sh b/bin/celery_beat.sh deleted file mode 100755 index b9bce2b..0000000 --- a/bin/celery_beat.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -e - -LOGLEVEL=${CELERY_LOGLEVEL:-INFO} - -mkdir -p celerybeat - -echo "Starting celery beat" -exec celery beat \ - --app referentielijsten \ - -l $LOGLEVEL \ - --workdir src \ - -s ../celerybeat/beat diff --git a/bin/celery_flower.sh b/bin/celery_flower.sh deleted file mode 100755 index a90c30c..0000000 --- a/bin/celery_flower.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -exec celery flower --app referentielijsten --workdir src diff --git a/bin/celery_worker.sh b/bin/celery_worker.sh deleted file mode 100755 index 9374a13..0000000 --- a/bin/celery_worker.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -e - -LOGLEVEL=${CELERY_LOGLEVEL:-INFO} -CONCURRENCY=${CELERY_WORKER_CONCURRENCY:-1} - -QUEUE=${1:-${CELERY_WORKER_QUEUE:=celery}} -WORKER_NAME=${2:-${CELERY_WORKER_NAME:="${QUEUE}"@%n}} - -echo "Starting celery worker $WORKER_NAME with queue $QUEUE" -exec celery worker \ - --app referentielijsten \ - -Q $QUEUE \ - -n $WORKER_NAME \ - -l $LOGLEVEL \ - --workdir src \ - -O fair \ - -c $CONCURRENCY - diff --git a/src/referentielijsten/api/openapi.yaml b/src/referentielijsten/api/openapi.yaml index e3218c5..fe947a1 100644 --- a/src/referentielijsten/api/openapi.yaml +++ b/src/referentielijsten/api/openapi.yaml @@ -32,6 +32,7 @@ paths: type: string description: 'De waarde van de `tabel__code` die gelinkt is aan de items: VERPLICHT' + required: true tags: - items security: @@ -47,13 +48,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ValidationError' + $ref: '#/components/schemas/FieldValidationError' examples: Tabel_codeQueryParamNotProvided: value: name: tabel__code - code: invalid - reason: Verplichte query parameter `tabel__code` niet mee gegeven. + code: required + reason: Dit veld is vereist. summary: tabel__code query param not provided description: '' /api/v1/tabellen: @@ -123,6 +124,23 @@ components: type: string description: De organisatie naam van de beheerder van dit tabel. maxLength: 200 + FieldValidationError: + type: object + description: Formaat van validatiefouten. + properties: + name: + type: string + description: Naam van het veld met ongeldige gegevens + code: + type: string + description: Systeemcode die het type fout aangeeft + reason: + type: string + description: Uitleg wat er precies fout is met de gegevens + required: + - code + - name + - reason Item: type: object properties: @@ -152,6 +170,9 @@ components: - naam PaginatedItemList: type: object + required: + - count + - results properties: count: type: integer @@ -172,6 +193,9 @@ components: $ref: '#/components/schemas/Item' PaginatedTabelList: type: object + required: + - count + - results properties: count: type: integer @@ -214,24 +238,3 @@ components: required: - code - naam - ValidationError: - type: object - description: |- - Validation error format, following the NL API Strategy. - - See https://docs.geostandaarden.nl/api/API-Strategie/ and - https://docs.geostandaarden.nl/api/API-Strategie-ext/#error-handling-0 - properties: - name: - type: string - description: Name of the field with invalid data - code: - type: string - description: System code of the type of error - reason: - type: string - description: Explanation of what went wrong with the data - required: - - code - - name - - reason diff --git a/src/referentielijsten/conf/base.py b/src/referentielijsten/conf/base.py index 7f7930d..440fadc 100644 --- a/src/referentielijsten/conf/base.py +++ b/src/referentielijsten/conf/base.py @@ -11,9 +11,13 @@ # Build paths inside the project, so further paths can be defined relative to # the code root. -DJANGO_PROJECT_DIR = Path(__file__).resolve().parent.parent +DJANGO_PROJECT_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.path.pardir) +) -BASE_DIR = DJANGO_PROJECT_DIR.parent.parent +BASE_DIR = os.path.abspath( + os.path.join(DJANGO_PROJECT_DIR, os.path.pardir, os.path.pardir) +) # # Core Django settings @@ -117,7 +121,6 @@ "rest_framework", "vng_api_common", # Project applications. - "referentielijsten", "referentielijsten.accounts", "referentielijsten.utils", "referentielijsten.api", @@ -148,7 +151,7 @@ TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [DJANGO_PROJECT_DIR / "templates"], + "DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")], "APP_DIRS": False, # conflicts with explicity specifying the loaders "OPTIONS": { "context_processors": [ @@ -166,7 +169,7 @@ WSGI_APPLICATION = "referentielijsten.wsgi.application" # Translations -LOCALE_PATHS = (DJANGO_PROJECT_DIR / "conf" / "locale",) +LOCALE_PATHS = (os.path.join(DJANGO_PROJECT_DIR, "conf", "locale"),) # # SERVING of static and media files @@ -174,10 +177,10 @@ STATIC_URL = "/static/" -STATIC_ROOT = BASE_DIR / "static" +STATIC_ROOT = os.path.join(BASE_DIR, "static") # Additional locations of static files -STATICFILES_DIRS = [DJANGO_PROJECT_DIR / "static"] +STATICFILES_DIRS = [os.path.join(DJANGO_PROJECT_DIR, "static")] # List of finder classes that know how to find static files in # various locations. @@ -186,7 +189,7 @@ "django.contrib.staticfiles.finders.AppDirectoriesFinder", ] -MEDIA_ROOT = BASE_DIR / "media" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_URL = "/media/" @@ -211,7 +214,7 @@ # LOG_STDOUT = config("LOG_STDOUT", default=False) -LOGGING_DIR = BASE_DIR / "log" +LOGGING_DIR = os.path.join(BASE_DIR, "log") LOGGING = { "version": 1, @@ -247,7 +250,7 @@ "django": { "level": "DEBUG", "class": "logging.handlers.RotatingFileHandler", - "filename": LOGGING_DIR / "django.log", + "filename": os.path.join(LOGGING_DIR, "django.log"), "formatter": "verbose", "maxBytes": 1024 * 1024 * 10, # 10 MB "backupCount": 10, @@ -255,7 +258,7 @@ "project": { "level": "DEBUG", "class": "logging.handlers.RotatingFileHandler", - "filename": LOGGING_DIR / "referentielijsten.log", + "filename": os.path.join(LOGGING_DIR, "referentielijsten.log"), "formatter": "verbose", "maxBytes": 1024 * 1024 * 10, # 10 MB "backupCount": 10, @@ -263,7 +266,7 @@ "performance": { "level": "INFO", "class": "logging.handlers.RotatingFileHandler", - "filename": LOGGING_DIR / "performance.log", + "filename": os.path.join(LOGGING_DIR, "performance.log"), "formatter": "performance", "maxBytes": 1024 * 1024 * 10, # 10 MB "backupCount": 10, @@ -332,7 +335,7 @@ # FIXTURES # -FIXTURE_DIRS = (DJANGO_PROJECT_DIR / "fixtures",) +FIXTURE_DIRS = (os.path.join(DJANGO_PROJECT_DIR, "fixtures"),) # # Custom settings @@ -351,7 +354,7 @@ if "GIT_SHA" in os.environ: GIT_SHA = config("GIT_SHA", "") # in docker (build) context, there is no .git directory -elif (BASE_DIR / ".git").exists(): +elif os.path.exists(os.path.join(BASE_DIR, ".git")): try: import git except ImportError: diff --git a/src/referentielijsten/conf/locale/nl/LC_MESSAGES/django.po b/src/referentielijsten/conf/locale/nl/LC_MESSAGES/django.po index f513c5c..4ebedc1 100644 --- a/src/referentielijsten/conf/locale/nl/LC_MESSAGES/django.po +++ b/src/referentielijsten/conf/locale/nl/LC_MESSAGES/django.po @@ -119,15 +119,6 @@ msgstr "overnemen" msgid "Hijack %(username)s" msgstr "Inloggen als %(username)s" -#: foobar/templates/samples/pager.html:8 foobar/templates/samples/pager.html:10 -msgid "Previous" -msgstr "Vorige" - -#: foobar/templates/samples/pager.html:16 -#: foobar/templates/samples/pager.html:18 -msgid "Next" -msgstr "Volgende" - #: foobar/utils/management/commands/clear_cache.py:10 msgid "Clear given cache only" msgstr "Leeg alleen de opgegeven cache" diff --git a/src/referentielijsten/conf/utils.py b/src/referentielijsten/conf/utils.py index 53c045f..9baebe6 100644 --- a/src/referentielijsten/conf/utils.py +++ b/src/referentielijsten/conf/utils.py @@ -37,11 +37,4 @@ def get_sentry_integrations() -> list: ] extra = [] - try: - from sentry_sdk.integrations import celery - except DidNotEnable: # happens if the celery import fails by the integration - pass - else: - extra.append(celery.CeleryIntegration()) - return [*default, *extra] diff --git a/src/referentielijsten/fixtures/default_admin_index.json b/src/referentielijsten/fixtures/default_admin_index.json new file mode 100644 index 0000000..ba03e25 --- /dev/null +++ b/src/referentielijsten/fixtures/default_admin_index.json @@ -0,0 +1,102 @@ +[ + { + "model": "admin_index.appgroup", + "fields": { + "order": 0, + "translations": { + "en": "Accounts", + "nl": "Accounts" + }, + "name": "Accounts", + "slug": "accounts", + "models": [ + [ + "accounts", + "user" + ], + [ + "auth", + "group" + ] + ] + } + }, + { + "model": "admin_index.appgroup", + "fields": { + "order": 1, + "translations": { + "en": "Referentielijsten API", + "nl": "Referencelists API" + }, + "name": "Referentielijsten API", + "slug": "referentielijsten-api", + "models": [ + [ + "api", + "tabel" + ], + [ + "api", + "item" + ] + ] + } + }, + { + "model": "admin_index.appgroup", + "fields": { + "order": 2, + "translations": { + "en": "Logging", + "nl": "Logging" + }, + "name": "Logging", + "slug": "logging", + "models": [ + [ + "axes", + "accessattempt" + ], + [ + "axes", + "accesslog" + ], + [ + "axes", + "accessfailurelog" + ] + ] + } + }, + { + "model": "admin_index.appgroup", + "fields": { + "order": 3, + "translations": { + "en": "Configuration", + "nl": "Configuratie" + }, + "name": "Configuratie", + "slug": "configuration", + "models": [ + [ + "admin_index", + "appgroup" + ], + [ + "otp_static", + "staticdevice" + ], + [ + "otp_totp", + "totpdevice" + ], + [ + "two_factor_webauthn", + "webauthndevice" + ] + ] + } + } +] diff --git a/src/referentielijsten/templates/hijack/contrib/admin/button.html b/src/referentielijsten/templates/hijack/contrib/admin/button.html deleted file mode 100644 index 81118a6..0000000 --- a/src/referentielijsten/templates/hijack/contrib/admin/button.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n hijack %} -{% if request.user|can_hijack:another_user %} - -{% endif %} diff --git a/src/referentielijsten/templates/index.html b/src/referentielijsten/templates/index.html index 54e6237..1124c5b 100644 --- a/src/referentielijsten/templates/index.html +++ b/src/referentielijsten/templates/index.html @@ -25,7 +25,7 @@

{{ settings.PROJECT_NAME }} API

- Licensed under the + Licensed under the European Union Public License (EUPL) 1.2

diff --git a/src/referentielijsten/templates/samples/menu.html b/src/referentielijsten/templates/samples/menu.html deleted file mode 100644 index 9b25670..0000000 --- a/src/referentielijsten/templates/samples/menu.html +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/src/referentielijsten/templates/samples/pager.html b/src/referentielijsten/templates/samples/pager.html deleted file mode 100644 index 1de51f6..0000000 --- a/src/referentielijsten/templates/samples/pager.html +++ /dev/null @@ -1,23 +0,0 @@ -{% load i18n querystring %} - -{% if page_obj.has_previous or page_obj.has_next %} - - - -{% endif %} - diff --git a/src/referentielijsten/templates/samples/pagination.html b/src/referentielijsten/templates/samples/pagination.html deleted file mode 100644 index f9d3bd2..0000000 --- a/src/referentielijsten/templates/samples/pagination.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "includes/pager.html" %} -{% load i18n querystring %} - -{% block pagination_class %}pagination{% endblock %} - -{% block pages %} - - {% for p in paginator.page_range %} -
  • {{ p }}
  • - {% endfor %} - -{% endblock %} diff --git a/src/referentielijsten/templates/sniplates/form.html b/src/referentielijsten/templates/sniplates/form.html deleted file mode 100644 index 757fc0f..0000000 --- a/src/referentielijsten/templates/sniplates/form.html +++ /dev/null @@ -1,102 +0,0 @@ -{% extends 'sniplates/django.html' %} -{% load sniplates staticfiles i18n %} - - -{% block _label %} -{% if label %} - -{% endif %} -{% endblock %} - - -{% block _errors %} -{% if errors %} - {% for error in errors %} - {{ error }} - {% endfor %} -{% endif %} -{% endblock %} - - -{% block input %} -{% with input_type=input_type|default:"text" %} - -{% endwith %} -{% endblock %} - - -{% block TextInput %} -{% reuse "_label" %} -{% reuse "input" %} - -{% if not no_error %} - {% reuse "_errors" %} -{% endif %} -{% endblock %} - - -{% block EmailInput %} -{% reuse "_label" %} -{% reuse "input" input_type="email" %} -{% reuse "_errors" %} -{% endblock %} - - -{% block NumberInput %} -{% reuse "_label" %} -{% reuse "input" input_type="number" raw_value=value %} -{% reuse "_errors" %} -{% endblock %} - - -{% block URLInput %} -{% reuse "_label" %} -{% reuse "input" input_type="url" %} -{% reuse "_errors" %} -{% endblock %} - - -{% block PasswordInput %} -{% reuse "_label" %} -{% reuse "input" input_type="password" value="" %} -{% reuse "_errors" %} -{% endblock %} - - -{% block Textarea %} -{% reuse "_label" %} - -{% reuse "_errors" %} -{% endblock %} - - -{% block RadioButton %} -
    - {% for val, display in choices %} -
    - - -
    - {% endfor %} -
    -{% reuse "_errors" %} -{% endblock %} diff --git a/src/referentielijsten/utils/pdf.py b/src/referentielijsten/utils/pdf.py deleted file mode 100644 index 2b577e3..0000000 --- a/src/referentielijsten/utils/pdf.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Utilities for PDF rendering from HTML using WeasyPrint. - -Note that you need to add https://pypi.org/project/weasyprint/ to your dependencies -if you want to make use of HTML-to-PDF rendering. This is not included by default as -it's quite heavy and requires OS-level dependencies. - -This module exposes the public function :func:`render_to_pdf` which renders a template -with a context into a PDF document (bytes output). You can use "external" stylesheets -in these templates, and they will be resolved through django's staticfiles machinery -by the custom :class:`UrlFetcher`. -""" - -import logging -import mimetypes -from io import BytesIO -from pathlib import PurePosixPath -from typing import Tuple -from urllib.parse import urljoin, urlparse - -from django.conf import settings -from django.contrib.staticfiles import finders -from django.contrib.staticfiles.storage import staticfiles_storage -from django.core.files.storage import FileSystemStorage, default_storage -from django.template.loader import render_to_string - -import weasyprint - -logger = logging.getLogger(__name__) - -__all__ = ["render_to_pdf"] - - -def get_base_url() -> str: - """ - Get the base URL where the project is served. - - You should tweak this after starting the project with a solution fitting - your project, as we cannot guess your set-up or where your project is hosted. - - The base URL is required to be able to download/resolve custom fonts and/or any - image URLs included in the document to render. - """ - # some hints: - # * define a setting `BASE_URL` in your settings matching the canonical domain where - # your project is deployed - # * if you only need to serve static assets (=no user-uploaded content), you can use - # a dummy URL like "https://referentielijsten.dev" - raise NotImplementedError("You must implement 'get_base_url'.") - - -class UrlFetcher: - """ - URL fetcher that skips the network for /static/* files. - """ - - def __init__(self): - self.static_url = self._get_fully_qualified_url(settings.STATIC_URL) - is_static_local_storage = issubclass( - staticfiles_storage.__class__, FileSystemStorage - ) - - self.media_url = self._get_fully_qualified_url(settings.MEDIA_URL) - is_media_local_storage = issubclass( - default_storage.__class__, FileSystemStorage - ) - - self.candidates = ( - (self.static_url, staticfiles_storage, is_static_local_storage), - (self.media_url, default_storage, is_media_local_storage), - ) - - @staticmethod - def _get_fully_qualified_url(setting: str): - fully_qualified_url = setting - if not urlparse(setting).netloc: - fully_qualified_url = urljoin(get_base_url(), setting) - return urlparse(fully_qualified_url) - - def __call__(self, url: str) -> dict: - orig_url = url - parsed_url = urlparse(url) - - candidate = self.get_match_candidate(parsed_url) - if candidate is not None: - base_url, storage = candidate - path = PurePosixPath(parsed_url.path).relative_to(base_url.path) - - absolute_path = None - if storage.exists(path): - absolute_path = storage.path(path) - elif settings.DEBUG and storage is staticfiles_storage: - # use finders so that it works in dev too, we already check that it's - # using filesystem storage earlier - absolute_path = finders.find(str(path)) - - if absolute_path is None: - logger.error("Could not resolve path '%s'", path) - return weasyprint.default_url_fetcher(orig_url) - - content_type, encoding = mimetypes.guess_type(absolute_path) - result = dict( - mime_type=content_type, - encoding=encoding, - redirected_url=orig_url, - filename=path.parts[-1], - ) - with open(absolute_path, "rb") as f: - result["file_obj"] = BytesIO(f.read()) - return result - return weasyprint.default_url_fetcher(orig_url) - - -def render_to_pdf(template_name: str, context: dict) -> Tuple[str, bytes]: - """ - Render a (HTML) template to PDF with the given context. - """ - rendered_html = render_to_string(template_name, context=context) - html_object = weasyprint.HTML( - string=rendered_html, - url_fetcher=UrlFetcher(), - base_url=get_base_url(), - ) - pdf: bytes = html_object.write_pdf() - return rendered_html, pdf diff --git a/src/referentielijsten/utils/tests/test_celery_beat.py b/src/referentielijsten/utils/tests/test_celery_beat.py deleted file mode 100644 index a83e04e..0000000 --- a/src/referentielijsten/utils/tests/test_celery_beat.py +++ /dev/null @@ -1,25 +0,0 @@ -from unittest import skipIf - -from django.conf import settings -from django.test import SimpleTestCase -from django.utils.module_loading import import_string - - -class BeatConfigTests(SimpleTestCase): - @skipIf( - not hasattr(settings, "CELERY_BEAT_SCHEDULE"), - "Project does not have celery (beat config)", - ) - def test_task_references_correct(self): - """ - Assert that the task import paths in the Beat config are valid. - """ - for entry in settings.CELERY_BEAT_SCHEDULE.values(): - task = entry["task"] - with self.subTest(task=task): - try: - import_string(task) - except ImportError: - self.fail( - f"Could not import task '{task}' in settings.CELERY_BEAT_SCHEDULE" - )