diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b2516b9ec034623bc679680e228e6b96bbc0ef91..50274a4c79f664d404541cb861925112610b871a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,6 +10,10 @@ include: - project: "AlekSIS/official/AlekSIS" file: /ci/build/dist.yml - project: "AlekSIS/official/AlekSIS" - file: /ci/deploy/pages.yml + file: /ci/publish/pypi.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/docker/image.yml - project: "AlekSIS/official/AlekSIS" - file: /ci/deploy/pypi.yml + file: "/ci/deploy/trigger_dist.yml" + - project: "AlekSIS/official/AlekSIS" + file: /ci/deploy/pages.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..72447ae0b2efa114b3073f0838a817ccd769912e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +FROM python:3.9-buster AS core + +# Build arguments +ARG EXTRAS="ldap" +ARG APP_VERSION="" + +# Configure Python to be nice inside Docker and pip to stfu +ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PIP_DEFAULT_TIMEOUT 100 +ENV PIP_DISABLE_PIP_VERSION_CHECK 1 +ENV PIP_NO_CACHE_DIR 1 +ENV PIP_EXTRA_INDEX_URL https://edugit.org/api/v4/projects/461/packages/pypi/simple +ENV PIP_USE_DEPRECATED legacy-resolver + +# Configure app settings for build and runtime +ENV ALEKSIS_static__root /usr/share/aleksis/static +ENV ALEKSIS_media__root /var/lib/aleksis/media +ENV ALEKSIS_backup__location /var/lib/aleksis/backups +ENV ALEKSIS_dev__uwsgi__celery false + +# Install necessary Debian and PyPI packages for build and runtime +RUN apt-get -y update && \ + apt-get -y install eatmydata && \ + eatmydata apt-get -y upgrade && \ + eatmydata apt-get install -y --no-install-recommends \ + build-essential \ + dumb-init \ + gettext \ + libpq5 \ + libpq-dev \ + libssl-dev \ + netcat-openbsd \ + yarnpkg && \ + eatmydata pip install uwsgi django-compressor + +# Install extra dependencies +RUN case ",$EXTRAS," in \ + (*",ldap,"*) \ + eatmydata apt-get install -y --no-install-recommends \ + libldap2-dev \ + libsasl2-dev \ + ldap-utils \ + ;; \ + esac + +# Install core +RUN set -e; \ + mkdir -p /var/lib/aleksis/media /usr/share/aleksis/static /var/lib/aleksis/backups; \ + eatmydata pip install AlekSIS-Core\[$EXTRAS\]$APP_VERSION + +# Declare a persistent volume for all data +VOLUME /var/lib/aleksis + +# Define entrypoint and uWSGI running on port 8000 +EXPOSE 8000 +COPY docker-startup.sh /usr/local/bin/aleksis-docker-startup +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["/usr/local/bin/aleksis-docker-startup"] + +# Install assets +FROM core as assets +RUN eatmydata aleksis-admin yarn install + +# Clean up build dependencies +FROM assets AS clean +RUN set -e; \ + eatmydata apt-get remove --purge -y \ + build-essential \ + gettext \ + libpq-dev \ + libssl-dev \ + libldap2-dev \ + libsasl2-dev \ + yarnpkg; \ + eatmydata apt-get autoremove --purge -y; \ + apt-get clean -y; \ + rm -f /var/lib/apt/lists/*_*; \ + rm -rf /root/.cache diff --git a/README.rst b/README.rst index 664d44feccd825af419d30650fa31a27e97234c2..067f83e32bfedf6251c91b5d5e42162c1db97b9e 100644 --- a/README.rst +++ b/README.rst @@ -36,6 +36,7 @@ Licence :: + Copyright © 2021 magicfelix <felix@felix-zauberer.de> Copyright © 2017, 2018, 2019, 2020 Jonathan Weth <wethjo@katharineum.de> Copyright © 2017, 2018, 2019 Frank Poetzsch-Heffter <p-h@katharineum.de> Copyright © 2018, 2019, 2020 Julian Leucker <leuckeju@katharineum.de> diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index 42edc4bceaa61a55de310f59cea74dc270a3cf0b..a3b9b7a9a3957b9af7ad0b9ddeb57d00a630ca73 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -28,6 +28,7 @@ class CoreConfig(AppConfig): } licence = "EUPL-1.2+" copyright_info = ( + ([2021], "magicfelix", "felix@felix-zauberer.de"), ([2017, 2018, 2019, 2020], "Jonathan Weth", "wethjo@katharineum.de"), ([2017, 2018, 2019], "Frank Poetzsch-Heffter", "p-h@katharineum.de"), ([2018, 2019, 2020], "Julian Leucker", "leuckeju@katharineum.de"), diff --git a/aleksis/core/data_checks.py b/aleksis/core/data_checks.py index dba02b190f6a69c66a8f134b6df5823dcc4800c0..7804698ea811d47a830b184c3a099490f80f5ff6 100644 --- a/aleksis/core/data_checks.py +++ b/aleksis/core/data_checks.py @@ -9,7 +9,7 @@ import reversion from reversion import set_comment from templated_email import send_templated_mail -from .celery import app +from .util.celery_progress import ProgressRecorder, recorded_task from .util.core_helpers import get_site_preferences @@ -208,10 +208,10 @@ class DataCheckRegistry: return [(check.name, check.verbose_name) for check in cls.data_checks] -@app.task -def check_data(): +@recorded_task +def check_data(recorder: ProgressRecorder): """Execute all registered data checks and send email if activated.""" - for check in DataCheckRegistry.data_checks: + for check in recorder.iterate(DataCheckRegistry.data_checks): logging.info(f"Run check: {check.verbose_name}") check.check_data() diff --git a/aleksis/core/migrations/0010_external_link_widget.py b/aleksis/core/migrations/0010_external_link_widget.py new file mode 100644 index 0000000000000000000000000000000000000000..7605e0743d925d0b8b0c655464be86cc8fa70ce2 --- /dev/null +++ b/aleksis/core/migrations/0010_external_link_widget.py @@ -0,0 +1,28 @@ +# Generated by Django 3.1.7 on 2021-03-03 20:04 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_default_dashboard'), + ] + + operations = [ + migrations.CreateModel( + name='ExternalLinkWidget', + fields=[ + ('dashboardwidget_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.dashboardwidget')), + ('url', models.URLField(verbose_name='URL')), + ('icon_url', models.URLField(verbose_name='Icon URL')), + ], + options={ + 'verbose_name': 'External link widget', + 'verbose_name_plural': 'External link widgets', + }, + bases=('core.dashboardwidget',), + ), + ] diff --git a/aleksis/core/models.py b/aleksis/core/models.py index de5c89b3565218d9ad9ffea2ea13e5d9a62538e2..16de589dc8b16d8b18486f91ccfadb560423e61d 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -3,6 +3,7 @@ from datetime import date, datetime from typing import Iterable, List, Optional, Sequence, Union +from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import Group as DjangoGroup from django.contrib.contenttypes.fields import GenericForeignKey @@ -289,9 +290,7 @@ class Person(ExtensibleModel): # Ensure we have an admin user user = get_user_model() if not user.objects.filter(is_superuser=True).exists(): - admin = user.objects.create_superuser( - username="admin", email="root@example.com", password="admin" - ) + admin = user.objects.create_superuser(**settings.AUTH_INITIAL_SUPERUSER) admin.save() def auto_select_primary_group( @@ -814,6 +813,20 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel): verbose_name_plural = _("Dashboard Widgets") +class ExternalLinkWidget(DashboardWidget): + template = "core/dashboard_widget/external_link_widget.html" + + url = models.URLField(verbose_name=_("URL")) + icon_url = models.URLField(verbose_name=_("Icon URL")) + + def get_context(self, request): + return {"title": self.title, "url": self.url, "icon_url": self.icon_url} + + class Meta: + verbose_name = _("External link widget") + verbose_name_plural = _("External link widgets") + + class DashboardWidgetOrder(ExtensibleModel): widget = models.ForeignKey( DashboardWidget, on_delete=models.CASCADE, verbose_name=_("Dashboard widget") diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py index ad90929ef8f4332daf50ef3485b47383f79a9dea..3df1a91a21c79f40ab0894d3bbc14a97da0b90fc 100644 --- a/aleksis/core/preferences.py +++ b/aleksis/core/preferences.py @@ -252,3 +252,21 @@ class DataChecksEmailsRecipientGroups(ModelMultipleChoicePreference): default = [] model = Group verbose_name = _("Email recipient groups for data checks problem emails") + + +@site_preferences_registry.register +class AnonymousDashboard(BooleanPreference): + section = general + name = "anonymous_dashboard" + default = False + required = False + verbose_name = _("Show dashboard to users without login") + + +@site_preferences_registry.register +class DashboardEditing(BooleanPreference): + section = general + name = "dashboard_editing" + default = True + required = False + verbose_name = _("Allow users to edit their dashboard") diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index 5e39c1af4d38f15985c4ac13e6348a3130702391..c5902d2a21e86691b12e81f87c060836dcd1a0fc 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -9,12 +9,14 @@ from .util.predicates import ( is_current_person, is_group_owner, is_notification_recipient, + is_site_preference_set, ) rules.add_perm("core", rules.always_allow) # View dashboard -rules.add_perm("core.view_dashboard", has_person) +view_dashboard_predicate = is_site_preference_set("general", "anonymous_dashboard") | has_person +rules.add_perm("core.view_dashboard", view_dashboard_predicate) # View notifications rules.add_perm("core.view_notifications", has_person) @@ -310,6 +312,9 @@ rules.add_perm("core.edit_dashboardwidget", edit_dashboard_widget_predicate) delete_dashboard_widget_predicate = has_person & has_global_perm("core.delete_dashboardwidget") rules.add_perm("core.delete_dashboardwidget", delete_dashboard_widget_predicate) +edit_dashboard_predicate = is_site_preference_set("general", "dashboard_editing") & has_person +rules.add_perm("core.edit_dashboard", edit_dashboard_predicate) + edit_default_dashboard_predicate = has_person & has_global_perm("core.edit_default_dashboard") rules.add_perm("core.edit_default_dashboard", edit_default_dashboard_predicate) diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index f40be2fa9d1ce690b81200f710616c51f605b5c8..f75b9bac705e7e753e565ffb55b7b106ee517418 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -12,6 +12,8 @@ from .util.core_helpers import ( merge_app_settings, ) +IN_PYTEST = "PYTEST_CURRENT_TEST" in os.environ or "TOX_ENV_DIR" in os.environ + ENVVAR_PREFIX_FOR_DYNACONF = "ALEKSIS" DIRS_FOR_DYNACONF = ["/etc/aleksis"] @@ -58,8 +60,14 @@ DEBUG_TOOLBAR_PANELS = [ "debug_toolbar.panels.signals.SignalsPanel", "debug_toolbar.panels.logging.LoggingPanel", "debug_toolbar.panels.profiling.ProfilingPanel", + "django_uwsgi.panels.UwsgiPanel", ] +UWSGI = { + "module": "aleksis.core.wsgi", +} +UWSGI_SERVE_STATIC = True +UWSGI_SERVE_MEDIA = True ALLOWED_HOSTS = _settings.get("http.allowed_hosts", []) @@ -73,6 +81,7 @@ INSTALLED_APPS = [ "django.contrib.sites", "django.contrib.staticfiles", "django.contrib.humanize", + "django_uwsgi", "django_extensions", "guardian", "rules.apps.AutodiscoverRulesConfig", @@ -201,18 +210,42 @@ DATABASES = { merge_app_settings("DATABASES", DATABASES, False) -if _settings.get("caching.memcached.enabled", False): +REDIS_HOST = _settings.get("redis.host", "localhost") +REDIS_PORT = _settings.get("redis.port", 6379) +REDIS_DB = _settings.get("redis.database", 0) +REDIS_USER = _settings.get("redis.user", None) +REDIS_PASSWORD = _settings.get("redis.password", None) + +REDIS_URL = f"redis://{REDIS_USER+'@' if REDIS_USER else ''}{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}" + +if _settings.get("caching.redis.enabled", not IN_PYTEST): CACHES = { "default": { - "BACKEND": "django_prometheus.cache.backends.memcached.MemcachedCache", - "LOCATION": _settings.get("caching.memcached.address", "127.0.0.1:11211"), + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": _settings.get("caching.redis.address", REDIS_URL), + "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient",}, } } - INSTALLED_APPS.append("cachalot") - DEBUG_TOOLBAR_PANELS.append("cachalot.panels.CachalotPanel") - CACHALOT_TIMEOUT = _settings.get("caching.cachalot.timeout", None) - CACHALOT_DATABASES = set(["default"]) - SILENCED_SYSTEM_CHECKS.append("cachalot.W001") + if REDIS_PASSWORD: + CACHES["default"]["OPTIONS"]["PASSWORD"] = REDIS_PASSWORD + DJANGO_REDIS_IGNORE_EXCEPTIONS = True + DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = True +else: + CACHES = { + "default": { + # Use uWSGI if available (will auot-fallback to LocMemCache) + "BACKEND": "django_uwsgi.cache.UwsgiCache" + } + } + +INSTALLED_APPS.append("cachalot") +DEBUG_TOOLBAR_PANELS.append("cachalot.panels.CachalotPanel") +CACHALOT_TIMEOUT = _settings.get("caching.cachalot.timeout", None) +CACHALOT_DATABASES = set(["default"]) +SILENCED_SYSTEM_CHECKS.append("cachalot.W001") + +SESSION_ENGINE = "django.contrib.sessions.backends.cache" +SESSION_CACHE_ALIAS = "default" # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators @@ -224,6 +257,12 @@ AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, ] +AUTH_INITIAL_SUPERUSER = { + "username": _settings.get("auth.superuser.username", "admin"), + "password": _settings.get("auth.superuser.password", "admin"), + "email": _settings.get("auth.superuser.email", "root@example.com"), +} + # Authentication backends are dynamically populated AUTHENTICATION_BACKENDS = [] @@ -488,7 +527,7 @@ if _settings.get("twilio.sid", None): TWILIO_TOKEN = _settings.get("twilio.token") TWILIO_CALLER_ID = _settings.get("twilio.callerid") -CELERY_BROKER_URL = _settings.get("celery.broker", "redis://localhost") +CELERY_BROKER_URL = _settings.get("celery.broker", REDIS_URL) CELERY_RESULT_BACKEND = "django-db" CELERY_CACHE_BACKEND = "django-cache" CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" @@ -496,6 +535,12 @@ CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" if _settings.get("celery.email", False): EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" +if _settings.get("dev.uwsgi.celery", DEBUG): + concurrency = _settings.get("celery.uwsgi.concurrency", 2) + UWSGI.setdefault("attach-daemon", []) + UWSGI["attach-daemon"].append(f"celery -A aleksis.core worker --concurrency={concurrency}") + UWSGI["attach-daemon"].append("celery -A aleksis.core beat") + PWA_APP_NAME = lazy_preference("general", "title") PWA_APP_DESCRIPTION = lazy_preference("general", "description") PWA_APP_THEME_COLOR = lazy_preference("theme", "primary") diff --git a/aleksis/core/templates/core/dashboard_widget/external_link_widget.html b/aleksis/core/templates/core/dashboard_widget/external_link_widget.html new file mode 100644 index 0000000000000000000000000000000000000000..bfbb5bda23e0e5034425ff745efab7f630119355 --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/external_link_widget.html @@ -0,0 +1,8 @@ +<div class="card"> + <div class="card-image"> + <img src="{{ icon_url }}" alt="{{ title }}" /> + </div> + <div class="card-action"> + <a href="{{ url }}">{{ title }}</a> + </div> +</div> diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html index 419bb59f11b5084eee7ac379605c3cefe00d530b..70103f517963f3a9e306e43c9212ac856594b229 100644 --- a/aleksis/core/templates/core/index.html +++ b/aleksis/core/templates/core/index.html @@ -1,5 +1,5 @@ {% extends 'core/base.html' %} -{% load i18n static dashboard %} +{% load i18n static dashboard rules %} {% block browser_title %}{% blocktrans %}Home{% endblocktrans %}{% endblock %} {% block no_page_title %}{% endblock %} @@ -9,51 +9,54 @@ {% endblock %} {% block content %} - <a class="btn-flat waves-effect waves-light right" href="{% url "edit_dashboard" %}"> - <i class="material-icons left">edit</i> - {% trans "Edit dashboard" %} - </a> + {% has_perm "core.edit_dashboard" user as can_edit_dashboard %} + {% if can_edit_dashboard %} + <a class="btn-flat waves-effect waves-light right" href="{% url "edit_dashboard" %}"> + <i class="material-icons left">edit</i> + {% trans "Edit dashboard" %} + </a> + {% endif %} <h4> {{ request.site.preferences.general__title }} </h4> - {% if user.is_authenticated %} - {% for notification in unread_notifications %} - <div class="alert primary scale-transition"> - <div> - <i class="material-icons left">info</i> - - <div class="right"> - <a class="btn-flat waves-effect" href="{% url "notification_mark_read" notification.id %}"> - <i class="material-icons center">close</i> - </a> - </div> + {% for notification in unread_notifications %} + <div class="alert primary scale-transition"> + <div> + <i class="material-icons left">info</i> - <strong>{{ notification.title }}</strong> - <p>{{ notification.description }}</p> + <div class="right"> + <a class="btn-flat waves-effect" href="{% url "notification_mark_read" notification.id %}"> + <i class="material-icons center">close</i> + </a> </div> - </div> - {% endfor %} - - {% include "core/partials/announcements.html" with announcements=announcements %} - <div class="row" id="live_load"> - {% for widget in widgets %} - <div class="col s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}"> - {% include_widget widget %} - </div> - {% endfor %} + <strong>{{ notification.title }}</strong> + <p>{{ notification.description|linebreaks }}</p> + </div> </div> + {% endfor %} + + {% include "core/partials/announcements.html" with announcements=announcements %} - {% if default_dashboard and widgets %} - <div class="grey-text right"> - {% blocktrans %} - You didn't customise your dashboard so that you see the system default. Please click on "Edit dashboard" to - customise your personal dashboard. - {% endblocktrans %} + <div class="row" id="live_load"> + {% for widget in widgets %} + <div class="col s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}"> + {% include_widget widget %} </div> - {% endif %} + {% endfor %} + </div> + + {% if default_dashboard and widgets and can_edit_dashboard %} + <div class="grey-text right"> + {% blocktrans %} + You didn't customise your dashboard so that you see the system default. Please click on "Edit dashboard" to + customise your personal dashboard. + {% endblocktrans %} + </div> + {% endif %} + {% if activities or notifications %} <div class="row"> <div class="col s12 m6"> <h5>{% blocktrans %}Last activities{% endblocktrans %}</h5> diff --git a/aleksis/core/templates/core/notifications.html b/aleksis/core/templates/core/notifications.html index a49bc9e8e2a8ad87041acbf20e79e0486714cde6..499bbcc4c866142ce9062efd75fab039dbd303f6 100644 --- a/aleksis/core/templates/core/notifications.html +++ b/aleksis/core/templates/core/notifications.html @@ -16,7 +16,7 @@ <i class="material-icons left">access_time</i> {{ notification.created }} </p> <p> - {{ notification.description }} + {{ notification.description|linebreaks }} </p> {% if notification.link %} <p> diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email index 0e5d9bdee3c8bd9ec3df5c9d008ae68d1f274c23..68a3e79570d1ba93013f2287b6d96cf8679a3572 100644 --- a/aleksis/core/templates/templated_email/notification.email +++ b/aleksis/core/templates/templated_email/notification.email @@ -30,7 +30,7 @@ <blockquote> <h5>{{ notification.title }}</h5> - <p>{{ notification.description }}</p> + <p>{{ notification.description|linebreaks }}</p> {% if notification.link %} <a href="{{ notification.link }}">{% trans "More information" %} →</a> {% endif %} diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 8183fba0d6da1ea956eb1da94b720b49e02363ac..f966c8dac14c021b606f2c1a3a3cd71645283203 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -1,6 +1,5 @@ from django.apps import apps from django.conf import settings -from django.conf.urls.static import static from django.contrib import admin from django.contrib.auth import views as auth_views from django.urls import include, path @@ -21,6 +20,7 @@ urlpatterns = [ path("", include("pwa.urls"), name="pwa"), path("about/", views.about, name="about_aleksis"), path("admin/", admin.site.urls), + path("admin/uwsgi/", include("django_uwsgi.urls")), path("data_management/", views.data_management, name="data_management"), path("status/", views.SystemStatus.as_view(), name="system_status"), path("", include(tf_urls)), @@ -198,13 +198,6 @@ urlpatterns = [ ), ] -# Serve static files from STATIC_ROOT to make it work with runserver -# collectstatic is also required in development for this -urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) - -# Serve media files from MEDIA_ROOT to make it work with runserver -urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - # Add URLs for optional features if hasattr(settings, "TWILIO_ACCOUNT_SID"): from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls # noqa diff --git a/aleksis/core/views.py b/aleksis/core/views.py index f1bc04013f92ed2bbde449c47cf44473be70ece0..0eb64c62cdc8368e664d3a2a031d7d6dc5d003f2 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -8,7 +8,7 @@ from django.db.models import QuerySet from django.forms.models import BaseModelForm, modelform_factory from django.http import HttpRequest, HttpResponse, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse_lazy +from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ from django.views.decorators.cache import never_cache @@ -54,6 +54,7 @@ from .models import ( DashboardWidget, DashboardWidgetOrder, DataCheckResult, + DummyPerson, Group, GroupType, Notification, @@ -75,7 +76,7 @@ from .tables import ( ) from .util import messages from .util.apps import AppConfig -from .util.core_helpers import objectgetter_optional +from .util.core_helpers import has_person, objectgetter_optional from .util.forms import PreferenceLayout @@ -84,19 +85,24 @@ def index(request: HttpRequest) -> HttpResponse: """View for dashboard.""" context = {} - activities = request.user.person.activities.all()[:5] - notifications = request.user.person.notifications.all()[:5] - unread_notifications = request.user.person.notifications.all().filter(read=False) + if has_person(request.user): + person = request.user.person + widgets = person.dashboard_widgets + else: + person = DummyPerson() + widgets = [] + + activities = person.activities.all()[:5] + notifications = person.notifications.all()[:5] + unread_notifications = person.notifications.all().filter(read=False) context["activities"] = activities context["notifications"] = notifications context["unread_notifications"] = unread_notifications - announcements = Announcement.objects.at_time().for_person(request.user.person) + announcements = Announcement.objects.at_time().for_person(person) context["announcements"] = announcements - widgets = request.user.person.dashboard_widgets - if len(widgets) == 0: # Use default dashboard if there are no widgets widgets = DashboardWidgetOrder.default_dashboard_widgets @@ -750,17 +756,19 @@ class RunDataChecks(PermissionRequiredMixin, View): permission_required = "core.run_data_checks" def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: - if not check_data()[1]: - messages.success( - request, - _( - "The data check has been started. Please note that it may take " - "a while before you are able to fetch the data on this page." - ), - ) - else: - messages.success(request, _("The data check has finished.")) - return redirect("check_data") + result = check_data.delay() + + context = { + "title": _("Progress: Run data checks"), + "back_url": reverse("check_data"), + "progress": { + "task_id": result.task_id, + "title": _("Run data checks …"), + "success": _("The data checks were run successfully."), + "error": _("There was a problem while running data checks."), + }, + } + return render(request, "core/pages/progress.html", context) class SolveDataCheckView(PermissionRequiredMixin, RevisionMixin, DetailView): @@ -859,9 +867,11 @@ class DashboardWidgetDeleteView(PermissionRequiredMixin, AdvancedDeleteView): success_message = _("The dashboard widget has been deleted.") -class EditDashboardView(View): +class EditDashboardView(PermissionRequiredMixin, View): """View for editing dashboard widget order.""" + permission_required = "core.edit_dashboard" + def get_context_data(self, request, **kwargs): context = {} self.default_dashboard = kwargs.get("default", False) diff --git a/docker-startup.sh b/docker-startup.sh new file mode 100755 index 0000000000000000000000000000000000000000..f0fd18ee63353f6867ea455e6190ac57a0695ee1 --- /dev/null +++ b/docker-startup.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +HTTP_PORT=${HTTP_PORT:8000} + +export ALEKSIS_database__host=${ALEKSIS_database__host:-127.0.0.1} +export ALEKSIS_database__port=${ALEKSIS_database__port:-5432} + +if [[ -z $ALEKSIS_secret_key ]]; then + if [[ ! -e /var/lib/aleksis/secret_key ]]; then + touch /var/lib/aleksis/secret_key; chmod 600 /var/lib/aleksis/secret_key + LC_ALL=C tr -dc 'A-Za-z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' </dev/urandom | head -c 64 >/var/lib/aleksis/secret_key + fi + ALEKSIS_secret_key=$(</var/lib/aleksis/secret_key) +fi + +while ! nc -z $ALEKSIS_database__host $ALEKSIS_database__port; do + sleep 0.1 +done + +aleksis-admin migrate +aleksis-admin createinitialrevisions +aleksis-admin compilescss +aleksis-admin collectstatic --no-input --clear + +exec aleksis-admin runuwsgi -- --http-socket=:$HTTP_PORT diff --git a/docs/admin/01_config.rst b/docs/admin/01_config.rst index a8e593b4afb1bf98b980c1bb548a75fb513b6bbf..1af436d556ab82254d1c5cfbd213e5eaae85bb0d 100644 --- a/docs/admin/01_config.rst +++ b/docs/admin/01_config.rst @@ -38,11 +38,11 @@ A configuration file might look like this:: password = "SuperSecretPassword" [caching] - memcached = { enabled = true, address = "127.0.0.1" } + redis = { enabled = true, address = "127.0.0.1" } The `secret_key` setting above defines a single value. The following `http` section defines a table (cf. a dictionary) in one way, and you can see the -second form of such a table in the `memcached` setting (we could as well +second form of such a table in the `redis` setting (we could as well have defined another section and placed `enabled` and `address` below it as scalars). diff --git a/docs/dev/01_setup.rst b/docs/dev/01_setup.rst index d61c21e9a074310ec599f8bcffb6824dabe64bac..31c72ee6c8bcaea746291ab038318fa614f0103d 100644 --- a/docs/dev/01_setup.rst +++ b/docs/dev/01_setup.rst @@ -78,12 +78,14 @@ Running the development server ------------------------------ The development server can be started using Django's ``runserver`` command. +If you want to automatically start other necessary tools in development, +like the `Celery` worker and scheduler, use ``runuwsgi`` instead. You can either configure AlekSIS like in a production environment, or pass basic settings in as environment variable. Here is an example that runs the development server against a local PostgreSQL database with password `aleksis` (all else remains default) and with the `debug` setting enabled:: - ALEKSIS_debug=true ALEKSIS_database__password=aleksis poetry run ./manage.py runserver + ALEKSIS_debug=true ALEKSIS_database__password=aleksis poetry run ./manage.py runuwsgi .. figure:: /screenshots/index.png :scale: 50% @@ -95,3 +97,4 @@ development server against a local PostgreSQL database with password .. _Poetry: https://poetry.eustace.io/ .. _Poetry installation methods: https://poetry.eustace.io/docs/#installation .. _Yarn: https://yarnpkg.com +.. _Celery: https://celeryproject.org/ diff --git a/poetry.lock b/poetry.lock index 74540c0891437cb28a661ccbe3f5176d7249888f..1e055a4ccb638636e49fdb2ca092ecdd55422e27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "aleksis-builddeps" -version = "1" +version = "2" description = "AlekSIS (School Information System) — Build/Dev dependencies for apps" category = "dev" optional = false @@ -16,6 +16,7 @@ python-versions = "*" [package.dependencies] black = ">=19.10b0,<20.0" +curlylint = ">=0.12.0,<0.13.0" django-stubs = ">=1.1,<2.0" flake8 = ">=3.7.9,<4.0.0" flake8-bandit = ">=2.1.2,<3.0.0" @@ -26,11 +27,11 @@ flake8-docstrings = ">=1.5.0,<2.0.0" flake8-fixme = ">=1.1.1,<2.0.0" flake8-isort = ">=4.0.0,<5.0.0" flake8-mypy = ">=17.8.0,<18.0.0" -flake8-rst-docstrings = ">=0.0.13,<0.0.14" +flake8-rst-docstrings = ">=0.0.14,<0.0.15" isort = ">=5.0.0,<6.0.0" pytest = ">=6.0,<7.0" pytest-cov = ">=2.8.1,<3.0.0" -pytest-django = ">=3.7,<4.0" +pytest-django = ">=4.1,<5.0" pytest-django-testing-postgresql = ">=0.1,<0.2" pytest-sugar = ">=0.9.2,<0.10.0" safety = ">=1.8.5,<2.0.0" @@ -82,6 +83,14 @@ python-versions = ">=3.5" [package.extras] tests = ["pytest", "pytest-asyncio"] +[[package]] +name = "asn1crypto" +version = "1.4.0" +description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "atomicwrites" version = "1.4.0" @@ -407,6 +416,24 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] toml = ["toml"] +[[package]] +name = "curlylint" +version = "0.12.2" +description = "{{ 🎀}} Experimental HTML templates linting for Jinja, Nunjucks, Django templates, Twig, Liquid" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +attrs = ">=17.2.0" +click = ">=6.5" +parsy = "1.1.0" +pathspec = ">=0.6,<1" +toml = ">=0.9.4" + +[package.extras] +dev = ["black (==19.10b0)", "flake8 (==3.8.4)", "mypy (==0.812)", "pytest (==6.2.2)", "coverage (==5.4)"] + [[package]] name = "decorator" version = "4.4.2" @@ -510,11 +537,11 @@ Django = ">=2" [[package]] name = "django-cache-memoize" -version = "0.1.7" +version = "0.1.8" description = "Django utility for a memoization decorator that uses the Django cache framework." category = "main" optional = false -python-versions = ">=3.4" +python-versions = ">=3.5" [package.extras] dev = ["flake8", "tox", "twine", "therapist", "black"] @@ -876,6 +903,18 @@ python-versions = "*" [package.dependencies] django = ">=1.8" +[[package]] +name = "django-redis" +version = "4.12.1" +description = "Full featured redis cache backend for Django." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +Django = ">=2.2" +redis = ">=3.0.0" + [[package]] name = "django-render-block" version = "0.8.1" @@ -912,7 +951,7 @@ management-command = ["django-compressor (>=2.4)"] [[package]] name = "django-select2" -version = "7.6.1" +version = "7.6.2" description = "Select2 option fields for Django" category = "main" optional = false @@ -1012,6 +1051,17 @@ phonenumberslite = ["phonenumberslite (>=7.0.9,<8.99)"] sms = ["twilio (>=6.0)"] yubikey = ["django-otp-yubikey"] +[[package]] +name = "django-uwsgi-ng" +version = "1.1.0" +description = "uWSGI stuff for Django projects" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +uwsgi = ["uwsgi"] + [[package]] name = "django-widget-tweaks" version = "1.4.8" @@ -1058,7 +1108,7 @@ pipenv = ["pipenv"] [[package]] name = "dynaconf" -version = "3.1.2" +version = "3.1.4" description = "The dynamic configurator for your Python Project" category = "main" optional = false @@ -1080,7 +1130,7 @@ yaml = ["ruamel.yaml"] [[package]] name = "faker" -version = "6.5.0" +version = "6.6.0" description = "Faker is a Python package that generates fake data for you." category = "main" optional = false @@ -1217,7 +1267,7 @@ flake8 = "*" [[package]] name = "flake8-rst-docstrings" -version = "0.0.13" +version = "0.0.14" description = "Python docstring reStructuredText (RST) validator" category = "dev" optional = false @@ -1275,7 +1325,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "3.7.0" +version = "3.7.2" description = "Read metadata from Python packages" category = "main" optional = false @@ -1491,6 +1541,14 @@ python-versions = ">=3.6" qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] testing = ["docopt", "pytest (<6.0.0)"] +[[package]] +name = "parsy" +version = "1.1.0" +description = "easy-to-use parser combinators, for parsing in pure Python" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "pathspec" version = "0.8.1" @@ -1528,18 +1586,18 @@ ptyprocess = ">=0.5" [[package]] name = "pg8000" -version = "1.17.0" +version = "1.18.0" description = "PostgreSQL interface library" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -scramp = "1.2.0" +scramp = "1.2.2" [[package]] name = "phonenumbers" -version = "8.12.18" +version = "8.12.19" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." category = "main" optional = false @@ -1555,7 +1613,7 @@ python-versions = "*" [[package]] name = "pillow" -version = "8.1.0" +version = "8.1.2" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -1588,7 +1646,7 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.16" +version = "3.0.17" description = "Library for building powerful interactive command lines in Python" category = "main" optional = false @@ -1688,7 +1746,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.8.0" +version = "2.8.1" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -1754,18 +1812,18 @@ testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", [[package]] name = "pytest-django" -version = "3.10.0" +version = "4.1.0" description = "A Django plugin for pytest." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [package.dependencies] -pytest = ">=3.6" +pytest = ">=5.4.0" [package.extras] docs = ["sphinx", "sphinx-rtd-theme"] -testing = ["django", "django-configurations (>=2.0)", "six"] +testing = ["django", "django-configurations (>=2.0)"] [[package]] name = "pytest-django-testing-postgresql" @@ -1830,17 +1888,6 @@ python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" pyasn1 = ">=0.3.7" pyasn1_modules = ">=0.1.5" -[[package]] -name = "python-memcached" -version = "1.59" -description = "Pure python memcached client" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = ">=1.4.0" - [[package]] name = "pytz" version = "2021.1" @@ -1925,14 +1972,14 @@ docutils = ">=0.11,<1.0" [[package]] name = "ruamel.yaml" -version = "0.16.12" +version = "0.16.13" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" category = "main" optional = false python-versions = "*" [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.9\""} +"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.10\""} [package.extras] docs = ["ryd"] @@ -1970,11 +2017,14 @@ requests = "*" [[package]] name = "scramp" -version = "1.2.0" +version = "1.2.2" description = "An implementation of the SCRAM protocol." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" + +[package.dependencies] +asn1crypto = "1.4.0" [[package]] name = "selenium" @@ -2029,7 +2079,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.1" +version = "3.5.2" description = "Python documentation generator" category = "dev" optional = false @@ -2237,7 +2287,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tqdm" -version = "4.58.0" +version = "4.59.0" description = "Fast, Extensible Progress Meter" category = "main" optional = false @@ -2245,6 +2295,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] telegram = ["requests"] [[package]] @@ -2304,6 +2355,14 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uwsgi" +version = "2.0.19.1" +description = "The uWSGI server" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "vine" version = "5.0.0" @@ -2341,15 +2400,15 @@ pycryptodome = "*" [[package]] name = "zipp" -version = "3.4.0" +version = "3.4.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.6" [package.extras] -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"] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] ldap = ["django-auth-ldap"] @@ -2357,7 +2416,7 @@ ldap = ["django-auth-ldap"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "534fdfeaf6f6485b019e56f27bee4c707593499967358dc8be6c73d9c1c90ff9" +content-hash = "54320b0755855f08a6b304d526ca62a4650e269513bd2460b348ffa47ef81250" [metadata.files] alabaster = [ @@ -2365,7 +2424,7 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] aleksis-builddeps = [ - {file = "AlekSIS-Builddeps-1.tar.gz", hash = "sha256:97a19597f422593cbdc438aabf17f95748126c8951df6ac7db7991fc99c108c4"}, + {file = "AlekSIS-Builddeps-2.tar.gz", hash = "sha256:fdf8b230ba4a690c279d99004316e84d7d9d72962768ca6b3205df54db9abaab"}, ] amqp = [ {file = "amqp-5.0.5-py3-none-any.whl", hash = "sha256:1e759a7f202d910939de6eca45c23a107f6b71111f41d1282c648e9ac3d21901"}, @@ -2383,6 +2442,10 @@ asgiref = [ {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, ] +asn1crypto = [ + {file = "asn1crypto-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8"}, + {file = "asn1crypto-1.4.0.tar.gz", hash = "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, @@ -2531,6 +2594,10 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] +curlylint = [ + {file = "curlylint-0.12.2-py3-none-any.whl", hash = "sha256:98bc15609ce858387dd70a28c7ddda96e82d0f1cb8bf51b8902532ce0fc1a97e"}, + {file = "curlylint-0.12.2.tar.gz", hash = "sha256:76b557cf8d007bd92df2dae61a02e65f8aa2ff3e05c6398b1314d92692fbb0d8"}, +] decorator = [ {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, @@ -2567,8 +2634,8 @@ django-cachalot = [ {file = "django_cachalot-2.3.3-py3-none-any.whl", hash = "sha256:55f94e94f7000f5f6bd92188d3d7535cfdef79f2e697e36daf69cba8f435e156"}, ] django-cache-memoize = [ - {file = "django-cache-memoize-0.1.7.tar.gz", hash = "sha256:5e96349b0159aec1eb79257199a1902ea3ed538231ce7b4fee12e563127ca657"}, - {file = "django_cache_memoize-0.1.7-py2.py3-none-any.whl", hash = "sha256:bc7f53725558244af62197d0125732d7ec88ecc1281a3a2f37d77ae1a8c269d3"}, + {file = "django-cache-memoize-0.1.8.tar.gz", hash = "sha256:f85ca71ddfe3d61d561d5a382736f83148fb75e542585e7028b65d6d3681ec85"}, + {file = "django_cache_memoize-0.1.8-py3-none-any.whl", hash = "sha256:81b00714b50917431ce12a4544e0630a70c86fed27755a82186efc2945b8f8b3"}, ] django-celery-beat = [ {file = "django-celery-beat-2.2.0.tar.gz", hash = "sha256:b8a13afb15e7c53fc04f4f847ac71a6d32088959aba701eb7c4a59f0c28ba543"}, @@ -2694,6 +2761,10 @@ django-pwa = [ {file = "django-pwa-1.0.10.tar.gz", hash = "sha256:07ed9dd57108838e3fe44b551a82032ca4ed76e31cb3c3e8d51604e0fe7e81e9"}, {file = "django_pwa-1.0.10-py3-none-any.whl", hash = "sha256:b1a2057b1e72c40c3a14beb90b958482da185f1d40a141fcae3d76580984b930"}, ] +django-redis = [ + {file = "django-redis-4.12.1.tar.gz", hash = "sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63"}, + {file = "django_redis-4.12.1-py3-none-any.whl", hash = "sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5"}, +] django-render-block = [ {file = "django-render-block-0.8.1.tar.gz", hash = "sha256:edbc5d444cc50f3eb3387cf17f6f1014bf19d6018f680861cdeae9e0306003fa"}, {file = "django_render_block-0.8.1-py3-none-any.whl", hash = "sha256:903969efd0949f750c5fe71affe6e6b1ea66d03005c102a67fda36d5b9f4e1e1"}, @@ -2706,8 +2777,8 @@ django-sass-processor = [ {file = "django-sass-processor-0.8.2.tar.gz", hash = "sha256:9b46a12ca8bdcb397d46fbcc49e6a926ff9f76a93c5efeb23b495419fd01fc7a"}, ] django-select2 = [ - {file = "django-select2-7.6.1.tar.gz", hash = "sha256:25362c5bafe082a19add598fb0a69e3239b94759691a0ac8e01ab7fba8e650ad"}, - {file = "django_select2-7.6.1-py2.py3-none-any.whl", hash = "sha256:dc6b6fa737b6ea0b673e27c218955dd51a3fb81b2b28af93ce87703b24f4faf8"}, + {file = "django-select2-7.6.2.tar.gz", hash = "sha256:be687a1350cb199d3e60e4cb43686111eb04b3fbd0d2cf3309b48685b63fc0e4"}, + {file = "django_select2-7.6.2-py2.py3-none-any.whl", hash = "sha256:76eb9d378c441ad0959eadfc47929e9258894884f801572589b99429f8c241c3"}, ] django-settings-context-processor = [ {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"}, @@ -2731,6 +2802,9 @@ django-two-factor-auth = [ {file = "django-two-factor-auth-1.13.tar.gz", hash = "sha256:24c2850a687c86800f4aa4131b7cebadf56f35be04ca359c4990578df1cc249a"}, {file = "django_two_factor_auth-1.13-py2.py3-none-any.whl", hash = "sha256:afb60e62f22b1f29a568666c0444ab05cabe8acc4d7c54d833d67f7b50f842fd"}, ] +django-uwsgi-ng = [ + {file = "django-uwsgi-ng-1.1.0.tar.gz", hash = "sha256:ea6485b5f33acd6721dff3008ad4e20f9ec311555dad2a37e0c47fa360b0fcc5"}, +] django-widget-tweaks = [ {file = "django-widget-tweaks-1.4.8.tar.gz", hash = "sha256:9f91ca4217199b7671971d3c1f323a2bec71a0c27dec6260b3c006fa541bc489"}, {file = "django_widget_tweaks-1.4.8-py2.py3-none-any.whl", hash = "sha256:f80bff4a8a59b278bb277a405a76a8b9a884e4bae7a6c70e78a39c626cd1c836"}, @@ -2747,12 +2821,12 @@ dparse = [ {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"}, ] dynaconf = [ - {file = "dynaconf-3.1.2-py2.py3-none-any.whl", hash = "sha256:808adfe964f10695846dbf8dad7632e47fc3bc38860fd1887ed57dddffc4eff2"}, - {file = "dynaconf-3.1.2.tar.gz", hash = "sha256:9b34ab2f811a81755f5eb4beac77a69e1e0887528c7e37fc4bc83fed52dcf502"}, + {file = "dynaconf-3.1.4-py2.py3-none-any.whl", hash = "sha256:e6f383b84150b70fc439c8b2757581a38a58d07962aa14517292dcce1a77e160"}, + {file = "dynaconf-3.1.4.tar.gz", hash = "sha256:b2f472d83052f809c5925565b8a2ba76a103d5dc1dbb9748b693ed67212781b9"}, ] faker = [ - {file = "Faker-6.5.0-py3-none-any.whl", hash = "sha256:90b69e9e05d622edb2fa5ebfda7bef41c88675cace85e72689fde5b8723d00a3"}, - {file = "Faker-6.5.0.tar.gz", hash = "sha256:da395fe545f40d4366b82b1a02448847a4586bd2b28af393b3edbd1e45d1e0fc"}, + {file = "Faker-6.6.0-py3-none-any.whl", hash = "sha256:38bd2ff0afb7d9956fed477a2cd1007a7012b401a9ffd3de5fb909d739880582"}, + {file = "Faker-6.6.0.tar.gz", hash = "sha256:0a68a539b236a614157581a135c82a27024a66dc96b9a491883fe583ae3689f9"}, ] flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, @@ -2793,7 +2867,7 @@ flake8-polyfill = [ {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, ] flake8-rst-docstrings = [ - {file = "flake8-rst-docstrings-0.0.13.tar.gz", hash = "sha256:b1b619d81d879b874533973ac04ee5d823fdbe8c9f3701bfe802bb41813997b4"}, + {file = "flake8-rst-docstrings-0.0.14.tar.gz", hash = "sha256:8f8bcb18f1408b506dd8ba2c99af3eac6128f6911d4bf6ff874b94caa70182a2"}, ] gitdb = [ {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, @@ -2816,8 +2890,8 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.7.0-py3-none-any.whl", hash = "sha256:c6af5dbf1126cd959c4a8d8efd61d4d3c83bddb0459a17e554284a077574b614"}, - {file = "importlib_metadata-3.7.0.tar.gz", hash = "sha256:24499ffde1b80be08284100393955842be4a59c7c16bbf2738aad0e464a8e0aa"}, + {file = "importlib_metadata-3.7.2-py3-none-any.whl", hash = "sha256:407d13f55dc6f2a844e62325d18ad7019a436c4bfcaee34cda35f2be6e7c3e34"}, + {file = "importlib_metadata-3.7.2.tar.gz", hash = "sha256:18d5ff601069f98d5d605b6a4b50c18a34811d655c55548adc833e687289acde"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -2885,39 +2959,20 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] mccabe = [ @@ -2960,6 +3015,10 @@ parso = [ {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, ] +parsy = [ + {file = "parsy-1.1.0-py3-none-any.whl", hash = "sha256:25bd5cea2954950ebbfdf71f8bdaf7fd45a5df5325fd36a1064be2204d9d4c94"}, + {file = "parsy-1.1.0.tar.gz", hash = "sha256:36173ba01a5372c7a1b32352cc73a279a49198f52252adf1c8c1ed41d1f94e8d"}, +] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, @@ -2976,50 +3035,51 @@ pexpect = [ {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] pg8000 = [ - {file = "pg8000-1.17.0-py3-none-any.whl", hash = "sha256:3276fe9cf38fee4fd4006c64d50fa621841b550f0f068d88b4694ee423188a5f"}, - {file = "pg8000-1.17.0.tar.gz", hash = "sha256:14198c5afeb289106e40ee6e5e4c0529c5369939f6ca588a028b371a75fe20dd"}, + {file = "pg8000-1.18.0-py3-none-any.whl", hash = "sha256:240a5e7c3118ea07179a02ff8daeacf93d68ab9546ea140ca9d77970c4c5fc9d"}, + {file = "pg8000-1.18.0.tar.gz", hash = "sha256:35baf2c8bf5445e85f516449474b547dbbd0e08c0baa3a6b20aa355a92eb72da"}, ] phonenumbers = [ - {file = "phonenumbers-8.12.18-py2.py3-none-any.whl", hash = "sha256:f60b1cc7b424cdadf5c291ed839d1c623a46ca1e4d0a04d3e85d1fdf754c1a26"}, - {file = "phonenumbers-8.12.18.tar.gz", hash = "sha256:0aa0f5e1382d292a7ff2f8bc08673126521461c7f908e0220756449a734d8fef"}, + {file = "phonenumbers-8.12.19-py2.py3-none-any.whl", hash = "sha256:dadc72b81effefa499f2ee7f77fcad601fb725c024f444c9ea60500e4d79aa4e"}, + {file = "phonenumbers-8.12.19.tar.gz", hash = "sha256:0f597b602e64af90c06b14c8223e94fdb0ed20f203e1c9785a8bbe4de00c45e8"}, ] pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] 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"}, - {file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913"}, - {file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820"}, - {file = "Pillow-8.1.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5"}, - {file = "Pillow-8.1.0.tar.gz", hash = "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba"}, + {file = "Pillow-8.1.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:5cf03b9534aca63b192856aa601c68d0764810857786ea5da652581f3a44c2b0"}, + {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f91b50ad88048d795c0ad004abbe1390aa1882073b1dca10bfd55d0b8cf18ec5"}, + {file = "Pillow-8.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5762ebb4436f46b566fc6351d67a9b5386b5e5de4e58fdaa18a1c83e0e20f1a8"}, + {file = "Pillow-8.1.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e2cd8ac157c1e5ae88b6dd790648ee5d2777e76f1e5c7d184eaddb2938594f34"}, + {file = "Pillow-8.1.2-cp36-cp36m-win32.whl", hash = "sha256:72027ebf682abc9bafd93b43edc44279f641e8996fb2945104471419113cfc71"}, + {file = "Pillow-8.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d1d6bca39bb6dd94fba23cdb3eeaea5e30c7717c5343004d900e2a63b132c341"}, + {file = "Pillow-8.1.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:90882c6f084ef68b71bba190209a734bf90abb82ab5e8f64444c71d5974008c6"}, + {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:89e4c757a91b8c55d97c91fa09c69b3677c227b942fa749e9a66eef602f59c28"}, + {file = "Pillow-8.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8c4e32218c764bc27fe49b7328195579581aa419920edcc321c4cb877c65258d"}, + {file = "Pillow-8.1.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a01da2c266d9868c4f91a9c6faf47a251f23b9a862dce81d2ff583135206f5be"}, + {file = "Pillow-8.1.2-cp37-cp37m-win32.whl", hash = "sha256:30d33a1a6400132e6f521640dd3f64578ac9bfb79a619416d7e8802b4ce1dd55"}, + {file = "Pillow-8.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:71b01ee69e7df527439d7752a2ce8fb89e19a32df484a308eca3e81f673d3a03"}, + {file = "Pillow-8.1.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:5a2d957eb4aba9d48170b8fe6538ec1fbc2119ffe6373782c03d8acad3323f2e"}, + {file = "Pillow-8.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:87f42c976f91ca2fc21a3293e25bd3cd895918597db1b95b93cbd949f7d019ce"}, + {file = "Pillow-8.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:15306d71a1e96d7e271fd2a0737038b5a92ca2978d2e38b6ced7966583e3d5af"}, + {file = "Pillow-8.1.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:71f31ee4df3d5e0b366dd362007740106d3210fb6a56ec4b581a5324ba254f06"}, + {file = "Pillow-8.1.2-cp38-cp38-win32.whl", hash = "sha256:98afcac3205d31ab6a10c5006b0cf040d0026a68ec051edd3517b776c1d78b09"}, + {file = "Pillow-8.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:328240f7dddf77783e72d5ed79899a6b48bc6681f8d1f6001f55933cb4905060"}, + {file = "Pillow-8.1.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bead24c0ae3f1f6afcb915a057943ccf65fc755d11a1410a909c1fefb6c06ad1"}, + {file = "Pillow-8.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81b3716cc9744ffdf76b39afb6247eae754186838cedad0b0ac63b2571253fe6"}, + {file = "Pillow-8.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:63cd413ac52ee3f67057223d363f4f82ce966e64906aea046daf46695e3c8238"}, + {file = "Pillow-8.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8565355a29655b28fdc2c666fd9a3890fe5edc6639d128814fafecfae2d70910"}, + {file = "Pillow-8.1.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1940fc4d361f9cc7e558d6f56ff38d7351b53052fd7911f4b60cd7bc091ea3b1"}, + {file = "Pillow-8.1.2-cp39-cp39-win32.whl", hash = "sha256:46c2bcf8e1e75d154e78417b3e3c64e96def738c2a25435e74909e127a8cba5e"}, + {file = "Pillow-8.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:aeab4cd016e11e7aa5cfc49dcff8e51561fa64818a0be86efa82c7038e9369d0"}, + {file = "Pillow-8.1.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:74cd9aa648ed6dd25e572453eb09b08817a1e3d9f8d1bd4d8403d99e42ea790b"}, + {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:e5739ae63636a52b706a0facec77b2b58e485637e1638202556156e424a02dc2"}, + {file = "Pillow-8.1.2-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:903293320efe2466c1ab3509a33d6b866dc850cfd0c5d9cc92632014cec185fb"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5daba2b40782c1c5157a788ec4454067c6616f5a0c1b70e26ac326a880c2d328"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:1f93f2fe211f1ef75e6f589327f4d4f8545d5c8e826231b042b483d8383e8a7c"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:6efac40344d8f668b6c4533ae02a48d52fd852ef0654cc6f19f6ac146399c733"}, + {file = "Pillow-8.1.2-pp37-pypy37_pp73-win32.whl", hash = "sha256:f36c3ff63d6fc509ce599a2f5b0d0732189eed653420e7294c039d342c6e204a"}, + {file = "Pillow-8.1.2.tar.gz", hash = "sha256:b07c660e014852d98a00a91adfbe25033898a9d90a8f39beb2437d22a203fc44"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -3030,8 +3090,8 @@ prometheus-client = [ {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.16-py3-none-any.whl", hash = "sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"}, - {file = "prompt_toolkit-3.0.16.tar.gz", hash = "sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974"}, + {file = "prompt_toolkit-3.0.17-py3-none-any.whl", hash = "sha256:4cea7d09e46723885cb8bc54678175453e5071e9449821dce6f017b1d1fbfc1a"}, + {file = "prompt_toolkit-3.0.17.tar.gz", hash = "sha256:9397a7162cf45449147ad6042fa37983a081b8a73363a5253dd4072666333137"}, ] psutil = [ {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, @@ -3163,8 +3223,8 @@ pyflakes = [ {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ - {file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"}, - {file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"}, + {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, + {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, ] pyjwt = [ {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, @@ -3183,8 +3243,8 @@ pytest-cov = [ {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, ] pytest-django = [ - {file = "pytest-django-3.10.0.tar.gz", hash = "sha256:4de6dbd077ed8606616958f77655fed0d5e3ee45159475671c7fa67596c6dba6"}, - {file = "pytest_django-3.10.0-py2.py3-none-any.whl", hash = "sha256:c33e3d3da14d8409b125d825d4e74da17bb252191bf6fc3da6856e27a8b73ea4"}, + {file = "pytest-django-4.1.0.tar.gz", hash = "sha256:26f02c16d36fd4c8672390deebe3413678d89f30720c16efb8b2a6bf63b9041f"}, + {file = "pytest_django-4.1.0-py3-none-any.whl", hash = "sha256:10e384e6b8912ded92db64c58be8139d9ae23fb8361e5fc139d8e4f8fc601bc2"}, ] pytest-django-testing-postgresql = [ {file = "pytest-django-testing-postgresql-0.1.post0.tar.gz", hash = "sha256:78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45"}, @@ -3203,10 +3263,6 @@ python-dateutil = [ python-ldap = [ {file = "python-ldap-3.3.1.tar.gz", hash = "sha256:4711cacf013e298754abd70058ccc995758177fb425f1c2d30e71adfc1d00aa5"}, ] -python-memcached = [ - {file = "python-memcached-1.59.tar.gz", hash = "sha256:a2e28637be13ee0bf1a8b6843e7490f9456fd3f2a4cb60471733c7b5d5557e4f"}, - {file = "python_memcached-1.59-py2.py3-none-any.whl", hash = "sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594"}, -] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, @@ -3293,8 +3349,8 @@ restructuredtext-lint = [ {file = "restructuredtext_lint-1.3.2.tar.gz", hash = "sha256:d3b10a1fe2ecac537e51ae6d151b223b78de9fafdd50e5eb6b08c243df173c80"}, ] "ruamel.yaml" = [ - {file = "ruamel.yaml-0.16.12-py2.py3-none-any.whl", hash = "sha256:012b9470a0ea06e4e44e99e7920277edf6b46eee0232a04487ea73a7386340a5"}, - {file = "ruamel.yaml-0.16.12.tar.gz", hash = "sha256:076cc0bc34f1966d920a49f18b52b6ad559fbe656a0748e3535cf7b3f29ebf9e"}, + {file = "ruamel.yaml-0.16.13-py2.py3-none-any.whl", hash = "sha256:64b06e7873eb8e1125525ecef7345447d786368cadca92a7cd9b59eae62e95a3"}, + {file = "ruamel.yaml-0.16.13.tar.gz", hash = "sha256:bb48c514222702878759a05af96f4b7ecdba9b33cd4efcf25c86b882cef3a942"}, ] "ruamel.yaml.clib" = [ {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc"}, @@ -3337,8 +3393,8 @@ safety = [ {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"}, ] scramp = [ - {file = "scramp-1.2.0-py3-none-any.whl", hash = "sha256:74815c25aad1fe0b5fb994e96c3de63e8695164358a80138352aaadfa4760350"}, - {file = "scramp-1.2.0.tar.gz", hash = "sha256:d6865ed1d135ddb124a619d7cd3a5b505f69a7c92e248024dd7e48bc77752af5"}, + {file = "scramp-1.2.2-py3-none-any.whl", hash = "sha256:c1d0b8d6f890e4e72ccd9bae23e802bfb377d50c2843396e5997d262fbfe2103"}, + {file = "scramp-1.2.2.tar.gz", hash = "sha256:ac578bf7b49645ca1083117e40f4e8af2073b003750d5bf21b3285ff342a4f33"}, ] selenium = [ {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, @@ -3365,8 +3421,8 @@ spdx-license-list = [ {file = "spdx_license_list-0.5.2.tar.gz", hash = "sha256:952996f72ab807972dc2278bb9b91e5294767211e51f09aad9c0e2ff5b82a31b"}, ] sphinx = [ - {file = "Sphinx-3.5.1-py3-none-any.whl", hash = "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"}, - {file = "Sphinx-3.5.1.tar.gz", hash = "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9"}, + {file = "Sphinx-3.5.2-py3-none-any.whl", hash = "sha256:ef64a814576f46ec7de06adf11b433a0d6049be007fefe7fd0d183d28b581fac"}, + {file = "Sphinx-3.5.2.tar.gz", hash = "sha256:672cfcc24b6b69235c97c750cb190a44ecd72696b4452acaf75c2d9cc78ca5ff"}, ] sphinx-autodoc-typehints = [ {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"}, @@ -3432,8 +3488,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tqdm = [ - {file = "tqdm-4.58.0-py2.py3-none-any.whl", hash = "sha256:2c44efa73b8914dba7807aefd09653ac63c22b5b4ea34f7a80973f418f1a3089"}, - {file = "tqdm-4.58.0.tar.gz", hash = "sha256:c23ac707e8e8aabb825e4d91f8e17247f9cc14b0d64dd9e97be0781e9e525bba"}, + {file = "tqdm-4.59.0-py2.py3-none-any.whl", hash = "sha256:9fdf349068d047d4cfbe24862c425883af1db29bcddf4b0eeb2524f6fbdb23c7"}, + {file = "tqdm-4.59.0.tar.gz", hash = "sha256:d666ae29164da3e517fcf125e41d4fe96e5bb375cd87ff9763f6b38b5592fe33"}, ] traitlets = [ {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, @@ -3483,6 +3539,9 @@ urllib3 = [ {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, ] +uwsgi = [ + {file = "uWSGI-2.0.19.1.tar.gz", hash = "sha256:faa85e053c0b1be4d5585b0858d3a511d2cd10201802e8676060fd0a109e5869"}, +] vine = [ {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, @@ -3500,6 +3559,6 @@ yubiotp = [ {file = "YubiOTP-1.0.0.post1.tar.gz", hash = "sha256:c13825f7b76a69afb92f19521f4dea9f5031d70f45123b505dc2e0ac03132065"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, + {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, ] diff --git a/pyproject.toml b/pyproject.toml index 00fbafa26a4b3bf5dab1d617223754383c2f8f69..d331ca596025ebc8893ab9934d874b7a163bd156 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,6 @@ django-auth-ldap = { version = "^2.2", optional = true } django-maintenance-mode = "^0.15.0" django-ipware = "^3.0" django-impersonate = "^1.4" -python-memcached = "^1.59" django-hattori = "^0.2" psycopg2 = "^2.8" django_select2 = "^7.1" @@ -94,14 +93,17 @@ django-prometheus = "^2.1.0" importlib-metadata = {version = "^3.0.0", python = "<3.9"} django-model-utils = "^4.0.0" bs4 = "^0.0.1" +django-uwsgi-ng = "^1.1.0" django-extensions = "^3.1.1" ipython = "^7.20.0" +django-redis = "^4.12.1" [tool.poetry.extras] ldap = ["django-auth-ldap"] [tool.poetry.dev-dependencies] aleksis-builddeps = "*" +uwsgi = "^2.0" [tool.poetry.scripts] aleksis-admin = 'aleksis.core.__main__:aleksis_cmd'