diff --git a/aleksis/core/data_checks.py b/aleksis/core/data_checks.py index 1279822f8d1f28bb40e040d9eaf0bb8126540940..f7152686f61fb63997eb9e0a6af9e23b47237f66 100644 --- a/aleksis/core/data_checks.py +++ b/aleksis/core/data_checks.py @@ -9,7 +9,8 @@ import reversion from reversion import set_comment from templated_email import send_templated_mail -from .util.core_helpers import celery_optional, get_site_preferences +from .celery import app +from .util.core_helpers import get_site_preferences class SolveOption: @@ -207,7 +208,7 @@ class DataCheckRegistry: return [(check.name, check.verbose_name) for check in cls.data_checks] -@celery_optional +@app.task def check_data(): """Execute all registered data checks and send email if activated.""" for check in DataCheckRegistry.data_checks: diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index ec033ede3403f8b0477fb89d8194e881db47123a..cad7ea6b2c0acc8707d0922c67ddc16ed94b3c46 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -81,6 +81,12 @@ INSTALLED_APPS = [ "polymorphic", "django_global_request", "dbbackup", + "django_celery_beat", + "django_celery_results", + "celery_progress", + "health_check.contrib.celery", + "djcelery_email", + "celery_haystack", "settings_context_processor", "sass_processor", "django_any_js", @@ -190,7 +196,6 @@ DATABASES = { "PASSWORD": _settings.get("database.password", None), "HOST": _settings.get("database.host", "127.0.0.1"), "PORT": _settings.get("database.port", "5432"), - "ATOMIC_REQUESTS": True, "CONN_MAX_AGE": _settings.get("database.conn_max_age", None), } } @@ -484,21 +489,13 @@ if _settings.get("twilio.sid", None): TWILIO_TOKEN = _settings.get("twilio.token") TWILIO_CALLER_ID = _settings.get("twilio.callerid") -if _settings.get("celery.enabled", False): - INSTALLED_APPS += ( - "django_celery_beat", - "django_celery_results", - "celery_progress", - "health_check.contrib.celery", - ) - CELERY_BROKER_URL = _settings.get("celery.broker", "redis://localhost") - CELERY_RESULT_BACKEND = "django-db" - CELERY_CACHE_BACKEND = "django-cache" - CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" +CELERY_BROKER_URL = _settings.get("celery.broker", "redis://localhost") +CELERY_RESULT_BACKEND = "django-db" +CELERY_CACHE_BACKEND = "django-cache" +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" - if _settings.get("celery.email", False): - INSTALLED_APPS += ("djcelery_email",) - EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" +if _settings.get("celery.email", False): + EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" PWA_APP_NAME = lazy_preference("general", "title") PWA_APP_DESCRIPTION = lazy_preference("general", "description") @@ -721,12 +718,8 @@ elif HAYSTACK_BACKEND_SHORT == "whoosh": }, } -if _settings.get("celery.enabled", False) and _settings.get("search.celery", True): - INSTALLED_APPS.append("celery_haystack") - HAYSTACK_SIGNAL_PROCESSOR = "celery_haystack.signals.CelerySignalProcessor" - CELERY_HAYSTACK_IGNORE_RESULT = True -else: - HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor" +HAYSTACK_SIGNAL_PROCESSOR = "celery_haystack.signals.CelerySignalProcessor" +CELERY_HAYSTACK_IGNORE_RESULT = True HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10 diff --git a/aleksis/core/tasks.py b/aleksis/core/tasks.py index d085f027fad09e875bf612d8352d83151e77d880..731c4356c20bba915bd5ad0811307c3877810631 100644 --- a/aleksis/core/tasks.py +++ b/aleksis/core/tasks.py @@ -1,11 +1,11 @@ from django.conf import settings from django.core import management -from .util.core_helpers import celery_optional +from .celery import app from .util.notifications import send_notification as _send_notification -@celery_optional +@app.task def send_notification(notification: int, resend: bool = False) -> None: """Send a notification object to its recipient. @@ -15,7 +15,7 @@ def send_notification(notification: int, resend: bool = False) -> None: _send_notification(notification, resend) -@celery_optional +@app.task def backup_data() -> None: """Backup database and media using django-dbbackup.""" # Assemble command-line options for dbbackup management command diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index eb5c6c41ac26173ebe81df366b19acf80d6ed234..8183fba0d6da1ea956eb1da94b720b49e02363ac 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -15,7 +15,6 @@ from rules.contrib.views import permission_required from two_factor.urls import urlpatterns as tf_urls from . import views -from .util.core_helpers import is_celery_enabled urlpatterns = [ path("", include("django_prometheus.urls")), @@ -25,6 +24,7 @@ urlpatterns = [ path("data_management/", views.data_management, name="data_management"), path("status/", views.SystemStatus.as_view(), name="system_status"), path("", include(tf_urls)), + path("celery_progress/", include("celery_progress.urls")), path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"), path("school_terms/", views.SchoolTermListView.as_view(), name="school_terms"), path("school_terms/create/", views.SchoolTermCreateView.as_view(), name="create_school_term"), @@ -211,9 +211,6 @@ if hasattr(settings, "TWILIO_ACCOUNT_SID"): urlpatterns += [path("", include(tf_twilio_urls))] -if is_celery_enabled(): - urlpatterns.append(path("celery_progress/", include("celery_progress.urls"))) - # Serve javascript-common if in development if settings.DEBUG: urlpatterns.append(path("__debug__/", include(debug_toolbar.urls))) diff --git a/aleksis/core/util/celery_progress.py b/aleksis/core/util/celery_progress.py index 9de760eeb56b8ce535a0cd8e88f7380c5411f78d..e957dd4f938291b11a85d00e115d302f512e609a 100644 --- a/aleksis/core/util/celery_progress.py +++ b/aleksis/core/util/celery_progress.py @@ -1,34 +1,162 @@ -from decimal import Decimal -from typing import Union +from functools import wraps +from numbers import Number +from typing import Callable, Generator, Iterable, Optional, Sequence, Union + +from django.contrib import messages from celery_progress.backend import PROGRESS_STATE, AbstractProgressRecorder +from ..celery import app + class ProgressRecorder(AbstractProgressRecorder): + """Track the progress of a Celery task and give data to the frontend. + + This recorder provides the functions `set_progress` and `add_message` + which can be used to track the status of a Celery task. + + How to use + ---------- + 1. Write a function and include tracking methods + + :: + + from django.contrib import messages + + from aleksis.core.util.celery_progress import recorded_task + + @recorded_task + def do_something(foo, bar, recorder, baz=None): + # ... + recorder.set_progress(total=len(list_with_data)) + + for i, item in enumerate(list_with_data): + # ... + recorder.set_progress(i+1) + # ... + + recorder.add_message(messages.SUCCESS, "All data were imported successfully.") + + You can also use `recorder.iterate` to simplify iterating and counting. + + 2. Track progress in view: + + :: + + def my_view(request): + context = {} + # ... + result = do_something.delay(foo, bar, baz=baz) + + context = { + "title": _("Progress: Import data"), + "back_url": reverse("index"), + "progress": { + "task_id": result.task_id, + "title": _("Import objects …"), + "success": _("The import was done successfully."), + "error": _("There was a problem while importing data."), + }, + } + + # Render progress view + return render(request, "core/progress.html", context) + """ + def __init__(self, task): self.task = task - self.messages = [] - self.total = 100 - self.current = 0 + self._messages = [] + self._current = 0 + self._total = 100 - def set_progress(self, current: Union[int, float], **kwargs): - self.current = current + def iterate(self, data: Union[Iterable, Sequence], total: Optional[int] = None) -> Generator: + """Iterate over a sequence or iterable, updating progress on the move. + + :: + + @recorded_task + def do_something(long_list, recorder): + for item in recorder.iterate(long_list): + do_something_with(item) + + :param data: A sequence (tuple, list, set,...) or an iterable + :param total: Total number of items, in case data does not support len() + """ + if total is None and hasattr(data, "__len__"): + total = len(data) + else: + raise TypeError("No total value passed, and data does not support len()") + + for current, item in enumerate(data): + self.set_progress(current, total) + yield item + + def set_progress( + self, + current: Optional[Number] = None, + total: Optional[Number] = None, + description: Optional[str] = None, + level: int = messages.INFO, + ): + """Set the current progress in the frontend. + + The progress percentage is automatically calculated in relation to self.total. + + :param current: The number of processed items; relative to total, default unchanged + :param total: The total number of items (or 100 if using a percentage), default unchanged + :param description: A textual description, routed to the frontend as an INFO message + """ + if current is not None: + self._current = current + if total is not None: + self._total = total percent = 0 - if self.total > 0: - percent = (Decimal(current) / Decimal(self.total)) * Decimal(100) - percent = float(round(percent, 2)) + if self._total > 0: + percent = self._current / self._total * 100 + + if description is not None: + self._messages.append((level, description)) self.task.update_state( state=PROGRESS_STATE, meta={ - "current": current, - "total": self.total, + "current": self._current, + "total": self._total, "percent": percent, - "messages": self.messages, + "messages": self._messages, }, ) - def add_message(self, level: int, message: str, **kwargs): - self.messages.append((level, message)) - self.set_progress(self.current) + def add_message(self, level: int, message: str) -> None: + """Show a message in the progress frontend. + + This method is a shortcut for set_progress with no new progress arguments, + passing only the message and level as description. + + :param level: The message level (default levels from django.contrib.messages) + :param message: The actual message (should be translated) + """ + self.set_progress(description=message, level=level) + + +def recorded_task(orig: Optional[Callable] = None, **kwargs) -> Union[Callable, app.Task]: + """Create a Celery task that receives a ProgressRecorder. + + Returns a Task object with a wrapper that passes the recorder instance + as the recorder keyword argument. + """ + + def _real_decorator(orig: Callable) -> app.Task: + @wraps(orig) + def _inject_recorder(task, *args, **kwargs): + recorder = ProgressRecorder(task) + return orig(*args, **kwargs, recorder=recorder) + + # Force bind to True because _inject_recorder needs the Task object + kwargs["bind"] = True + return app.task(_inject_recorder, **kwargs) + + if orig and not kwargs: + return _real_decorator(orig) + return _real_decorator diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 9e9a90faf5547b71f3d556d89d6f2459b743c04b..676894d28d62f32ee9c08de874edf6857f94a629 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -1,6 +1,5 @@ import os import sys -import time from datetime import datetime, timedelta from importlib import import_module from itertools import groupby @@ -14,7 +13,6 @@ else: import importlib_metadata as metadata from django.conf import settings -from django.db import transaction from django.db.models import Model, QuerySet from django.http import HttpRequest from django.shortcuts import get_object_or_404 @@ -22,9 +20,6 @@ from django.utils import timezone from django.utils.functional import lazy from cache_memoize import cache_memoize -from django_global_request.middleware import get_request - -from aleksis.core.util import messages def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "–") -> str: @@ -188,140 +183,6 @@ def has_person(obj: Union[HttpRequest, Model]) -> bool: return True -def is_celery_enabled(): - """Check whether celery support is enabled.""" - return hasattr(settings, "CELERY_RESULT_BACKEND") - - -def celery_optional(orig: Callable) -> Callable: - """Add a decorator that makes Celery optional for a function. - - If Celery is configured and available, it wraps the function in a Task - and calls its delay method when invoked; if not, it leaves it untouched - and it is executed synchronously. - - The wrapped function returns a tuple with either - the return value of the task's delay method and False - if the method has been executed asynchronously - or the return value of the executed method and True - if the method has been executed synchronously. - """ - if is_celery_enabled(): - from ..celery import app # noqa - - task = app.task(orig) - - def wrapped(*args, **kwargs): - if is_celery_enabled(): - return transaction.on_commit(lambda: task.delay(*args, **kwargs)), False - else: - return orig(*args, **kwargs), True - - return wrapped - - -class DummyRecorder: - def set_progress(self, *args, **kwargs): - pass - - def add_message(self, level: int, message: str, **kwargs) -> Optional[Any]: - request = get_request() - return messages.add_message(request, level, message, **kwargs) - - -def celery_optional_progress(orig: Callable) -> Callable: - """Add a decorator that makes Celery with progress bar support optional for a function. - - If Celery is configured and available, it wraps the function in a Task - and calls its delay method when invoked; if not, it leaves it untouched - and it is executed synchronously. - - Additionally, it adds a recorder class as first argument - (`ProgressRecorder` if Celery is enabled, else `DummyRecoder`). - - This recorder provides the functions `set_progress` and `add_message` - which can be used to track the status of the task. - For further information, see the respective recorder classes. - - How to use - ---------- - 1. Write a function and include tracking methods - - :: - - from django.contrib import messages - - from aleksis.core.util.core_helpers import celery_optional_progress - - @celery_optional_progress - def do_something(recorder: Union[ProgressRecorder, DummyRecorder], foo, bar, baz=None): - # ... - recorder.total = len(list_with_data) - - for i, item in list_with_data: - # ... - recorder.set_progress(i + 1) - # ... - - recorder.add_message(messages.SUCCESS, "All data were imported successfully.") - - 2. Track process in view: - - :: - - def my_view(request): - context = {} - # ... - result = do_something(foo, bar, baz=baz) - - if result: - context = { - "title": _("Progress: Import data"), - "back_url": reverse("index"), - "progress": { - "task_id": result.task_id, - "title": _("Import objects …"), - "success": _("The import was done successfully."), - "error": _("There was a problem while importing data."), - }, - } - - # Render progress view - return render(request, "core/progress.html", context) - - # Render other view if Celery isn't enabled - return render(request, "my-app/other-view.html", context) - """ - - def recorder_func(self, *args, **kwargs): - if is_celery_enabled(): - from .celery_progress import ProgressRecorder # noqa - - recorder = ProgressRecorder(self) - else: - recorder = DummyRecorder() - orig(recorder, *args, **kwargs) - - # Needed to ensure that all messages are displayed by frontend - time.sleep(0.7) - - var_name = f"{orig.__module__}.{orig.__name__}" - - if is_celery_enabled(): - from ..celery import app # noqa - - task = app.task(recorder_func, bind=True, name=var_name) - - def wrapped(*args, **kwargs): - if is_celery_enabled(): - return task.delay(*args, **kwargs) - else: - recorder_func(None, *args, **kwargs) - return None - - return wrapped - - def path_and_rename(instance, filename: str, upload_to: str = "files") -> str: """Update path of an uploaded file and renames it to a random UUID in Django FileField.""" _, ext = os.path.splitext(filename) diff --git a/aleksis/core/util/search.py b/aleksis/core/util/search.py index 5c8af9f860df94649d46f1fc0dd0741e35c6ad07..34c736f9e13b55d6ac277d41b76a374efa2b43d1 100644 --- a/aleksis/core/util/search.py +++ b/aleksis/core/util/search.py @@ -1,15 +1,9 @@ -from django.conf import settings - +from celery_haystack.indexes import CelerySearchIndex as BaseSearchIndex from haystack import indexes # Not used here, but simplifies imports for apps Indexable = indexes.Indexable # noqa -if settings.HAYSTACK_SIGNAL_PROCESSOR == "celery_haystack.signals.CelerySignalProcessor": - from celery_haystack.indexes import CelerySearchIndex as BaseSearchIndex -else: - from haystack.indexes import SearchIndex as BaseSearchIndex - class SearchIndex(BaseSearchIndex): """Base class for search indexes on AlekSIS models. diff --git a/aleksis/core/views.py b/aleksis/core/views.py index d110b30f63c8f6748198c3fc8268e81bb0caab9b..f1bc04013f92ed2bbde449c47cf44473be70ece0 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -1,7 +1,6 @@ from typing import Any, Dict, Optional, Type from django.apps import apps -from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator @@ -18,6 +17,7 @@ from django.views.generic.detail import DetailView from django.views.generic.list import ListView import reversion +from django_celery_results.models import TaskResult from django_tables2 import RequestConfig, SingleTableView from dynamic_preferences.forms import preference_form_builder from guardian.shortcuts import get_objects_for_user @@ -31,6 +31,7 @@ from rules.contrib.views import PermissionRequiredMixin, permission_required from aleksis.core.data_checks import DataCheckRegistry, check_data +from .celery import app from .filters import GroupFilter, PersonFilter from .forms import ( AnnouncementForm, @@ -411,17 +412,12 @@ class SystemStatus(PermissionRequiredMixin, MainView): status_code = 500 if self.errors else 200 task_results = [] - if "django_celery_results" in settings.INSTALLED_APPS: - from django_celery_results.models import TaskResult # noqa - - from .celery import app # noqa - - if app.control.inspect().registered_tasks(): - job_list = list(app.control.inspect().registered_tasks().values())[0] - for job in job_list: - task_results.append( - TaskResult.objects.filter(task_name=job).order_by("date_done").last() - ) + if app.control.inspect().registered_tasks(): + job_list = list(app.control.inspect().registered_tasks().values())[0] + for job in job_list: + task_results.append( + TaskResult.objects.filter(task_name=job).order_by("date_done").last() + ) context = {"plugins": self.plugins, "status_code": status_code, "tasks": task_results} return self.render_to_response(context, status=status_code) diff --git a/poetry.lock b/poetry.lock index 06443174db81ce59e51c3af70f2c4b9e58647671..697d09b19fd5db3dc309338bff5a534491407b85 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,7 +49,7 @@ name = "amqp" version = "5.0.5" description = "Low-level AMQP client for Python (fork of amqplib)." category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -158,7 +158,7 @@ name = "billiard" version = "3.6.3.0" description = "Python multiprocessing fork with improvements and bugfixes" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -229,7 +229,7 @@ name = "celery" version = "5.0.5" description = "Distributed Task Queue." category = "main" -optional = true +optional = false python-versions = ">=3.6," [package.dependencies] @@ -283,7 +283,7 @@ name = "celery-haystack-ng" version = "0.20.post2" description = "An app for integrating Celery with Haystack" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -333,7 +333,7 @@ name = "click-didyoumean" version = "0.0.3" description = "Enable git-like did-you-mean feature in click." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -344,7 +344,7 @@ name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -358,7 +358,7 @@ name = "click-repl" version = "0.1.6" description = "REPL plugin for Click" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -464,14 +464,14 @@ django = "*" [[package]] name = "django-auth-ldap" -version = "2.2.0" +version = "2.3.0" description = "Django LDAP authentication backend." category = "main" optional = true -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -Django = ">=1.11" +Django = ">=2.2" python-ldap = ">=3.1" [[package]] @@ -524,7 +524,7 @@ name = "django-celery-beat" version = "2.2.0" description = "Database-backed Periodic Tasks." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -538,7 +538,7 @@ name = "django-celery-email" version = "3.0.0" description = "An async Django email backend using celery" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -551,7 +551,7 @@ name = "django-celery-results" version = "2.0.1" description = "Celery result backends for Django." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -761,7 +761,7 @@ python-versions = "*" [[package]] name = "django-material" -version = "1.7.4" +version = "1.7.5" description = "Material design for django forms and admin" category = "main" optional = false @@ -977,7 +977,7 @@ name = "django-timezone-field" version = "4.1.1" description = "A Django app providing database and form fields for pytz timezone objects." category = "main" -optional = true +optional = false python-versions = ">=3.5" [package.dependencies] @@ -1383,7 +1383,7 @@ name = "kombu" version = "5.0.2" description = "Messaging library for Python." category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -1688,7 +1688,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.7.4" +version = "2.8.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -1797,7 +1797,7 @@ name = "python-crontab" version = "2.5.1" description = "Python Crontab API" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -1880,7 +1880,7 @@ name = "redis" version = "3.5.3" description = "Python client for Redis key-value store" category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] @@ -2029,7 +2029,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.4.3" +version = "3.5.1" description = "Python documentation generator" category = "dev" optional = false @@ -2055,7 +2055,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.790)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -2309,7 +2309,7 @@ name = "vine" version = "5.0.0" description = "Promises, promises, promises." category = "main" -optional = true +optional = false python-versions = ">=3.6" [[package]] @@ -2352,13 +2352,12 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] -celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack-ng"] ldap = ["django-auth-ldap"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "fff497acb1f86363cd099c56eaf7fa063da88090b5d211d423e3b13c1e016a16" +content-hash = "70b2b90f0d23ad6037d01bcadd9002a442a8e9e16bc1d09f148c9e74a30f3a4c" [metadata.files] alabaster = [ @@ -2549,8 +2548,8 @@ django-appconf = [ {file = "django_appconf-1.0.4-py2.py3-none-any.whl", hash = "sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06"}, ] django-auth-ldap = [ - {file = "django-auth-ldap-2.2.0.tar.gz", hash = "sha256:11af1773b08613339d2c3a0cec1308a4d563518f17b1719c3759994d0b4d04bf"}, - {file = "django_auth_ldap-2.2.0-py3-none-any.whl", hash = "sha256:0ed2d88d81c39be915a9ab53b97ec0a33a3d16055518ab4c9bcffe8236d40370"}, + {file = "django-auth-ldap-2.3.0.tar.gz", hash = "sha256:5894317122a086c9955ed366562869a81459cf6b663636b152857bb5d3a0a3b7"}, + {file = "django_auth_ldap-2.3.0-py3-none-any.whl", hash = "sha256:cbbb476eff2504b5ab4fdf1fa92d93d2d3408fd9c8bc0c426169d987d0733153"}, ] django-bleach = [ {file = "django-bleach-0.6.1.tar.gz", hash = "sha256:674709c26040618aff0741ce8261fd151e5ead405bd50568c2034662d69daac3"}, @@ -2654,8 +2653,8 @@ django-maintenance-mode = [ {file = "django_maintenance_mode-0.15.1-py3-none-any.whl", hash = "sha256:8c45b400253076655562c99a2ffb88f8353fc1c84496c1b9de812cc8132aea6f"}, ] django-material = [ - {file = "django-material-1.7.4.tar.gz", hash = "sha256:93af86e740b6db15a3b9df913c343217b198d7342a083db694acb319b49cb2dd"}, - {file = "django_material-1.7.4-py2.py3-none-any.whl", hash = "sha256:70dcaa34b35dbc31fbdb7454c7a376358586d0f166abe15870e07e468d729425"}, + {file = "django-material-1.7.5.tar.gz", hash = "sha256:d0df25b1d3ff629a4dfe2bc869550b25289f556940b45fd6d7c4897859446491"}, + {file = "django_material-1.7.5-py2.py3-none-any.whl", hash = "sha256:141bdd1b3ded91be8c77f6de687523d63df986a559ec3eb82cd33f4af7d5983b"}, ] django-menu-generator-ng = [ {file = "django-menu-generator-ng-1.2.1.tar.gz", hash = "sha256:06097f6611913a0770d633b6fc02cc83af1d427cc42a4048ceefe5f3a0f9d3ab"}, @@ -2970,25 +2969,21 @@ pillow = [ {file = "Pillow-8.1.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a"}, {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2"}, {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174"}, - {file = "Pillow-8.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded"}, {file = "Pillow-8.1.0-cp36-cp36m-win32.whl", hash = "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d"}, {file = "Pillow-8.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d"}, {file = "Pillow-8.1.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234"}, {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8"}, {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17"}, - {file = "Pillow-8.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7"}, {file = "Pillow-8.1.0-cp37-cp37m-win32.whl", hash = "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e"}, {file = "Pillow-8.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b"}, {file = "Pillow-8.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0"}, {file = "Pillow-8.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a"}, {file = "Pillow-8.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d"}, - {file = "Pillow-8.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"}, {file = "Pillow-8.1.0-cp38-cp38-win32.whl", hash = "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59"}, {file = "Pillow-8.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c"}, {file = "Pillow-8.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6"}, {file = "Pillow-8.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378"}, {file = "Pillow-8.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7"}, - {file = "Pillow-8.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0"}, {file = "Pillow-8.1.0-cp39-cp39-win32.whl", hash = "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b"}, {file = "Pillow-8.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865"}, {file = "Pillow-8.1.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9"}, @@ -3055,8 +3050,6 @@ psycopg2 = [ {file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"}, {file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"}, {file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"}, - {file = "psycopg2-2.8.6-cp39-cp39-win32.whl", hash = "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3"}, - {file = "psycopg2-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7"}, {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, ] ptyprocess = [ @@ -3142,8 +3135,8 @@ pyflakes = [ {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ - {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"}, - {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"}, + {file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"}, + {file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"}, ] pyjwt = [ {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, @@ -3283,29 +3276,22 @@ restructuredtext-lint = [ {file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"}, - {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"}, - {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"}, - {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"}, - {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"}, {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"}, {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win32.whl", hash = "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb"}, {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"}, ] rules = [ @@ -3344,8 +3330,8 @@ spdx-license-list = [ {file = "spdx_license_list-0.5.2.tar.gz", hash = "sha256:952996f72ab807972dc2278bb9b91e5294767211e51f09aad9c0e2ff5b82a31b"}, ] sphinx = [ - {file = "Sphinx-3.4.3-py3-none-any.whl", hash = "sha256:c314c857e7cd47c856d2c5adff514ac2e6495f8b8e0f886a8a37e9305dfea0d8"}, - {file = "Sphinx-3.4.3.tar.gz", hash = "sha256:41cad293f954f7d37f803d97eb184158cfd90f51195131e94875bc07cd08b93c"}, + {file = "Sphinx-3.5.1-py3-none-any.whl", hash = "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"}, + {file = "Sphinx-3.5.1.tar.gz", hash = "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9"}, ] sphinx-autodoc-typehints = [ {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"}, diff --git a/pyproject.toml b/pyproject.toml index 37a8497170dc423a5040533f23cfed740ecbd036..cc3e3fe7c6b0b84438e3870728a38c4d15c2ec6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,10 +68,10 @@ html2text = "^2020.0.0" django-ckeditor = "^6.0.0" django-js-reverse = "^0.9.1" calendarweek = "^0.4.3" -Celery = {version="^5.0.0", optional=true, extras=["django", "redis"]} -django-celery-results = {version="^2.0.0", optional=true} -django-celery-beat = {version="^2.0.0", optional=true} -django-celery-email = {version="^3.0.0", optional=true} +Celery = {version="^5.0.0", extras=["django", "redis"]} +django-celery-results = "^2.0.1" +django-celery-beat = "^2.2.0" +django-celery-email = "^3.0.0" django-jsonstore = "^0.5.0" django-polymorphic = "^3.0.0" django-colorfield = "^0.3.0" @@ -80,7 +80,7 @@ django-guardian = "^2.2.0" rules = "^2.2" django-cache-memoize = "^0.1.6" django-haystack = {version="3.0b1", allow-prereleases = true} -celery-haystack-ng = {version = "^0.20", optional = true} +celery-haystack-ng = "^0.20" django-dbbackup = "^3.3.0" spdx-license-list = "^0.5.0" license-expression = "^1.2" @@ -99,7 +99,6 @@ ipython = "^7.20.0" [tool.poetry.extras] ldap = ["django-auth-ldap"] -celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack-ng"] [tool.poetry.dev-dependencies] aleksis-builddeps = "*"