diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e79081953846c2db12c3c9e2a91656e5126759b..955750958d170e723e9714295750d0e1204e878f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,9 @@ include: - - local: "/ci/general.yml" - - local: "/ci/test.yml" - - local: "/ci/build_dist.yml" - - local: "/ci/build_docker.yml" - - local: "/ci/pages.yml" - - local: "/ci/deploy.yml" + - project: "AlekSIS/official/AlekSIS" + file: /ci/general.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/test.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/build_dist.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/deploy_pypi.yml diff --git a/.gitmodules b/.gitmodules index a7fa3c3c15f5415169a0f2b0ce67876831caf5f7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,20 +0,0 @@ -[submodule "apps/official/AlekSIS-App-Chronos"] - path = apps/official/AlekSIS-App-Chronos - url = https://edugit.org/AlekSIS/Official/AlekSIS-App-Chronos - ignore = untracked -[submodule "apps/official/AlekSIS-App-DashboardFeeds"] - path = apps/official/AlekSIS-App-DashboardFeeds - url = https://edugit.org/AlekSIS/Official/AlekSIS-App-DashboardFeeds - ignore = untracked -[submodule "apps/official/AlekSIS-App-LDAP"] - path = apps/official/AlekSIS-App-LDAP - url = https://edugit.org/AlekSIS/official/AlekSIS-App-LDAP - ignore = untracked -[submodule "apps/official/AlekSIS-App-Untis"] - path = apps/official/AlekSIS-App-Untis - url = https://edugit.org/AlekSIS/official/AlekSIS-App-Untis - ignore = untracked -[submodule "apps/official/AlekSIS-App-Hjelp"] - path = apps/official/AlekSIS-App-Hjelp - url = https://edugit.org/AlekSIS/official/AlekSIS-App-Hjelp - ignore = untracked diff --git a/CODE_OF_CONDUCT.rst b/CODE_OF_CONDUCT.rst deleted file mode 100644 index 9618c5f7d4285f366f05a5f8b2fd4cef43cf829b..0000000000000000000000000000000000000000 --- a/CODE_OF_CONDUCT.rst +++ /dev/null @@ -1,144 +0,0 @@ -Contributor Covenant Code of Conduct -==================================== - -Our Pledge ----------- - -We as members, contributors, and leaders pledge to make participation in -our community a harassment-free experience for everyone, regardless of -age, body size, visible or invisible disability, ethnicity, sex -characteristics, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, -race, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, -welcoming, diverse, inclusive, and healthy community. - -Our Standards -------------- - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our - mistakes, and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political - attacks -- Public or private harassment -- Publishing others’ private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -Enforcement Responsibilities ----------------------------- - -Community leaders are responsible for clarifying and enforcing our -standards of acceptable behavior and will take appropriate and fair -corrective action in response to any behavior that they deem -inappropriate, threatening, offensive, or harmful. - -Community leaders have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other -contributions that are not aligned to this Code of Conduct, and will -communicate reasons for moderation decisions when appropriate. - -Scope ------ - -This Code of Conduct applies within all community spaces, and also -applies when an individual is officially representing the community in -public spaces. Examples of representing our community include using an -official e-mail address, posting via an official social media account, -or acting as an appointed representative at an online or offline event. - -Enforcement ------------ - -Instances of abusive, harassing, or otherwise unacceptable behavior may -be reported to the community leaders responsible for enforcement at -foss@teckids.org. All complaints will be reviewed and investigated -promptly and fairly. - -All community leaders are obligated to respect the privacy and security -of the reporter of any incident. - -Enforcement Guidelines ----------------------- - -Community leaders will follow these Community Impact Guidelines in -determining the consequences for any action they deem in violation of -this Code of Conduct: - -1. Correction -~~~~~~~~~~~~~ - -**Community Impact**: Use of inappropriate language or other behavior -deemed unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, -providing clarity around the nature of the violation and an explanation -of why the behavior was inappropriate. A public apology may be -requested. - -2. Warning -~~~~~~~~~~ - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, for a specified period of -time. This includes avoiding interactions in community spaces as well as -external channels like social media. Violating these terms may lead to a -temporary or permanent ban. - -3. Temporary Ban -~~~~~~~~~~~~~~~~ - -**Community Impact**: A serious violation of community standards, -including sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No -public or private interaction with the people involved, including -unsolicited interaction with those enforcing the Code of Conduct, is -allowed during this period. Violating these terms may lead to a -permanent ban. - -4. Permanent Ban -~~~~~~~~~~~~~~~~ - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of -individuals. - -**Consequence**: A permanent ban from any sort of public interaction -within the project community. - -Attribution ------------ - -This Code of Conduct is adapted from the `Contributor -Covenant <https://www.contributor-covenant.org>`__, version 2.0, -available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by `Mozilla’s code of conduct -enforcement ladder <https://github.com/mozilla/diversity>`__. - -For answers to common questions about this code of conduct, see the FAQ -at https://www.contributor-covenant.org/faq. Translations are available -at https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index be12afc1552dc2b6d6cb398bc2a456392ef21b9d..0000000000000000000000000000000000000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,232 +0,0 @@ -Development principles and contribution guidelines -================================================== - -In order to create a high-quality software product, the AlekSIS developers -have agreed upon fundamental principles governing the code layout, coding -style and repository management for AlekSIS and all official apps. - - -Coding layout and style ------------------------ - -The coding style is defined in `PEP 8`_, with the following differences and -decisions: - -- The defaults of the `black`_ code formatter are used - - This implies all string literals usin double-quotes, if it does not lead - to more escaping. As proposed by `black`: "My recommendation here is to - keep using whatever is faster to type and let Black handle the transformation." -- The maximum line length is 100 characters -- Imports are structured in five blocks, each of them sorted as defined in - PEP 8 and the Django style guide: - - 1. Standard library imports - 2. Django imports - 3. Third-party imports - 4. Imports from AlekSIS core and other apps (absolute imports) - 5. Imports from the same AlekSIS app (realtive imports) - - Use `isort` to take care of this - -For the layout of source trees and style recommendations specific to Django, -the `Django coding style`_ is a good source of information, together with -the `Django Best Practices`_ collection. - -To ensure code is styled correctly, before commiting, run:: - - tox -e reformat - -Text documents -~~~~~~~~~~~~~~ - -If there is no objective reason against it, all text documents accompanying -the source use `reStructuredText`_. - - -Working with the Git repository -------------------------------- - -The Git repository shall be used as a historic documentation of development -and as change management. It is important that the Git commit history -describes what was changed, by whom and why. - -Help and information on Git for beginners are available in the `Git guide`_ - -Feature and issue branches -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -All features and bug fixes should be developed in their own branch and later -merged into the master branch as a whole. Of course, sometimes, it is -sensible to not do that, e.g. for fixing mere typos and the like. - -Within the feature branch, every logical step should be commited separately. -It is neither required nor desired to do micro-commits about every -development step. The commit history should describe the trains of thought -the design and implementation is based on. - -If you work on multiple issues at the same time, you have to change between -branches. Never work on unrelated issues in the same branch. - -Branches should either contain the number and title of the related issue (as -generated by GitLab), or follow the naming convention type/name, where type -is one of bugfix, feature, or refactor. - -All changes on the code should be commited and pushed before stopping work on -in order to prevent data loss. If a logical step is continued later, you -should amend and force-push the commit. - -Issue branches should be rebased onto the current master regularly to avoid -merge conflicts. - -Commit messages -~~~~~~~~~~~~~~~ - -Commit messages should be written as described in `How to Write a Git Commit -Message`_. - -Commit messages should mention or even close any related issues. For merely -mentioning progress on an issue, use the keyword `advances`; for closing an -issue, use `closes`; for referring to a related issue for informational -purposes, use `cf.`. This should be done in the body of the commit message. - -The subject of a commit message can (and should) be prepended with a tag in -square brackets if it relates to a certain part of the repository, e.g. [CI] -when changing CI/CD configuration or support code, [Dev] when changing -something in the development utilities, etc. - -Example:: - - Solve LDAP connection problems - - - Add the ldap-with-unicorn-dust dependency - - Configure settings.py to accept the correct groups from LDAP - - Closes #10. - -Merge Requests -~~~~~~~~~~~~~~ - -If you think that the work on your feature branch is finished, you have to -create a merge request on EduGit in order to let other developers and the -maintainers take a look at it. - -See below on how to submit patches if you cannot use the development -platform. - -Manifestos governing development --------------------------------- - -The FOSS community has created some manifestos describing several aspects of -software development, to agree upon a baseline for these aspects. The -AlekSIS developers have agreed to adhere to the following manifestos: - -- The `Sane software manifesto`_ -- The `Accessibility Manifesto`_ -- The `User Data Manifesto`_ - -Not all theses from these manifestos are applicable. For example, most data -about persons in a school information system are dictated by the school and -probably governed by laws defining what and when to store. In that case, -giving the user control over these decisions is not possible. Developers -need to decide what should resonably be followed. - -The case on supporting non-free services -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Defined by the `Free Software Definition`_, it is an essential freedom to -be allowed to use free software for any purpose, without limitation. Thus, -interoperability with non-free services shall not be ruled out, and the -AlekSIS project explicitly welcomes implementing support for -interoperability with non-free services. - -However, to purposefully foster free software and services, if -interoperability for a certain kind of non-free service is implemented, this -must be done in a generalised manner (i.e. using open protocols and -interfaces). For example, if implementing interoperability with some -cloud-hosted calendar provider can be implemented either through a -proprietary API, or through a standard iCalendar/Webcal interfaces, the -latter is to be preferred. Lacking such support, if a proprietary service -is connected through a proprietary, single-purpose interface, measures shall -be taken to also support alternative free services. - - -Documentation -------------- - -The documentation in the AlekSIS project shall consist of three layers. - -Source code comments -~~~~~~~~~~~~~~~~~~~~ - -The parts of your code that are not self-explaining have to be commented. -Ideally, source code is self-explaining, in the sense that its logical -structure, naming of variables, and the like makes it easy to read and -understand, for a reasonably talented programmer, to follow what it does. - -Docstrings -~~~~~~~~~~ - -All functions, methods, classes and modules that are newly added (or changed -extensively) must contain a docstring for other developers to understand -what it does. Docstrings of public elements will be included in the -developer documentation. - -Sphinx documentation -~~~~~~~~~~~~~~~~~~~~ - -In addition to that you should document the function or the way the app -works in the project documentation (`docs/` directory). Use that especially -for functionality which is shared by your app for other apps (public APIs). - -Your Sphinx documentation should contain what the API can and shall be sued -for, and how other apps can benefit from it. - -When creating a new app, also include documentation about it targeted at -administrators and users. At least you have to document what new developers -and users have to do in order to get a working instance of the app. - -Sphinx documentation for all official apps will be published together. - - -Contributing to upstream ------------------------- - -If possible and reasonable, code that can be of use to others in the general -Django ecosystem shall be contributed to any upstream dependency, or a new -generalised upstream dependency be created, under the most permissive -licence possible. - - -How to contact the team ------------------------ - -Development platform -~~~~~~~~~~~~~~~~~~~~ - -Main development of AlekSIS is done on the `EduGit`_ platform in the -`AlekSIS group`_ and discussions are held on the linked `Mattermost team`_. - -All platforms and tools mandated for development are free software and -freely usable. EduGit accepts a variety of sources for login, so -contributors are free to decide where they want to register in order to -participate. - -If any contributor cannot use the platforms for whatever reasons, patches and -questions directed at the developers can also be e-mailed to -<aleksis-dev@lists.teckids.org>. - - -.. _PEP 8: https://pep8.org/ -.. _Django coding style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/ -.. _black: https://black.readthedocs.io/en/stable/ -.. _Django Best Practices: https://django-best-practices.readthedocs.io/en/latest/index.html -.. _Git guide: https://rogerdudler.github.io/git-guide/ -.. _How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/ -.. _Sane software manifesto: https://sane-software.globalcode.info/ -.. _Accessibility Manifesto: http://accessibilitymanifesto.com/ -.. _User Data Manifesto: https://userdatamanifesto.org/ -.. _Free Software Definition: https://www.gnu.org/philosophy/free-sw.en.html -.. _reStructuredText: http://docutils.sourceforge.net/rst.html -.. _EduGit: https://edugit.org/ -.. _AlekSIS group: https://edugit.org/AlekSIS/ -.. _Mattermost team: https://mattermost.edugit.org/biscuit/ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0c4f3b52ededb87de3ea36f7814d155aaa20026d..0000000000000000000000000000000000000000 --- a/Dockerfile +++ /dev/null @@ -1,97 +0,0 @@ -FROM python:3.8-buster AS core - -# 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 - -# 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 - -# Install necessary Debian 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 \ - gettext \ - libpq5 \ - libpq-dev \ - libssl-dev \ - netcat-openbsd \ - yarnpkg - -# Install core -WORKDIR /usr/src/app -COPY LICENCE.rst README.rst manage.py poetry.lock pyproject.toml ./ -COPY aleksis ./aleksis/ -RUN set -e; \ - mkdir -p /var/lib/aleksis/media /usr/share/aleksis/static /var/lib/aleksis/backups; \ - eatmydata pip install poetry; \ - poetry config virtualenvs.create false; \ - eatmydata poetry install; \ - eatmydata pip install gunicorn django-compressor - -# Declare a persistent volume for all data -VOLUME /var/lib/aleksis - -# Define entrypoint and gunicorn running on port 8000 -EXPOSE 8000 -COPY docker/entrypoint.sh /usr/local/bin/ -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] - -# Install core extras -FROM core AS core-extras -ARG EXTRA_LDAP=1 -ARG EXTRA_CELERY=1 -WORKDIR /usr/src/app - -# LDAP -RUN if [ $EXTRA_LDAP = 1 ] ; then \ - eatmydata apt-get install -y --no-install-recommends \ - libldap2-dev \ - libsasl2-dev \ - ldap-utils; \ - eatmydata poetry install -E ldap; \ - fi; - -# Celery -RUN if [ $EXTRA_CELERY = 1 ] ; then \ - eatmydata poetry install -E celery; \ - fi; - -# Install official apps -FROM core-extras AS apps -COPY apps ./apps/ -RUN set -e; \ - for d in apps/official/*; do \ - cd $d; \ - rm -rf .git; \ - poetry install; \ - cd ../../..; \ - done - -# Build messages and assets -FROM apps as assets -RUN eatmydata python manage.py compilemessages && \ - eatmydata python manage.py yarn install && \ - eatmydata python manage.py collectstatic --no-input --clear - -# Clean up build dependencies -FROM assets AS clean -RUN set -e; \ - eatmydata apt-get remove --purge -y \ - build-essential \ - gettext \ - libpq-dev \ - libssl-dev \ - yarnpkg; \ - eatmydata apt-get autoremove --purge -y; \ - apt-get clean -y; \ - eatmydata pip uninstall -y poetry; \ - rm -f /var/lib/apt/lists/*_*; \ - rm -rf /root/.cache diff --git a/README.rst b/README.rst index e5d0251f38d496da608ba3c99526b31678378caf..664d44feccd825af419d30650fa31a27e97234c2 100644 --- a/README.rst +++ b/README.rst @@ -1,38 +1,16 @@ -AlekSIS — All-libre extensible kit for school information systems -================================================================= +AlekSIS (School Information System) — Core (Core functionality and app framework) +================================================================================= -Warning -------- - -**This is an alpha version of AlekSIS, the free school information system. -The AlekSIS team is looking for schools who want to help shape the 2.0 -final release and supports interested schools in operating AlekSIS.** - -What AlekSIS is ----------------- - -`AlekSIS`_ is a web-based school information system (SIS) which can be used to -manage and/or publish organisational subjects of educational institutions. - -Formerly two separate projects (BiscuIT and SchoolApps), developed by -`Teckids e.V.`_ and a team of students at `Katharineum zu Lübeck`_, they -were merged into the AlekSIS project in 2020. +AlekSIS standard distribution +----------------------------- -AlekSIS is a platform based on Django, that provides central funstions -and data structures that can be used by apps that are developed and provided -seperately. The AlekSIS team also maintains a set of official apps which -make AlekSIS a fully-featured software solutions for the information -management needs of schools. +The AlekSIS standard distribution with information about all official apps +can be found on `EduGit`_. -By design, the platform can be used by schools to write their own apps for -specific needs they face, also in coding classes. Students are empowered to -create real-world applications that bring direct value to their environment. +Features +-------- -AlekSIS is part of the `schul-frei`_ project as a component in sustainable -educational networks. - -Core features --------------- +The AlekSIS-Core currently provides the following features: * For users: @@ -53,24 +31,6 @@ Core features * Authentication via LDAP * Automatic backup of database, static and media files -Official apps -------------- - -+--------------------------------------+---------------------------------------------------------------------------------------------+ -| App name | Purpose | -+======================================+=============================================================================================+ -| `AlekSIS-App-Chronos`_ | The Chronos app provides functionality for digital timetables. | -+--------------------------------------+---------------------------------------------------------------------------------------------+ -| `AlekSIS-App-DashboardFeeds`_ | The DashboardFeeds app provides functionality to add RSS or Atom feeds to dashboard | -+--------------------------------------+---------------------------------------------------------------------------------------------+ -| `AlekSIS-App-Hjelp`_ | The Hjelp app provides functionality for aiding users. | -+--------------------------------------+---------------------------------------------------------------------------------------------+ -| `AlekSIS-App-LDAP`_ | The LDAP app provides functionality to import users and groups from LDAP | -+--------------------------------------+---------------------------------------------------------------------------------------------+ -| `AlekSIS-App-Untis`_ | This app provides import and export functions to interact with Untis, a timetable software. | -+--------------------------------------+---------------------------------------------------------------------------------------------+ - - Licence ------- @@ -91,13 +51,6 @@ full licence text or on the `European Union Public Licence`_ website https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers (including all other official language versions). -.. _AlekSIS: https://aleksis.org/ -.. _Teckids e.V.: https://www.teckids.org/ -.. _Katharineum zu Lübeck: https://www.katharineum.de/ +.. _AlekSIS: https://edugit.org/AlekSIS/Official/AlekSIS .. _European Union Public Licence: https://eupl.eu/ -.. _schul-frei: https://schul-frei.org/ -.. _AlekSIS-App-Chronos: https://edugit.org/AlekSIS/official/AlekSIS-App-Chronos -.. _AlekSIS-App-DashboardFeeds: https://edugit.org/AlekSIS/official/AlekSIS-App-DashboardFeeds -.. _AlekSIS-App-Hjelp: https://edugit.org/AlekSIS/official/AlekSIS-App-Hjelp -.. _AlekSIS-App-LDAP: https://edugit.org/AlekSIS/official/AlekSIS-App-LDAP -.. _AlekSIS-App-Untis: https://edugit.org/AlekSIS/official/AlekSIS-App-Untis +.. _EduGit: https://edugit.org/AlekSIS/official/AlekSIS diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index e627b0a2e6b93a6e661cdceaef21ec824e4daedb..e88d812bde928fcdc2f38027b8323aa8f09ad07b 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -2,7 +2,7 @@ from typing import Any, List, Optional, Tuple import django.apps from django.conf import settings -from django.db import ProgrammingError +from django.db import OperationalError, ProgrammingError from django.http import HttpRequest from django.utils.module_loading import autodiscover_modules @@ -67,7 +67,7 @@ class CoreConfig(AppConfig): for backend in get_site_preferences()["auth__backends"]: settings._wrapped.AUTHENTICATION_BACKENDS.insert(idx, backend) idx += 1 - except ProgrammingError: + except (ProgrammingError, OperationalError): pass def preference_updated( diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py index e1c0be7ef4f28e609c93f3b58208aa0ea4d58b3a..f4599d4d0e13ac02d01fda147331a3997d0f8dd0 100644 --- a/aleksis/core/forms.py +++ b/aleksis/core/forms.py @@ -5,12 +5,20 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ -from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget +from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget from dynamic_preferences.forms import PreferenceForm from material import Fieldset, Layout, Row from .mixins import ExtensibleForm, SchoolTermRelatedExtensibleForm -from .models import AdditionalField, Announcement, Group, GroupType, Person, SchoolTerm +from .models import ( + AdditionalField, + Announcement, + DashboardWidget, + Group, + GroupType, + Person, + SchoolTerm, +) from .registries import ( group_preferences_registry, person_preferences_registry, @@ -24,7 +32,7 @@ class PersonAccountForm(forms.ModelForm): class Meta: model = Person fields = ["last_name", "first_name", "user"] - widgets = {"user": Select2Widget} + widgets = {"user": Select2Widget(attrs={"class": "browser-default"})} new_user = forms.CharField(required=False) @@ -107,7 +115,19 @@ class EditPersonForm(ExtensibleForm): "primary_group", ] widgets = { - "user": Select2Widget, + "user": Select2Widget(attrs={"class": "browser-default"}), + "primary_group": ModelSelect2Widget( + search_fields=["name__icontains", "short_name__icontains"], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), + "guardians": ModelSelect2MultipleWidget( + search_fields=[ + "first_name__icontains", + "last_name__icontains", + "short_name__icontains", + ], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), } new_user = forms.CharField( @@ -138,19 +158,25 @@ class EditGroupForm(SchoolTermRelatedExtensibleForm): "first_name__icontains", "last_name__icontains", "short_name__icontains", - ] + ], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, ), "owners": ModelSelect2MultipleWidget( search_fields=[ "first_name__icontains", "last_name__icontains", "short_name__icontains", - ] + ], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, ), "parent_groups": ModelSelect2MultipleWidget( - search_fields=["name__icontains", "short_name__icontains"] + search_fields=["name__icontains", "short_name__icontains"], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), + "additional_fields": ModelSelect2MultipleWidget( + search_fields=["title__icontains",], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, ), - "additional_fields": ModelSelect2MultipleWidget(search_fields=["title__icontains",]), } @@ -167,9 +193,27 @@ class AnnouncementForm(ExtensibleForm): valid_until_time = forms.TimeField(label=_("Time")) persons = forms.ModelMultipleChoiceField( - Person.objects.all(), label=_("Persons"), required=False + queryset=Person.objects.all(), + label=_("Persons"), + required=False, + widget=ModelSelect2MultipleWidget( + search_fields=[ + "first_name__icontains", + "last_name__icontains", + "short_name__icontains", + ], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), + ) + groups = forms.ModelMultipleChoiceField( + queryset=None, + label=_("Groups"), + required=False, + widget=ModelSelect2MultipleWidget( + search_fields=["name__icontains", "short_name__icontains",], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), ) - groups = forms.ModelMultipleChoiceField(queryset=None, label=_("Groups"), required=False) layout = Layout( Fieldset( @@ -309,3 +353,20 @@ class SchoolTermForm(ExtensibleForm): class Meta: model = SchoolTerm exclude = [] + + +class DashboardWidgetOrderForm(ExtensibleForm): + pk = forms.ModelChoiceField( + queryset=DashboardWidget.objects.all(), + widget=forms.HiddenInput(attrs={"class": "pk-input"}), + ) + order = forms.IntegerField(initial=0, widget=forms.HiddenInput(attrs={"class": "order-input"})) + + class Meta: + model = DashboardWidget + fields = [] + + +DashboardWidgetOrderFormSet = forms.formset_factory( + form=DashboardWidgetOrderForm, max_num=0, extra=0 +) diff --git a/aleksis/core/locale/ar/LC_MESSAGES/django.po b/aleksis/core/locale/ar/LC_MESSAGES/django.po index 92ad8782664c5f8ec329b96d4054bdc057596d40..a3e781c6b389c7e3c6814fe9491358e6810b1b41 100644 --- a/aleksis/core/locale/ar/LC_MESSAGES/django.po +++ b/aleksis/core/locale/ar/LC_MESSAGES/django.po @@ -1506,8 +1506,8 @@ msgstr "" msgid "" "\n" " To start using a token generator, please use your\n" -" smartphone to scan the QR code below. For example, use Google\n" -" Authenticator. Then, enter the token generated by the app.\n" +" favourite two factor authentication (TOTP) app to scan the QR code below.\n" +" Then, enter the token generated by the app.\n" " " msgstr "" diff --git a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po index e59806834f65d4acced6f3adea79b24ccf3b2f3f..6f6d8fc83f732fbd2abdedbb8f4793e2591c1ca1 100644 --- a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po +++ b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: AlekSIS (School Information System) 0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-08-02 16:29+0200\n" -"PO-Revision-Date: 2020-08-02 15:00+0000\n" +"PO-Revision-Date: 2020-12-19 12:57+0000\n" "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n" "Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis/" "de/>\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.0.1\n" +"X-Generator: Weblate 4.3.2\n" #: filters.py:37 templates/core/base.html:77 templates/core/group/list.html:20 #: templates/core/person/list.html:24 templates/search/search.html:7 @@ -412,7 +412,7 @@ msgstr "Kann Kindgruppen zu Gruppen zuordnen" #: models.py:330 msgid "Long name" -msgstr "Langer Name" +msgstr "Langname" #: models.py:340 templates/core/group/full.html:65 msgid "Members" @@ -1655,14 +1655,14 @@ msgstr "" msgid "" "\n" " To start using a token generator, please use your\n" -" smartphone to scan the QR code below. For example, use Google\n" -" Authenticator. Then, enter the token generated by the app.\n" +" favourite two factor authentication (TOTP) app to scan the QR code below.\n" +" Then, enter the token generated by the app.\n" " " msgstr "" "\n" -" Um mit dem Codegenerator zu starten, benutzen Sie bitte Ihr Smartphone,\n" -"um diesen QR-Code zu scannen (z. B. den Google Authenticator). Dann geben Sie \n" -"den in der App angezeigten Code an:\n" +" Um mit dem Codegenerator zu starten, benutzen Sie bitte Ihre\n" +"App für Zwei-Faktor-Authentifizierung (TOTP), um diesen QR-Code zu scannen.\n" +"Dann geben Sie den in der App angezeigten Code an:\n" " " #: templates/two_factor/core/setup.html:34 diff --git a/aleksis/core/locale/fr/LC_MESSAGES/django.po b/aleksis/core/locale/fr/LC_MESSAGES/django.po index d800a36f8aa7db3f7279e2ddfc496e3a4f10ff8f..a8556727d98887123c05abb853a0bd46e829ead4 100644 --- a/aleksis/core/locale/fr/LC_MESSAGES/django.po +++ b/aleksis/core/locale/fr/LC_MESSAGES/django.po @@ -1558,8 +1558,8 @@ msgstr "" msgid "" "\n" " To start using a token generator, please use your\n" -" smartphone to scan the QR code below. For example, use Google\n" -" Authenticator. Then, enter the token generated by the app.\n" +" favourite two factor authentication (TOTP) app to scan the QR code below.\n" +" Then, enter the token generated by the app.\n" " " msgstr "" diff --git a/aleksis/core/locale/la/LC_MESSAGES/django.po b/aleksis/core/locale/la/LC_MESSAGES/django.po index 0fe692359d53218a555763890f363f7c9ed9e382..6fa38602c123109a48b5f34b07415b76a666f5a5 100644 --- a/aleksis/core/locale/la/LC_MESSAGES/django.po +++ b/aleksis/core/locale/la/LC_MESSAGES/django.po @@ -8,27 +8,26 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-08-02 16:29+0200\n" -"PO-Revision-Date: 2020-04-27 13:03+0000\n" +"PO-Revision-Date: 2020-12-19 12:57+0000\n" "Last-Translator: Julian <leuckerj@gmail.com>\n" -"Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis/la/>\n" +"Language-Team: Latin <https://translate.edugit.org/projects/aleksis/aleksis/" +"la/>\n" "Language: la\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.0.1\n" +"X-Generator: Weblate 4.3.2\n" #: filters.py:37 templates/core/base.html:77 templates/core/group/list.html:20 #: templates/core/person/list.html:24 templates/search/search.html:7 #: templates/search/search.html:22 msgid "Search" -msgstr "" +msgstr "Quaerere" #: filters.py:53 -#, fuzzy -#| msgid "Short name" msgid "Search by name" -msgstr "Breve nomen" +msgstr "Quaerere cum breve nomine" #: filters.py:65 #, fuzzy @@ -249,7 +248,7 @@ msgstr "" #: models.py:36 msgid "Date and time" -msgstr "" +msgstr "Dies et hora" #: models.py:37 msgid "Decimal number" @@ -1631,8 +1630,8 @@ msgstr "" msgid "" "\n" " To start using a token generator, please use your\n" -" smartphone to scan the QR code below. For example, use Google\n" -" Authenticator. Then, enter the token generated by the app.\n" +" favourite two factor authentication (TOTP) app to scan the QR code below.\n" +" Then, enter the token generated by the app.\n" " " msgstr "" diff --git a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po index 6cfc5ee7e1a2e39ea35727fd66d5bd22d8e8b235..5be24e272186dd1985d749b948562bb13f222bc7 100644 --- a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po +++ b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po @@ -1505,8 +1505,8 @@ msgstr "" msgid "" "\n" " To start using a token generator, please use your\n" -" smartphone to scan the QR code below. For example, use Google\n" -" Authenticator. Then, enter the token generated by the app.\n" +" favourite two factor authentication (TOTP) app to scan the QR code below.\n" +" Then, enter the token generated by the app.\n" " " msgstr "" diff --git a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po index d1efaccfa330d6f6989f9baf39bca163cd3cadf2..d0417b32bd8aa0df398290eacf5a23491641abd2 100644 --- a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po +++ b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po @@ -1505,8 +1505,8 @@ msgstr "" msgid "" "\n" " To start using a token generator, please use your\n" -" smartphone to scan the QR code below. For example, use Google\n" -" Authenticator. Then, enter the token generated by the app.\n" +" favourite two factor authentication (TOTP) app to scan the QR code below.\n" +" Then, enter the token generated by the app.\n" " " msgstr "" diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py index 2e71c357384c1bc7e4ffa4ff1485f4edc4ffc7de..9b1c42e931235333fb8d4a9dca55cd7f977b47de 100644 --- a/aleksis/core/menus.py +++ b/aleksis/core/menus.py @@ -93,6 +93,17 @@ MENUS = { ), ], }, + { + "name": _("Dashboard widgets"), + "url": "dashboard_widgets", + "icon": "dashboard", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "core.view_dashboardwidget", + ), + ], + }, { "name": _("Data management"), "url": "data_management", diff --git a/aleksis/core/migrations/0001_initial.py b/aleksis/core/migrations/0001_initial.py index 4dd6486f478a00e23a42876d302bbbe954648084..9999e1a6615138251c1745d4e33b3d9d404d0c7b 100644 --- a/aleksis/core/migrations/0001_initial.py +++ b/aleksis/core/migrations/0001_initial.py @@ -8,7 +8,6 @@ import django.contrib.postgres.fields.jsonb import django.contrib.sites.managers from django.db import migrations, models import django.db.models.deletion -import image_cropping.fields import phonenumber_field.modelfields @@ -125,8 +124,8 @@ class Migration(migrations.Migration): ('email', models.EmailField(blank=True, max_length=254, verbose_name='E-mail address')), ('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of birth')), ('sex', models.CharField(blank=True, choices=[('f', 'female'), ('m', 'male')], max_length=1, verbose_name='Sex')), - ('photo', image_cropping.fields.ImageCropField(blank=True, null=True, upload_to='', verbose_name='Photo')), - ('photo_cropping', image_cropping.fields.ImageRatioField('photo', '600x800', adapt_rotation=False, allow_fullsize=False, free_crop=False, help_text=None, hide_image_field=False, size_warning=True, verbose_name='photo cropping')), + ('photo', models.CharField(blank=True, max_length=1)), + ('photo_cropping', models.CharField(blank=True, max_length=1)), ('description', models.TextField(blank=True, verbose_name='Description')), ('guardians', models.ManyToManyField(blank=True, related_name='children', to='core.Person', verbose_name='Guardians / Parents')), ('primary_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.Group', verbose_name='Primary group')), diff --git a/aleksis/core/migrations/0004_add_permissions_for_group_stats.py b/aleksis/core/migrations/0004_add_permissions_for_group_stats.py new file mode 100644 index 0000000000000000000000000000000000000000..ab0e6bf5fbad7af778589c0cf3c64076dfba4fe3 --- /dev/null +++ b/aleksis/core/migrations/0004_add_permissions_for_group_stats.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.7 on 2020-06-28 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_drop_image_cropping'), + ] + + operations = [ + migrations.AlterModelOptions( + name='group', + options={'ordering': ['short_name', 'name'], 'permissions': (('assign_child_groups_to_groups', 'Can assign child groups to groups'), ('view_group_stats', 'Can view statistics about group.')), 'verbose_name': 'Group', 'verbose_name_plural': 'Groups'}, + ), + migrations.AlterField( + model_name='group', + name='additional_fields', + field=models.ManyToManyField(blank=True, to='core.AdditionalField', verbose_name='Additional fields'), + ), + ] diff --git a/aleksis/core/migrations/0005_timestamped_activity_notification.py b/aleksis/core/migrations/0005_timestamped_activity_notification.py new file mode 100644 index 0000000000000000000000000000000000000000..611c55196dc8c703662380e7c73ca0ae20a42059 --- /dev/null +++ b/aleksis/core/migrations/0005_timestamped_activity_notification.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.3 on 2020-11-16 13:04 + +from django.db import migrations, models +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_add_permissions_for_group_stats'), + ] + + operations = [ + migrations.AddField( + model_name='activity', + name='created', + field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), + ), + migrations.AddField( + model_name='activity', + name='modified', + field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), + ), + migrations.AddField( + model_name='notification', + name='created', + field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), + ), + migrations.AddField( + model_name='notification', + name='modified', + field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), + ), + ] diff --git a/aleksis/core/migrations/0006_dashboard_widget_size.py b/aleksis/core/migrations/0006_dashboard_widget_size.py new file mode 100644 index 0000000000000000000000000000000000000000..713ff1792ce87e4124025a34561cd602004ea8c7 --- /dev/null +++ b/aleksis/core/migrations/0006_dashboard_widget_size.py @@ -0,0 +1,36 @@ +# Generated by Django 3.1.4 on 2020-12-20 15:55 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_timestamped_activity_notification'), + ] + + operations = [ + migrations.AddField( + model_name='dashboardwidget', + name='size_l', + field=models.PositiveSmallIntegerField(default=6, help_text='> 992 px, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on desktop devices'), + ), + migrations.AddField( + model_name='dashboardwidget', + name='size_m', + field=models.PositiveSmallIntegerField(default=12, help_text='> 600 px, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on tablet devices'), + ), + migrations.AddField( + model_name='dashboardwidget', + name='size_s', + field=models.PositiveSmallIntegerField(default=12, help_text='<= 600 px, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on mobile devices'), + ), + migrations.AddField( + model_name='dashboardwidget', + name='size_xl', + field=models.PositiveSmallIntegerField(default=4, help_text='> 1200 px>, 12 columns', validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Size on large desktop devices'), + ), + ] diff --git a/aleksis/core/migrations/0007_dashboard_widget_order.py b/aleksis/core/migrations/0007_dashboard_widget_order.py new file mode 100644 index 0000000000000000000000000000000000000000..9387f4ca7c0dbb3307d2a3681f091e4bf631f847 --- /dev/null +++ b/aleksis/core/migrations/0007_dashboard_widget_order.py @@ -0,0 +1,35 @@ +# Generated by Django 3.1.4 on 2020-12-21 13:38 + +import django.contrib.sites.managers +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('core', '0006_dashboard_widget_size'), + ] + + operations = [ + migrations.CreateModel( + name='DashboardWidgetOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('extended_data', models.JSONField(default=dict, editable=False)), + ('order', models.PositiveIntegerField(verbose_name='Order')), + ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.person', verbose_name='Person')), + ('site', models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.site')), + ('widget', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.dashboardwidget', verbose_name='Dashboard widget')), + ], + options={ + 'verbose_name': 'Dashboard widget order', + 'verbose_name_plural': 'Dashboard widget orders', + }, + managers=[ + ('objects', django.contrib.sites.managers.CurrentSiteManager()), + ], + ), + ] diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index 9926457faa08b33e8ec4c0c23f95dbee664cf9c8..f3edd31338ebf5fce1dd915aed3cdb373c71086a 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.sites.managers import CurrentSiteManager from django.contrib.sites.models import Site from django.db import models -from django.db.models import QuerySet +from django.db.models import JSONField, QuerySet from django.forms.forms import BaseForm from django.forms.models import ModelForm, ModelFormMetaclass from django.http import HttpResponse @@ -19,9 +19,8 @@ from django.views.generic import CreateView, UpdateView from django.views.generic.edit import DeleteView, ModelFormMixin import reversion -from easyaudit.models import CRUDEvent from guardian.admin import GuardedModelAdmin -from jsonstore.fields import IntegerField, JSONField, JSONFieldMixin +from jsonstore.fields import IntegerField, JSONFieldMixin from material.base import Layout, LayoutNode from rules.contrib.admin import ObjectPermissionsModelAdmin @@ -54,8 +53,7 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase): This base model ensures all objects in AlekSIS apps fulfill the following properties: - * crud_events property to retrieve easyaudit's CRUD event log - * created_at and updated_at properties based n CRUD events + * `versions` property to retrieve all versions of the model from reversion * Allow injection of fields and code from AlekSIS apps to extend model functionality. @@ -109,50 +107,30 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase): pass @property - def crud_events(self) -> QuerySet: - """Get all CRUD events connected to this object from easyaudit.""" - content_type = ContentType.objects.get_for_model(self) + def versions(self) -> List[Tuple[str, Tuple[Any, Any]]]: + """Get all versions of this object from django-reversion. - return CRUDEvent.objects.filter( - object_id=self.pk, content_type=content_type - ).select_related("user", "user__person") + Includes diffs to previous version. + """ + versions = reversion.models.Version.objects.get_for_object(self) - @property - def crud_event_create(self) -> Optional[CRUDEvent]: - """Return create event of this object.""" - return self.crud_events.filter(event_type=CRUDEvent.CREATE).latest("datetime") + versions_with_changes = [] + for i, version in enumerate(versions): + diff = {} + if i > 0: + prev_version = versions[i - 1] - @property - def crud_event_update(self) -> Optional[CRUDEvent]: - """Return last event of this object.""" - return self.crud_events.latest("datetime") + for k, val in version.field_dict.items(): + prev_val = prev_version.field_dict.get(k, None) + if prev_val != val: + diff[k] = (prev_val, val) - @property - def created_at(self) -> Optional[datetime]: - """Determine creation timestamp from CRUD log.""" - if self.crud_event_create: - return self.crud_event_create.datetime + versions_with_changes.append((version, diff)) - @property - def updated_at(self) -> Optional[datetime]: - """Determine last timestamp from CRUD log.""" - if self.crud_event_update: - return self.crud_event_update.datetime + return versions_with_changes extended_data = JSONField(default=dict, editable=False) - @property - def created_by(self) -> Optional[models.Model]: - """Determine user who created this object from CRUD log.""" - if self.crud_event_create: - return self.crud_event_create.user - - @property - def updated_by(self) -> Optional[models.Model]: - """Determine user who last updated this object from CRUD log.""" - if self.crud_event_update: - return self.crud_event_update.user - extended_data = JSONField(default=dict, editable=False) @classmethod @@ -366,11 +344,11 @@ class SuccessMessageMixin(ModelFormMixin): return super().form_valid(form) -class AdvancedCreateView(CreateView, SuccessMessageMixin): +class AdvancedCreateView(SuccessMessageMixin, CreateView): pass -class AdvancedEditView(UpdateView, SuccessMessageMixin): +class AdvancedEditView(SuccessMessageMixin, UpdateView): pass diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 2231fb36057c2c33edec8d6b4e1384e5693ac331..0f7a80d416cfe1dec30b57f2bf1e070e664a8966 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -9,18 +9,20 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.core.exceptions import ValidationError +from django.core.validators import MaxValueValidator from django.db import models, transaction from django.db.models import QuerySet from django.forms.widgets import Media from django.urls import reverse from django.utils import timezone -from django.utils.decorators import classproperty +from django.utils.functional import classproperty from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ import jsonstore from cache_memoize import cache_memoize from dynamic_preferences.models import PerInstancePreferenceModel +from model_utils.models import TimeStampedModel from phonenumber_field.modelfields import PhoneNumberField from polymorphic.models import PolymorphicModel @@ -221,16 +223,22 @@ class Person(ExtensibleModel): @property def age(self): """Age of the person at current time.""" - return self.age_at(timezone.datetime.now().date()) + return self.age_at(timezone.now().date()) def age_at(self, today): - """Age of the person at a given date and time.""" - years = today.year - self.date_of_birth.year - if self.date_of_birth.month > today.month or ( - self.date_of_birth.month == today.month and self.date_of_birth.day > today.day - ): - years -= 1 - return years + if self.date_of_birth: + years = today.year - self.date_of_birth.year + if self.date_of_birth.month > today.month or ( + self.date_of_birth.month == today.month and self.date_of_birth.day > today.day + ): + years -= 1 + return years + + @property + def dashboard_widgets(self): + return [ + w.widget for w in DashboardWidgetOrder.objects.filter(person=self).order_by("order") + ] def save(self, *args, **kwargs): super().save(*args, **kwargs) @@ -326,7 +334,10 @@ class Group(SchoolTermRelatedExtensibleModel): ordering = ["short_name", "name"] verbose_name = _("Group") verbose_name_plural = _("Groups") - permissions = (("assign_child_groups_to_groups", _("Can assign child groups to groups")),) + permissions = ( + ("assign_child_groups_to_groups", _("Can assign child groups to groups")), + ("view_group_stats", _("Can view statistics about group.")), + ) constraints = [ models.UniqueConstraint(fields=["school_term", "name"], name="unique_school_term_name"), models.UniqueConstraint( @@ -380,6 +391,22 @@ class Group(SchoolTermRelatedExtensibleModel): """Flat list of all members and owners to fulfill announcement API contract.""" return list(self.members.all()) + list(self.owners.all()) + @property + def get_group_stats(self) -> dict: + """ Get stats about a given group """ + stats = {} + + stats["members"] = len(self.members.all()) + + ages = [person.age for person in self.members.filter(date_of_birth__isnull=False)] + + if ages: + stats["age_avg"] = sum(ages) / len(ages) + stats["age_range_min"] = min(ages) + stats["age_range_max"] = max(ages) + + return stats + def __str__(self) -> str: if self.school_term: return f"{self.name} ({self.short_name}) ({self.school_term})" @@ -421,7 +448,7 @@ class PersonGroupThrough(ExtensibleModel): setattr(self, field_name, field_instance) -class Activity(ExtensibleModel): +class Activity(ExtensibleModel, TimeStampedModel): """Activity of a user to trace some actions done in AlekSIS in displayable form.""" user = models.ForeignKey( @@ -441,7 +468,7 @@ class Activity(ExtensibleModel): verbose_name_plural = _("Activities") -class Notification(ExtensibleModel): +class Notification(ExtensibleModel, TimeStampedModel): """Notification to submit to a user.""" sender = models.CharField(max_length=100, verbose_name=_("Sender")) @@ -494,7 +521,7 @@ class AnnouncementQuerySet(models.QuerySet): def at_time(self, when: Optional[datetime] = None) -> models.QuerySet: """Get all announcements at a certain time.""" - when = when or timezone.datetime.now() + when = when or timezone.now() # Get announcements by time announcements = self.filter(valid_from__lte=when, valid_until__gte=when) @@ -503,7 +530,7 @@ class AnnouncementQuerySet(models.QuerySet): def on_date(self, when: Optional[date] = None) -> models.QuerySet: """Get all announcements at a certain date.""" - when = when or timezone.datetime.now().date() + when = when or timezone.now().date() # Get announcements by time announcements = self.filter(valid_from__date__lte=when, valid_until__date__gte=when) @@ -542,7 +569,7 @@ class Announcement(ExtensibleModel): link = models.URLField(blank=True, verbose_name=_("Link to detailed view")) valid_from = models.DateTimeField( - verbose_name=_("Date and time from when to show"), default=timezone.datetime.now + verbose_name=_("Date and time from when to show"), default=timezone.now ) valid_until = models.DateTimeField( verbose_name=_("Date and time until when to show"), default=now_tomorrow, @@ -661,6 +688,31 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel): title = models.CharField(max_length=150, verbose_name=_("Widget Title")) active = models.BooleanField(verbose_name=_("Activate Widget")) + size_s = models.PositiveSmallIntegerField( + verbose_name=_("Size on mobile devices"), + help_text=_("<= 600 px, 12 columns"), + validators=[MaxValueValidator(12)], + default=12, + ) + size_m = models.PositiveSmallIntegerField( + verbose_name=_("Size on tablet devices"), + help_text=_("> 600 px, 12 columns"), + validators=[MaxValueValidator(12)], + default=12, + ) + size_l = models.PositiveSmallIntegerField( + verbose_name=_("Size on desktop devices"), + help_text=_("> 992 px, 12 columns"), + validators=[MaxValueValidator(12)], + default=6, + ) + size_xl = models.PositiveSmallIntegerField( + verbose_name=_("Size on large desktop devices"), + help_text=_("> 1200 px>, 12 columns"), + validators=[MaxValueValidator(12)], + default=4, + ) + def get_context(self): """Get the context dictionary to pass to the widget template.""" raise NotImplementedError("A widget subclass needs to implement the get_context method.") @@ -681,6 +733,18 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel): verbose_name_plural = _("Dashboard Widgets") +class DashboardWidgetOrder(ExtensibleModel): + widget = models.ForeignKey( + DashboardWidget, on_delete=models.CASCADE, verbose_name=_("Dashboard widget") + ) + person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person")) + order = models.PositiveIntegerField(verbose_name=_("Order")) + + class Meta: + verbose_name = _("Dashboard widget order") + verbose_name_plural = _("Dashboard widget orders") + + class CustomMenu(ExtensibleModel): """A custom menu to display in the footer.""" diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py index 150ab5f24f0bab113aa852369fc13b56acd2cf74..2f079e295fe57b4cd680208bdc6513966621e199 100644 --- a/aleksis/core/preferences.py +++ b/aleksis/core/preferences.py @@ -1,5 +1,6 @@ from django.conf import settings from django.forms import EmailField, ImageField, URLField +from django.forms.widgets import SelectMultiple from django.utils.translation import gettext_lazy as _ from dynamic_preferences.preferences import Section @@ -22,6 +23,7 @@ notification = Section("notification") footer = Section("footer") account = Section("account") auth = Section("auth", verbose_name=_("Authentication")) +internationalisation = Section("internationalisation", verbose_name=_("Internationalisation")) @site_preferences_registry.register @@ -230,3 +232,14 @@ class AuthenticationBackends(MultipleChoicePreference): def get_choices(self): return [(b, b) for b in settings.CUSTOM_AUTHENTICATION_BACKENDS] + + +@site_preferences_registry.register +class AvailableLanguages(MultipleChoicePreference): + section = internationalisation + name = "languages" + default = [code[0] for code in settings.LANGUAGES] + widget = SelectMultiple + verbose_name = _("Available languages") + field_attribute = {"initial": []} + choices = settings.LANGUAGES diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index 35b00f23770f80e3a5ca821393af3bf0956833f8..2e36bc08bf3a307c0b8a116a5731f0f4793f2475 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -1,4 +1,4 @@ -from rules import add_perm, always_allow +import rules from .models import AdditionalField, Announcement, Group, GroupType, Person from .util.predicates import ( @@ -11,32 +11,32 @@ from .util.predicates import ( is_notification_recipient, ) -add_perm("core", always_allow) +rules.add_perm("core", rules.always_allow) # View dashboard -add_perm("core.view_dashboard", has_person) +rules.add_perm("core.view_dashboard", has_person) # Use search search_predicate = has_person & has_global_perm("core.search") -add_perm("core.search", search_predicate) +rules.add_perm("core.search", search_predicate) # View persons view_persons_predicate = has_person & ( has_global_perm("core.view_person") | has_any_object("core.view_person", Person) ) -add_perm("core.view_persons", view_persons_predicate) +rules.add_perm("core.view_persons", view_persons_predicate) # View person view_person_predicate = has_person & ( has_global_perm("core.view_person") | has_object_perm("core.view_person") | is_current_person ) -add_perm("core.view_person", view_person_predicate) +rules.add_perm("core.view_person", view_person_predicate) # View person address view_address_predicate = has_person & ( has_global_perm("core.view_address") | has_object_perm("core.view_address") | is_current_person ) -add_perm("core.view_address", view_address_predicate) +rules.add_perm("core.view_address", view_address_predicate) # View person contact details view_contact_details_predicate = has_person & ( @@ -44,13 +44,13 @@ view_contact_details_predicate = has_person & ( | has_object_perm("core.view_contact_details") | is_current_person ) -add_perm("core.view_contact_details", view_contact_details_predicate) +rules.add_perm("core.view_contact_details", view_contact_details_predicate) # View person photo view_photo_predicate = has_person & ( has_global_perm("core.view_photo") | has_object_perm("core.view_photo") | is_current_person ) -add_perm("core.view_photo", view_photo_predicate) +rules.add_perm("core.view_photo", view_photo_predicate) # View persons groups view_groups_predicate = has_person & ( @@ -58,96 +58,96 @@ view_groups_predicate = has_person & ( | has_object_perm("core.view_person_groups") | is_current_person ) -add_perm("core.view_person_groups", view_groups_predicate) +rules.add_perm("core.view_person_groups", view_groups_predicate) # Edit person edit_person_predicate = has_person & ( has_global_perm("core.change_person") | has_object_perm("core.change_person") ) -add_perm("core.edit_person", edit_person_predicate) +rules.add_perm("core.edit_person", edit_person_predicate) # Delete person delete_person_predicate = has_person & ( has_global_perm("core.delete_person") | has_object_perm("core.delete_person") ) -add_perm("core.delete_person", delete_person_predicate) +rules.add_perm("core.delete_person", delete_person_predicate) # Link persons with accounts link_persons_accounts_predicate = has_person & has_global_perm("core.link_persons_accounts") -add_perm("core.link_persons_accounts", link_persons_accounts_predicate) +rules.add_perm("core.link_persons_accounts", link_persons_accounts_predicate) # View groups view_groups_predicate = has_person & ( has_global_perm("core.view_group") | has_any_object("core.view_group", Group) ) -add_perm("core.view_groups", view_groups_predicate) +rules.add_perm("core.view_groups", view_groups_predicate) # View group view_group_predicate = has_person & ( has_global_perm("core.view_group") | has_object_perm("core.view_group") ) -add_perm("core.view_group", view_group_predicate) +rules.add_perm("core.view_group", view_group_predicate) # Edit group edit_group_predicate = has_person & ( has_global_perm("core.change_group") | has_object_perm("core.change_group") ) -add_perm("core.edit_group", edit_group_predicate) +rules.add_perm("core.edit_group", edit_group_predicate) # Delete group delete_group_predicate = has_person & ( has_global_perm("core.delete_group") | has_object_perm("core.delete_group") ) -add_perm("core.delete_group", delete_group_predicate) +rules.add_perm("core.delete_group", delete_group_predicate) # Assign child groups to groups assign_child_groups_to_groups_predicate = has_person & has_global_perm( "core.assign_child_groups_to_groups" ) -add_perm("core.assign_child_groups_to_groups", assign_child_groups_to_groups_predicate) +rules.add_perm("core.assign_child_groups_to_groups", assign_child_groups_to_groups_predicate) # Edit school information edit_school_information_predicate = has_person & has_global_perm("core.change_school") -add_perm("core.edit_school_information", edit_school_information_predicate) +rules.add_perm("core.edit_school_information", edit_school_information_predicate) # Manage data manage_data_predicate = has_person & has_global_perm("core.manage_data") -add_perm("core.manage_data", manage_data_predicate) +rules.add_perm("core.manage_data", manage_data_predicate) # Mark notification as read mark_notification_as_read_predicate = has_person & is_notification_recipient -add_perm("core.mark_notification_as_read", mark_notification_as_read_predicate) +rules.add_perm("core.mark_notification_as_read", mark_notification_as_read_predicate) # View announcements view_announcements_predicate = has_person & ( has_global_perm("core.view_announcement") | has_any_object("core.view_announcement", Announcement) ) -add_perm("core.view_announcements", view_announcements_predicate) +rules.add_perm("core.view_announcements", view_announcements_predicate) # Create or edit announcement create_or_edit_announcement_predicate = has_person & ( has_global_perm("core.add_announcement") & (has_global_perm("core.change_announcement") | has_object_perm("core.change_announcement")) ) -add_perm("core.create_or_edit_announcement", create_or_edit_announcement_predicate) +rules.add_perm("core.create_or_edit_announcement", create_or_edit_announcement_predicate) # Delete announcement delete_announcement_predicate = has_person & ( has_global_perm("core.delete_announcement") | has_object_perm("core.delete_announcement") ) -add_perm("core.delete_announcement", delete_announcement_predicate) +rules.add_perm("core.delete_announcement", delete_announcement_predicate) # Use impersonate impersonate_predicate = has_person & has_global_perm("core.impersonate") -add_perm("core.impersonate", impersonate_predicate) +rules.add_perm("core.impersonate", impersonate_predicate) # View system status view_system_status_predicate = has_person & has_global_perm("core.view_system_status") -add_perm("core.view_system_status", view_system_status_predicate) +rules.add_perm("core.view_system_status", view_system_status_predicate) # View people menu (persons + objects) -add_perm( +rules.add_perm( "core.view_people_menu", has_person & ( @@ -164,14 +164,14 @@ view_personal_details_predicate = has_person & ( | has_object_perm("core.view_personal_details") | is_current_person ) -add_perm("core.view_personal_details", view_personal_details_predicate) +rules.add_perm("core.view_personal_details", view_personal_details_predicate) # Change site preferences change_site_preferences = has_person & ( has_global_perm("core.change_site_preferences") | has_object_perm("core.change_site_preferences") ) -add_perm("core.change_site_preferences", change_site_preferences) +rules.add_perm("core.change_site_preferences", change_site_preferences) # Change person preferences change_person_preferences = has_person & ( @@ -179,7 +179,7 @@ change_person_preferences = has_person & ( | has_object_perm("core.change_person_preferences") | is_current_person ) -add_perm("core.change_person_preferences", change_person_preferences) +rules.add_perm("core.change_person_preferences", change_person_preferences) # Change group preferences change_group_preferences = has_person & ( @@ -187,81 +187,81 @@ change_group_preferences = has_person & ( | has_object_perm("core.change_group_preferences") | is_group_owner ) -add_perm("core.change_group_preferences", change_group_preferences) +rules.add_perm("core.change_group_preferences", change_group_preferences) # Edit additional field change_additional_field_predicate = has_person & ( has_global_perm("core.change_additionalfield") | has_object_perm("core.change_additionalfield") ) -add_perm("core.change_additionalfield", change_additional_field_predicate) +rules.add_perm("core.change_additionalfield", change_additional_field_predicate) # Edit additional field create_additional_field_predicate = has_person & ( has_global_perm("core.create_additionalfield") | has_object_perm("core.create_additionalfield") ) -add_perm("core.create_additionalfield", create_additional_field_predicate) +rules.add_perm("core.create_additionalfield", create_additional_field_predicate) # Delete additional field delete_additional_field_predicate = has_person & ( has_global_perm("core.delete_additionalfield") | has_object_perm("core.delete_additionalfield") ) -add_perm("core.delete_additionalfield", delete_additional_field_predicate) +rules.add_perm("core.delete_additionalfield", delete_additional_field_predicate) # View additional fields view_additional_field_predicate = has_person & ( has_global_perm("core.view_additionalfield") | has_any_object("core.view_additionalfield", AdditionalField) ) -add_perm("core.view_additionalfield", view_additional_field_predicate) +rules.add_perm("core.view_additionalfield", view_additional_field_predicate) # Edit group type change_group_type_predicate = has_person & ( has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype") ) -add_perm("core.edit_grouptype", change_group_type_predicate) +rules.add_perm("core.edit_grouptype", change_group_type_predicate) # Create group type create_group_type_predicate = has_person & ( has_global_perm("core.create_grouptype") | has_object_perm("core.change_grouptype") ) -add_perm("core.create_grouptype", create_group_type_predicate) +rules.add_perm("core.create_grouptype", create_group_type_predicate) # Delete group type delete_group_type_predicate = has_person & ( has_global_perm("core.delete_grouptype") | has_object_perm("core.delete_grouptype") ) -add_perm("core.delete_grouptype", delete_group_type_predicate) +rules.add_perm("core.delete_grouptype", delete_group_type_predicate) # View group types view_group_type_predicate = has_person & ( has_global_perm("core.view_grouptype") | has_any_object("core.view_grouptype", GroupType) ) -add_perm("core.view_grouptype", view_group_type_predicate) +rules.add_perm("core.view_grouptype", view_group_type_predicate) # Create person create_person_predicate = has_person & ( has_global_perm("core.create_person") | has_object_perm("core.create_person") ) -add_perm("core.create_person", create_person_predicate) +rules.add_perm("core.create_person", create_person_predicate) # Create group create_group_predicate = has_person & ( has_global_perm("core.create_group") | has_object_perm("core.create_group") ) -add_perm("core.create_group", create_group_predicate) +rules.add_perm("core.create_group", create_group_predicate) # School years view_school_term_predicate = has_person & has_global_perm("core.view_schoolterm") -add_perm("core.view_schoolterm", view_school_term_predicate) +rules.add_perm("core.view_schoolterm", view_school_term_predicate) create_school_term_predicate = has_person & has_global_perm("core.add_schoolterm") -add_perm("core.create_schoolterm", create_school_term_predicate) +rules.add_perm("core.create_schoolterm", create_school_term_predicate) edit_school_term_predicate = has_person & has_global_perm("core.change_schoolterm") -add_perm("core.edit_schoolterm", edit_school_term_predicate) +rules.add_perm("core.edit_schoolterm", edit_school_term_predicate) # View admin menu view_admin_menu_predicate = has_person & ( @@ -271,4 +271,23 @@ view_admin_menu_predicate = has_person & ( | view_system_status_predicate | view_announcements_predicate ) -add_perm("core.view_admin_menu", view_admin_menu_predicate) +rules.add_perm("core.view_admin_menu", view_admin_menu_predicate) + +# View group stats +view_group_stats_predicate = has_person & ( + has_global_perm("core.view_group_stats") | has_object_perm("core.view_group_stats") +) +rules.add_perm("core.view_group_stats", view_group_stats_predicate) + + +view_dashboard_widget_predicate = has_person & has_global_perm("core.view_dashboardwidget") +rules.add_perm("core.view_dashboardwidget", view_dashboard_widget_predicate) + +create_dashboard_widget_predicate = has_person & has_global_perm("core.add_dashboardwidget") +rules.add_perm("core.create_dashboardwidget", create_dashboard_widget_predicate) + +edit_dashboard_widget_predicate = has_person & has_global_perm("core.change_dashboardwidget") +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) diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index 5eea8c972a3edb2510f944ed33cfa10bcc9ff31e..e8870dc656e05c566ab1e83131d7fc4c70e0b9d5 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -4,7 +4,6 @@ from glob import glob from django.utils.translation import gettext_lazy as _ from dynaconf import LazySettings -from easy_thumbnails.conf import settings as thumbnail_settings from .util.core_helpers import ( get_app_packages, @@ -45,6 +44,23 @@ DEBUG_TOOLBAR_CONFIG = { "SHOW_TOOLBAR_CALLBACK": "aleksis.core.util.core_helpers.dt_show_toolbar", } +DEBUG_TOOLBAR_PANELS = [ + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", + "debug_toolbar.panels.profiling.ProfilingPanel", +] + + ALLOWED_HOSTS = _settings.get("http.allowed_hosts", []) # Application definition @@ -65,16 +81,15 @@ INSTALLED_APPS = [ "dbbackup", "settings_context_processor", "sass_processor", - "easyaudit", "django_any_js", "django_yarnpkg", "django_tables2", - "easy_thumbnails", "maintenance_mode", "menu_generator", "reversion", "phonenumber_field", "debug_toolbar", + "django_prometheus", "django_select2", "hattori", "templated_email", @@ -89,6 +104,7 @@ INSTALLED_APPS = [ "health_check.cache", "health_check.storage", "health_check.contrib.psutil", + "health_check.contrib.migrations", "dynamic_preferences", "dynamic_preferences.users.apps.UserPreferencesConfig", "impersonate", @@ -100,6 +116,7 @@ INSTALLED_APPS = [ "colorfield", "django_bleach", "favicon", + "django_filters", ] merge_app_settings("INSTALLED_APPS", INSTALLED_APPS, True) @@ -114,6 +131,7 @@ STATICFILES_FINDERS = [ MIDDLEWARE = [ # 'django.middleware.cache.UpdateCacheMiddleware', + "django_prometheus.middleware.PrometheusBeforeMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", @@ -128,9 +146,9 @@ MIDDLEWARE = [ "impersonate.middleware.ImpersonateMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - "easyaudit.middleware.easyaudit.EasyAuditMiddleware", "maintenance_mode.middleware.MaintenanceModeMiddleware", "aleksis.core.util.middlewares.EnsurePersonMiddleware", + "django_prometheus.middleware.PrometheusAfterMiddleware", # 'django.middleware.cache.FetchFromCacheMiddleware' ] @@ -156,8 +174,6 @@ TEMPLATES = [ }, ] -THUMBNAIL_PROCESSORS = () + thumbnail_settings.THUMBNAIL_PROCESSORS - WSGI_APPLICATION = "aleksis.core.wsgi.application" # Database @@ -165,7 +181,7 @@ WSGI_APPLICATION = "aleksis.core.wsgi.application" DATABASES = { "default": { - "ENGINE": "django.db.backends.postgresql", + "ENGINE": "django_prometheus.db.backends.postgresql", "NAME": _settings.get("database.name", "aleksis"), "USER": _settings.get("database.username", "aleksis"), "PASSWORD": _settings.get("database.password", None), @@ -181,10 +197,15 @@ merge_app_settings("DATABASES", DATABASES, False) if _settings.get("caching.memcached.enabled", False): CACHES = { "default": { - "BACKEND": "django.core.cache.backends.memcached.MemcachedCache", + "BACKEND": "django_prometheus.cache.backends.memcached.MemcachedCache", "LOCATION": _settings.get("caching.memcached.address", "127.0.0.1:11211"), } } + 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") # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators @@ -331,6 +352,8 @@ YARN_INSTALLED_APPS = [ "select2", "select2-materialize", "paper-css", + "jquery-sortablejs", + "sortablejs", ] merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True) @@ -354,6 +377,8 @@ ANY_JS = { "css_url": JS_URL + "/select2-materialize/select2-materialize.css", "js_url": JS_URL + "/select2-materialize/index.js", }, + "sortablejs": {"js_url": JS_URL + "/sortablejs/dist/sortable.umd.js"}, + "jquery-sortablejs": {"js_url": JS_URL + "/jquery-sortablejs/jquery-sortable.js"}, } merge_app_settings("ANY_JS", ANY_JS, True) @@ -366,6 +391,7 @@ SASS_PROCESSOR_CUSTOM_FUNCTIONS = { } SASS_PROCESSOR_INCLUDE_DIRS = [ _settings.get("materialize.sass_path", JS_ROOT + "/materialize-css/sass/"), + STATIC_ROOT + "/materialize-css/sass/", STATIC_ROOT, ] @@ -415,6 +441,9 @@ DBBACKUP_COMPRESS_MEDIA = _settings.get("backup.media.compress", True) DBBACKUP_ENCRYPT_MEDIA = _settings.get("backup.media.encrypt", DBBACKUP_GPG_RECIPIENT is not None) DBBACKUP_CLEANUP_DB = _settings.get("backup.database.clean", True) DBBACKUP_CLEANUP_MEDIA = _settings.get("backup.media.clean", True) +DBBACKUP_CONNECTOR_MAPPING = { + "django_prometheus.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpConnector", +} IMPERSONATE = {"USE_HTTP_REFERER": True, "REQUIRE_SUPERUSER": True, "ALLOW_SUPERUSER": True} @@ -695,3 +724,5 @@ HEALTH_CHECK = { } ORIGINAL_AUTHENTICATION_BACKENDS = AUTHENTICATION_BACKENDS[:] + +PROMETHEUS_EXPORT_MIGRATIONS = False diff --git a/aleksis/core/static/js/edit_dashboard.js b/aleksis/core/static/js/edit_dashboard.js new file mode 100644 index 0000000000000000000000000000000000000000..0cc90de60305497a682d219b121e77550d273d1d --- /dev/null +++ b/aleksis/core/static/js/edit_dashboard.js @@ -0,0 +1,22 @@ +function refreshOrder() { + $(".order-input").val(0); + $("#widgets > .col").each(function (index) { + const order = (index + 1) * 10; + let pk = $(this).attr("data-pk"); + let sel = $("#order-form input[value=" + pk + "].pk-input").next(); + sel.val(order); + }) +} + +$(document).ready(function () { + $('#not-used-widgets').sortable({ + group: 'widgets', + animation: 150, + onEnd: refreshOrder + }); + $('#widgets').sortable({ + group: 'widgets', + animation: 150, + onEnd: refreshOrder + }); +}); diff --git a/aleksis/core/static/js/main.js b/aleksis/core/static/js/main.js index ba4e56cd54d8909d1f74cb54bbdb3c669b4cfeab..615f8b287df18ed38c57d1f44358073bb74bcf61 100644 --- a/aleksis/core/static/js/main.js +++ b/aleksis/core/static/js/main.js @@ -1,21 +1,15 @@ -$(document).ready( function () { - $("dmc-datetime input").addClass("datepicker"); - $("[data-form-control='date']").addClass("datepicker"); - $("[data-form-control='time']").addClass("timepicker"); - - // Initialize sidenav [MAT] - $(".sidenav").sidenav(); - +function initDatePicker(sel) { // Initialize datepicker [MAT] - $('.datepicker').datepicker({ - format: get_format('SHORT_DATE_FORMAT').toLowerCase().replace('d', 'dd').replace('m', 'mm').replace('y', 'yyyy'), + const format = get_format('SHORT_DATE_FORMAT').toLowerCase().replace('d', 'dd').replace('m', 'mm').replace('y', 'yyyy'); + const el = $(sel).datepicker({ + format: format, // Pull translations from Django helpers i18n: { months: calendarweek_i18n.month_names, monthsShort: calendarweek_i18n.month_abbrs, weekdays: calendarweek_i18n.day_names, weekdaysShort: calendarweek_i18n.day_abbrs, - weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v])=> v), + weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v]) => v), // Buttons today: gettext('Today'), @@ -27,9 +21,13 @@ $(document).ready( function () { firstDay: get_format('FIRST_DAY_OF_WEEK'), autoClose: true }); + el.datepicker("setDate", $(sel).val()); + return el; +} +function initTimePicker(sel) { // Initialize timepicker [MAT] - $('.timepicker').timepicker({ + return $(sel).timepicker({ twelveHour: false, autoClose: true, i18n: { @@ -38,6 +36,21 @@ $(document).ready( function () { done: 'OK' }, }); +} + +$(document).ready(function () { + $("dmc-datetime input").addClass("datepicker"); + $("[data-form-control='date']").addClass("datepicker"); + $("[data-form-control='time']").addClass("timepicker"); + + // Initialize sidenav [MAT] + $(".sidenav").sidenav(); + + // Initialize datepicker [MAT] + initDatePicker(".datepicker"); + + // Initialize timepicker [MAT] + initTimePicker(".timepicker"); // Initialize tooltip [MAT] $('.tooltipped').tooltip(); diff --git a/aleksis/core/static/js/serviceworker.js b/aleksis/core/static/js/serviceworker.js index 93d9797c78db9756cba6f1f6caa46fc8c14ae57b..818e27cb7d28e820431a4ac28ba6fdf0c422deb7 100644 --- a/aleksis/core/static/js/serviceworker.js +++ b/aleksis/core/static/js/serviceworker.js @@ -1,35 +1,9 @@ -// This is the AlekSIS service worker - -const CACHE = "aleksis-cache"; - -const precacheFiles = [ - '', -]; -const offlineFallbackPage = '/offline'; - -const avoidCachingPaths = [ - '/admin', - '/settings', - '/accounts/login' -]; // TODO: More paths are needed - -function pathComparer(requestUrl, pathRegEx) { - return requestUrl.match(new RegExp(pathRegEx)); -} +// This is the AlekSIS service worker -function comparePaths(requestUrl, pathsArray) { - if (requestUrl) { - for (let index = 0; index < pathsArray.length; index++) { - const pathRegEx = pathsArray[index]; - if (pathComparer(requestUrl, pathRegEx)) { - return true; - } - } - } +const CACHE = 'aleksis-cache'; - return false; -} +const offlineFallbackPage = 'offline/'; self.addEventListener("install", function (event) { console.log("[AlekSIS PWA] Install Event processing."); @@ -40,10 +14,7 @@ self.addEventListener("install", function (event) { event.waitUntil( caches.open(CACHE).then(function (cache) { console.log("[AlekSIS PWA] Caching pages during install."); - - return cache.addAll(precacheFiles).then(function () { - return cache.add(offlineFallbackPage); - }); + return cache.add(offlineFallbackPage); }) ); }); @@ -95,11 +66,11 @@ function fromCache(event) { } function updateCache(request, response) { - if (!comparePaths(request.url, avoidCachingPaths)) { + if (response.headers.get('cache-control') && response.headers.get('cache-control').includes('no-cache')) { + return Promise.resolve(); + } else { return caches.open(CACHE).then(function (cache) { return cache.put(request, response); }); } - - return Promise.resolve(); } diff --git a/aleksis/core/static/style.scss b/aleksis/core/static/style.scss index e2ea3bbd9953477469350e1c477556947bf8f0e2..81f443f75ab79c34121390bf03da9ab1e611dd5b 100644 --- a/aleksis/core/static/style.scss +++ b/aleksis/core/static/style.scss @@ -70,6 +70,10 @@ header, main, footer { } } +.materialize-circle { + @extend .circle; +} + /**********/ /* HEADER */ /**********/ @@ -622,3 +626,7 @@ main .alert p:first-child, main .alert div:first-child { overflow: visible; width: 100%; } + +.draggable { + cursor: grab; +} diff --git a/aleksis/core/tables.py b/aleksis/core/tables.py index f562b61a151487c5047ef2861255a00ef7a5f785..54d3f3e5c75e4c5ac0dca4b8ccfe868f70b7efb2 100644 --- a/aleksis/core/tables.py +++ b/aleksis/core/tables.py @@ -70,3 +70,24 @@ class GroupTypesTable(tables.Table): delete = tables.LinkColumn( "delete_group_type_by_id", args=[A("id")], verbose_name=_("Delete"), text=_("Delete") ) + + +class DashboardWidgetTable(tables.Table): + """Table to list dashboard widgets.""" + + class Meta: + attrs = {"class": "responsive-table highlight"} + + widget_name = tables.Column(accessor="pk") + title = tables.LinkColumn("edit_dashboard_widget", args=[A("id")]) + active = tables.BooleanColumn(yesno="check,cancel", attrs={"span": {"class": "material-icons"}}) + delete = tables.LinkColumn( + "delete_dashboard_widget", + args=[A("id")], + text=_("Delete"), + attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}}, + verbose_name=_("Actions"), + ) + + def render_widget_name(self, value, record): + return record._meta.verbose_name diff --git a/aleksis/core/templates/core/announcement/form.html b/aleksis/core/templates/core/announcement/form.html index 0cd31cdefc11e8f1a2d29a30fb72a001357a946f..44d60280c6d358f74c60cb9c5ccff2c7aba89b07 100644 --- a/aleksis/core/templates/core/announcement/form.html +++ b/aleksis/core/templates/core/announcement/form.html @@ -2,8 +2,12 @@ {% extends "core/base.html" %} -{% load i18n material_form %} +{% load i18n material_form any_js %} +{% block extra_head %} + {{ form.media.css }} + {% include_css "select2-materialize" %} +{% endblock %} {% block browser_title %} {% if mode == "edit" %} @@ -30,4 +34,6 @@ {% trans "Save und publish announcement" %} </button> </form> + {% include_js "select2-materialize" %} + {{ form.media.js }} {% endblock %} diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index 07db50c7fb3a29dc169543ae03c4f26dff1ec25f..2087829da24c157b7dc0a797da776d7bb1cc9a3b 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -170,6 +170,8 @@ {% include_js "materialize" %} +{% include_js "sortablejs" %} +{% include_js "jquery-sortablejs" %} <script type="text/javascript" src="{% static 'js/search.js' %}"></script> <script type="text/javascript" src="{% static 'js/main.js' %}"></script> </body> diff --git a/aleksis/core/templates/core/dashboard_widget/create.html b/aleksis/core/templates/core/dashboard_widget/create.html new file mode 100644 index 0000000000000000000000000000000000000000..cfaa296eefc7afb0abce56ce1802eeba2ef70a76 --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/create.html @@ -0,0 +1,23 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n data_helpers %} + +{% block browser_title %} + {% verbose_name_object model as widget_title %} + {% blocktrans with widget=widget_title %}Create {{ widget }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% verbose_name_object model as widget_title %} + {% blocktrans with widget=widget_title %}Create {{ widget }}{% endblocktrans %} +{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/core/templates/core/dashboard_widget/edit.html b/aleksis/core/templates/core/dashboard_widget/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..64dfe0eed64c36257fed529475f355d0ee2e8fc0 --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/edit.html @@ -0,0 +1,23 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n data_helpers %} + +{% block browser_title %} + {% verbose_name_object object as widget_title %} + {% blocktrans with widget=widget_title %}Edit {{ widget }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% verbose_name_object object as widget_title %} + {% blocktrans with widget=widget_title %}Edit {{ widget }}{% endblocktrans %} +{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/core/templates/core/dashboard_widget/list.html b/aleksis/core/templates/core/dashboard_widget/list.html new file mode 100644 index 0000000000000000000000000000000000000000..ab384e8620f7c6499adb3e663fdb98e4eb598b2c --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/list.html @@ -0,0 +1,22 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n data_helpers %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %} + +{% block content %} + + {% for ct, model in widget_types %} + <a class="btn green waves-effect waves-light" href="{% url 'create_dashboard_widget' ct.app_label ct.model %}"> + <i class="material-icons left">add</i> + {% verbose_name_object model as widget_name %} + {% blocktrans with name=widget_name %}Create {{ name }}{% endblocktrans %} + </a> + {% endfor %} + + {% render_table table %} +{% endblock %} diff --git a/aleksis/core/templates/core/edit_dashboard.html b/aleksis/core/templates/core/edit_dashboard.html new file mode 100644 index 0000000000000000000000000000000000000000..a15f24bff8ebcca6a7f9b4ea081d9d8c008f3b0a --- /dev/null +++ b/aleksis/core/templates/core/edit_dashboard.html @@ -0,0 +1,41 @@ +{% extends 'core/base.html' %} +{% load i18n static dashboard any_js %} + +{% block browser_title %}{% blocktrans %}Edit dashboard{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit dashboard{% endblocktrans %}{% endblock %} + +{% block content %} + <div class="alert primary"> + <p> + <i class="material-icons left">info</i> + On this page you can arrange your personal dashboard. You can drag any items from "Available widgets" to "Your + Dashboard" or change the order by moving the widgets. After you have finished, please don't forget to click on + "Save". + </p> + </div> + + <form action="" method="post" id="order-form"> + {% csrf_token %} + {{ formset.management_form }} + {% for form in formset %} + {{ form.as_p }} + {% endfor %} + {% include "core/partials/save_button.html" %} + </form> + + <h5>{% trans "Available widgets" %}</h5> + <div class="row card-panel grey lighten-3" id="not-used-widgets"> + {% for widget in not_used_widgets %} + {% include "core/partials/edit_dashboard_widget.html" %} + {% endfor %} + </div> + + <h5>{% trans "Your dashboard" %}</h5> + <div class="row card-panel grey lighten-3" id="widgets"> + {% for widget in widgets %} + {% include "core/partials/edit_dashboard_widget.html" %} + {% endfor %} + </div> + + <script src="{% static "js/edit_dashboard.js" %}"></script> +{% endblock %} diff --git a/aleksis/core/templates/core/group/edit.html b/aleksis/core/templates/core/group/edit.html index b26a28d1efc5ee292a257220bca00754512c1b99..146a5d83abe78307c20f5f70aaff28dc82b62fed 100644 --- a/aleksis/core/templates/core/group/edit.html +++ b/aleksis/core/templates/core/group/edit.html @@ -1,7 +1,12 @@ {# -*- engine:django -*- #} {% extends "core/base.html" %} -{% load material_form i18n %} +{% load material_form i18n any_js %} + +{% block extra_head %} + {{ edit_group_form.media.css }} + {% include_css "select2-materialize" %} +{% endblock %} {% block browser_title %}{% blocktrans %}Edit group{% endblocktrans %}{% endblock %} {% block page_title %}{% blocktrans %}Edit group{% endblocktrans %}{% endblock %} @@ -13,5 +18,7 @@ {% form form=edit_group_form %}{% endform %} {% include "core/partials/save_button.html" %} </form> + {% include_js "select2-materialize" %} + {{ edit_group_form.media.js }} {% endblock %} diff --git a/aleksis/core/templates/core/group/full.html b/aleksis/core/templates/core/group/full.html index def54269f884515d3f07f96ee353616df2db3c7e..1a15daf5c490c4cd1af12e7c43c02a3872c8b883 100644 --- a/aleksis/core/templates/core/group/full.html +++ b/aleksis/core/templates/core/group/full.html @@ -14,6 +14,7 @@ {% has_perm 'core.edit_group' user group as can_change_group %} {% has_perm 'core.change_group_preferences' user group as can_change_group_preferences %} {% has_perm 'core.delete_group' user group as can_delete_group %} + {% has_perm 'core.view_group_stats' user group as can_view_group_stats %} {% if can_change_group or can_change_group_preferences or can_delete_group %} <p> @@ -59,6 +60,25 @@ </tr> </table> + {% if can_view_group_stats %} + <h5>{% blocktrans %}Statistics{% endblocktrans %}</h5> + <ul> + <li> + {% trans "Count of members" %}: {{ stats.members }} + </li> + {% if stats.age_avg %} + <li> + {% trans "Average age" %}: {{ stats.age_avg|floatformat }} + </li> + {% endif %} + {% if stats.age_range_min %} + <li> + {% trans "Age range" %}: {{ stats.age_range_min }} {% trans "years to" %} {{ stats.age_range_max }} {% trans "years "%} + </li> + {% endif %} + </ul> + {% endif %} + <h5>{% blocktrans %}Owners{% endblocktrans %}</h5> {% render_table owners_table %} diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html index 683adca900d46a8ba50790829db4d3ecd0ba2716..93b80ddefffaa8cf72db56a7ef503a694f0514f5 100644 --- a/aleksis/core/templates/core/index.html +++ b/aleksis/core/templates/core/index.html @@ -2,13 +2,21 @@ {% load i18n static dashboard %} {% block browser_title %}{% blocktrans %}Home{% endblocktrans %}{% endblock %} -{% block page_title %}{{ request.site.preferences.general__title }}{% endblock %} +{% block no_page_title %}{% endblock %} {% block extra_head %} {{ media }} {% 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> + <h4> + {{ request.site.preferences.general__title }} + </h4> + {% if user.is_authenticated %} {% for notification in unread_notifications %} <div class="alert primary scale-transition"> @@ -31,9 +39,19 @@ <div class="row" id="live_load"> {% for widget in widgets %} - <div class="col s12 m12 l6 xl4"> + <div class="col s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}"> {% include_widget widget %} </div> + {% empty %} + <div class="col s12 grey-text center"> + <i class="material-icons medium ">widgets</i> + <p class="flow-text"> + {% blocktrans %} + You haven't selected any dashboard widgets. Please click on "Edit dashboard" to add widgets to your + personal dashboard. + {% endblocktrans %} + </p> + </div> {% endfor %} </div> @@ -48,7 +66,7 @@ <span class="badge new primary-color">{{ activity.app }}</span> <span class="title">{{ activity.title }}</span> <p> - <i class="material-icons left">access_time</i> {{ activity.created_at }} + <i class="material-icons left">access_time</i> {{ activity.created }} </p> <p> {{ activity.description }} @@ -71,7 +89,7 @@ <span class="badge new primary-color">{{ notification.app }}</span> <span class="title">{{ notification.title }}</span> <p> - <i class="material-icons left">access_time</i> {{ notification.created_at }} + <i class="material-icons left">access_time</i> {{ notification.created }} </p> <p> {{ notification.description }} diff --git a/aleksis/core/templates/core/partials/crud_events.html b/aleksis/core/templates/core/partials/crud_events.html index 50e4f73cab492804b163b7ddfe0dda08b9e267b7..b0edf14d154690e33a2fac523f5fa38429fcdb5e 100644 --- a/aleksis/core/templates/core/partials/crud_events.html +++ b/aleksis/core/templates/core/partials/crud_events.html @@ -1,62 +1,32 @@ {% load i18n data_helpers %} -<ul class="collection"> - {% for event in obj.crud_events %} - {% if no_m2m and event.event_type == event.M2M_CHANGE or event.event_type == event.M2M_CHANGE_REV %} - {% else %} - <li class="collection-item"> - <strong> - {% if event.event_type == event.CREATE %} - {% blocktrans with person=event.user.person %} - Created by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.UPDATE %} - {% blocktrans with person=event.user.person %} - Updated by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.DELETE %} - {% blocktrans with person=event.user.person %} - Deleted by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.M2M_CHANGE %} - {% blocktrans with person=event.user.person %} - Updated by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.M2M_CHANGE_REV %} - {% blocktrans with person=event.user.person %} - Updated by {{ person }} - {% endblocktrans %} - {% endif %} - </strong> - - <div class="left" style="margin-right: 10px;"> - {% if event.event_type == event.CREATE %} - <i class="material-icons">add_circle</i> - {% elif event.event_type == event.UPDATE %} - <i class="material-icons">edit</i> - {% elif event.event_type == event.DELETE %} - <i class="material-icons">delete</i> - {% elif event.event_type == event.M2M_CHANGE %} - <i class="material-icons">edit</i> - {% elif event.event_type == event.M2M_CHANGE_REV %} - <i class="material-icons">edit</i> - {% endif %} - </div> - <div class="right"> - {{ event.datetime }} - </div> - {% parse_json event.changed_fields as changed_fields %} - {% if changed_fields %} - <ul> - {% for field, change in changed_fields.items %} - {% verbose_name event.content_type.app_label event.content_type.model field as verbose_name %} - <li> - {{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }} - </li> - {% endfor %} - </ul> +<div class="collection"> + {% for version in obj.versions %} + <div class="collection-item"> + <div class="left" style="margin-right: 10px;"> + {% if forloop.first %} + <i class="material-icons">add_circle</i> + {% else %} + <i class="material-icons">edit</i> {% endif %} - </li> - {% endif %} + </div> + <strong> + {{ version.0.revision.get_comment }} + {% trans "Changed by" %} {% firstof version.0.revision.user.person _("Unknown") %} + </strong> + <div class="right"> + {{ version.0.revision.date_created }} + </div> + {% if version.1 %} + <ul> + {% for field, change in version.1.items %} + {% verbose_name version.0.content_type.app_label version.0.content_type.model field as verbose_name %} + <li> + {{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }} + </li> + {% endfor %} + </ul> + {% endif %} + </div> {% endfor %} -</ul> +</div> diff --git a/aleksis/core/templates/core/partials/edit_dashboard_widget.html b/aleksis/core/templates/core/partials/edit_dashboard_widget.html new file mode 100644 index 0000000000000000000000000000000000000000..b842c0937f05c4e90a4d7337871460130fa43012 --- /dev/null +++ b/aleksis/core/templates/core/partials/edit_dashboard_widget.html @@ -0,0 +1,9 @@ +<div class="col draggable s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}" + data-pk="{{ widget.pk }}"> + <div class="card placeholder"> + <div class="card-content"> + <i class="material-icons left small">drag_handle</i> + <span class="card-title">{{ widget.title }}</span> + </div> + </div> +</div> diff --git a/aleksis/core/templates/core/partials/language_form.html b/aleksis/core/templates/core/partials/language_form.html index 36ffa81a8c6017c4fdc86dac8749b0b681e0553f..9a1c62e2c009070b40e4aca11649a8e0ce322101 100644 --- a/aleksis/core/templates/core/partials/language_form.html +++ b/aleksis/core/templates/core/partials/language_form.html @@ -8,8 +8,7 @@ <input name="next" type="hidden" value="{{ request.get_full_path }}"> {% get_current_language as LANGUAGE_CODE %} - {% get_available_languages as LANGUAGES %} - {% get_language_info_list for LANGUAGES as languages %} + {% get_language_info_list for request.site.preferences.internationalisation__languages as languages %} {# Select #} <div class="input-field language-field"> diff --git a/aleksis/core/templates/core/person/accounts.html b/aleksis/core/templates/core/person/accounts.html index 672b089aaa949560e958d829597e1b7abd986265..03725518dfcbf8552a53199790daa44d541bac32 100644 --- a/aleksis/core/templates/core/person/accounts.html +++ b/aleksis/core/templates/core/person/accounts.html @@ -2,7 +2,12 @@ {% extends "core/base.html" %} -{% load i18n %} +{% load i18n any_js %} + +{% block extra_head %} + {{ persons_accounts_formset.media.css }} + {% include_css "select2-materialize" %} +{% endblock %} {% block browser_title %}{% blocktrans %}Link persons to accounts{% endblocktrans %}{% endblock %} {% block page_title %} @@ -55,4 +60,6 @@ {% blocktrans %}Update{% endblocktrans %} </button> </form> + {% include_js "select2-materialize" %} + {{ persons_accounts_formset.media.js }} {% endblock %} diff --git a/aleksis/core/templates/core/person/edit.html b/aleksis/core/templates/core/person/edit.html index 261249f6867a4370745f66d3bd0abc05891442cf..3bf16ca3521ea744acfe48c6829dfae21db06eb5 100644 --- a/aleksis/core/templates/core/person/edit.html +++ b/aleksis/core/templates/core/person/edit.html @@ -2,10 +2,11 @@ {% extends "core/base.html" %} -{% load material_form i18n %} +{% load material_form i18n any_js %} {% block extra_head %} {{ edit_person_form.media }} + {% include_css "select2-materialize" %} {% endblock %} {% block browser_title %}{% blocktrans %}Edit person{% endblocktrans %}{% endblock %} @@ -19,5 +20,7 @@ {% form form=edit_person_form %}{% endform %} {% include "core/partials/save_button.html" %} </form> + {% include_js "select2-materialize" %} + {{ edit_group_form.media.js }} {% endblock %} diff --git a/aleksis/core/templates/material/fields/django_select2_modelselect2multiplewidget.html b/aleksis/core/templates/material/fields/django_select2_modelselect2multiplewidget.html new file mode 100644 index 0000000000000000000000000000000000000000..1b19d8ec88bf72a694e32066c935327691c00738 --- /dev/null +++ b/aleksis/core/templates/material/fields/django_select2_modelselect2multiplewidget.html @@ -0,0 +1,21 @@ +{% load l10n material_form material_form_internal %} +{% part bound_field.field %}<{{ field.widget.component|default:'dmc-select' }}> + <label>{{ bound_field.label }}</label> + <div class="row"> + <div{% attrs bound_field 'group' %} + id="id_{{ bound_field.html_name }}_container" + class="input-field col s12{% if field.required %} required{% endif %}{% if bound_field.errors %} has-error{% endif %}" + style="margin-top: 0" + {% endattrs %}> + {% part field prefix %}{% endpart %} + {% part field control %} + {{ bound_field }} + {% endpart %} + {% part field help_text %}{% if field.help_text %} + <div class="help-block">{{ bound_field.help_text|safe }}</div> + {% endif %}{% endpart %}{% part field errors %} + {% if bound_field.errors %} + {% include 'material/field_errors.html' %} + {% endif %}{% endpart %}{{ hidden_initial }} + </div> +</div></{{ field.widget.component|default:'dmc-select' }}>{% endpart %} diff --git a/aleksis/core/templates/material/fields/django_select2_select2widget.html b/aleksis/core/templates/material/fields/django_select2_select2widget.html new file mode 100644 index 0000000000000000000000000000000000000000..1b19d8ec88bf72a694e32066c935327691c00738 --- /dev/null +++ b/aleksis/core/templates/material/fields/django_select2_select2widget.html @@ -0,0 +1,21 @@ +{% load l10n material_form material_form_internal %} +{% part bound_field.field %}<{{ field.widget.component|default:'dmc-select' }}> + <label>{{ bound_field.label }}</label> + <div class="row"> + <div{% attrs bound_field 'group' %} + id="id_{{ bound_field.html_name }}_container" + class="input-field col s12{% if field.required %} required{% endif %}{% if bound_field.errors %} has-error{% endif %}" + style="margin-top: 0" + {% endattrs %}> + {% part field prefix %}{% endpart %} + {% part field control %} + {{ bound_field }} + {% endpart %} + {% part field help_text %}{% if field.help_text %} + <div class="help-block">{{ bound_field.help_text|safe }}</div> + {% endif %}{% endpart %}{% part field errors %} + {% if bound_field.errors %} + {% include 'material/field_errors.html' %} + {% endif %}{% endpart %}{{ hidden_initial }} + </div> +</div></{{ field.widget.component|default:'dmc-select' }}>{% endpart %} diff --git a/aleksis/core/templates/core/pages/offline.html b/aleksis/core/templates/offline.html similarity index 87% rename from aleksis/core/templates/core/pages/offline.html rename to aleksis/core/templates/offline.html index a6a70dc19f074e8c3f3ede50b5e9b5b80b5682f1..bd741268a8902ac6708f33e353337695c03158a3 100644 --- a/aleksis/core/templates/core/pages/offline.html +++ b/aleksis/core/templates/offline.html @@ -2,6 +2,8 @@ {% load i18n %} +{% block browser_title %}{% blocktrans %}Network error{% endblocktrans %}{% endblock %} + {% block content %} <h3><i class="material-icons left medium" style="font-size: 2.92rem;">signal_wifi_off</i>{% blocktrans %}No internet connection.{% endblocktrans %}</h3> diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email index 8e61fd0df28b5bc6342c891921831e147e9cc8d9..0e5d9bdee3c8bd9ec3df5c9d008ae68d1f274c23 100644 --- a/aleksis/core/templates/templated_email/notification.email +++ b/aleksis/core/templates/templated_email/notification.email @@ -15,7 +15,7 @@ {% trans "More information" %} → {{ notification.link }} {% endif %} - {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %} + {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %} Sent by {{ trans_sender }} at {{ trans_created_at }} {% endblocktrans %} @@ -37,7 +37,7 @@ </blockquote> <p> - {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %} + {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created %} Sent by {{ trans_sender }} at {{ trans_created_at }} {% endblocktrans %} </p> diff --git a/aleksis/core/templates/two_factor/core/setup.html b/aleksis/core/templates/two_factor/core/setup.html index 2eb4ecb2828ac60104f284b2d75857e9b6be303b..8048403964a5bd4b7c9761d0189f18bb53524a22 100644 --- a/aleksis/core/templates/two_factor/core/setup.html +++ b/aleksis/core/templates/two_factor/core/setup.html @@ -22,8 +22,8 @@ <p> {% blocktrans %} To start using a token generator, please use your - smartphone to scan the QR code below. For example, use Google - Authenticator. Then, enter the token generated by the app. + favourite two factor authentication (TOTP) app to scan the QR code below. + Then, enter the token generated by the app. {% endblocktrans %} </p> <p> diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 51048e5c83bdb6c540254a36754f32cdc3059591..7f0f81b0960174c8df67fb862a38e4d2bdd38164 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -16,6 +16,7 @@ from . import views from .util.core_helpers import is_celery_enabled urlpatterns = [ + path("", include("django_prometheus.urls")), path("", include("pwa.urls"), name="pwa"), path("about/", views.about, name="about_aleksis"), path("admin/", admin.site.urls), @@ -56,6 +57,7 @@ urlpatterns = [ path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"), path("group/<int:id_>/delete", views.delete_group, name="delete_group_by_id"), path("", views.index, name="index"), + path("dashboard/edit/", views.EditDashboardView.as_view(), name="edit_dashboard"), path( "notifications/mark-read/<int:id_>", views.notification_mark_read, @@ -152,6 +154,22 @@ urlpatterns = [ name="preferences_group", ), path("health/", include(health_urls)), + path("dashboard_widgets/", views.DashboardWidgetListView.as_view(), name="dashboard_widgets"), + path( + "dashboard_widgets/<int:pk>/edit/", + views.DashboardWidgetEditView.as_view(), + name="edit_dashboard_widget", + ), + path( + "dashboard_widgets/<int:pk>/delete/", + views.DashboardWidgetDeleteView.as_view(), + name="delete_dashboard_widget", + ), + path( + "dashboard_widgets/<str:app>/<str:model>/new/", + views.DashboardWidgetCreateView.as_view(), + name="create_dashboard_widget", + ), ] # Serve static files from STATIC_ROOT to make it work with runserver diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 6428e293c158ecd37af26b8e3a3625b24011a5a0..bef457feee2b0dd53f72591523387d912c4038b1 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -1,5 +1,5 @@ import os -import pkgutil +import sys import time from datetime import datetime, timedelta from importlib import import_module @@ -8,6 +8,11 @@ from operator import itemgetter from typing import Any, Callable, Optional, Sequence, Union from uuid import uuid4 +if sys.version_info >= (3, 9): + from importlib import metadata +else: + import importlib_metadata as metadata + from django.conf import settings from django.db.models import Model, QuerySet from django.http import HttpRequest @@ -59,14 +64,8 @@ def dt_show_toolbar(request: HttpRequest) -> bool: def get_app_packages() -> Sequence[str]: - """Find all packages within the aleksis.apps namespace.""" - # Import error are non-fatal here because probably simply no app is installed. - try: - import aleksis.apps - except ImportError: - return [] - - return [f"aleksis.apps.{pkg[1]}" for pkg in pkgutil.iter_modules(aleksis.apps.__path__)] + """Find all registered apps from the setuptools entrypoint.""" + return [f"{ep.module}.{ep.attr}" for ep in metadata.entry_points().get("aleksis.app", [])] def merge_app_settings( @@ -81,11 +80,19 @@ def merge_app_settings( Note: Only selected names will be imported frm it to minimise impact of potentially malicious apps! """ - for pkg in get_app_packages(): - try: - mod_settings = import_module(pkg + ".settings") - except ImportError: - # Import errors are non-fatal. They mean that the app has no settings.py. + for app in get_app_packages(): + pkg = ".".join(app.split(".")[:-2]) + mod_settings = None + while "." in pkg: + try: + mod_settings = import_module(pkg + ".settings") + except ImportError: + # Import errors are non-fatal. + pkg = ".".join(pkg.split(".")[:-1]) + continue + break + if not mod_settings: + # The app does not have settings continue app_setting = getattr(mod_settings, setting, None) @@ -361,6 +368,18 @@ def handle_uploaded_file(f, filename: str): destination.write(chunk) +@cache_memoize(3600) +def get_content_type_by_perm(perm: str) -> Union["ContentType", None]: + from django.contrib.contenttypes.models import ContentType # noqa + + try: + return ContentType.objects.get( + app_label=perm.split(".", 1)[0], permission__codename=perm.split(".", 1)[1] + ) + except ContentType.DoesNotExist: + return None + + @cache_memoize(3600) def queryset_rules_filter( obj: Union[HttpRequest, Model], queryset: QuerySet, perm: str diff --git a/aleksis/core/util/manage.py b/aleksis/core/util/manage.py new file mode 100644 index 0000000000000000000000000000000000000000..64441f62dfdadbb589b973dcd65633e100bf647b --- /dev/null +++ b/aleksis/core/util/manage.py @@ -0,0 +1,12 @@ +"""Management utilities for an AlekSIS installation.""" + +import os +import sys + +from django.core.management import execute_from_command_line + + +def aleksis_cmd(): + """Run django-admin command with correct settings path.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings") + execute_from_command_line(sys.argv) diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py index 46fe98c7379034eb07b3bad2a7c442ab4d9e02b5..d107b425bab206b9a1a0c57d0cb4a4e997a82d34 100644 --- a/aleksis/core/util/predicates.py +++ b/aleksis/core/util/predicates.py @@ -1,6 +1,5 @@ from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType from django.db.models import Model from django.http import HttpRequest @@ -9,7 +8,7 @@ from guardian.shortcuts import get_objects_for_user from rules import predicate from ..models import Group -from .core_helpers import get_site_preferences +from .core_helpers import get_content_type_by_perm, get_site_preferences from .core_helpers import has_person as has_person_helper from .core_helpers import queryset_rules_filter @@ -65,12 +64,7 @@ def has_any_object(perm: str, klass): @predicate(name) def fn(user: User) -> bool: - try: - ct_perm = ContentType.objects.get( - app_label=perm.split(".", 1)[0], permission__codename=perm.split(".", 1)[1] - ) - except ContentType.DoesNotExist: - ct_perm = None + ct_perm = get_content_type_by_perm(perm) if ct_perm and ct_perm.model_class() == klass: return get_objects_for_user(user, perm, klass).exists() else: diff --git a/aleksis/core/views.py b/aleksis/core/views.py index af2f285a7ec48c5499015c9f54388818e68d2bc1..14cbf2dbeafc7dd84edfadcd5375e97560159011 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -1,13 +1,18 @@ -from typing import Optional +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 +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.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ +from django.views.decorators.cache import never_cache +from django.views.generic.base import View import reversion from django_tables2 import RequestConfig, SingleTableView @@ -17,12 +22,14 @@ from haystack.inputs import AutoQuery from haystack.query import SearchQuerySet from haystack.views import SearchView from health_check.views import MainView +from reversion import set_user from rules.contrib.views import PermissionRequiredMixin, permission_required from .filters import GroupFilter, PersonFilter from .forms import ( AnnouncementForm, ChildGroupsForm, + DashboardWidgetOrderFormSet, EditAdditionalFieldForm, EditGroupForm, EditGroupTypeForm, @@ -33,11 +40,12 @@ from .forms import ( SchoolTermForm, SitePreferenceForm, ) -from .mixins import AdvancedCreateView, AdvancedEditView +from .mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from .models import ( AdditionalField, Announcement, DashboardWidget, + DashboardWidgetOrder, Group, GroupType, Notification, @@ -51,6 +59,7 @@ from .registries import ( ) from .tables import ( AdditionalFieldsTable, + DashboardWidgetTable, GroupsTable, GroupTypesTable, PersonsTable, @@ -77,7 +86,7 @@ def index(request: HttpRequest) -> HttpResponse: announcements = Announcement.objects.at_time().for_person(request.user.person) context["announcements"] = announcements - widgets = DashboardWidget.objects.filter(active=True) + widgets = request.user.person.dashboard_widgets media = DashboardWidget.get_media(widgets) context["widgets"] = widgets @@ -86,11 +95,6 @@ def index(request: HttpRequest) -> HttpResponse: return render(request, "core/index.html", context) -def offline(request: HttpRequest) -> HttpResponse: - """Offline message for PWA.""" - return render(request, "core/pages/offline.html") - - def about(request: HttpRequest) -> HttpResponse: """About page listing all apps.""" context = {} @@ -111,6 +115,7 @@ class SchoolTermListView(SingleTableView, PermissionRequiredMixin): template_name = "core/school_term/list.html" +@method_decorator(never_cache, name="dispatch") class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin): """Create view for school terms.""" @@ -122,6 +127,7 @@ class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin): success_message = _("The school term has been created.") +@method_decorator(never_cache, name="dispatch") class SchoolTermEditView(AdvancedEditView, PermissionRequiredMixin): """Edit view for school terms.""" @@ -203,6 +209,9 @@ def group(request: HttpRequest, id_: int) -> HttpResponse: RequestConfig(request).configure(owners_table) context["owners_table"] = owners_table + # Get statistics + context["stats"] = group.get_group_stats + return render(request, "core/group/full.html", context) @@ -226,6 +235,7 @@ def groups(request: HttpRequest) -> HttpResponse: return render(request, "core/group/list.html", context) +@never_cache @permission_required("core.link_persons_accounts") def persons_accounts(request: HttpRequest) -> HttpResponse: """View allowing to batch-process linking of users to persons.""" @@ -246,6 +256,7 @@ def persons_accounts(request: HttpRequest) -> HttpResponse: return render(request, "core/person/accounts.html", context) +@never_cache @permission_required("core.assign_child_groups_to_groups") def groups_child_groups(request: HttpRequest) -> HttpResponse: """View for batch-processing assignment from child groups to groups.""" @@ -283,6 +294,7 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse: return render(request, "core/group/child_groups.html", context) +@never_cache @permission_required("core.edit_person", fn=objectgetter_optional(Person)) def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """Edit view for a single person, defaulting to logged-in person.""" @@ -305,6 +317,7 @@ def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse if request.method == "POST": if edit_person_form.is_valid(): with reversion.create_revision(): + set_user(request.user) edit_person_form.save(commit=True) messages.success(request, _("The person has been saved.")) @@ -320,6 +333,7 @@ def get_group_by_id(request: HttpRequest, id_: Optional[int] = None): return None +@never_cache @permission_required("core.edit_group", fn=objectgetter_optional(Group, None, False)) def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """View to edit or create a group.""" @@ -341,6 +355,7 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: if request.method == "POST": if edit_group_form.is_valid(): with reversion.create_revision(): + set_user(request.user) group = edit_group_form.save(commit=True) messages.success(request, _("The group has been saved.")) @@ -372,10 +387,11 @@ class SystemStatus(MainView, PermissionRequiredMixin): if "django_celery_results" in settings.INSTALLED_APPS: from django_celery_results.models import TaskResult # noqa - from celery.task.control import inspect # noqa - if inspect().registered_tasks(): - job_list = list(inspect().registered_tasks().values())[0] + 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() @@ -411,6 +427,7 @@ def announcements(request: HttpRequest) -> HttpResponse: return render(request, "core/announcement/list.html", context) +@never_cache @permission_required( "core.create_or_edit_announcement", fn=objectgetter_optional(Announcement, None, False) ) @@ -478,6 +495,7 @@ class PermissionSearchView(PermissionRequiredMixin, SearchView): return render(self.request, self.template, context) +@never_cache def preferences( request: HttpRequest, registry_name: str = "person", @@ -540,6 +558,7 @@ def delete_person(request: HttpRequest, id_: int) -> HttpResponse: person = objectgetter_optional(Person)(request, id_) with reversion.create_revision(): + set_user(request.user) person.save() person.delete() @@ -553,6 +572,7 @@ def delete_group(request: HttpRequest, id_: int) -> HttpResponse: """View to delete an group.""" group = objectgetter_optional(Group)(request, id_) with reversion.create_revision(): + set_user(request.user) group.save() group.delete() @@ -561,6 +581,7 @@ def delete_group(request: HttpRequest, id_: int) -> HttpResponse: return redirect("groups") +@never_cache @permission_required( "core.change_additionalfield", fn=objectgetter_optional(AdditionalField, None, False) ) @@ -626,6 +647,7 @@ def delete_additional_field(request: HttpRequest, id_: int) -> HttpResponse: return redirect("additional_fields") +@never_cache @permission_required("core.change_grouptype", fn=objectgetter_optional(GroupType, None, False)) def edit_group_type(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """View to edit or create a group_type.""" @@ -678,3 +700,133 @@ def delete_group_type(request: HttpRequest, id_: int) -> HttpResponse: messages.success(request, _("The group type has been deleted.")) return redirect("group_types") + + +class DashboardWidgetListView(SingleTableView, PermissionRequiredMixin): + """Table of all dashboard widgets.""" + + model = DashboardWidget + table_class = DashboardWidgetTable + permission_required = "core.view_dashboardwidget" + template_name = "core/dashboard_widget/list.html" + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + context = super().get_context_data(**kwargs) + context["widget_types"] = [ + (ContentType.objects.get_for_model(m, False), m) + for m in DashboardWidget.__subclasses__() + ] + return context + + +@method_decorator(never_cache, name="dispatch") +class DashboardWidgetEditView(AdvancedEditView, PermissionRequiredMixin): + """Edit view for dashboard widgets.""" + + def get_form_class(self) -> Type[BaseModelForm]: + return modelform_factory(self.object.__class__, fields=self.fields) + + model = DashboardWidget + fields = "__all__" + permission_required = "core.edit_dashboardwidget" + template_name = "core/dashboard_widget/edit.html" + success_url = reverse_lazy("dashboard_widgets") + success_message = _("The dashboard widget has been saved.") + + +@method_decorator(never_cache, name="dispatch") +class DashboardWidgetCreateView(AdvancedCreateView, PermissionRequiredMixin): + """Create view for dashboard widgets.""" + + def get_model(self, request, *args, **kwargs): + app_label = kwargs.get("app") + model = kwargs.get("model") + ct = get_object_or_404(ContentType, app_label=app_label, model=model) + return ct.model_class() + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + context = super().get_context_data(**kwargs) + context["model"] = self.model + return context + + def get(self, request, *args, **kwargs): + self.model = self.get_model(request, *args, **kwargs) + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.model = self.get_model(request, *args, **kwargs) + return super().post(request, *args, **kwargs) + + fields = "__all__" + permission_required = "core.add_dashboardwidget" + template_name = "core/dashboard_widget/create.html" + success_url = reverse_lazy("dashboard_widgets") + success_message = _("The dashboard widget has been created.") + + +class DashboardWidgetDeleteView(PermissionRequiredMixin, AdvancedDeleteView): + """Delete view for dashboard widgets.""" + + model = DashboardWidget + permission_required = "core.delete_dashboardwidget" + template_name = "core/pages/delete.html" + success_url = reverse_lazy("dashboard_widgets") + success_message = _("The dashboard widget has been deleted.") + + +class EditDashboardView(View): + """View for editing dashboard widget order.""" + + def get_context_data(self, request): + context = {} + + widgets = request.user.person.dashboard_widgets + not_used_widgets = DashboardWidget.objects.exclude(pk__in=[w.pk for w in widgets]) + context["widgets"] = widgets + context["not_used_widgets"] = not_used_widgets + + order = 10 + initial = [] + for widget in widgets: + initial.append({"pk": widget, "order": order}) + order += 10 + for widget in not_used_widgets: + initial.append({"pk": widget, "order": 0}) + + formset = DashboardWidgetOrderFormSet( + request.POST or None, initial=initial, prefix="widget_order" + ) + context["formset"] = formset + + return context + + def post(self, request): + context = self.get_context_data(request) + + if context["formset"].is_valid(): + added_objects = [] + for form in context["formset"]: + if not form.cleaned_data["order"]: + continue + + obj, created = DashboardWidgetOrder.objects.update_or_create( + widget=form.cleaned_data["pk"], + person=request.user.person, + defaults={"order": form.cleaned_data["order"]}, + ) + + added_objects.append(obj.pk) + + DashboardWidgetOrder.objects.filter(person=request.user.person).exclude( + pk__in=added_objects + ).delete() + + messages.success( + request, _("Your dashboard configuration has been saved successfully.") + ) + return redirect("index") + + def get(self, request): + context = self.get_context_data(request) + + return render(request, "core/edit_dashboard.html", context=context) diff --git a/apps/official/AlekSIS-App-Chronos b/apps/official/AlekSIS-App-Chronos deleted file mode 160000 index b081e4d32922d38e4f6229f33e7cecf5a1b408e0..0000000000000000000000000000000000000000 --- a/apps/official/AlekSIS-App-Chronos +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b081e4d32922d38e4f6229f33e7cecf5a1b408e0 diff --git a/apps/official/AlekSIS-App-DashboardFeeds b/apps/official/AlekSIS-App-DashboardFeeds deleted file mode 160000 index 85814a4f6c4fc1d94a853b46be8544c11b5767ee..0000000000000000000000000000000000000000 --- a/apps/official/AlekSIS-App-DashboardFeeds +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 85814a4f6c4fc1d94a853b46be8544c11b5767ee diff --git a/apps/official/AlekSIS-App-Hjelp b/apps/official/AlekSIS-App-Hjelp deleted file mode 160000 index 31500a0580b6839122df35f114e471334903d26e..0000000000000000000000000000000000000000 --- a/apps/official/AlekSIS-App-Hjelp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 31500a0580b6839122df35f114e471334903d26e diff --git a/apps/official/AlekSIS-App-LDAP b/apps/official/AlekSIS-App-LDAP deleted file mode 160000 index 487a3423abc8b8bc8a4d89e474336c76b5cafa61..0000000000000000000000000000000000000000 --- a/apps/official/AlekSIS-App-LDAP +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 487a3423abc8b8bc8a4d89e474336c76b5cafa61 diff --git a/apps/official/AlekSIS-App-Untis b/apps/official/AlekSIS-App-Untis deleted file mode 160000 index 0b417dfef095fb7a52f03301abb66066a841b467..0000000000000000000000000000000000000000 --- a/apps/official/AlekSIS-App-Untis +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0b417dfef095fb7a52f03301abb66066a841b467 diff --git a/ci/build_dist.yml b/ci/build_dist.yml deleted file mode 100644 index 939cdf326decd7642edff67a62f84ff028f30d56..0000000000000000000000000000000000000000 --- a/ci/build_dist.yml +++ /dev/null @@ -1,7 +0,0 @@ -build_dist: - stage: build - script: - - tox -e build - artifacts: - paths: - - dist/ diff --git a/ci/build_docker.yml b/ci/build_docker.yml deleted file mode 100644 index 92a1572c6ac4d1878110500f458262a6a7c5bdf4..0000000000000000000000000000000000000000 --- a/ci/build_docker.yml +++ /dev/null @@ -1,22 +0,0 @@ -build_docker: - stage: build - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] - script: - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" >/kaniko/.docker/config.json - - /kaniko/executor - --context $CI_PROJECT_DIR - --dockerfile $CI_PROJECT_DIR/Dockerfile - --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME - --cache=true - --cleanup - - /kaniko/executor - --context $CI_PROJECT_DIR/docker/nginx - --dockerfile $CI_PROJECT_DIR/docker/nginx/Dockerfile - --destination $CI_REGISTRY_IMAGE/nginx:$CI_COMMIT_REF_NAME - --cache=true - --cleanup - only: - - master - - tags diff --git a/ci/deploy.yml b/ci/deploy.yml deleted file mode 100644 index 249e4c3333cf7c8800600a7801727110305a487e..0000000000000000000000000000000000000000 --- a/ci/deploy.yml +++ /dev/null @@ -1,30 +0,0 @@ -deploy_demo-master: - stage: deploy - environment: - name: demo/master - url: http://demo-master.aleksis.org - before_script: - - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - - eval $(ssh-agent -s) - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - - mkdir -p ~/.ssh - - chmod 700 ~/.ssh - - echo "$SSH_KNOWN_HOSTS" >~/.ssh/known_hosts - - chmod 644 ~/.ssh/known_hosts - script: - - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org - env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME} - docker-compose - -p aleksis-${CI_ENVIRONMENT_SLUG} - -f /dev/stdin - pull - - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org - env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME} - NGINX_HTTP_PORT=80 - ALEKSIS_maintenance__debug=true - docker-compose - -p aleksis-${CI_ENVIRONMENT_SLUG} - -f /dev/stdin - up -d - only: - - master diff --git a/ci/general.yml b/ci/general.yml deleted file mode 100644 index 58891983dbeda6b9c8f49dc07e490a021a4c31dd..0000000000000000000000000000000000000000 --- a/ci/general.yml +++ /dev/null @@ -1,20 +0,0 @@ -image: registry.edugit.org/teckids/team-sysadmin/docker-images/python-pimped:latest - -stages: - - test - - build - - deploy - -variables: - GIT_SUBMODULE_STRATEGY: recursive - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - FF_NETWORK_PER_BUILD: "true" - -cache: - key: - files: - - poetry.lock - - pyproject.toml - paths: - - .cache/pip - - .tox diff --git a/ci/pages.yml b/ci/pages.yml deleted file mode 100644 index 31d8866e1d1c1b665d12ce15da9a7c918440aa7a..0000000000000000000000000000000000000000 --- a/ci/pages.yml +++ /dev/null @@ -1,12 +0,0 @@ -pages: - stage: deploy - before_script: - - cp -r .tox/screenshots/firefox docs/screenshots - script: - - export LC_ALL=en_GB.utf8 - - tox -e docs -- BUILDDIR=../public/docs - artifacts: - paths: - - public/ - only: - - master diff --git a/ci/test.yml b/ci/test.yml deleted file mode 100644 index 2ab64bc324536c8716c5cb8641d09d9c43dbf844..0000000000000000000000000000000000000000 --- a/ci/test.yml +++ /dev/null @@ -1,26 +0,0 @@ -test: - stage: test - services: - - name: selenium/standalone-firefox - alias: selenium - before_script: - - adduser --disabled-password --gecos "Test User" testuser - - chown -R testuser . - script: - - sudo apt update - - sudo apt install python3-ldap libldap2-dev libssl-dev libsasl2-dev python3.7-dev -y - - sudo -u testuser - env TEST_SELENIUM_HUB=http://selenium:4444/wd/hub - TEST_SELENIUM_BROWSERS=firefox - TEST_HOST=build - tox -e selenium -- --junitxml=.tox/junit.xml - artifacts: - paths: - - .tox/screenshots - reports: - junit: .tox/junit.xml - -lint: - stage: test - script: - - tox -e lint,security diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 56d9d1edb57546360eaf73a18d2a95097b950288..0000000000000000000000000000000000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,74 +0,0 @@ -version: '3' - -services: - db: - image: postgres:12 - volumes: - - postgres_data:/var/lib/postgresql/data/ - environment: - - POSTGRES_USER=aleksis - - POSTGRES_DB=aleksis - memcached: - image: memcached:latest - app: - build: . - image: registry.edugit.org/aleksis/official/aleksis:${ALEKSIS_IMAGE_TAG:-latest} - volumes: - - aleksis_data:/var/lib/aleksis/ - - aleksis_static:/usr/share/aleksis/static/ - environment: - - ALEKSIS_http__allowed_hosts="['*']" - - ALEKSIS_caching__memcached__address=memcached:11211 - - ALEKSIS_caching__memcached__enabled=true - - ALEKSIS_database__host=db - - ALEKSIS_maintenance__debug=${ALEKSIS_maintenance__debug:-false} - - ALEKSIS_backup__location=/var/lib/aleksis/backups - depends_on: - - db - - memcached - worker: - build: . - image: registry.edugit.org/aleksis/official/aleksis:${ALEKSIS_IMAGE_TAG:-latest} - volumes: - - aleksis_data:/var/lib/aleksis/ - - aleksis_static:/usr/share/aleksis/static/ - command: celery_worker - environment: - - ALEKSIS_http__allowed_hosts="['*']" - - ALEKSIS_caching__memcached__address=memcached:11211 - - ALEKSIS_caching__memcached__enabled=true - - ALEKSIS_database__host=db - - ALEKSIS_maintenance__debug=${ALEKSIS_maintenance__debug:-false} - - ALEKSIS_backup__location=/var/lib/aleksis/backups - depends_on: - - app - scheduler: - build: . - image: registry.edugit.org/aleksis/official/aleksis:${ALEKSIS_IMAGE_TAG:-latest} - volumes: - - aleksis_data:/var/lib/aleksis/ - - aleksis_static:/usr/share/aleksis/static/ - command: celery_beat - environment: - - ALEKSIS_http__allowed_hosts="['*']" - - ALEKSIS_caching__memcached__address=memcached:11211 - - ALEKSIS_caching__memcached__enabled=true - - ALEKSIS_database__host=db - - ALEKSIS_maintenance__debug=${ALEKSIS_maintenance__debug:-false} - depends_on: - - worker - web: - build: ./docker/nginx - image: registry.edugit.org/aleksis/official/aleksis/nginx:${ALEKSIS_IMAGE_TAG:-latest} - volumes: - - aleksis_data:/var/lib/aleksis/ - - aleksis_static:/usr/share/aleksis/static/:ro - ports: - - ${NGINX_HTTP_PORT:-8080}:80 - depends_on: - - app - -volumes: - postgres_data: - aleksis_data: - aleksis_static: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100755 index 4b68fa884420b1a40ed34d4bd4410aabd2ac1e3f..0000000000000000000000000000000000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -GUNICORN_BIND=${GUNICORN_BIND:-0.0.0.0: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 - -python manage.py compilescss -python manage.py collectstatic --no-input --clear -python manage.py migrate -python manage.py createinitialrevisions - -ARG=${$1:-"gunicorn"} - -if [ $ARG = "celery_worker" ]; then - exec celery -A aleksis.core worker -l info -elif [ $ARG = "celery_beat" ]; then - exec celery -A aleksis.core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler -else - exec gunicorn aleksis.core.wsgi --bind ${GUNICORN_BIND} -fi diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile deleted file mode 100644 index 18b91b5f17398ee5b2dc6e95bdedc225eb934821..0000000000000000000000000000000000000000 --- a/docker/nginx/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM nginx - -RUN rm /etc/nginx/conf.d/default.conf -COPY nginx.conf /etc/nginx/conf.d/default.conf - -RUN mkdir /var/lib/aleksis diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf deleted file mode 100644 index 759f385041ce7bd7dc06b487ff2dfb2c43635482..0000000000000000000000000000000000000000 --- a/docker/nginx/nginx.conf +++ /dev/null @@ -1,23 +0,0 @@ -upstream aleksis { - server app:8000; -} - -server { - listen 80; - - location /media/ { - alias /var/lib/aleksis/media/; - } - - location /static/ { - alias /usr/share/aleksis/static/; - } - - location / { - proxy_pass http://aleksis; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; - proxy_redirect off; - } - -} diff --git a/docs/admin/01_config.rst b/docs/admin/01_config.rst index 089c4416b031f7cf78d92ef8a57e3d86084f9094..a8e593b4afb1bf98b980c1bb548a75fb513b6bbf 100644 --- a/docs/admin/01_config.rst +++ b/docs/admin/01_config.rst @@ -27,25 +27,24 @@ configuration by topic. A configuration file might look like this:: - [default] secret_key = "VerySecretKeyForSessionSecurity" - [default.http] + [http] allowed_hosts = [ "aleksis.myschool.example.com", "localhost" ] - [default.database] + [database] name = "aleksis" user = "aleksis" password = "SuperSecretPassword" - [default.caching] + [caching] memcached = { 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 -have defined another section called `[default.caching.memcached]` and placed -`enabled` and `address` below it as scalars). +have defined another section and placed `enabled` and `address` below it +as scalars). This can be a bit confusing, so this documentation will explain how to configure AlekSIS on a per-feature basis. diff --git a/docs/admin/02_ldap.rst b/docs/admin/02_ldap.rst index 02f0e6d9ee0a19a0a30ffd91a2b19cbc06e41860..b8fbd657dedc3d3e7a6ef04a7fa36d9b7f1b66bf 100644 --- a/docs/admin/02_ldap.rst +++ b/docs/admin/02_ldap.rst @@ -25,17 +25,16 @@ configuration file. For example, add something like the following to your configuration (normally in ``/etc/aleksis``; you can either append to an existing file or add a new one):: - # Authentication via LDAP, optional - [default.ldap] - uri = "ldaps://ldap.myschool.edu" - bind = { dn = "cn=reader,dc=myschool,dc=edu", password = "secret" } - map = { first_name = "givenName", last_name = "sn", email = "mail" } - - [default.ldap.users] - search = { base = "ou=people,dc=myschool,dc=edu", filter = "(uid=%(user)s)" } - - [default.ldap.groups] - search = { base = "ou=groups,dc=myschool,dc=edu" } - type = "groupOfNames" - # Users in group "admins" are superusers - flags = { is_superuser = "cn=admins,ou=groups,dc=myschool,dc=edu" } + [ldap] + uri = "ldaps://ldap.myschool.edu" + bind = { dn = "cn=reader,dc=myschool,dc=edu", password = "secret" } + + [ldap.users] + search = { base = "ou=people,dc=myschool,dc=edu", filter = "(uid=%(user)s)" } + map = { first_name = "givenName", last_name = "sn", email = "mail" } + + [ldap.groups] + search = { base = "ou=groups,dc=myschool,dc=edu" } + type = "groupOfNames" + # Users in group "admins" are superusers + flags = { is_superuser = "cn=admins,ou=groups,dc=myschool,dc=edu" } diff --git a/docs/admin/03_psql.rst b/docs/admin/03_psql.rst index 8c544041fdce1dc6ee83d3311526bb3561dcb487..6d5ab57ae3b157ca906f3cfb7df1d3897efdfbd7 100644 --- a/docs/admin/03_psql.rst +++ b/docs/admin/03_psql.rst @@ -32,7 +32,7 @@ Configure AlekSIS to use PostgreSQL Fill in the configuration under `/etc/aleksis/aleksis.toml` (or a file with any other name in this directory):: - [default.database] + [database] host = "localhost" name = "aleksis" username = "aleksis" diff --git a/docs/admin/04_monitoring.rst b/docs/admin/04_monitoring.rst new file mode 100644 index 0000000000000000000000000000000000000000..1b80a43be057a285d223be803bc347ae2758fdc4 --- /dev/null +++ b/docs/admin/04_monitoring.rst @@ -0,0 +1,39 @@ +Monitoring +########## + +Prometheus +********** + +AlekSIS provides a metric endpoint at `/metrics`, so you can scrape metrics in +your Prometheus instance. + +Available metrics +================= + +The exporter provides metrics about responses and requests, e.g. statistics +about response codes, request latency and requests per view. It also +provides data about database operations. + +Prometheus config to get metrics +================================ + +To get metrics of your AlekSIS instance, just add the following to your +`prometheus.yml`:: + + - job_name: aleksis + static_configs: + - targets: ['my.aleksis-instance.com'] + metrics_path: /metrics + + +Grafana +******* + +Visualise metrics with Grafana +============================== + +If you want to visualise your AlekSIS metrics with Grafana, you can use one +of the public available Grafana dashboards, for example the following one, +or just write your own. + +https://grafana.com/grafana/dashboards/9528 diff --git a/manage.py b/manage.py deleted file mode 100755 index 023e9fd47cbc4c0308997db1061aace8bd8afb61..0000000000000000000000000000000000000000 --- a/manage.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings") - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) diff --git a/poetry.lock b/poetry.lock index a6e35ccdfb68b098d8fec8eafc152cd1915f049a..be0e61cd822675c59e9c9fc318fd2f104c7f900a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,120 +1,142 @@ [[package]] -category = "dev" -description = "A configurable sidebar-enabled Sphinx theme" name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = "*" -version = "0.7.12" [[package]] -category = "main" -description = "Low-level AMQP client for Python (fork of amqplib)." +name = "aleksis-builddeps" +version = "1" +description = "AlekSIS (School Information System) — Build/Dev dependencies for apps" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +black = ">=19.10b0,<20.0" +django-stubs = ">=1.1,<2.0" +flake8 = ">=3.7.9,<4.0.0" +flake8-bandit = ">=2.1.2,<3.0.0" +flake8-black = ">=0.2.0,<0.3.0" +flake8-builtins = ">=1.4.1,<2.0.0" +flake8-django = ">=1.0.0,<2.0.0" +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" +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-testing-postgresql = ">=0.1,<0.2" +pytest-sugar = ">=0.9.2,<0.10.0" +safety = ">=1.8.5,<2.0.0" +selenium = ">=3.141.0,<4.0.0" +sphinx = ">=3.0,<4.0" +sphinx-autodoc-typehints = ">=1.7,<2.0" +sphinxcontrib-django = ">=0.5.0,<0.6.0" + +[package.source] +type = "legacy" +url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple" +reference = "gitlab" + +[[package]] name = "amqp" +version = "5.0.2" +description = "Low-level AMQP client for Python (fork of amqplib)." +category = "main" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.6.1" +python-versions = ">=3.6" [package.dependencies] -vine = ">=1.1.3,<5.0.0a1" +vine = "5.0.0" [[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = "*" -version = "1.4.4" [[package]] -category = "main" -description = "ASGI specs, helper code, and adapters" name = "asgiref" +version = "3.3.1" +description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.5" -version = "3.2.10" [package.extras] tests = ["pytest", "pytest-asyncio"] [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" [[package]] -category = "dev" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.3.0" +description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.2.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "main" -description = "Internationalization utilities" name = "babel" +version = "2.9.0" +description = "Internationalization utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8.0" [package.dependencies] pytz = ">=2015.7" [[package]] -category = "dev" -description = "Security oriented static analyser for python code." name = "bandit" +version = "1.7.0" +description = "Security oriented static analyser for python code." +category = "dev" optional = false -python-versions = "*" -version = "1.6.2" +python-versions = ">=3.5" [package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" -PyYAML = ">=3.13" -colorama = ">=0.3.9" +PyYAML = ">=5.3.1" six = ">=1.10.0" stevedore = ">=1.20.0" [[package]] -category = "main" -description = "Screen-scraping library" -name = "beautifulsoup4" -optional = false -python-versions = "*" -version = "4.9.1" - -[package.dependencies] -soupsieve = [">1.2", "<2.0"] - -[package.extras] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -category = "main" -description = "Python multiprocessing fork with improvements and bugfixes" name = "billiard" +version = "3.6.3.0" +description = "Python multiprocessing fork with improvements and bugfixes" +category = "main" optional = true python-versions = "*" -version = "3.6.3.0" [[package]] -category = "dev" -description = "The uncompromising code formatter." name = "black" +version = "19.10b0" +description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.6" -version = "19.10b0" [package.dependencies] appdirs = "*" @@ -129,12 +151,12 @@ typed-ast = ">=1.4.0" d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -category = "main" -description = "An easy safelist-based HTML-sanitizing tool." name = "bleach" +version = "3.2.1" +description = "An easy safelist-based HTML-sanitizing tool." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.1.5" [package.dependencies] packaging = "*" @@ -142,98 +164,96 @@ six = ">=1.9.0" webencodings = "*" [[package]] -category = "main" -description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL." name = "boolean.py" +version = "3.8" +description = "Define boolean algebras, create and parse boolean expressions and create custom boolean DSL." +category = "main" optional = false python-versions = "*" -version = "3.8" [[package]] -category = "main" -description = "Utilities for working with calendar weeks in Python and Django" name = "calendarweek" +version = "0.4.7" +description = "Utilities for working with calendar weeks in Python and Django" +category = "main" optional = false python-versions = ">=3.7,<4.0" -version = "0.4.5" [package.extras] django = ["Django (>=2.2,<4.0)"] [[package]] -category = "main" -description = "Distributed Task Queue." name = "celery" +version = "5.0.5" +description = "Distributed Task Queue." +category = "main" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.4.7" +python-versions = ">=3.6," [package.dependencies] billiard = ">=3.6.3.0,<4.0" -kombu = ">=4.6.10,<4.7" +click = ">=7.0,<8.0" +click-didyoumean = ">=0.0.3" +click-plugins = ">=1.1.1" +click-repl = ">=0.1.6" +Django = {version = ">=1.11", optional = true, markers = "extra == \"django\""} +kombu = ">=5.0.0,<6.0" pytz = ">0.0-dev" -vine = "1.3.0" - -[package.dependencies.Django] -optional = true -version = ">=1.11" - -[package.dependencies.redis] -optional = true -version = ">=3.2.0" +redis = {version = ">=3.2.0", optional = true, markers = "extra == \"redis\""} +vine = ">=5.0.0,<6.0" [package.extras] arangodb = ["pyArango (>=1.3.2)"] auth = ["cryptography"] -azureblockblob = ["azure-storage (0.36.0)", "azure-common (1.1.5)", "azure-storage-common (1.1.0)"] +azureblockblob = ["azure-storage (==0.36.0)", "azure-common (==1.1.5)", "azure-storage-common (==1.1.0)"] brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] cassandra = ["cassandra-driver (<3.21.0)"] consul = ["python-consul"] -cosmosdbsql = ["pydocumentdb (2.3.2)"] -couchbase = ["couchbase-cffi (<3.0.0)", "couchbase (<3.0.0)"] +cosmosdbsql = ["pydocumentdb (==2.3.2)"] +couchbase = ["couchbase (>=3.0.0)"] couchdb = ["pycouchdb"] django = ["Django (>=1.11)"] dynamodb = ["boto3 (>=1.9.178)"] elasticsearch = ["elasticsearch"] -eventlet = ["eventlet (>=0.24.1)"] -gevent = ["gevent"] +eventlet = ["eventlet (>=0.26.1)"] +gevent = ["gevent (>=1.0.0)"] librabbitmq = ["librabbitmq (>=1.5.0)"] lzma = ["backports.lzma"] memcache = ["pylibmc"] -mongodb = ["pymongo (>=3.3.0)"] +mongodb = ["pymongo[srv] (>=3.3.0)"] msgpack = ["msgpack"] pymemcache = ["python-memcached"] pyro = ["pyro4"] +pytest = ["pytest-celery"] redis = ["redis (>=3.2.0)"] -riak = ["riak (>=2.0)"] s3 = ["boto3 (>=1.9.125)"] slmq = ["softlayer-messaging (>=1.0.3)"] solar = ["ephem"] sqlalchemy = ["sqlalchemy"] -sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.5)"] +sqs = ["boto3 (>=1.9.125)", "pycurl (==7.43.0.5)"] tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] zstd = ["zstandard"] [[package]] -category = "main" -description = "An app for integrating Celery with Haystack." name = "celery-haystack" +version = "0.10" +description = "An app for integrating Celery with Haystack." +category = "main" optional = true python-versions = "*" -version = "0.10" [package.dependencies] django-appconf = ">=0.4.1" [[package]] -category = "main" -description = "Drop in, configurable, dependency-free progress bars for your Django/Celery applications." name = "celery-progress" +version = "0.0.14" +description = "Drop in, configurable, dependency-free progress bars for your Django/Celery applications." +category = "main" optional = false python-versions = "*" -version = "0.0.12" [package.extras] rabbitmq = ["channels-rabbitmq"] @@ -241,89 +261,126 @@ redis = ["channels-redis"] websockets = ["channels"] [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.6.20" [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false -python-versions = "*" -version = "3.0.4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] -category = "main" -description = "Composable command line interface toolkit" name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" [[package]] +name = "click-didyoumean" +version = "0.0.3" +description = "Enable git-like did-you-mean feature in click." category = "main" -description = "Cross-platform colored terminal text." -marker = "platform_system == \"Windows\" or sys_platform == \"win32\"" +optional = true +python-versions = "*" + +[package.dependencies] +click = "*" + +[[package]] +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 +python-versions = "*" + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["pytest (>=3.6)", "pytest-cov", "wheel", "coveralls"] + +[[package]] +name = "click-repl" +version = "0.1.6" +description = "REPL plugin for Click" +category = "main" +optional = true +python-versions = "*" + +[package.dependencies] +click = "*" +prompt-toolkit = "*" +six = "*" + +[[package]] name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" [[package]] -category = "main" -description = "converts and manipulates various color representation (HSL, RVB, web, X11, ...)" name = "colour" +version = "0.1.5" +description = "converts and manipulates various color representation (HSL, RVB, web, X11, ...)" +category = "main" optional = false python-versions = "*" -version = "0.1.5" [package.extras] test = ["nose"] [[package]] -category = "main" -description = "Config file reading, writing and validation." name = "configobj" +version = "5.0.6" +description = "Config file reading, writing and validation." +category = "main" optional = false python-versions = "*" -version = "5.0.6" [package.dependencies] six = "*" [[package]] -category = "dev" -description = "Code coverage measurement for Python" name = "coverage" +version = "5.3" +description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "5.2.1" [package.extras] toml = ["toml"] [[package]] -category = "dev" -description = "Use Database URLs in your Django Application." name = "dj-database-url" +version = "0.5.0" +description = "Use Database URLs in your Django Application." +category = "dev" optional = false python-versions = "*" -version = "0.5.0" [[package]] -category = "main" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." name = "django" +version = "3.1.4" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" optional = false python-versions = ">=3.6" -version = "3.1.1" [package.dependencies] -asgiref = ">=3.2.10,<3.3.0" +asgiref = ">=3.2.10,<4" pytz = "*" sqlparse = ">=0.2.2" @@ -332,94 +389,105 @@ argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] -category = "main" -description = "Include JavaScript libraries with readable template tags" name = "django-any-js" +version = "1.0.3.post0" +description = "Include JavaScript libraries with readable template tags" +category = "main" optional = false python-versions = "*" -version = "1.0.3.post0" [package.dependencies] Django = ">=1.11" [[package]] -category = "main" -description = "A helper class for handling configuration defaults of packaged apps gracefully." name = "django-appconf" +version = "1.0.4" +description = "A helper class for handling configuration defaults of packaged apps gracefully." +category = "main" optional = false python-versions = "*" -version = "1.0.4" [package.dependencies] django = "*" [[package]] -category = "main" -description = "Django LDAP authentication backend." name = "django-auth-ldap" +version = "2.2.0" +description = "Django LDAP authentication backend." +category = "main" optional = true python-versions = ">=3.5" -version = "2.2.0" [package.dependencies] Django = ">=1.11" python-ldap = ">=3.1" [[package]] -category = "main" -description = "Easily use bleach with Django models and templates" name = "django-bleach" +version = "0.6.1" +description = "Easily use bleach with Django models and templates" +category = "main" optional = false python-versions = "*" -version = "0.6.1" [package.dependencies] -Django = ">=1.11" bleach = ">=1.5.0" +Django = ">=1.11" [[package]] -category = "main" -description = "Bulk update using one query over Django ORM." name = "django-bulk-update" +version = "2.2.0" +description = "Bulk update using one query over Django ORM." +category = "main" optional = false python-versions = "*" -version = "2.2.0" [package.dependencies] Django = ">=1.8" [[package]] +name = "django-cachalot" +version = "2.3.3" +description = "Caches your Django ORM queries and automatically invalidates them." category = "main" -description = "Django utility for a memoization decorator that uses the Django cache framework." +optional = false +python-versions = "*" + +[package.dependencies] +Django = ">=2" + +[[package]] name = "django-cache-memoize" +version = "0.1.7" +description = "Django utility for a memoization decorator that uses the Django cache framework." +category = "main" optional = false python-versions = ">=3.4" -version = "0.1.7" [package.extras] dev = ["flake8", "tox", "twine", "therapist", "black"] [[package]] -category = "main" -description = "Database-backed Periodic Tasks." name = "django-celery-beat" +version = "2.1.0" +description = "Database-backed Periodic Tasks." +category = "main" optional = true python-versions = "*" -version = "2.0.0" [package.dependencies] -Django = ">=1.11.17" -celery = "*" +celery = ">=4.4,<6.0" +Django = ">=2.2" django-timezone-field = ">=4.0,<5.0" python-crontab = ">=2.3.4" [[package]] -category = "main" -description = "An async Django email backend using celery" name = "django-celery-email" +version = "3.0.0" +description = "An async Django email backend using celery" +category = "main" optional = true python-versions = "*" -version = "3.0.0" [package.dependencies] celery = ">=4.0" @@ -427,42 +495,42 @@ django = ">=2.2" django-appconf = "*" [[package]] -category = "main" -description = "Celery result backends for Django." name = "django-celery-results" +version = "2.0.0" +description = "Celery result backends for Django." +category = "main" optional = true python-versions = "*" -version = "1.2.1" [package.dependencies] -celery = ">=4.4,<5.0" +celery = ">=4.4,<6.0" [[package]] -category = "main" -description = "Django admin CKEditor integration." name = "django-ckeditor" +version = "6.0.0" +description = "Django admin CKEditor integration." +category = "main" optional = false python-versions = "*" -version = "6.0.0" [package.dependencies] django-js-asset = ">=1.2.2" [[package]] -category = "main" -description = "simple color field for your models with a nice color-picker in the admin-interface." name = "django-colorfield" +version = "0.3.2" +description = "simple color field for your models with a nice color-picker in the admin-interface." +category = "main" optional = false python-versions = "*" -version = "0.3.2" [[package]] -category = "main" -description = "Management commands to help backup and restore a project database and media" name = "django-dbbackup" +version = "3.3.0" +description = "Management commands to help backup and restore a project database and media" +category = "main" optional = false python-versions = "*" -version = "3.3.0" [package.dependencies] Django = ">=1.5" @@ -470,24 +538,24 @@ pytz = "*" six = "*" [[package]] -category = "main" -description = "A configurable set of panels that display various debug information about the current request/response." name = "django-debug-toolbar" +version = "2.2" +description = "A configurable set of panels that display various debug information about the current request/response." +category = "main" optional = false python-versions = ">=3.5" -version = "2.2" [package.dependencies] Django = ">=1.11" sqlparse = ">=0.2.0" [[package]] -category = "main" -description = "Dynamic global and instance settings for your django project" name = "django-dynamic-preferences" +version = "1.10.1" +description = "Dynamic global and instance settings for your django project" +category = "main" optional = false python-versions = "*" -version = "1.10.1" [package.dependencies] django = ">=1.11" @@ -495,300 +563,300 @@ persisting-theory = ">=0.2.1" six = "*" [[package]] -category = "main" -description = "Yet another Django audit log app, hopefully the simplest one." -name = "django-easy-audit" -optional = false -python-versions = ">=3.5" -version = "1.3.0a5" - -[package.dependencies] -beautifulsoup4 = "*" -django = ">=2.2,<3.2" - -[[package]] -category = "main" -description = "simple Django app which allows you to upload a image and it renders a wide variety for html link tags to display the favicon" name = "django-favicon-plus-reloaded" +version = "1.0.4" +description = "simple Django app which allows you to upload a image and it renders a wide variety for html link tags to display the favicon" +category = "main" optional = false python-versions = "*" -version = "1.0.4" [package.dependencies] django = "*" pillow = "*" [[package]] -category = "main" -description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." name = "django-filter" +version = "2.4.0" +description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." +category = "main" optional = false python-versions = ">=3.5" -version = "2.3.0" [package.dependencies] Django = ">=2.2" [[package]] -category = "main" -description = "A set of high-level abstractions for Django forms" name = "django-formtools" +version = "2.2" +description = "A set of high-level abstractions for Django forms" +category = "main" optional = false python-versions = "*" -version = "2.2" [package.dependencies] Django = ">=1.11" [[package]] -category = "main" -description = "Implementation of per object permissions for Django." name = "django-guardian" +version = "2.3.0" +description = "Implementation of per object permissions for Django." +category = "main" optional = false python-versions = ">=3.5" -version = "2.3.0" [package.dependencies] Django = ">=2.2" [[package]] -category = "main" -description = "Command to anonymize sensitive data." name = "django-hattori" +version = "0.2.1" +description = "Command to anonymize sensitive data." +category = "main" optional = false python-versions = "*" -version = "0.2.1" [package.dependencies] Django = ">=1.8" -Faker = ">=0.8.13" django-bulk-update = ">=2.2.0" +Faker = ">=0.8.13" six = "*" tqdm = ">=4.23.4" [[package]] -category = "main" -description = "Pluggable search for Django." name = "django-haystack" +version = "3.0b1" +description = "Pluggable search for Django." +category = "main" optional = false python-versions = "*" -version = "3.0b1" [package.dependencies] Django = ">=2.2" [[package]] -category = "main" -description = "Run checks on services like databases, queue servers, celery processes, etc." name = "django-health-check" +version = "3.16.1" +description = "Run checks on services like databases, queue servers, celery processes, etc." +category = "main" optional = false python-versions = "*" -version = "3.12.1" [package.dependencies] django = ">=1.11" [[package]] -category = "main" -description = "A reusable app for cropping images easily and non-destructively in Django" -name = "django-image-cropping" -optional = false -python-versions = ">=3.5" -version = "1.4.0" - -[package.dependencies] -django-appconf = ">=1.0.2" - -[[package]] -category = "main" -description = "Django app to allow superusers to impersonate other users." name = "django-impersonate" +version = "1.7" +description = "Django app to allow superusers to impersonate other users." +category = "main" optional = false python-versions = "*" -version = "1.5.1" [[package]] -category = "main" -description = "A Django utility application that returns client's real IP address" name = "django-ipware" +version = "3.0.2" +description = "A Django utility application that returns client's real IP address" +category = "main" optional = false python-versions = "*" -version = "3.0.1" [[package]] -category = "main" -description = "script tag with additional attributes for django.forms.Media" name = "django-js-asset" +version = "1.2.2" +description = "script tag with additional attributes for django.forms.Media" +category = "main" optional = false python-versions = "*" -version = "1.2.2" [[package]] -category = "main" -description = "Javascript url handling for Django that doesn't hurt." name = "django-js-reverse" +version = "0.9.1" +description = "Javascript url handling for Django that doesn't hurt." +category = "main" optional = false python-versions = "*" -version = "0.9.1" [package.dependencies] Django = ">=1.5" [[package]] -category = "main" -description = "Expose JSONField data as a virtual django model fields." name = "django-jsonstore" +version = "0.4.1" +description = "Expose JSONField data as a virtual django model fields." +category = "main" optional = false python-versions = "*" -version = "0.4.1" [package.dependencies] Django = ">=1.11" six = "*" [[package]] -category = "main" -description = "django-maintenance-mode shows a 503 error page when maintenance-mode is on." name = "django-maintenance-mode" +version = "0.15.1" +description = "django-maintenance-mode shows a 503 error page when maintenance-mode is on." +category = "main" optional = false python-versions = "*" -version = "0.14.0" [[package]] -category = "main" -description = "Material design for django forms and admin" name = "django-material" +version = "1.7.3" +description = "Material design for django forms and admin" +category = "main" optional = false python-versions = "*" -version = "1.7.0" [package.dependencies] six = "*" [[package]] -category = "main" -description = "A straightforward menu generator for Django" name = "django-menu-generator" +version = "1.1.0" +description = "A straightforward menu generator for Django" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "django-middleware-global-request" +version = "0.1.2" +description = "Django middleware that keep request instance for every thread." +category = "main" optional = false python-versions = "*" -version = "1.0.4" + +[package.dependencies] +django = "*" [[package]] +name = "django-model-utils" +version = "4.1.1" +description = "Django model mixins and utilities" category = "main" -description = "Django middleware that keep request instance for every thread." -name = "django-middleware-global-request" optional = false python-versions = "*" -version = "0.1.2" [package.dependencies] -django = "*" +Django = ">=2.0.1" [[package]] -category = "main" -description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords." name = "django-otp" +version = "1.0.2" +description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords." +category = "main" optional = false python-versions = "*" -version = "0.9.4" [package.dependencies] -django = ">=1.11" +django = ">=2.2" [package.extras] qrcode = ["qrcode"] [[package]] -category = "main" -description = "A django-otp plugin that verifies YubiKey OTP tokens." name = "django-otp-yubikey" +version = "1.0.0" +description = "A django-otp plugin that verifies YubiKey OTP tokens." +category = "main" optional = false python-versions = "*" -version = "0.6.0" [package.dependencies] +django-otp = ">=1.0.0" YubiOTP = ">=0.2.2" -django-otp = ">=0.5.0" [[package]] -category = "main" -description = "An international phone number field for django models." name = "django-phonenumber-field" +version = "3.0.1" +description = "An international phone number field for django models." +category = "main" optional = false python-versions = ">=3.5" -version = "3.0.1" [package.dependencies] -Django = ">=1.11.3" babel = "*" +Django = ">=1.11.3" +phonenumbers = {version = ">=7.0.2", optional = true, markers = "extra == \"phonenumbers\""} [package.extras] phonenumbers = ["phonenumbers (>=7.0.2)"] phonenumberslite = ["phonenumberslite (>=7.0.2)"] [[package]] -category = "main" -description = "Seamless polymorphic inheritance for Django models" name = "django-polymorphic" +version = "3.0.0" +description = "Seamless polymorphic inheritance for Django models" +category = "main" optional = false python-versions = "*" -version = "2.1.2" [package.dependencies] -Django = ">=1.11" +Django = ">=2.1" [[package]] +name = "django-prometheus" +version = "2.1.0" +description = "Django middlewares to monitor your application with Prometheus.io." category = "main" -description = "A Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior" -name = "django-pwa" optional = false python-versions = "*" + +[package.dependencies] +prometheus-client = ">=0.7" + +[[package]] +name = "django-pwa" version = "1.0.10" +description = "A Django app to include a manifest.json and Service Worker instance to enable progressive web app behavior" +category = "main" +optional = false +python-versions = "*" [package.dependencies] django = ">=1.8" [[package]] -category = "main" -description = "Render a particular block from a template to a string." name = "django-render-block" +version = "0.8.1" +description = "Render a particular block from a template to a string." +category = "main" optional = false python-versions = ">=3.5" -version = "0.7" [package.dependencies] django = ">=2.2" [[package]] -category = "main" -description = "An extension to the Django web framework that provides version control for model instances." name = "django-reversion" +version = "3.0.8" +description = "An extension to the Django web framework that provides version control for model instances." +category = "main" optional = false python-versions = ">=3.6" -version = "3.0.8" [package.dependencies] django = ">=1.11" [[package]] -category = "main" -description = "SASS processor to compile SCSS files into *.css, while rendering, or offline." name = "django-sass-processor" +version = "0.8.2" +description = "SASS processor to compile SCSS files into *.css, while rendering, or offline." +category = "main" optional = false python-versions = "*" -version = "0.8.2" [package.extras] dev = ["libsass (>=0.13)"] management-command = ["django-compressor (>=2.4)"] [[package]] -category = "main" -description = "Select2 option fields for Django" name = "django-select2" +version = "7.5.0" +description = "Select2 option fields for Django" +category = "main" optional = false python-versions = "*" -version = "7.4.2" [package.dependencies] django = ">=2.2" @@ -798,33 +866,33 @@ django-appconf = ">=0.6.0" test = ["pytest", "pytest-cov", "pytest-django", "selenium"] [[package]] -category = "main" -description = "Makes specified django settings visible in template rendering context." name = "django-settings-context-processor" +version = "0.2" +description = "Makes specified django settings visible in template rendering context." +category = "main" optional = false python-versions = "*" -version = "0.2" [[package]] -category = "dev" -description = "Mypy stubs for Django" name = "django-stubs" +version = "1.7.0" +description = "Mypy stubs for Django" +category = "dev" optional = false python-versions = ">=3.6" -version = "1.5.0" [package.dependencies] django = "*" -mypy = ">=0.770,<0.780" +mypy = ">=0.790" typing-extensions = "*" [[package]] -category = "main" -description = "Table/data-grid framework for Django" name = "django-tables2" +version = "2.3.3" +description = "Table/data-grid framework for Django" +category = "main" optional = false python-versions = "*" -version = "2.3.1" [package.dependencies] Django = ">=1.11" @@ -833,55 +901,49 @@ Django = ">=1.11" tablib = ["tablib"] [[package]] -category = "main" -description = "A Django oriented templated / transaction email abstraction" name = "django-templated-email" +version = "2.3.0" +description = "A Django oriented templated / transaction email abstraction" +category = "main" optional = false python-versions = "*" -version = "2.3.0" [package.dependencies] django-render-block = ">=0.5" six = ">=1" [[package]] -category = "main" -description = "A Django app providing database and form fields for pytz timezone objects." 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 python-versions = ">=3.5" -version = "4.0" [package.dependencies] django = ">=2.2" pytz = "*" +[package.extras] +rest_framework = ["djangorestframework (>=3.0.0)"] + [[package]] -category = "main" -description = "Complete Two-Factor Authentication for Django" name = "django-two-factor-auth" +version = "1.13" +description = "Complete Two-Factor Authentication for Django" +category = "main" optional = false python-versions = "*" -version = "1.12.1" [package.dependencies] Django = ">=2.2" django-formtools = "*" -django-otp = ">=0.6.0,<0.99" +django-otp = ">=0.8.0" +django-otp-yubikey = {version = "*", optional = true, markers = "extra == \"yubikey\""} django-phonenumber-field = ">=1.1.0,<3.99" +phonenumbers = {version = ">=7.0.9,<8.99", optional = true, markers = "extra == \"phonenumbers\""} qrcode = ">=4.0.0,<6.99" - -[package.dependencies.django-otp-yubikey] -optional = true -version = "*" - -[package.dependencies.phonenumbers] -optional = true -version = ">=7.0.9,<8.99" - -[package.dependencies.twilio] -optional = true -version = ">=6.0" +twilio = {version = ">=6.0", optional = true, markers = "extra == \"call\""} [package.extras] call = ["twilio (>=6.0)"] @@ -891,40 +953,40 @@ sms = ["twilio (>=6.0)"] yubikey = ["django-otp-yubikey"] [[package]] -category = "main" -description = "Tweak the form field rendering in templates, not in python-level form definitions." name = "django-widget-tweaks" +version = "1.4.8" +description = "Tweak the form field rendering in templates, not in python-level form definitions." +category = "main" optional = false python-versions = "*" -version = "1.4.8" [[package]] -category = "main" -description = "Integrate django with yarnpkg" name = "django-yarnpkg" +version = "6.0.1" +description = "Integrate django with yarnpkg" +category = "main" optional = false python-versions = "*" -version = "6.0.1" [package.dependencies] django = "*" six = "*" [[package]] -category = "dev" -description = "Docutils -- Python Documentation Utilities" name = "docutils" +version = "0.16" +description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.16" [[package]] -category = "dev" -description = "A parser for Python dependency files" name = "dparse" +version = "0.5.1" +description = "A parser for Python dependency files" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.5.1" [package.dependencies] packaging = "*" @@ -935,84 +997,60 @@ toml = "*" pipenv = ["pipenv"] [[package]] -category = "main" -description = "The dynamic configurator for your Python Project" name = "dynaconf" +version = "3.1.2" +description = "The dynamic configurator for your Python Project" +category = "main" optional = false python-versions = "*" -version = "2.2.3" [package.dependencies] -click = "*" -python-box = "<4.0.0" -python-dotenv = "*" -toml = "*" - -[package.dependencies.PyYAML] -optional = true -version = "*" - -[package.dependencies.configobj] -optional = true -version = "*" +configobj = {version = "*", optional = true, markers = "extra == \"ini\""} +"ruamel.yaml" = {version = "*", optional = true, markers = "extra == \"yaml\""} +toml = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] -all = ["redis", "pyyaml", "configobj", "hvac"] +all = ["redis", "ruamel.yaml", "configobj", "hvac"] configobj = ["configobj"] ini = ["configobj"] redis = ["redis"] toml = ["toml"] vault = ["hvac"] -yaml = ["pyyaml"] - -[[package]] -category = "main" -description = "Easy thumbnails for Django" -name = "easy-thumbnails" -optional = false -python-versions = ">=3.5" -version = "2.7" - -[package.dependencies] -django = ">=1.11,<4.0" -pillow = "*" +yaml = ["ruamel.yaml"] [[package]] -category = "main" -description = "Faker is a Python package that generates fake data for you." name = "faker" +version = "5.0.2" +description = "Faker is a Python package that generates fake data for you." +category = "main" optional = false -python-versions = ">=3.5" -version = "4.1.2" +python-versions = ">=3.6" [package.dependencies] python-dateutil = ">=2.4" text-unidecode = "1.3" [[package]] -category = "dev" -description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.3" [package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - [[package]] -category = "dev" -description = "Automated security testing with bandit and flake8." name = "flake8-bandit" +version = "2.1.2" +description = "Automated security testing with bandit and flake8." +category = "dev" optional = false python-versions = "*" -version = "2.1.2" [package.dependencies] bandit = "*" @@ -1021,24 +1059,24 @@ flake8-polyfill = "*" pycodestyle = "*" [[package]] -category = "dev" -description = "flake8 plugin to call black as a code style validator" name = "flake8-black" +version = "0.2.1" +description = "flake8 plugin to call black as a code style validator" +category = "dev" optional = false python-versions = "*" -version = "0.2.1" [package.dependencies] black = "*" flake8 = ">=3.0.0" [[package]] -category = "dev" -description = "Check for python builtins being used as variables or parameters." name = "flake8-builtins" +version = "1.5.3" +description = "Check for python builtins being used as variables or parameters." +category = "dev" optional = false python-versions = "*" -version = "1.5.3" [package.dependencies] flake8 = "*" @@ -1047,43 +1085,43 @@ flake8 = "*" test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] [[package]] -category = "dev" -description = "Plugin to catch bad style specific to Django Projects" name = "flake8-django" +version = "1.1.1" +description = "Plugin to catch bad style specific to Django Projects" +category = "dev" optional = false python-versions = "*" -version = "1.1.1" [package.dependencies] flake8 = "*" [[package]] -category = "dev" -description = "Extension for flake8 which uses pydocstyle to check docstrings" name = "flake8-docstrings" +version = "1.5.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +category = "dev" optional = false python-versions = "*" -version = "1.5.0" [package.dependencies] flake8 = ">=3" pydocstyle = ">=2.1" [[package]] -category = "dev" -description = "Check for FIXME, TODO and other temporary developer notes. Plugin for flake8." name = "flake8-fixme" +version = "1.1.1" +description = "Check for FIXME, TODO and other temporary developer notes. Plugin for flake8." +category = "dev" optional = false python-versions = "*" -version = "1.1.1" [[package]] -category = "dev" -description = "flake8 plugin that integrates isort ." name = "flake8-isort" +version = "4.0.0" +description = "flake8 plugin that integrates isort ." +category = "dev" optional = false python-versions = "*" -version = "4.0.0" [package.dependencies] flake8 = ">=3.2.1,<4" @@ -1094,12 +1132,12 @@ testfixtures = ">=6.8.0,<7" test = ["pytest (>=4.0.2,<6)", "toml"] [[package]] -category = "dev" -description = "A plugin for flake8 integrating mypy." name = "flake8-mypy" +version = "17.8.0" +description = "A plugin for flake8 integrating mypy." +category = "dev" optional = false python-versions = "*" -version = "17.8.0" [package.dependencies] attrs = "*" @@ -1107,118 +1145,118 @@ flake8 = ">=3.0.0" mypy = "*" [[package]] -category = "dev" -description = "Polyfill package for Flake8 plugins" name = "flake8-polyfill" +version = "1.0.2" +description = "Polyfill package for Flake8 plugins" +category = "dev" optional = false python-versions = "*" -version = "1.0.2" [package.dependencies] flake8 = "*" [[package]] -category = "dev" -description = "Python docstring reStructuredText (RST) validator" name = "flake8-rst-docstrings" +version = "0.0.13" +description = "Python docstring reStructuredText (RST) validator" +category = "dev" optional = false python-versions = "*" -version = "0.0.13" [package.dependencies] flake8 = ">=3.0.0" restructuredtext_lint = "*" [[package]] -category = "dev" -description = "Git Object Database" name = "gitdb" +version = "4.0.5" +description = "Git Object Database" +category = "dev" optional = false python-versions = ">=3.4" -version = "4.0.5" [package.dependencies] smmap = ">=3.0.1,<4" [[package]] -category = "dev" -description = "Python Git Library" name = "gitpython" +version = "3.1.11" +description = "Python Git Library" +category = "dev" optional = false python-versions = ">=3.4" -version = "3.1.8" [package.dependencies] gitdb = ">=4.0.1,<5" [[package]] -category = "main" -description = "Turn HTML into equivalent Markdown-structured text." name = "html2text" +version = "2020.1.16" +description = "Turn HTML into equivalent Markdown-structured text." +category = "main" optional = false python-versions = ">=3.5" -version = "2020.1.16" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "dev" -description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" +version = "1.2.0" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.2.0" [[package]] -category = "main" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "3.3.0" +description = "Read metadata from Python packages" +category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.7.0" +python-versions = ">=3.6" [package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] +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)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] -category = "dev" -description = "iniconfig: brain-dead simple config-ini parsing" name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = "*" -version = "1.0.1" [[package]] -category = "dev" -description = "A Python utility / library to sort Python imports." name = "isort" +version = "5.6.4" +description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.6,<4.0" -version = "5.5.1" [package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] [[package]] -category = "dev" -description = "A very fast and expressive template engine." name = "jinja2" +version = "2.11.2" +description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -1227,19 +1265,16 @@ MarkupSafe = ">=0.23" i18n = ["Babel (>=0.8)"] [[package]] -category = "main" -description = "Messaging library for Python." name = "kombu" +version = "5.0.2" +description = "Messaging library for Python." +category = "main" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.6.11" +python-versions = ">=3.6" [package.dependencies] -amqp = ">=2.6.0,<2.7" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.18" +amqp = ">=5.0.0,<6.0.0" +importlib-metadata = {version = ">=0.18", markers = "python_version < \"3.8\""} [package.extras] azureservicebus = ["azure-servicebus (>=0.21.1)"] @@ -1253,63 +1288,55 @@ qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"] redis = ["redis (>=3.3.11)"] slmq = ["softlayer-messaging (>=1.0.3)"] sqlalchemy = ["sqlalchemy"] -sqs = ["boto3 (>=1.4.4)", "pycurl (7.43.0.2)"] +sqs = ["boto3 (>=1.4.4)", "pycurl (==7.43.0.2)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] [[package]] -category = "main" -description = "Sass for Python: A straightforward binding of libsass for Python." name = "libsass" +version = "0.20.1" +description = "Sass for Python: A straightforward binding of libsass for Python." +category = "main" optional = false python-versions = "*" -version = "0.20.1" [package.dependencies] six = "*" [[package]] -category = "main" -description = "license-expression is small utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." name = "license-expression" +version = "1.2" +description = "license-expression is small utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic." +category = "main" optional = false python-versions = "*" -version = "1.2" [package.dependencies] "boolean.py" = ">=3.6,<4.0.0" [[package]] -category = "dev" -description = "Safely add untrusted strings to HTML/XML markup." name = "markupsafe" +version = "1.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.1.1" [[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" name = "mccabe" -optional = false -python-versions = "*" version = "0.6.1" - -[[package]] +description = "McCabe checker, plugin for flake8" category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" optional = false -python-versions = ">=3.5" -version = "8.5.0" +python-versions = "*" [[package]] -category = "dev" -description = "Optional static typing for Python" name = "mypy" +version = "0.790" +description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.770" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -1320,188 +1347,207 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [[package]] -category = "main" -description = "Core utilities for Python packages" name = "packaging" +version = "20.8" +description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" -six = "*" [[package]] -category = "dev" -description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" [[package]] -category = "dev" -description = "Python Build Reasonableness" name = "pbr" +version = "5.5.1" +description = "Python Build Reasonableness" +category = "dev" optional = false python-versions = ">=2.6" -version = "5.5.0" [[package]] -category = "main" -description = "Registries that can autodiscover values accross your project apps" name = "persisting-theory" +version = "0.2.1" +description = "Registries that can autodiscover values accross your project apps" +category = "main" optional = false python-versions = "*" -version = "0.2.1" [[package]] -category = "dev" -description = "PostgreSQL interface library" name = "pg8000" +version = "1.16.6" +description = "PostgreSQL interface library" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.16.5" [package.dependencies] scramp = "1.2.0" [[package]] -category = "main" -description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." name = "phonenumbers" +version = "8.12.15" +description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." +category = "main" optional = false python-versions = "*" -version = "8.12.9" [[package]] -category = "main" -description = "Python Imaging Library (Fork)" name = "pillow" +version = "8.0.1" +description = "Python Imaging Library (Fork)" +category = "main" optional = false -python-versions = ">=3.5" -version = "7.2.0" +python-versions = ">=3.6" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] +name = "prometheus-client" +version = "0.9.0" +description = "Python client for the Prometheus monitoring system." category = "main" -description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "*" + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.8" +description = "Library for building powerful interactive command lines in Python" +category = "main" +optional = true +python-versions = ">=3.6.1" + +[package.dependencies] +wcwidth = "*" + +[[package]] name = "psutil" +version = "5.8.0" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "5.7.2" [package.extras] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] [[package]] -category = "main" -description = "psycopg2 - Python-PostgreSQL Database Adapter" name = "psycopg2" +version = "2.8.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "2.8.6" [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.10.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.0" [[package]] -category = "main" -description = "ASN.1 types and codecs" name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" optional = true python-versions = "*" -version = "0.4.8" [[package]] -category = "main" -description = "A collection of ASN.1-based protocols modules." name = "pyasn1-modules" +version = "0.2.8" +description = "A collection of ASN.1-based protocols modules." +category = "main" optional = true python-versions = "*" -version = "0.2.8" [package.dependencies] pyasn1 = ">=0.4.6,<0.5.0" [[package]] -category = "dev" -description = "Python style guide checker" name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" [[package]] -category = "main" -description = "Cryptographic library for Python" name = "pycryptodome" +version = "3.9.9" +description = "Cryptographic library for Python" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.9.8" [[package]] -category = "dev" -description = "Python docstring style checker" name = "pydocstyle" +version = "5.1.1" +description = "Python docstring style checker" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.1.1" [package.dependencies] snowballstemmer = "*" [[package]] -category = "dev" -description = "passive checker of Python programs" name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.2.0" [[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." name = "pygments" +version = "2.7.3" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.5" -version = "2.6.1" [[package]] -category = "main" -description = "JSON Web Token implementation in Python" name = "pyjwt" +version = "1.7.1" +description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = "*" -version = "1.7.1" [package.extras] crypto = ["cryptography (>=1.4)"] @@ -1509,62 +1555,57 @@ flake8 = ["flake8", "flake8-import-order", "pep8-naming"] test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] [[package]] -category = "main" -description = "Python parsing module" name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "6.2.1" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false -python-versions = ">=3.5" -version = "6.0.1" +python-versions = ">=3.6" [package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = "*" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" -more-itertools = ">=4.0.0" packaging = "*" -pluggy = ">=0.12,<1.0" +pluggy = ">=0.12,<1.0.0a1" py = ">=1.8.2" toml = "*" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] -checkqa_mypy = ["mypy (0.780)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "dev" -description = "Pytest plugin for measuring coverage." name = "pytest-cov" +version = "2.10.1" +description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.10.1" [package.dependencies] coverage = ">=4.4" pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] -category = "dev" -description = "A Django plugin for pytest." name = "pytest-django" +version = "3.10.0" +description = "A Django plugin for pytest." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.9.0" [package.dependencies] pytest = ">=3.6" @@ -1574,24 +1615,24 @@ docs = ["sphinx", "sphinx-rtd-theme"] testing = ["django", "django-configurations (>=2.0)", "six"] [[package]] -category = "dev" -description = "Use a temporary PostgreSQL database with pytest-django" name = "pytest-django-testing-postgresql" +version = "0.1.post0" +description = "Use a temporary PostgreSQL database with pytest-django" +category = "dev" optional = false python-versions = "*" -version = "0.1.post0" [package.dependencies] dj-database-url = "*" "testing.postgresql" = "*" [[package]] -category = "dev" -description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." name = "pytest-sugar" +version = "0.9.4" +description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." +category = "dev" optional = false python-versions = "*" -version = "0.9.4" [package.dependencies] packaging = ">=14.1" @@ -1599,23 +1640,12 @@ pytest = ">=2.9" termcolor = ">=1.1.0" [[package]] -category = "main" -description = "Advanced Python dictionaries with dot notation access" -name = "python-box" -optional = false -python-versions = "*" -version = "3.4.6" - -[package.extras] -testing = ["pytest", "coverage (>=3.6)", "pytest-cov"] - -[[package]] -category = "main" -description = "Python Crontab API" name = "python-crontab" +version = "2.5.1" +description = "Python Crontab API" +category = "main" optional = true python-versions = "*" -version = "2.5.1" [package.dependencies] python-dateutil = "*" @@ -1625,76 +1655,65 @@ cron-description = ["cron-descriptor"] cron-schedule = ["croniter"] [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "Add .env support to your django/flask apps in development and deployments" -name = "python-dotenv" -optional = false -python-versions = "*" -version = "0.14.0" - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -category = "main" -description = "Python modules for implementing LDAP clients" name = "python-ldap" +version = "3.3.1" +description = "Python modules for implementing LDAP clients" +category = "main" optional = true python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "3.3.1" [package.dependencies] pyasn1 = ">=0.3.7" pyasn1_modules = ">=0.1.5" [[package]] -category = "main" -description = "Pure python memcached client" name = "python-memcached" +version = "1.59" +description = "Pure python memcached client" +category = "main" optional = false python-versions = "*" -version = "1.59" [package.dependencies] six = ">=1.4.0" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2020.4" +description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" -version = "2020.1" [[package]] -category = "main" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" [[package]] -category = "main" -description = "QR Code image generator" name = "qrcode" +version = "6.1" +description = "QR Code image generator" +category = "main" optional = false python-versions = "*" -version = "6.1" [package.dependencies] -colorama = "*" +colorama = {version = "*", markers = "platform_system == \"Windows\""} six = "*" [package.extras] @@ -1704,154 +1723,167 @@ pil = ["pillow"] test = ["pytest", "pytest-cov", "mock"] [[package]] -category = "main" -description = "Python client for Redis key-value store" name = "redis" +version = "3.5.3" +description = "Python client for Redis key-value store" +category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] [[package]] -category = "dev" -description = "Alternative regular expression module, to replace re." name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = "*" -version = "2020.7.14" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" +chardet = ">=3.0.2,<5" idna = ">=2.5,<3" -urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +urllib3 = ">=1.21.1,<1.27" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "dev" -description = "reStructuredText linter" name = "restructuredtext-lint" +version = "1.3.2" +description = "reStructuredText linter" +category = "dev" optional = false python-versions = "*" -version = "1.3.1" [package.dependencies] docutils = ">=0.11,<1.0" [[package]] +name = "ruamel.yaml" +version = "0.16.12" +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\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel.yaml.clib" +version = "0.2.2" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" category = "main" -description = "Awesome Django authorization, without the database" -name = "rules" optional = false python-versions = "*" + +[[package]] +name = "rules" version = "2.2" +description = "Awesome Django authorization, without the database" +category = "main" +optional = false +python-versions = "*" [[package]] -category = "dev" -description = "Checks installed dependencies for known vulnerabilities." name = "safety" +version = "1.9.0" +description = "Checks installed dependencies for known vulnerabilities." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.9.0" [package.dependencies] Click = ">=6.0" dparse = ">=0.5.1" packaging = "*" requests = "*" -setuptools = "*" [[package]] -category = "dev" -description = "An implementation of the SCRAM protocol." name = "scramp" +version = "1.2.0" +description = "An implementation of the SCRAM protocol." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.2.0" [[package]] -category = "dev" -description = "Python bindings for Selenium" name = "selenium" +version = "3.141.0" +description = "Python bindings for Selenium" +category = "dev" optional = false python-versions = "*" -version = "3.141.0" [package.dependencies] urllib3 = "*" [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" [[package]] -category = "dev" -description = "A pure Python implementation of a sliding window memory map manager" name = "smmap" +version = "3.0.4" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.4" [[package]] -category = "dev" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." name = "snowballstemmer" -optional = false -python-versions = "*" version = "2.0.0" - -[[package]] -category = "main" -description = "A modern CSS selector implementation for Beautiful Soup." -name = "soupsieve" +description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" -version = "1.9.6" [[package]] -category = "main" -description = "A simple tool/library for working with SPDX license definitions." name = "spdx-license-list" +version = "0.5.2" +description = "A simple tool/library for working with SPDX license definitions." +category = "main" optional = false python-versions = "*" -version = "0.5.1" [[package]] -category = "dev" -description = "Python documentation generator" name = "sphinx" +version = "3.3.1" +description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=3.5" -version = "3.2.1" [package.dependencies] -Jinja2 = ">=2.3" -Pygments = ">=2.0" alabaster = ">=0.7,<0.8" babel = ">=1.3" -colorama = ">=0.3.5" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.12" imagesize = "*" +Jinja2 = ">=2.3" packaging = "*" +Pygments = ">=2.0" requests = ">=2.5.0" -setuptools = "*" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" @@ -1862,141 +1894,138 @@ sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.790)", "docutils-stubs"] test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"] [[package]] -category = "dev" -description = "Type hints (PEP 484) support for the Sphinx autodoc extension" name = "sphinx-autodoc-typehints" +version = "1.11.1" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +category = "dev" optional = false python-versions = ">=3.5.2" -version = "1.11.0" [package.dependencies] Sphinx = ">=3.0" [package.extras] -test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "dataclasses"] +test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "Sphinx (>=3.2.0)", "dataclasses"] type_comments = ["typed-ast (>=1.4.0)"] [[package]] -category = "dev" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.2" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "dev" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.2" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "dev" -description = "Improve the Sphinx autodoc for Django classes." name = "sphinxcontrib-django" +version = "0.5.1" +description = "Improve the Sphinx autodoc for Django classes." +category = "dev" optional = false python-versions = "*" -version = "0.5.1" [[package]] -category = "dev" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" name = "sphinxcontrib-htmlhelp" +version = "1.0.3" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.3" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest", "html5lib"] [[package]] -category = "dev" -description = "A sphinx extension which renders display math in HTML via JavaScript" name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.1" [package.extras] test = ["pytest", "flake8", "mypy"] [[package]] -category = "dev" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.0.3" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "dev" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." name = "sphinxcontrib-serializinghtml" +version = "1.1.4" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" -version = "1.1.4" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] [[package]] -category = "main" -description = "Non-validating SQL parser" name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.3.1" +python-versions = ">=3.5" [[package]] -category = "dev" -description = "Manage dynamic plugins for Python applications" name = "stevedore" +version = "3.3.0" +description = "Manage dynamic plugins for Python applications" +category = "dev" optional = false python-versions = ">=3.6" -version = "3.2.1" [package.dependencies] +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=1.7.0" - [[package]] -category = "dev" -description = "ANSII Color formatting for output in terminal." name = "termcolor" +version = "1.1.0" +description = "ANSII Color formatting for output in terminal." +category = "dev" optional = false python-versions = "*" -version = "1.1.0" [[package]] -category = "dev" -description = "A collection of helpers and mock objects for unit tests and doc tests." name = "testfixtures" +version = "6.17.0" +description = "A collection of helpers and mock objects for unit tests and doc tests." +category = "dev" optional = false python-versions = "*" -version = "6.14.2" [package.extras] build = ["setuptools-git", "wheel", "twine"] @@ -2004,23 +2033,23 @@ docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", " test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"] [[package]] -category = "dev" -description = "utilities for testing.* packages" name = "testing.common.database" +version = "2.0.3" +description = "utilities for testing.* packages" +category = "dev" optional = false python-versions = "*" -version = "2.0.3" [package.extras] testing = ["nose"] [[package]] -category = "dev" -description = "automatically setups a postgresql instance in a temporary directory, and destroys it after testing" name = "testing.postgresql" +version = "1.3.0" +description = "automatically setups a postgresql instance in a temporary directory, and destroys it after testing" +category = "dev" optional = false python-versions = "*" -version = "1.3.0" [package.dependencies] pg8000 = ">=1.10" @@ -2030,164 +2059,166 @@ pg8000 = ">=1.10" testing = ["sqlalchemy", "nose", "psycopg2"] [[package]] -category = "main" -description = "The most basic Text::Unidecode port" name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +category = "main" optional = false python-versions = "*" -version = "1.3" [[package]] -category = "main" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" optional = false -python-versions = "*" -version = "0.10.1" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -category = "main" -description = "Fast, Extensible Progress Meter" name = "tqdm" +version = "4.54.1" +description = "Fast, Extensible Progress Meter" +category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.48.2" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.extras] -dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] +dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown", "wheel"] [[package]] -category = "main" -description = "Twilio API client and TwiML generator" name = "twilio" +version = "6.50.1" +description = "Twilio API client and TwiML generator" +category = "main" optional = false python-versions = "*" -version = "6.45.1" [package.dependencies] PyJWT = ">=1.4.2" pytz = "*" +requests = {version = ">=2.0.0", markers = "python_version >= \"3.0\""} six = "*" -[package.dependencies.requests] -python = ">=3.0" -version = ">=2.0.0" - [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.1" [[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" optional = false python-versions = "*" -version = "3.7.4.3" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.26.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.10" [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.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]] -category = "main" -description = "Promises, promises, promises." name = "vine" +version = "5.0.0" +description = "Promises, promises, promises." +category = "main" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" +python-versions = ">=3.6" [[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" category = "main" -description = "Character encoding aliases for legacy web content" +optional = true +python-versions = "*" + +[[package]] name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" optional = false python-versions = "*" -version = "0.5.1" [[package]] -category = "main" -description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service." name = "yubiotp" +version = "1.0.0.post1" +description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service." +category = "main" optional = false python-versions = "*" -version = "1.0.0.post1" [package.dependencies] pycryptodome = "*" [[package]] -category = "main" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "3.4.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = false python-versions = ">=3.6" -version = "3.1.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] +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"] ldap = ["django-auth-ldap"] [metadata] -content-hash = "c914df99dc2cd3722b54f635d716e498ff7da050a867e85b9e85a8e5fae9949e" -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.7" +content-hash = "65a4b7b891965a330e7fec9ed64213579550aac1ac8295aa8c3022a4978d488c" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] +aleksis-builddeps = [ + {file = "AlekSIS-Builddeps-1.tar.gz", hash = "sha256:97a19597f422593cbdc438aabf17f95748126c8951df6ac7db7991fc99c108c4"}, +] amqp = [ - {file = "amqp-2.6.1-py2.py3-none-any.whl", hash = "sha256:aa7f313fb887c91f15474c1229907a04dac0b8135822d6603437803424c0aa59"}, - {file = "amqp-2.6.1.tar.gz", hash = "sha256:70cdb10628468ff14e57ec2f751c7aa9e48e7e3651cfd62d431213c0c4e58f21"}, + {file = "amqp-5.0.2-py3-none-any.whl", hash = "sha256:5b9062d5c0812335c75434bf17ce33d7a20ecfedaa0733faec7379868eb4068a"}, + {file = "amqp-5.0.2.tar.gz", hash = "sha256:fcd5b3baeeb7fc19b3486ff6d10543099d40ae1f5c9196eae695d1cde1b2f784"}, ] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] asgiref = [ - {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"}, - {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"}, + {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, + {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, ] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, - {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, + {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, + {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] babel = [ - {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, - {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, + {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, + {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, ] bandit = [ - {file = "bandit-1.6.2-py2.py3-none-any.whl", hash = "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952"}, - {file = "bandit-1.6.2.tar.gz", hash = "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"}, -] -beautifulsoup4 = [ - {file = "beautifulsoup4-4.9.1-py2-none-any.whl", hash = "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c"}, - {file = "beautifulsoup4-4.9.1-py3-none-any.whl", hash = "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8"}, - {file = "beautifulsoup4-4.9.1.tar.gz", hash = "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7"}, + {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, + {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, ] billiard = [ {file = "billiard-3.6.3.0-py3-none-any.whl", hash = "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede"}, @@ -2198,44 +2229,54 @@ black = [ {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] bleach = [ - {file = "bleach-3.1.5-py2.py3-none-any.whl", hash = "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f"}, - {file = "bleach-3.1.5.tar.gz", hash = "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"}, + {file = "bleach-3.2.1-py2.py3-none-any.whl", hash = "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"}, + {file = "bleach-3.2.1.tar.gz", hash = "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080"}, ] "boolean.py" = [ {file = "boolean.py-3.8-py2.py3-none-any.whl", hash = "sha256:d75da0fd0354425fa64f6bbc6cec6ae1485d0eec3447b73187ff8cbf9b572e26"}, {file = "boolean.py-3.8.tar.gz", hash = "sha256:cc24e20f985d60cd4a3a5a1c0956dd12611159d32a75081dabd0c9ab981acaa4"}, ] calendarweek = [ - {file = "calendarweek-0.4.5-py3-none-any.whl", hash = "sha256:b35fcc087073969d017cede62a7295bcd714a1304bcb4c4e2b0f23acb0265fb1"}, - {file = "calendarweek-0.4.5.tar.gz", hash = "sha256:5b1788ca435022f9348fc81a718974e51dd85d080f9aa3dad717df70a1bc6e1f"}, + {file = "calendarweek-0.4.7-py3-none-any.whl", hash = "sha256:ee65caea113503dcdb33d96bca9f79f88b3ab4f66279d4cb568d89f1f662608a"}, + {file = "calendarweek-0.4.7.tar.gz", hash = "sha256:7655d6a4c3b4f6a4e01aa7d23b49cd121db0399050e9c08cd8d1210155be25dd"}, ] celery = [ - {file = "celery-4.4.7-py2.py3-none-any.whl", hash = "sha256:a92e1d56e650781fb747032a3997d16236d037c8199eacd5217d1a72893bca45"}, - {file = "celery-4.4.7.tar.gz", hash = "sha256:d220b13a8ed57c78149acf82c006785356071844afe0b27012a4991d44026f9f"}, + {file = "celery-5.0.5-py3-none-any.whl", hash = "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13"}, + {file = "celery-5.0.5.tar.gz", hash = "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c"}, ] celery-haystack = [ {file = "celery-haystack-0.10.tar.gz", hash = "sha256:b6e2a3c70071bef0838ca1a7d9f14fae6c2ecf385704092e59b82147a1ee552e"}, {file = "celery_haystack-0.10-py2.py3-none-any.whl", hash = "sha256:ec1f39050661e033f554de99cb9393c2e94427667ff5401f16393b2a68f888fc"}, ] celery-progress = [ - {file = "celery-progress-0.0.12.tar.gz", hash = "sha256:df61d61ac2b29e51b61a2cbd070d28b69f9f538d31e5f4b8076d9721251d6c59"}, - {file = "celery_progress-0.0.12-py3-none-any.whl", hash = "sha256:b3727b1b65c79ec072513eb42f1903eaec64a75d2f691b5664fa660f2bd319ad"}, + {file = "celery-progress-0.0.14.tar.gz", hash = "sha256:002ead0d3fa3602bd74cf328206b8e2352994ab599711dc20058a5cf2b4db2d1"}, + {file = "celery_progress-0.0.14-py3-none-any.whl", hash = "sha256:6d95c01fe044dd5dbb1e2d507724f9ace70bde796bc6db51ba19c8a95e94da07"}, ] certifi = [ - {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, - {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, ] chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] +click-didyoumean = [ + {file = "click-didyoumean-0.0.3.tar.gz", hash = "sha256:112229485c9704ff51362fe34b2d4f0b12fc71cc20f6d2b3afabed4b8bfa6aeb"}, +] +click-plugins = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] +click-repl = [ + {file = "click-repl-0.1.6.tar.gz", hash = "sha256:b9f29d52abc4d6059f8e276132a111ab8d94980afe6a5432b9d996544afa95d5"}, + {file = "click_repl-0.1.6-py3-none-any.whl", hash = "sha256:9c4c3d022789cae912aad8a3f5e1d7c2cdd016ee1225b5212ad3e8691563cda5"}, +] colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, ] colour = [ {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"}, @@ -2245,48 +2286,48 @@ configobj = [ {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"}, ] coverage = [ - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"}, - {file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"}, - {file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"}, - {file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"}, - {file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"}, - {file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"}, - {file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"}, - {file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"}, - {file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"}, - {file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"}, - {file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"}, - {file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"}, - {file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"}, - {file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"}, - {file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"}, - {file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"}, - {file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"}, - {file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"}, - {file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"}, - {file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"}, - {file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"}, - {file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"}, - {file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"}, - {file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"}, - {file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"}, - {file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"}, - {file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270"}, + {file = "coverage-5.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9"}, + {file = "coverage-5.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729"}, + {file = "coverage-5.3-cp27-cp27m-win32.whl", hash = "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d"}, + {file = "coverage-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9"}, + {file = "coverage-5.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5"}, + {file = "coverage-5.3-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097"}, + {file = "coverage-5.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9"}, + {file = "coverage-5.3-cp35-cp35m-win32.whl", hash = "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636"}, + {file = "coverage-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f"}, + {file = "coverage-5.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54"}, + {file = "coverage-5.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7"}, + {file = "coverage-5.3-cp36-cp36m-win32.whl", hash = "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a"}, + {file = "coverage-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d"}, + {file = "coverage-5.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f"}, + {file = "coverage-5.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c"}, + {file = "coverage-5.3-cp37-cp37m-win32.whl", hash = "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751"}, + {file = "coverage-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709"}, + {file = "coverage-5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f"}, + {file = "coverage-5.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259"}, + {file = "coverage-5.3-cp38-cp38-win32.whl", hash = "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82"}, + {file = "coverage-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221"}, + {file = "coverage-5.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21"}, + {file = "coverage-5.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24"}, + {file = "coverage-5.3-cp39-cp39-win32.whl", hash = "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7"}, + {file = "coverage-5.3-cp39-cp39-win_amd64.whl", hash = "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7"}, + {file = "coverage-5.3.tar.gz", hash = "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0"}, ] dj-database-url = [ {file = "dj-database-url-0.5.0.tar.gz", hash = "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163"}, {file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"}, ] django = [ - {file = "Django-3.1.1-py3-none-any.whl", hash = "sha256:b5fbb818e751f660fa2d576d9f40c34a4c615c8b48dd383f5216e609f383371f"}, - {file = "Django-3.1.1.tar.gz", hash = "sha256:59c8125ca873ed3bdae9c12b146fbbd6ed8d0f743e4cf5f5817af50c51f1fc2f"}, + {file = "Django-3.1.4-py3-none-any.whl", hash = "sha256:5c866205f15e7a7123f1eec6ab939d22d5bde1416635cab259684af66d8e48a2"}, + {file = "Django-3.1.4.tar.gz", hash = "sha256:edb10b5c45e7e9c0fb1dc00b76ec7449aca258a39ffd613dbd078c51d19c9f03"}, ] django-any-js = [ {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"}, @@ -2307,21 +2348,25 @@ django-bulk-update = [ {file = "django-bulk-update-2.2.0.tar.gz", hash = "sha256:5ab7ce8a65eac26d19143cc189c0f041d5c03b9d1b290ca240dc4f3d6aaeb337"}, {file = "django_bulk_update-2.2.0-py2.py3-none-any.whl", hash = "sha256:49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985"}, ] +django-cachalot = [ + {file = "django-cachalot-2.3.3.tar.gz", hash = "sha256:ba3a6cabf834139196179c4f6d77409ae9170267ee8ce40e27bbf6c3f6733b2b"}, + {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"}, ] django-celery-beat = [ - {file = "django-celery-beat-2.0.0.tar.gz", hash = "sha256:fdf1255eecfbeb770c6521fe3e69989dfc6373cd5a7f0fe62038d37f80f47e48"}, - {file = "django_celery_beat-2.0.0-py2.py3-none-any.whl", hash = "sha256:fe0b2a1b31d4a6234fea4b31986ddfd4644a48fab216ce1843f3ed0ddd2e9097"}, + {file = "django-celery-beat-2.1.0.tar.gz", hash = "sha256:4eb0e8412e2e05ba0029912a6f80d1054731001eecbcb4d59688c4e07cf4d9d3"}, + {file = "django_celery_beat-2.1.0-py2.py3-none-any.whl", hash = "sha256:8a169e11d96faed8b72d505ddbc70e7fe0b16cdc854df43cb209c153ed08d651"}, ] django-celery-email = [ {file = "django-celery-email-3.0.0.tar.gz", hash = "sha256:5546cbba80952cc3b8a0ffa4206ce90a4a996a7ffd1c385a2bdb65903ca18ece"}, {file = "django_celery_email-3.0.0-py2.py3-none-any.whl", hash = "sha256:0f72da39cb2ea83c69440566e87f27cd72f68f247f98ce99fb29889fcf329406"}, ] django-celery-results = [ - {file = "django_celery_results-1.2.1-py2.py3-none-any.whl", hash = "sha256:a29ab580f0e38c66c39f51cc426bbdbb2a391b8cc0867df9dea748db2c961db2"}, - {file = "django_celery_results-1.2.1.tar.gz", hash = "sha256:e390f70cc43bbc2cd7c8e4607dc29ab6211a2ab968f93677583f0160921f670c"}, + {file = "django_celery_results-2.0.0-py2.py3-none-any.whl", hash = "sha256:f82280a9a25c44048b9e64ae4d47ade7d522c8221304b0e25388080021b95468"}, + {file = "django_celery_results-2.0.0.tar.gz", hash = "sha256:754e01f22f70fddee5f2ca95c18f183fccee42ad98f9803577bffa717d45ac5d"}, ] django-ckeditor = [ {file = "django-ckeditor-6.0.0.tar.gz", hash = "sha256:29fd1a333cb9741ac2c3fd4e427a5c00115ed33a2389716a09af7656022dcdde"}, @@ -2342,17 +2387,13 @@ django-dynamic-preferences = [ {file = "django-dynamic-preferences-1.10.1.tar.gz", hash = "sha256:e4b2bb7b2563c5064ba56dd76441c77e06b850ff1466a386a1cd308909a6c7de"}, {file = "django_dynamic_preferences-1.10.1-py2.py3-none-any.whl", hash = "sha256:9419fa925fd2cbb665269ae72059eb3058bf080913d853419b827e4e7a141902"}, ] -django-easy-audit = [ - {file = "django-easy-audit-1.3.0a5.tar.gz", hash = "sha256:bb6c0291c360fe305d5cdbbc02d9bdec82885788a675871f80ec1b2b0fd6d443"}, - {file = "django_easy_audit-1.3.0a5-py3-none-any.whl", hash = "sha256:01f66d19b15b19c377754848157f87dc78fe88cd1ed9bd7314d24438fb95504a"}, -] django-favicon-plus-reloaded = [ {file = "django-favicon-plus-reloaded-1.0.4.tar.gz", hash = "sha256:90c761c636a338e6e9fb1d086649d82095085f92cff816c9cf074607f28c85a5"}, {file = "django_favicon_plus_reloaded-1.0.4-py3-none-any.whl", hash = "sha256:26e4316d41328a61ced52c7fc0ead795f0eb194d6a30311c34a9833c6fe30a7c"}, ] django-filter = [ - {file = "django-filter-2.3.0.tar.gz", hash = "sha256:11e63dd759835d9ba7a763926ffb2662cf8a6dcb4c7971a95064de34dbc7e5af"}, - {file = "django_filter-2.3.0-py3-none-any.whl", hash = "sha256:616848eab6fc50193a1b3730140c49b60c57a3eda1f7fc57fa8505ac156c6c75"}, + {file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"}, + {file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"}, ] django-formtools = [ {file = "django-formtools-2.2.tar.gz", hash = "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"}, @@ -2371,18 +2412,14 @@ django-haystack = [ {file = "django_haystack-3.0b1-py3-none-any.whl", hash = "sha256:b83705e1cf8141cd1755fc6683ac65fea4e1281f4b4306bc9224af96495b0df3"}, ] django-health-check = [ - {file = "django-health-check-3.12.1.tar.gz", hash = "sha256:0563827e003d25fd4d9ebbd7467dea5f390435628d645aaa63f8889deaded73a"}, - {file = "django_health_check-3.12.1-py2.py3-none-any.whl", hash = "sha256:9e6b7d93d4902901474efd4e25d31b5aaea7563b570c0260adce52cd3c3a9e36"}, -] -django-image-cropping = [ - {file = "django-image-cropping-1.4.0.tar.gz", hash = "sha256:6cc4a6bd8901e69b710caceea29b942fdb202da26626313cd9271ae989a83a52"}, - {file = "django_image_cropping-1.4.0-py3-none-any.whl", hash = "sha256:fe6a139c6d5dfc480f2a1d4e7e3e928d5edaefc898e17be66bc5f73140762ad9"}, + {file = "django-health-check-3.16.1.tar.gz", hash = "sha256:2cb3944e313e435bdf299288e109f398b6c08b610e09cc90d7f5f6a2bcf469fc"}, + {file = "django_health_check-3.16.1-py2.py3-none-any.whl", hash = "sha256:8b0835f04ebaeb0d12498a5ef47dd22196237c3987ff28bcce9ed28b5a169d5e"}, ] django-impersonate = [ - {file = "django-impersonate-1.5.1.tar.gz", hash = "sha256:7c786ffaa7a5dd430f9277b53a64676c470b684eee5aa52c3b483298860d09b4"}, + {file = "django-impersonate-1.7.tar.gz", hash = "sha256:1dadb5239a5cb79d4327ef3000cd989f9d4e76428a5e2a080496a53b41fa785f"}, ] django-ipware = [ - {file = "django-ipware-3.0.1.tar.gz", hash = "sha256:73a640a5bff00aa7503a35e92e462001cfabb07d73d649c262f117423beee953"}, + {file = "django-ipware-3.0.2.tar.gz", hash = "sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94"}, ] django-js-asset = [ {file = "django-js-asset-1.2.2.tar.gz", hash = "sha256:c163ae80d2e0b22d8fb598047cd0dcef31f81830e127cfecae278ad574167260"}, @@ -2396,41 +2433,50 @@ django-jsonstore = [ {file = "django-jsonstore-0.4.1.tar.gz", hash = "sha256:d6e42152af3f924e4657c99e80144ba9a6410799256f6134b5a4e9fa4282ec10"}, ] django-maintenance-mode = [ - {file = "django-maintenance-mode-0.14.0.tar.gz", hash = "sha256:f3fef1760fdcda5e0bf6c2966aadc77eea6f328580a9c751920daba927281a68"}, - {file = "django_maintenance_mode-0.14.0-py2-none-any.whl", hash = "sha256:b4cc24a469ed10897826a28f05d64e6166a58d130e4940ac124ce198cd4cc778"}, + {file = "django-maintenance-mode-0.15.1.tar.gz", hash = "sha256:d07102cab88dd707a82232f0c552c287e62aa53af582a0ca4f2aa31f14f5ed27"}, + {file = "django_maintenance_mode-0.15.1-py3-none-any.whl", hash = "sha256:8c45b400253076655562c99a2ffb88f8353fc1c84496c1b9de812cc8132aea6f"}, ] django-material = [ - {file = "django-material-1.7.0.tar.gz", hash = "sha256:8d8e76605aa77de3d37d8ae4db1835479e484f1530485fd6b66535244f64131b"}, - {file = "django_material-1.7.0-py2.py3-none-any.whl", hash = "sha256:ff36170e400158a22f9b675e1c07d42267bf1a92122518f68db0c2bf3410096d"}, + {file = "django-material-1.7.3.tar.gz", hash = "sha256:00599cd87f19f3a66be065865b223801afa9da680744a5eac10c489a10d98eba"}, + {file = "django_material-1.7.3-py2.py3-none-any.whl", hash = "sha256:6c010aa47618ceae2617f8a476b55aaed0884b1a4a6f5bdf969448a1ba72e352"}, ] django-menu-generator = [ - {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"}, + {file = "django-menu-generator-1.1.0.tar.gz", hash = "sha256:e8f9b808080c4b281f9c5962f39078c76c2007a5ef8ab1f7a81c81dbbe6a9848"}, ] django-middleware-global-request = [ {file = "django-middleware-global-request-0.1.2.tar.gz", hash = "sha256:f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"}, ] +django-model-utils = [ + {file = "django-model-utils-4.1.1.tar.gz", hash = "sha256:eb5dd05ef7d7ce6bc79cae54ea7c4a221f6f81e2aad7722933aee66489e7264b"}, + {file = "django_model_utils-4.1.1-py3-none-any.whl", hash = "sha256:ef7c440024e797796a3811432abdd2be8b5225ae64ef346f8bfc6de7d8e5d73c"}, +] django-otp = [ - {file = "django-otp-0.9.4.tar.gz", hash = "sha256:50e54bc09bc435e2ad88f0aa7008718079c3529c422b469b3991a97d28b147bb"}, - {file = "django_otp-0.9.4-py3-none-any.whl", hash = "sha256:6b92c69021558765e80411479a01788977106d5696c391d2e5342074c1dd74d1"}, + {file = "django-otp-1.0.2.tar.gz", hash = "sha256:f523fb9dec420f28a29d3e2ad72ac06f64588956ed4f2b5b430d8e957ebb8287"}, + {file = "django_otp-1.0.2-py3-none-any.whl", hash = "sha256:8ba5ab9bd2738c7321376c349d7cce49cf4404e79f6804e0a3cc462a91728e18"}, ] django-otp-yubikey = [ - {file = "django-otp-yubikey-0.6.0.tar.gz", hash = "sha256:6961f16cfec1dddfa3f3c794ec5967cbb4d412f488de1d7d14441df462ba9843"}, - {file = "django_otp_yubikey-0.6.0-py2.py3-none-any.whl", hash = "sha256:0dd73c2145afc66f43d537d2789a7068a99bc05b5ffa33d2ad73a49e4c4c7dcb"}, + {file = "django-otp-yubikey-1.0.0.tar.gz", hash = "sha256:fbd409277892229b7e3578faa4f63ea766e242659456939164c8f71b845287b6"}, + {file = "django_otp_yubikey-1.0.0-py2.py3-none-any.whl", hash = "sha256:07743473024900c3b7a14647039f2cf66148cf6243d6aee0853ba45516c224a4"}, ] django-phonenumber-field = [ {file = "django-phonenumber-field-3.0.1.tar.gz", hash = "sha256:794ebbc3068a7af75aa72a80cb0cec67e714ff8409a965968040f1fd210b2d97"}, {file = "django_phonenumber_field-3.0.1-py3-none-any.whl", hash = "sha256:1ab19f723928582fed412bd9844221fa4ff466276d8526b8b4a9913ee1487c5e"}, ] django-polymorphic = [ - {file = "django-polymorphic-2.1.2.tar.gz", hash = "sha256:6e08a76c91066635ccb7ef3ebbe9a0ad149febae6b30be2579716ec16d3c6461"}, - {file = "django_polymorphic-2.1.2-py2.py3-none-any.whl", hash = "sha256:0a25058e95e5e99fe0beeabb8f4734effe242d7b5b77dca416fba9fd3062da6a"}, + {file = "django-polymorphic-3.0.0.tar.gz", hash = "sha256:9d886f19f031d26bb1391c055ed9be06fb226a04a4cec1842b372c58873b3caa"}, + {file = "django_polymorphic-3.0.0-py2.py3-none-any.whl", hash = "sha256:73b75eb44ea302bd32820f8661e469509d245ce7f7ff09cd2ad149e5c42034ff"}, +] +django-prometheus = [ + {file = "django-prometheus-2.1.0.tar.gz", hash = "sha256:dd3f8da1399140fbef5c00d1526a23d1ade286b144281c325f8e409a781643f2"}, + {file = "django_prometheus-2.1.0-py2.py3-none-any.whl", hash = "sha256:c338d6efde1ca336e90c540b5e87afe9287d7bcc82d651a778f302b0be17a933"}, ] 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-render-block = [ - {file = "django_render_block-0.7-py3-none-any.whl", hash = "sha256:3e5963a2332727ca0db2bb8ca031404fba3ac9024702446eed5b7fb02209f7f3"}, + {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"}, ] django-reversion = [ {file = "django-reversion-3.0.8.tar.gz", hash = "sha256:49e9930f90322dc6a2754dd26144285cfcc1c5bd0c1c39ca95d5602c5054ae32"}, @@ -2440,30 +2486,30 @@ django-sass-processor = [ {file = "django-sass-processor-0.8.2.tar.gz", hash = "sha256:9b46a12ca8bdcb397d46fbcc49e6a926ff9f76a93c5efeb23b495419fd01fc7a"}, ] django-select2 = [ - {file = "django-select2-7.4.2.tar.gz", hash = "sha256:9d3330fa0083a03fb69fceb5dcd2e78065cfd08e45c89d4fd727fce4673d3e08"}, - {file = "django_select2-7.4.2-py2.py3-none-any.whl", hash = "sha256:06531d563ce33c3133682ae2bb9e6d762103a863d0054ffef51bae8b4cfcca6c"}, + {file = "django-select2-7.5.0.tar.gz", hash = "sha256:df71dedba9a362041b65e3cd692cb8b4f9e1e17a19681c7b4e61f331868bae0c"}, + {file = "django_select2-7.5.0-py2.py3-none-any.whl", hash = "sha256:6662aa1c21d4839b8fff38e4c9d402ed3da81f7c5ef7f7e703c862255ba3b9ed"}, ] django-settings-context-processor = [ {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"}, ] django-stubs = [ - {file = "django-stubs-1.5.0.tar.gz", hash = "sha256:b4c9042f2c2fcb89dc0fb37df070cce0f9bf93b39ae193847b6e5a392b13c430"}, - {file = "django_stubs-1.5.0-py3-none-any.whl", hash = "sha256:4a03df70e062f4133085efc461148d0934f36ccfd60a2b6d7bc35821f62008b6"}, + {file = "django-stubs-1.7.0.tar.gz", hash = "sha256:ddd190aca5b9adb4d30760d5c64f67cb3658703f5f42c3bb0c2c71ff4d752c39"}, + {file = "django_stubs-1.7.0-py3-none-any.whl", hash = "sha256:30a7d99c694acf79c5d93d69a5a8e4b54d2a8c11dd672aa869006789e2189fa6"}, ] django-tables2 = [ - {file = "django-tables2-2.3.1.tar.gz", hash = "sha256:28da782f81f046c7d921246f43e7ba2df430cafe5a0e00a0f9dadef25a0e487d"}, - {file = "django_tables2-2.3.1-py2.py3-none-any.whl", hash = "sha256:7e425ad51e22caf5470351981f0fcd4fd35d4cf2d4c3b76fa1b7bf56251778d1"}, + {file = "django-tables2-2.3.3.tar.gz", hash = "sha256:ad38ece83157b8b9c1fb72b0316fcffc4b32d7fec53eec3f2847b83c4c0a2cb2"}, + {file = "django_tables2-2.3.3-py2.py3-none-any.whl", hash = "sha256:9c175834130ebb2b3a5644431391e09128405e4538637804d5e74a8b96fc9f68"}, ] django-templated-email = [ {file = "django-templated-email-2.3.0.tar.gz", hash = "sha256:536c4e5ae099eabfb9aab36087d4d7799948c654e73da55a744213d086d5bb33"}, ] django-timezone-field = [ - {file = "django-timezone-field-4.0.tar.gz", hash = "sha256:7e3620fe2211c2d372fad54db8f86ff884098d018d56fda4dca5e64929e05ffc"}, - {file = "django_timezone_field-4.0-py3-none-any.whl", hash = "sha256:758b7d41084e9ea2e89e59eb616e9b6326e6fbbf9d14b6ef062d624fe8cc6246"}, + {file = "django-timezone-field-4.1.1.tar.gz", hash = "sha256:b5b587aabed8db66eb3453691522164915c1aa1b326d8ddeadc8832a8580faeb"}, + {file = "django_timezone_field-4.1.1-py3-none-any.whl", hash = "sha256:068dc2c9b11c2230e126f511a515609d46f8cc49278b293e7536be07997fe892"}, ] django-two-factor-auth = [ - {file = "django-two-factor-auth-1.12.1.tar.gz", hash = "sha256:8e698d548a5a7c02c7ba343bc5376a7bbdc4e59c20ef13223743fe42fa4a1281"}, - {file = "django_two_factor_auth-1.12.1-py2.py3-none-any.whl", hash = "sha256:612adb0dd6e9ed3b4ecd6763f2e3f56358d7b5afb843a3a49994d1d3bc91ffc2"}, + {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-widget-tweaks = [ {file = "django-widget-tweaks-1.4.8.tar.gz", hash = "sha256:9f91ca4217199b7671971d3c1f323a2bec71a0c27dec6260b3c006fa541bc489"}, @@ -2481,19 +2527,16 @@ dparse = [ {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"}, ] dynaconf = [ - {file = "dynaconf-2.2.3-py2.py3-none-any.whl", hash = "sha256:e803cdab2d7addd539c4ee8d121f15ab0b63a83a5b723150e1746aa7e8063adb"}, - {file = "dynaconf-2.2.3.tar.gz", hash = "sha256:26b84f2b234a203f6005463d954c9f007181c09345eaaab3fc38503acbdadc7d"}, -] -easy-thumbnails = [ - {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"}, + {file = "dynaconf-3.1.2-py2.py3-none-any.whl", hash = "sha256:808adfe964f10695846dbf8dad7632e47fc3bc38860fd1887ed57dddffc4eff2"}, + {file = "dynaconf-3.1.2.tar.gz", hash = "sha256:9b34ab2f811a81755f5eb4beac77a69e1e0887528c7e37fc4bc83fed52dcf502"}, ] faker = [ - {file = "Faker-4.1.2-py3-none-any.whl", hash = "sha256:bc4b8c908dfcd84e4fe5d9fa2e52fbe17546515fb8f126909b98c47badf05658"}, - {file = "Faker-4.1.2.tar.gz", hash = "sha256:ff188c416864e3f7d8becd8f9ee683a4b4101a2a2d2bcdcb3e84bb1bdd06eaae"}, + {file = "Faker-5.0.2-py3-none-any.whl", hash = "sha256:5b17c95cfb013a22b062b8df18286f08ce4ea880f9948ec74295e5a42dbb2e44"}, + {file = "Faker-5.0.2.tar.gz", hash = "sha256:00ce4342c221b1931b2f35d46f5027d35bc62a4ca3a34628b2c5b514b4ca958a"}, ] flake8 = [ - {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"}, - {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"}, + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] flake8-bandit = [ {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"}, @@ -2537,8 +2580,8 @@ gitdb = [ {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, ] gitpython = [ - {file = "GitPython-3.1.8-py3-none-any.whl", hash = "sha256:1858f4fd089abe92ae465f01d5aaaf55e937eca565fb2c1fce35a51b5f85c910"}, - {file = "GitPython-3.1.8.tar.gz", hash = "sha256:080bf8e2cf1a2b907634761c2eaefbe83b69930c94c66ad11b65a8252959f912"}, + {file = "GitPython-3.1.11-py3-none-any.whl", hash = "sha256:6eea89b655917b500437e9668e4a12eabdcf00229a0df1762aabd692ef9b746b"}, + {file = "GitPython-3.1.11.tar.gz", hash = "sha256:befa4d101f91bad1b632df4308ec64555db684c360bd7d2130b4807d49ce86b8"}, ] html2text = [ {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, @@ -2553,24 +2596,23 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, - {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, + {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, + {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, ] iniconfig = [ - {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"}, - {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] isort = [ - {file = "isort-5.5.1-py3-none-any.whl", hash = "sha256:a200d47b7ee8b7f7d0a9646650160c4a51b6a91a9413fd31b1da2c4de789f5d3"}, - {file = "isort-5.5.1.tar.gz", hash = "sha256:92533892058de0306e51c88f22ece002a209dc8e80288aa3cec6d443060d584f"}, + {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, + {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, ] jinja2 = [ {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] kombu = [ - {file = "kombu-4.6.11-py2.py3-none-any.whl", hash = "sha256:be48cdffb54a2194d93ad6533d73f69408486483d189fe9f5990ee24255b0e0a"}, - {file = "kombu-4.6.11.tar.gz", hash = "sha256:ca1b45faac8c0b18493d02a8571792f3c40291cf2bcf1f55afed3d8f3aa7ba74"}, + {file = "kombu-5.0.2-py2.py3-none-any.whl", hash = "sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006"}, + {file = "kombu-5.0.2.tar.gz", hash = "sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c"}, ] libsass = [ {file = "libsass-0.20.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:4a246e4b88fd279abef8b669206228c92534d96ddcd0770d7012088c408dff23"}, @@ -2630,97 +2672,120 @@ mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] -more-itertools = [ - {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"}, - {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"}, -] mypy = [ - {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"}, - {file = "mypy-0.770-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754"}, - {file = "mypy-0.770-cp35-cp35m-win_amd64.whl", hash = "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65"}, - {file = "mypy-0.770-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce"}, - {file = "mypy-0.770-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761"}, - {file = "mypy-0.770-cp36-cp36m-win_amd64.whl", hash = "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2"}, - {file = "mypy-0.770-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8"}, - {file = "mypy-0.770-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913"}, - {file = "mypy-0.770-cp37-cp37m-win_amd64.whl", hash = "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9"}, - {file = "mypy-0.770-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1"}, - {file = "mypy-0.770-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27"}, - {file = "mypy-0.770-cp38-cp38-win_amd64.whl", hash = "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"}, - {file = "mypy-0.770-py3-none-any.whl", hash = "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164"}, - {file = "mypy-0.770.tar.gz", hash = "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae"}, + {file = "mypy-0.790-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669"}, + {file = "mypy-0.790-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802"}, + {file = "mypy-0.790-cp35-cp35m-win_amd64.whl", hash = "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de"}, + {file = "mypy-0.790-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1"}, + {file = "mypy-0.790-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc"}, + {file = "mypy-0.790-cp36-cp36m-win_amd64.whl", hash = "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7"}, + {file = "mypy-0.790-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c"}, + {file = "mypy-0.790-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178"}, + {file = "mypy-0.790-cp37-cp37m-win_amd64.whl", hash = "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324"}, + {file = "mypy-0.790-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01"}, + {file = "mypy-0.790-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666"}, + {file = "mypy-0.790-cp38-cp38-win_amd64.whl", hash = "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea"}, + {file = "mypy-0.790-py3-none-any.whl", hash = "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122"}, + {file = "mypy-0.790.tar.gz", hash = "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, + {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, + {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, ] pathspec = [ - {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, - {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, ] pbr = [ - {file = "pbr-5.5.0-py2.py3-none-any.whl", hash = "sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"}, - {file = "pbr-5.5.0.tar.gz", hash = "sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea"}, + {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"}, + {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"}, ] persisting-theory = [ {file = "persisting-theory-0.2.1.tar.gz", hash = "sha256:00ff7dcc8f481ff75c770ca5797d968e8725b6df1f77fe0cf7d20fa1e5790c0a"}, ] pg8000 = [ - {file = "pg8000-1.16.5-py3-none-any.whl", hash = "sha256:3d646b11227d94a3130a765a981dc6323bc959a3cd6ed54421d174b2ef256087"}, - {file = "pg8000-1.16.5.tar.gz", hash = "sha256:8af70cdfcc1fadafa32468a6af563e1c0b5271c4dcc99a4490030a128cb295a3"}, + {file = "pg8000-1.16.6-py3-none-any.whl", hash = "sha256:66fa16a402f38f8ba664206b4ba4040f24ea9641c4205b2b96a1ff3a613de3be"}, + {file = "pg8000-1.16.6.tar.gz", hash = "sha256:8fc1e6a62ccb7c9830f1e7e9288e2d20eaf373cc8875b5c55b7d5d9b7717be91"}, ] phonenumbers = [ - {file = "phonenumbers-8.12.9-py2.py3-none-any.whl", hash = "sha256:b8644c1dccd45d4c0f54c5b10effcc8c3f733e6a3c2caf40c9175fabc5010ffe"}, - {file = "phonenumbers-8.12.9.tar.gz", hash = "sha256:f887eceb3d9db17ec479a85245bd0ebe74c5f43489217784215ffb231f8c9e88"}, + {file = "phonenumbers-8.12.15-py2.py3-none-any.whl", hash = "sha256:13d499f7114c4b37c54ee844b188d5e7441951a7da41de5fc1a25ff8fdceef80"}, + {file = "phonenumbers-8.12.15.tar.gz", hash = "sha256:b734bfcf33e87ddae72196a40b3d1af35abd0beb263816ae18e1bff612926406"}, ] pillow = [ - {file = "Pillow-7.2.0-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae"}, - {file = "Pillow-7.2.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f"}, - {file = "Pillow-7.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38"}, - {file = "Pillow-7.2.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5"}, - {file = "Pillow-7.2.0-cp35-cp35m-win32.whl", hash = "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad"}, - {file = "Pillow-7.2.0-cp35-cp35m-win_amd64.whl", hash = "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f"}, - {file = "Pillow-7.2.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d"}, - {file = "Pillow-7.2.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233"}, - {file = "Pillow-7.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f"}, - {file = "Pillow-7.2.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8"}, - {file = "Pillow-7.2.0-cp36-cp36m-win32.whl", hash = "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a"}, - {file = "Pillow-7.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce"}, - {file = "Pillow-7.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4"}, - {file = "Pillow-7.2.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727"}, - {file = "Pillow-7.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b"}, - {file = "Pillow-7.2.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d"}, - {file = "Pillow-7.2.0-cp37-cp37m-win32.whl", hash = "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63"}, - {file = "Pillow-7.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1"}, - {file = "Pillow-7.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6"}, - {file = "Pillow-7.2.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9"}, - {file = "Pillow-7.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41"}, - {file = "Pillow-7.2.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8"}, - {file = "Pillow-7.2.0-cp38-cp38-win32.whl", hash = "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f"}, - {file = "Pillow-7.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6"}, - {file = "Pillow-7.2.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d"}, - {file = "Pillow-7.2.0.tar.gz", hash = "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626"}, + {file = "Pillow-8.0.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:b63d4ff734263ae4ce6593798bcfee6dbfb00523c82753a3a03cbc05555a9cc3"}, + {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f9403af9c790cc18411ea398a6950ee2def2a830ad0cfe6dc9122e6d528b302"}, + {file = "Pillow-8.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6b4a8fd632b4ebee28282a9fef4c341835a1aa8671e2770b6f89adc8e8c2703c"}, + {file = "Pillow-8.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:cc3ea6b23954da84dbee8025c616040d9aa5eaf34ea6895a0a762ee9d3e12e11"}, + {file = "Pillow-8.0.1-cp36-cp36m-win32.whl", hash = "sha256:d8a96747df78cda35980905bf26e72960cba6d355ace4780d4bdde3b217cdf1e"}, + {file = "Pillow-8.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:7ba0ba61252ab23052e642abdb17fd08fdcfdbbf3b74c969a30c58ac1ade7cd3"}, + {file = "Pillow-8.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:795e91a60f291e75de2e20e6bdd67770f793c8605b553cb6e4387ce0cb302e09"}, + {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a2e8d03787ec7ad71dc18aec9367c946ef8ef50e1e78c71f743bc3a770f9fae"}, + {file = "Pillow-8.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:006de60d7580d81f4a1a7e9f0173dc90a932e3905cc4d47ea909bc946302311a"}, + {file = "Pillow-8.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bd7bf289e05470b1bc74889d1466d9ad4a56d201f24397557b6f65c24a6844b8"}, + {file = "Pillow-8.0.1-cp37-cp37m-win32.whl", hash = "sha256:95edb1ed513e68bddc2aee3de66ceaf743590bf16c023fb9977adc4be15bd3f0"}, + {file = "Pillow-8.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:e38d58d9138ef972fceb7aeec4be02e3f01d383723965bfcef14d174c8ccd039"}, + {file = "Pillow-8.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d3d07c86d4efa1facdf32aa878bd508c0dc4f87c48125cc16b937baa4e5b5e11"}, + {file = "Pillow-8.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:fbd922f702582cb0d71ef94442bfca57624352622d75e3be7a1e7e9360b07e72"}, + {file = "Pillow-8.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:92c882b70a40c79de9f5294dc99390671e07fc0b0113d472cbea3fde15db1792"}, + {file = "Pillow-8.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7c9401e68730d6c4245b8e361d3d13e1035cbc94db86b49dc7da8bec235d0015"}, + {file = "Pillow-8.0.1-cp38-cp38-win32.whl", hash = "sha256:6c1aca8231625115104a06e4389fcd9ec88f0c9befbabd80dc206c35561be271"}, + {file = "Pillow-8.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:cc9ec588c6ef3a1325fa032ec14d97b7309db493782ea8c304666fb10c3bd9a7"}, + {file = "Pillow-8.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:eb472586374dc66b31e36e14720747595c2b265ae962987261f044e5cce644b5"}, + {file = "Pillow-8.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0eeeae397e5a79dc088d8297a4c2c6f901f8fb30db47795113a4a605d0f1e5ce"}, + {file = "Pillow-8.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:81f812d8f5e8a09b246515fac141e9d10113229bc33ea073fec11403b016bcf3"}, + {file = "Pillow-8.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:895d54c0ddc78a478c80f9c438579ac15f3e27bf442c2a9aa74d41d0e4d12544"}, + {file = "Pillow-8.0.1-cp39-cp39-win32.whl", hash = "sha256:2fb113757a369a6cdb189f8df3226e995acfed0a8919a72416626af1a0a71140"}, + {file = "Pillow-8.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:59e903ca800c8cfd1ebe482349ec7c35687b95e98cefae213e271c8c7fffa021"}, + {file = "Pillow-8.0.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:5abd653a23c35d980b332bc0431d39663b1709d64142e3652890df4c9b6970f6"}, + {file = "Pillow-8.0.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:4b0ef2470c4979e345e4e0cc1bbac65fda11d0d7b789dbac035e4c6ce3f98adb"}, + {file = "Pillow-8.0.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:8de332053707c80963b589b22f8e0229f1be1f3ca862a932c1bcd48dafb18dd8"}, + {file = "Pillow-8.0.1.tar.gz", hash = "sha256:11c5c6e9b02c9dac08af04f093eb5a2f84857df70a7d4a6a6ad461aca803fb9e"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] +prometheus-client = [ + {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"}, + {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.8-py3-none-any.whl", hash = "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"}, + {file = "prompt_toolkit-3.0.8.tar.gz", hash = "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c"}, +] psutil = [ - {file = "psutil-5.7.2-cp27-none-win32.whl", hash = "sha256:f2018461733b23f308c298653c8903d32aaad7873d25e1d228765e91ae42c3f2"}, - {file = "psutil-5.7.2-cp27-none-win_amd64.whl", hash = "sha256:66c18ca7680a31bf16ee22b1d21b6397869dda8059dbdb57d9f27efa6615f195"}, - {file = "psutil-5.7.2-cp35-cp35m-win32.whl", hash = "sha256:5e9d0f26d4194479a13d5f4b3798260c20cecf9ac9a461e718eb59ea520a360c"}, - {file = "psutil-5.7.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4080869ed93cce662905b029a1770fe89c98787e543fa7347f075ade761b19d6"}, - {file = "psutil-5.7.2-cp36-cp36m-win32.whl", hash = "sha256:d8a82162f23c53b8525cf5f14a355f5d1eea86fa8edde27287dd3a98399e4fdf"}, - {file = "psutil-5.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0ee3c36428f160d2d8fce3c583a0353e848abb7de9732c50cf3356dd49ad63f8"}, - {file = "psutil-5.7.2-cp37-cp37m-win32.whl", hash = "sha256:ff1977ba1a5f71f89166d5145c3da1cea89a0fdb044075a12c720ee9123ec818"}, - {file = "psutil-5.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a5b120bb3c0c71dfe27551f9da2f3209a8257a178ed6c628a819037a8df487f1"}, - {file = "psutil-5.7.2-cp38-cp38-win32.whl", hash = "sha256:10512b46c95b02842c225f58fa00385c08fa00c68bac7da2d9a58ebe2c517498"}, - {file = "psutil-5.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:68d36986ded5dac7c2dcd42f2682af1db80d4bce3faa126a6145c1637e1b559f"}, - {file = "psutil-5.7.2.tar.gz", hash = "sha256:90990af1c3c67195c44c9a889184f84f5b2320dce3ee3acbd054e3ba0b4a7beb"}, + {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, + {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c"}, + {file = "psutil-5.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df"}, + {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131"}, + {file = "psutil-5.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60"}, + {file = "psutil-5.8.0-cp27-none-win32.whl", hash = "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876"}, + {file = "psutil-5.8.0-cp27-none-win_amd64.whl", hash = "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65"}, + {file = "psutil-5.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8"}, + {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6"}, + {file = "psutil-5.8.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac"}, + {file = "psutil-5.8.0-cp36-cp36m-win32.whl", hash = "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2"}, + {file = "psutil-5.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d"}, + {file = "psutil-5.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935"}, + {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d"}, + {file = "psutil-5.8.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023"}, + {file = "psutil-5.8.0-cp37-cp37m-win32.whl", hash = "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394"}, + {file = "psutil-5.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563"}, + {file = "psutil-5.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef"}, + {file = "psutil-5.8.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28"}, + {file = "psutil-5.8.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b"}, + {file = "psutil-5.8.0-cp38-cp38-win32.whl", hash = "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d"}, + {file = "psutil-5.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d"}, + {file = "psutil-5.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7"}, + {file = "psutil-5.8.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4"}, + {file = "psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b"}, + {file = "psutil-5.8.0-cp39-cp39-win32.whl", hash = "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0"}, + {file = "psutil-5.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3"}, + {file = "psutil-5.8.0.tar.gz", hash = "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6"}, ] psycopg2 = [ {file = "psycopg2-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:068115e13c70dc5982dfc00c5d70437fe37c014c808acce119b5448361c03725"}, @@ -2738,8 +2803,8 @@ psycopg2 = [ {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, ] py = [ - {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, ] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, @@ -2776,36 +2841,30 @@ pycodestyle = [ {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] pycryptodome = [ - {file = "pycryptodome-3.9.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:50348edd283afdccddc0938cdc674484533912ba8a99a27c7bfebb75030aa856"}, - {file = "pycryptodome-3.9.8-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:80d57177a0b7c14d4594c62bbb47fe2f6309ad3b0a34348a291d570925c97a82"}, - {file = "pycryptodome-3.9.8-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:fbe65d5cfe04ff2f7684160d50f5118bdefb01e3af4718eeb618bfed40f19d94"}, - {file = "pycryptodome-3.9.8-cp27-cp27m-win32.whl", hash = "sha256:bcd5b8416e73e4b0d48afba3704d8c826414764dafaed7a1a93c442188d90ccc"}, - {file = "pycryptodome-3.9.8-cp27-cp27m-win_amd64.whl", hash = "sha256:360955eece2cd0fa694a708d10303c6abd7b39614fa2547b6bd245da76198beb"}, - {file = "pycryptodome-3.9.8-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:1e655746f539421d923fd48df8f6f40b3443d80b75532501c0085b64afed9df5"}, - {file = "pycryptodome-3.9.8-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:709b9f144d23e290b9863121d1ace14a72e01f66ea9c903fbdc690520dfdfcf0"}, - {file = "pycryptodome-3.9.8-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:6276478ada411aca97c0d5104916354b3d740d368407912722bd4d11aa9ee4c2"}, - {file = "pycryptodome-3.9.8-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:54bdedd28476dea8a3cd86cb67c0df1f0e3d71cae8022354b0f879c41a3d27b2"}, - {file = "pycryptodome-3.9.8-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f521178e5a991ffd04182ed08f552daca1affcb826aeda0e1945cd989a9d4345"}, - {file = "pycryptodome-3.9.8-cp35-cp35m-win32.whl", hash = "sha256:a207231a52426de3ff20f5608f0687261a3329d97a036c51f7d4c606a6f30c23"}, - {file = "pycryptodome-3.9.8-cp35-cp35m-win_amd64.whl", hash = "sha256:2b998dc45ef5f4e5cf5248a6edfcd8d8e9fb5e35df8e4259b13a1b10eda7b16b"}, - {file = "pycryptodome-3.9.8-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:03d5cca8618620f45fd40f827423f82b86b3a202c8d44108601b0f5f56b04299"}, - {file = "pycryptodome-3.9.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f78a68c2c820e4731e510a2df3eef0322f24fde1781ced970bf497b6c7d92982"}, - {file = "pycryptodome-3.9.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:132a56abba24e2e06a479d8e5db7a48271a73a215f605017bbd476d31f8e71c1"}, - {file = "pycryptodome-3.9.8-cp36-cp36m-win32.whl", hash = "sha256:67dcad1b8b201308586a8ca2ffe89df1e4f731d5a4cdd0610cc4ea790351c739"}, - {file = "pycryptodome-3.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:b56638d58a3a4be13229c6a815cd448f9e3ce40c00880a5398471b42ee86f50e"}, - {file = "pycryptodome-3.9.8-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:bec2bcdf7c9ce7f04d718e51887f3b05dc5c1cfaf5d2c2e9065ecddd1b2f6c9a"}, - {file = "pycryptodome-3.9.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:abc2e126c9490e58a36a0f83516479e781d83adfb134576a5cbe5c6af2a3e93c"}, - {file = "pycryptodome-3.9.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ef39c98d9b8c0736d91937d193653e47c3b19ddf4fc3bccdc5e09aaa4b0c5d21"}, - {file = "pycryptodome-3.9.8-cp37-cp37m-win32.whl", hash = "sha256:4350a42028240c344ee855f032c7d4ad6ff4f813bfbe7121547b7dc579ecc876"}, - {file = "pycryptodome-3.9.8-cp37-cp37m-win_amd64.whl", hash = "sha256:c8bf40cf6e281a4378e25846924327e728a887e8bf0ee83b2604a0f4b61692e8"}, - {file = "pycryptodome-3.9.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8074c8448cfd0705dfa71ca333277fce9786d0b9cac75d120545de6253f996a"}, - {file = "pycryptodome-3.9.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8063a712fba642f78d3c506b0896846601b6de7f5c3d534e388ad0cc07f5a149"}, - {file = "pycryptodome-3.9.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dd302b6ae3965afeb5ef1b0d92486f986c0e65183cd7835973f0b593800590e6"}, - {file = "pycryptodome-3.9.8-cp38-cp38-win32.whl", hash = "sha256:02e51e1d5828d58f154896ddfd003e2e7584869c275e5acbe290443575370fba"}, - {file = "pycryptodome-3.9.8-cp38-cp38-win_amd64.whl", hash = "sha256:55eb61aca2c883db770999f50d091ff7c14016f2769ad7bca3d9b75d1d7c1b68"}, - {file = "pycryptodome-3.9.8-cp39-cp39-manylinux1_i686.whl", hash = "sha256:39ef9fb52d6ec7728fce1f1693cb99d60ce302aeebd59bcedea70ca3203fda60"}, - {file = "pycryptodome-3.9.8-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:de6e1cd75677423ff64712c337521e62e3a7a4fc84caabbd93207752e831a85a"}, - {file = "pycryptodome-3.9.8.tar.gz", hash = "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:5598dc6c9dbfe882904e54584322893eff185b98960bbe2cdaaa20e8a437b6e5"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1cfdb92dca388e27e732caa72a1cc624520fe93752a665c3b6cd8f1a91b34916"}, + {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f19e6ef750f677d924d9c7141f54bade3cd56695bbfd8a9ef15d0378557dfe4"}, + {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:19cb674df6c74a14b8b408aa30ba8a89bd1c01e23505100fb45f930fbf0ed0d9"}, + {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:28f75e58d02019a7edc7d4135203d2501dfc47256d175c72c9798f9a129a49a7"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:6d3baaf82681cfb1a842f1c8f77beac791ceedd99af911e4f5fabec32bae2259"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:946399d15eccebafc8ce0257fc4caffe383c75e6b0633509bd011e357368306c"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:eb01f9997e4d6a8ec8a1ad1f676ba5a362781ff64e8189fe2985258ba9cb9706"}, + {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:411745c6dce4eff918906eebcde78771d44795d747e194462abb120d2e537cd9"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f9f84059039b672a5a705b3c5aa21747867bacc30a72e28bf0d147cc8ef85ed"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7798e73225a699651888489fbb1dbc565e03a509942a8ce6194bbe6fb582a41f"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:46e96aeb8a9ca8b1edf9b1fd0af4bf6afcf3f1ca7fa35529f5d60b98f3e4e959"}, + {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:843e5f10ecdf9d307032b8b91afe9da1d6ed5bb89d0bbec5c8dcb4ba44008e11"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4ed27951b0a17afd287299e2206a339b5b6d12de9321e1a1575261ef9c4a851b"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9000877383e2189dafd1b2fc68c6c726eca9a3cfb6d68148fbb72ccf651959b6"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:faa682c404c218e8788c3126c9a4b8fbcc54dc245b5b6e8ea5b46f3b63bd0c84"}, + {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:62c488a21c253dadc9f731a32f0ac61e4e436d81a1ea6f7d1d9146ed4d20d6bd"}, + {file = "pycryptodome-3.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:27397aee992af69d07502126561d851ba3845aa808f0e55c71ad0efa264dd7d4"}, + {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d7ec2bd8f57c559dd24e71891c51c25266a8deb66fc5f02cc97c7fb593d1780a"}, + {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e15bde67ccb7d4417f627dd16ffe2f5a4c2941ce5278444e884cb26d73ecbc61"}, + {file = "pycryptodome-3.9.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5c3c4865730dfb0263f822b966d6d58429d8b1e560d1ddae37685fd9e7c63161"}, + {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2a68df525b387201a43b27b879ce8c08948a430e883a756d6c9e3acdaa7d7bd8"}, + {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a4599c0ca0fc027c780c1c45ed996d5bef03e571470b7b1c7171ec1e1a90914c"}, + {file = "pycryptodome-3.9.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b4e6b269a8ddaede774e5c3adbef6bf452ee144e6db8a716d23694953348cd86"}, ] pydocstyle = [ {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, @@ -2816,8 +2875,8 @@ pyflakes = [ {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, + {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, + {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, ] pyjwt = [ {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, @@ -2828,16 +2887,16 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"}, - {file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"}, + {file = "pytest-6.2.1-py3-none-any.whl", hash = "sha256:1969f797a1a0dbd8ccf0fecc80262312729afea9c17f1d70ebf85c5e76c6f7c8"}, + {file = "pytest-6.2.1.tar.gz", hash = "sha256:66e419b1899bc27346cb2c993e12c5e5e8daba9073c1fbce33b9807abc95c306"}, ] pytest-cov = [ {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, ] pytest-django = [ - {file = "pytest-django-3.9.0.tar.gz", hash = "sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9"}, - {file = "pytest_django-3.9.0-py2.py3-none-any.whl", hash = "sha256:64f99d565dd9497af412fcab2989fe40982c1282d4118ff422b407f3f7275ca5"}, + {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"}, ] pytest-django-testing-postgresql = [ {file = "pytest-django-testing-postgresql-0.1.post0.tar.gz", hash = "sha256:78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45"}, @@ -2846,10 +2905,6 @@ pytest-django-testing-postgresql = [ pytest-sugar = [ {file = "pytest-sugar-0.9.4.tar.gz", hash = "sha256:b1b2186b0a72aada6859bea2a5764145e3aaa2c1cfbb23c3a19b5f7b697563d3"}, ] -python-box = [ - {file = "python-box-3.4.6.tar.gz", hash = "sha256:694a7555e3ff9fbbce734bbaef3aad92b8e4ed0659d3ed04d56b6a0a0eff26a9"}, - {file = "python_box-3.4.6-py2.py3-none-any.whl", hash = "sha256:a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10"}, -] python-crontab = [ {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"}, ] @@ -2857,10 +2912,6 @@ python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, ] -python-dotenv = [ - {file = "python-dotenv-0.14.0.tar.gz", hash = "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d"}, - {file = "python_dotenv-0.14.0-py2.py3-none-any.whl", hash = "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"}, -] python-ldap = [ {file = "python-ldap-3.3.1.tar.gz", hash = "sha256:4711cacf013e298754abd70058ccc995758177fb425f1c2d30e71adfc1d00aa5"}, ] @@ -2869,8 +2920,8 @@ python-memcached = [ {file = "python_memcached-1.59-py2.py3-none-any.whl", hash = "sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594"}, ] pytz = [ - {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, - {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, + {file = "pytz-2020.4-py2.py3-none-any.whl", hash = "sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd"}, + {file = "pytz-2020.4.tar.gz", hash = "sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268"}, ] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, @@ -2894,34 +2945,82 @@ redis = [ {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, ] regex = [ - {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"}, - {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"}, - {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"}, - {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"}, - {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"}, - {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"}, - {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"}, - {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"}, - {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"}, + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, ] requests = [ - {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, - {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] restructuredtext-lint = [ - {file = "restructuredtext_lint-1.3.1.tar.gz", hash = "sha256:470e53b64817211a42805c3a104d2216f6f5834b22fe7adb637d1de4d6501fb8"}, + {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"}, +] +"ruamel.yaml.clib" = [ + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-win32.whl", hash = "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7"}, + {file = "ruamel.yaml.clib-0.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"}, + {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-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-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-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-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.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"}, ] rules = [ {file = "rules-2.2.tar.gz", hash = "sha256:9bae429f9d4f91a375402990da1541f9e093b0ac077221d57124d06eeeca4405"}, @@ -2950,21 +3049,17 @@ snowballstemmer = [ {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] -soupsieve = [ - {file = "soupsieve-1.9.6-py2.py3-none-any.whl", hash = "sha256:feb1e937fa26a69e08436aad4a9037cd7e1d4c7212909502ba30701247ff8abd"}, - {file = "soupsieve-1.9.6.tar.gz", hash = "sha256:7985bacc98c34923a439967c1a602dc4f1e15f923b6fcf02344184f86cc7efaa"}, -] spdx-license-list = [ - {file = "spdx_license_list-0.5.1-py3-none-any.whl", hash = "sha256:32f1401e0077b46ba8b3d9c648b6503ef1d49c41aab51aa13816be2dde3b4a13"}, - {file = "spdx_license_list-0.5.1.tar.gz", hash = "sha256:64cb5de37724c64cdeccafa2ae68667ff8ccdb7b688f51c1c2be82d7ebe3a112"}, + {file = "spdx_license_list-0.5.2-py3-none-any.whl", hash = "sha256:1b338470c7b403dbecceca563a316382c7977516128ca6c1e8f7078e3ed6e7b0"}, + {file = "spdx_license_list-0.5.2.tar.gz", hash = "sha256:952996f72ab807972dc2278bb9b91e5294767211e51f09aad9c0e2ff5b82a31b"}, ] sphinx = [ - {file = "Sphinx-3.2.1-py3-none-any.whl", hash = "sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0"}, - {file = "Sphinx-3.2.1.tar.gz", hash = "sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8"}, + {file = "Sphinx-3.3.1-py3-none-any.whl", hash = "sha256:d4e59ad4ea55efbb3c05cde3bfc83bfc14f0c95aa95c3d75346fcce186a47960"}, + {file = "Sphinx-3.3.1.tar.gz", hash = "sha256:1e8d592225447104d1172be415bc2972bd1357e3e12fdc76edf2261105db4300"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx-autodoc-typehints-1.11.0.tar.gz", hash = "sha256:bbf0b203f1019b0f9843ee8eef0cff856dc04b341f6dbe1113e37f2ebf243e11"}, - {file = "sphinx_autodoc_typehints-1.11.0-py3-none-any.whl", hash = "sha256:89e19370a55db4aef1be2094d8fb1fb500ca455c55b3fcc8d2600ff805227e04"}, + {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"}, + {file = "sphinx_autodoc_typehints-1.11.1-py3-none-any.whl", hash = "sha256:da049791d719f4c9813642496ee4764203e317f0697eb75446183fa2a68e3f77"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2995,19 +3090,19 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, ] sqlparse = [ - {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"}, - {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"}, + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, ] stevedore = [ - {file = "stevedore-3.2.1-py3-none-any.whl", hash = "sha256:ddc09a744dc224c84ec8e8efcb70595042d21c97c76df60daee64c9ad53bc7ee"}, - {file = "stevedore-3.2.1.tar.gz", hash = "sha256:a34086819e2c7a7f86d5635363632829dab8014e5fd7be2454c7cba84ac7514e"}, + {file = "stevedore-3.3.0-py3-none-any.whl", hash = "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"}, + {file = "stevedore-3.3.0.tar.gz", hash = "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee"}, ] termcolor = [ {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, ] testfixtures = [ - {file = "testfixtures-6.14.2-py2.py3-none-any.whl", hash = "sha256:816557888877f498081c1b5c572049b4a2ddffedb77401308ff4cdc1bb9147b7"}, - {file = "testfixtures-6.14.2.tar.gz", hash = "sha256:14d9907390f5f9c7189b3d511b64f34f1072d07cc13b604a57e1bb79029376e3"}, + {file = "testfixtures-6.17.0-py2.py3-none-any.whl", hash = "sha256:ebcc3e024d47bb58a60cdc678604151baa0c920ae2814004c89ac9066de31b2c"}, + {file = "testfixtures-6.17.0.tar.gz", hash = "sha256:fa7c170df68ca6367eda061e9ec339ae3e6d3679c31e04033f83ef97a7d7d0ce"}, ] "testing.common.database" = [ {file = "testing.common.database-2.0.3-py2.py3-none-any.whl", hash = "sha256:e3ed492bf480a87f271f74c53b262caf5d85c8bc09989a8f534fa2283ec52492"}, @@ -3022,15 +3117,15 @@ text-unidecode = [ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tqdm = [ - {file = "tqdm-4.48.2-py2.py3-none-any.whl", hash = "sha256:1a336d2b829be50e46b84668691e0a2719f26c97c62846298dd5ae2937e4d5cf"}, - {file = "tqdm-4.48.2.tar.gz", hash = "sha256:564d632ea2b9cb52979f7956e093e831c28d441c11751682f84c86fc46e4fd21"}, + {file = "tqdm-4.54.1-py2.py3-none-any.whl", hash = "sha256:d4f413aecb61c9779888c64ddf0c62910ad56dcbe857d8922bb505d4dbff0df1"}, + {file = "tqdm-4.54.1.tar.gz", hash = "sha256:38b658a3e4ecf9b4f6f8ff75ca16221ae3378b2e175d846b6b33ea3a20852cf5"}, ] twilio = [ - {file = "twilio-6.45.1.tar.gz", hash = "sha256:97752ed9595dc23cd7217a812804c4a9e8c1b92e84529e82db718c921ed80edf"}, + {file = "twilio-6.50.1.tar.gz", hash = "sha256:dd8371c9b4ea422d6de7526b63b587da82e8488f2b3f6b1258d2cad6e4006a65"}, ] typed-ast = [ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, @@ -3061,12 +3156,16 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] urllib3 = [ - {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, - {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, ] vine = [ - {file = "vine-1.3.0-py2.py3-none-any.whl", hash = "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"}, - {file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"}, + {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, + {file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, @@ -3077,6 +3176,6 @@ yubiotp = [ {file = "YubiOTP-1.0.0.post1.tar.gz", hash = "sha256:c13825f7b76a69afb92f19521f4dea9f5031d70f45123b505dc2e0ac03132065"}, ] zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, + {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, + {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, ] diff --git a/pyproject.toml b/pyproject.toml index c960fdb9d09e722918156c00c38c7acfdce873d1..cfb02aa3649f591f00fac32004c0a397e43a09ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,19 @@ [tool.poetry] -name = "AlekSIS" +name = "AlekSIS-Core" version = "2.0a3.dev0" packages = [ { include = "aleksis" } ] readme = "README.rst" -include = ["CHANGELOG.rst", "CODE_OF_CONDUCT.rst", "CONTRIBUTING.rst", "Dockerfile", "LICENCE.rst", "manage.py", "docker/*", "docker/**/*", "docker-compose.yml", "docs/*", "docs/**/*", "tox.ini"] +include = ["CHANGELOG.rst", "LICENCE.rst", "docs/*", "docs/**/*", "aleksis/**/*.mo", "tox.ini"] description = "AlekSIS (School Information System) — Core" authors = ["Dominik George <dominik.george@teckids.org>", "Julian Leucker <leuckeju@katharineum.de>", "mirabilos <thorsten.glaser@teckids.org>", "Frank Poetzsch-Heffter <p-h@katharineum.de>", "Tom Teichler <tom.teichler@teckids.org>", "Jonathan Weth <wethjo@katharineum.de>", "Hangzhi Yu <yuha@katharineum.de>"] maintainers = ["Jonathan Weth <wethjo@katharineum.de>", "Dominik George <dominik.george@teckids.org>"] license = "EUPL-1.2-or-later" homepage = "https://aleksis.org/" -repository = "https://edugit.org/AlekSIS/official/AlekSIS" -documentation = "https://aleksis.org/AlekSIS/docs/html/" +repository = "https://edugit.org/AlekSIS/official/AlekSIS-Core" +documentation = "https://aleksis.org/AlekSIS-Core/docs/html/" keywords = ["SIS", "education", "school", "digitisation", "school apps"] classifiers = [ "Development Status :: 3 - Alpha", @@ -27,27 +27,29 @@ classifiers = [ "Typing :: Typed", ] +[[tool.poetry.source]] +name = "gitlab" +url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple" +secondary = true + [tool.poetry.dependencies] python = "^3.7" Django = "^3.0" django-any-js = "^1.0" django-debug-toolbar = "^2.0" -django-easy-audit = {version ="^1.2rc1", allow-prereleases = true} django-middleware-global-request = "^0.1.2" django-menu-generator = "^1.0.4" django-tables2 = "^2.1" -Pillow = "^7.0" +Pillow = "^8.0" django-phonenumber-field = {version = "<5.1", extras = ["phonenumbers"]} django-sass-processor = "^0.8" libsass = "^0.20.0" colour = "^0.1.5" -dynaconf = {version = "^2.0", extras = ["yaml", "toml", "ini"]} +dynaconf = {version = "^3.1", extras = ["yaml", "toml", "ini"]} django-settings-context-processor = "^0.2" django-auth-ldap = { version = "^2.2", optional = true } -django-maintenance-mode = "^0.14.0" +django-maintenance-mode = "^0.15.0" django-ipware = "^3.0" -easy-thumbnails = "^2.6" -django-image-cropping = "^1.2" django-impersonate = "^1.4" python-memcached = "^1.59" django-hattori = "^0.2" @@ -66,12 +68,12 @@ html2text = "^2020.0.0" django-ckeditor = "^6.0.0" django-js-reverse = "^0.9.1" calendarweek = "^0.4.3" -Celery = {version="^4.4.0", optional=true, extras=["django", "redis"]} -django-celery-results = {version="^1.1.2", optional=true} +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} django-jsonstore = "^0.4.1" -django-polymorphic = "^2.1.2" +django-polymorphic = "^3.0.0" django-colorfield = "^0.3.0" django-bleach = "^0.6.1" django-guardian = "^2.2.0" @@ -86,36 +88,21 @@ django-reversion = "^3.0.7" django-favicon-plus-reloaded = "^1.0.4" django-health-check = "^3.12.1" psutil = "^5.7.0" -celery-progress = "^0.0.12" +celery-progress = "^0.0.14" +django-cachalot = "^2.3.2" +django-prometheus = "^2.1.0" +importlib-metadata = {version = "^3.0.0", python = "<3.9"} +django-model-utils = "^4.0.0" [tool.poetry.extras] ldap = ["django-auth-ldap"] celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack"] [tool.poetry.dev-dependencies] -sphinx = "^3.0" -sphinxcontrib-django = "^0.5.0" -sphinx-autodoc-typehints = "^1.7" -django-stubs = "^1.1" -pytest = "^6.0" -pytest-django = "^3.7" -pytest-django-testing-postgresql = "^0.1" -selenium = "^3.141.0" -safety = "^1.8.5" -flake8 = "^3.7.9" -flake8-django = "^1.0.0" -flake8-fixme = "^1.1.1" -flake8-mypy = "^17.8.0" -flake8-bandit = "^2.1.2" -flake8-builtins = "^1.4.1" -flake8-docstrings = "^1.5.0" -flake8-rst-docstrings = "^0.0.13" -black = "^19.10b0" -flake8-black = "^0.2.0" -isort = "^5.0.0" -flake8-isort = "^4.0.0" -pytest-cov = "^2.8.1" -pytest-sugar = "^0.9.2" +aleksis-builddeps = "*" + +[tool.poetry.scripts] +aleksis-admin = 'aleksis.core.util.manage:aleksis_cmd' [tool.black] line-length = 100 diff --git a/tox.ini b/tox.ini index eca63a758a5f6a20b1cd11887330817fec7908d7..98fce3af53aa48d1eac2e28454c26e7b74c23f72 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] skipsdist = True skip_missing_interpreters = true -envlist = py37,py38 +envlist = py37,py38,py39 [testenv] whitelist_externals = poetry @@ -22,8 +22,8 @@ setenv = [testenv:lint] commands = - - poetry run black --check --diff aleksis/ - - poetry run isort -c --diff --stdout aleksis/ + poetry run black --check --diff aleksis/ + poetry run isort -c --diff --stdout aleksis/ poetry run flake8 {posargs} aleksis/ [testenv:security] @@ -49,9 +49,8 @@ exclude = migrations,tests ignore = BLK100,E203,E231,W503,D100,D101,D102,D103,D104,D105,D106,D107,RST215,RST214,F821,F841,S106,T100,T101,DJ05 [isort] +profile = black line_length = 100 -multi_line_output = 3 -include_trailing_comma = 1 default_section = THIRDPARTY known_first_party = aleksis known_django = django