diff --git a/.gitignore b/.gitignore index 4e43c2cca29a68a028594844be13bbe590de8ca6..a74536c95c84dedbf6b7a43c30b86e7490732443 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,5 @@ biscuit/node_modules/ .coverage .tox/ maintenance_mode_state.txt +htmlcov/ +.mypy_cache/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d6e05525fe817c5f718b720c3d2e3f7d7bd41328..a1be1e5ec7c2ec8812cb822e62747ece3917a3dd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,5 @@ +image: registry.edugit.org/teckids/docker-images/python-pimped:master + stages: - test - build @@ -5,18 +7,63 @@ stages: 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 test: stage: test - image: - name: python:3.8-buster + services: + - name: selenium/standalone-firefox + alias: selenium before_script: - - apt-get -y update && apt-get -y install postgresql libpq5 libpq-dev libssl-dev sudo - - pip install poetry - adduser --disabled-password --gecos "Test User" testuser + - chown -R testuser . + script: + - 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 + allow_failure: true + +build_dist: + stage: build script: - - sudo -u testuser poetry install - - sudo -u testuser poetry run tox + - tox -e build + artifacts: + paths: + - dist/ + +pages: + stage: deploy + before_script: + - cp -r .tox/screenshots/firefox docs/screenshots + script: + - tox -e docs -- BUILDDIR=../public/docs + artifacts: + paths: + - public/ + only: + - master build_docker: stage: build @@ -39,14 +86,13 @@ build_docker: --cleanup only: - master + - tags deploy_demo-master: stage: deploy environment: name: demo/master url: http://demo-master.biscuit-sis.org - image: - name: debian:buster-slim before_script: - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - eval $(ssh-agent -s) @@ -65,25 +111,10 @@ deploy_demo-master: - grep -v "build:" docker-compose.yml | ssh root@demo-master.biscuit-sis.org env BISCUIT_IMAGE_TAG=${CI_COMMIT_REF_NAME} NGINX_HTTP_PORT=80 + BISCUIT_maintenance__debug=true docker-compose -p biscuit-${CI_ENVIRONMENT_SLUG} -f /dev/stdin up -d only: - master - -pages: - stage: deploy - image: - name: python:3.8-buster - before_script: - - apt-get -y update && apt-get -y install make - - pip install poetry - script: - - poetry install - - poetry run make -C docs html BUILDDIR=../public/docs - artifacts: - paths: - - public/ - only: - - master diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 74f35ea4898a7e78de7d9927504601da7f442aee..8dd5324fd7c0c1ca5207c86f69329ea1a7e15a61 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -12,22 +12,30 @@ 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: + PEP 8 and the Django style guide: 1. Standard library imports 2. Django imports 3. Third-party imports - 4. Imports from other BiscuIT apps (absolute imports) + 4. Imports from BiscuIT core and other apps (absolute imports) 5. Imports from the same BiscuIT app (realtive imports) -- All string literals use single quotes + 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 + Working with the Git repository ------------------------------- @@ -82,6 +90,26 @@ 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 +BiscuIT project explicitly welcomes implementing support for +interoperability with non-free services. + +However, to purposefullt 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. + + Text documents -------------- @@ -100,9 +128,11 @@ licence possible. .. _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 .. _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 diff --git a/Dockerfile b/Dockerfile index ec6f771d02496a26629df0f0959af7ff7cbf69c3..a377af5a6194b2e0c528eecae2431508e53d73d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,13 +12,11 @@ ENV BISCUIT_static__root /var/lib/biscuit/static ENV BISCUIT_media__root /var/lib/biscuit/media ENV BISCUIT_backup__location /var/lib/biscuit/backups -# FIXME Use poetry pre-release for external build -ENV POETRY_VERSION 1.0.0b3 - # Install necessary Debian packages for build and runtime -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y --no-install-recommends \ +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 \ @@ -30,35 +28,32 @@ RUN apt-get update && \ # Install core dependnecies WORKDIR /usr/src/app COPY poetry.lock pyproject.toml ./ -RUN pip install "poetry==$POETRY_VERSION"; \ - poetry export -f requirements.txt | pip install -r /dev/stdin; \ - pip install gunicorn +RUN eatmydata pip install poetry; \ + poetry export -f requirements.txt | eatmydata pip install -r /dev/stdin; \ + eatmydata pip install gunicorn # Install core COPY biscuit ./biscuit/ COPY LICENCE README.rst manage.py ./ RUN mkdir -p /var/lib/biscuit/media /var/lib/biscuit/static /var/lib/biscuit/backups; \ - poetry build && pip install dist/*.whl + poetry build && eatmydata pip install dist/*.whl # Build messages and assets RUN python manage.py compilemessages; \ - python manage.py yarn install; \ - python manage.py collectstatic --no-input --clear + eatmydata python manage.py yarn install # Clean up build dependencies -RUN apt-get remove --purge -y \ +RUN eatmydata apt-get remove --purge -y \ build-essential \ gettext \ libpq-dev \ libssl-dev \ yarnpkg; \ - apt-get autoremove --purge -y; \ + eatmydata apt-get autoremove --purge -y; \ apt-get clean -y; \ - pip uninstall -y poetry; \ + eatmydata pip uninstall -y poetry; \ rm -f /var/lib/apt/lists/*_*; \ - rm -rf /root/.cache; \ - rm -rf biscuit/node_modules; \ - rm -rf /usr/local/lib/node_modules + rm -rf /root/.cache # Declare a persistent volume for all data VOLUME /var/lib/biscuit diff --git a/apps/official/BiscuIT-App-Alsijil b/apps/official/BiscuIT-App-Alsijil index 9014661e518c6d9457723571d7b0ef1ac9be6c6b..3973b529e0f0889376159fd83c7d6e49bef6d767 160000 --- a/apps/official/BiscuIT-App-Alsijil +++ b/apps/official/BiscuIT-App-Alsijil @@ -1 +1 @@ -Subproject commit 9014661e518c6d9457723571d7b0ef1ac9be6c6b +Subproject commit 3973b529e0f0889376159fd83c7d6e49bef6d767 diff --git a/apps/official/BiscuIT-App-Chronos b/apps/official/BiscuIT-App-Chronos index d4da734afa405a6af2adbe607f553e2b759737d6..51f8b6f703cbbeda26b64b45a2860c5e4921b1ba 160000 --- a/apps/official/BiscuIT-App-Chronos +++ b/apps/official/BiscuIT-App-Chronos @@ -1 +1 @@ -Subproject commit d4da734afa405a6af2adbe607f553e2b759737d6 +Subproject commit 51f8b6f703cbbeda26b64b45a2860c5e4921b1ba diff --git a/apps/official/BiscuIT-App-Exlibris b/apps/official/BiscuIT-App-Exlibris index b03369df8214f062a18a70824c6a257cf4ab427d..d76afcbcabf7e93a48f4710a779fa8f06c959577 160000 --- a/apps/official/BiscuIT-App-Exlibris +++ b/apps/official/BiscuIT-App-Exlibris @@ -1 +1 @@ -Subproject commit b03369df8214f062a18a70824c6a257cf4ab427d +Subproject commit d76afcbcabf7e93a48f4710a779fa8f06c959577 diff --git a/apps/official/BiscuIT-App-SchILD-NRW b/apps/official/BiscuIT-App-SchILD-NRW index d3503372579bb067e80c72d0c168ac6e998b8d56..679baee78cfe182f41536b061b8d0f3e2dc8d0bb 160000 --- a/apps/official/BiscuIT-App-SchILD-NRW +++ b/apps/official/BiscuIT-App-SchILD-NRW @@ -1 +1 @@ -Subproject commit d3503372579bb067e80c72d0c168ac6e998b8d56 +Subproject commit 679baee78cfe182f41536b061b8d0f3e2dc8d0bb diff --git a/apps/official/BiscuIT-App-Untis b/apps/official/BiscuIT-App-Untis index f216d0e9da645cfbd3d4e1d865bb16dc4de7946f..7b4f67f723c52fad8e4a58f4571a67b2f05c1363 160000 --- a/apps/official/BiscuIT-App-Untis +++ b/apps/official/BiscuIT-App-Untis @@ -1 +1 @@ -Subproject commit f216d0e9da645cfbd3d4e1d865bb16dc4de7946f +Subproject commit 7b4f67f723c52fad8e4a58f4571a67b2f05c1363 diff --git a/biscuit/core/__init__.py b/biscuit/core/__init__.py index ef696833a713e9bad35ffaeff41cdf46aa7ebd4e..4d87b04132c9610b1f3a2a340eb74cc9ede8ceb3 100644 --- a/biscuit/core/__init__.py +++ b/biscuit/core/__init__.py @@ -1,8 +1,8 @@ import pkg_resources try: - __version__ = pkg_resources.get_distribution('BiscuIT-ng').version + __version__ = pkg_resources.get_distribution("BiscuIT-ng").version except Exception: - __version__ = 'unknown' + __version__ = "unknown" -default_app_config = 'biscuit.core.apps.CoreConfig' +default_app_config = "biscuit.core.apps.CoreConfig" diff --git a/biscuit/core/admin.py b/biscuit/core/admin.py index 0810d4504654e0e1337c70810c41696d6e2abbd0..106da0677831f722aefa1d28d23dde9fea838b06 100644 --- a/biscuit/core/admin.py +++ b/biscuit/core/admin.py @@ -2,7 +2,6 @@ from django.contrib import admin from .models import Group, Person, School, SchoolTerm - admin.site.register(Person) admin.site.register(Group) admin.site.register(School) diff --git a/biscuit/core/anonymizers.py b/biscuit/core/anonymizers.py index 03d4f3bf3a8597a3a61a9683878eae1a9f1637ea..3a9094c4140347ac5b738402ef5731582d064a2e 100644 --- a/biscuit/core/anonymizers.py +++ b/biscuit/core/anonymizers.py @@ -7,17 +7,20 @@ class PersonAnonymizer(BaseAnonymizer): model = Person attributes = [ - ('first_name', faker.first_name), - ('last_name', faker.last_name), - ('additional_name', ''), - ('short_name', lambda **kwargs: faker.pystr(min_chars=3, max_chars=5, **kwargs)), - ('street', faker.street_name), - ('housenumber', faker.building_number), - ('postal_code', faker.postcode), - ('place', faker.city), - ('phone_number', ''), - ('mobile_number', ''), - ('email', faker.email), - ('date_of_birth', lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs)), - ('photo', '') + ("first_name", faker.first_name), + ("last_name", faker.last_name), + ("additional_name", ""), + ("short_name", lambda **kwargs: faker.pystr(min_chars=3, max_chars=5, **kwargs)), + ("street", faker.street_name), + ("housenumber", faker.building_number), + ("postal_code", faker.postcode), + ("place", faker.city), + ("phone_number", ""), + ("mobile_number", ""), + ("email", faker.email), + ( + "date_of_birth", + lambda **kwargs: faker.date_of_birth(minimum_age=8, maximum_age=66, **kwargs), + ), + ("photo", ""), ] diff --git a/biscuit/core/apps.py b/biscuit/core/apps.py index ef62cfb3085fa7d763f4506bc94613fcc77c7c53..5c5b3093b16309d23ffa0a985e7458fe356b800b 100644 --- a/biscuit/core/apps.py +++ b/biscuit/core/apps.py @@ -5,9 +5,9 @@ from .signals import clean_scss class CoreConfig(AppConfig): - name = 'biscuit.core' - verbose_name = 'BiscuIT - The Free School Information System' + name = "biscuit.core" + verbose_name = "BiscuIT - The Free School Information System" def ready(self) -> None: clean_scss() - post_save.connect(clean_scss, sender=apps.get_model('dbsettings', 'Setting')) + post_save.connect(clean_scss, sender=apps.get_model("dbsettings", "Setting")) diff --git a/biscuit/core/cronjobs.py b/biscuit/core/cronjobs.py index 229e017cf840a2eabfa9b624aa47e28cac636686..2ffe250b7939061afddf6246a8309fcc8a3e0feb 100644 --- a/biscuit/core/cronjobs.py +++ b/biscuit/core/cronjobs.py @@ -1,5 +1,6 @@ from django.conf import settings from django.core import management + from django_cron import CronJobBase, Schedule @@ -7,9 +8,11 @@ class Backup(CronJobBase): RUN_AT_TIMES = settings.DBBACKUP_CRON_TIMES RETRY_AFTER_FAILURE_MINS = 5 - schedule = Schedule(run_at_times=RUN_AT_TIMES, retry_after_failure_mins=RETRY_AFTER_FAILURE_MINS) - code = 'biscuit.core.Backup' + schedule = Schedule( + run_at_times=RUN_AT_TIMES, retry_after_failure_mins=RETRY_AFTER_FAILURE_MINS + ) + code = "biscuit.core.Backup" def do(self): - management.call_command('dbbackup', '-z') - management.call_command('mediabackup', '-z') + management.call_command("dbbackup", "-z") + management.call_command("mediabackup", "-z") diff --git a/biscuit/core/decorators.py b/biscuit/core/decorators.py index 732f120958924f683f05482bd57661cc503f2882..bd87884fbdace4a4277e9d81503a89c89924563d 100644 --- a/biscuit/core/decorators.py +++ b/biscuit/core/decorators.py @@ -1,8 +1,8 @@ from typing import Callable + from django.contrib.auth.decorators import user_passes_test def admin_required(function: Callable = None) -> Callable: - actual_decorator = user_passes_test( - lambda u: u.is_active and u.is_superuser) + actual_decorator = user_passes_test(lambda u: u.is_active and u.is_superuser) return actual_decorator(function) diff --git a/biscuit/core/forms.py b/biscuit/core/forms.py index c7e813fdec59f29777da3670fb1aeee377850b3f..cef045d867bd838acc8443c17eb695a48e90b31a 100644 --- a/biscuit/core/forms.py +++ b/biscuit/core/forms.py @@ -1,97 +1,134 @@ from django import forms from django.contrib.auth import get_user_model from django.utils.translation import ugettext_lazy as _ + from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget -from .models import Person, Group, School, SchoolTerm +from .models import Group, Person, School, SchoolTerm class PersonAccountForm(forms.ModelForm): class Meta: model = Person - fields = ['last_name', 'first_name', 'user'] - widgets = { - 'user': Select2Widget - } + fields = ["last_name", "first_name", "user"] + widgets = {"user": Select2Widget} new_user = forms.CharField(required=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['first_name'].disabled = True - self.fields['last_name'].disabled = True + self.fields["first_name"].disabled = True + self.fields["last_name"].disabled = True def clean(self) -> None: User = get_user_model() - if self.cleaned_data.get('new_user', None): - if self.cleaned_data.get('user', None): - self.add_error('new_user', _( - 'You cannot set a new username when also selecting an existing user.')) - elif User.objects.filter(username=self.cleaned_data['new_user']).exists(): - self.add_error('new_user', _('This username is already in use.')) + if self.cleaned_data.get("new_user", None): + if self.cleaned_data.get("user", None): + self.add_error( + "new_user", + _("You cannot set a new username when also selecting an existing user."), + ) + elif User.objects.filter(username=self.cleaned_data["new_user"]).exists(): + self.add_error("new_user", _("This username is already in use.")) else: - new_user_obj = User.objects.create_user(self.cleaned_data['new_user'], - self.instance.email, - first_name=self.instance.first_name, - last_name=self.instance.last_name) + new_user_obj = User.objects.create_user( + self.cleaned_data["new_user"], + self.instance.email, + first_name=self.instance.first_name, + last_name=self.instance.last_name, + ) - self.cleaned_data['user'] = new_user_obj + self.cleaned_data["user"] = new_user_obj PersonsAccountsFormSet = forms.modelformset_factory( - Person, form=PersonAccountForm, max_num=0, extra=0) + Person, form=PersonAccountForm, max_num=0, extra=0 +) class EditPersonForm(forms.ModelForm): class Meta: model = Person - fields = ['user', 'is_active', 'first_name', 'last_name', 'additional_name', 'short_name', 'street', 'housenumber', - 'postal_code', 'place', 'phone_number', 'mobile_number', 'email', 'date_of_birth', 'sex', 'photo', 'photo_cropping'] - widgets = { - 'user': Select2Widget - } + fields = [ + "user", + "is_active", + "first_name", + "last_name", + "additional_name", + "short_name", + "street", + "housenumber", + "postal_code", + "place", + "phone_number", + "mobile_number", + "email", + "date_of_birth", + "sex", + "photo", + "photo_cropping", + ] + widgets = {"user": Select2Widget} new_user = forms.CharField( - required=False, - label=_('New user'), - help_text=_('Create a new account')) + required=False, label=_("New user"), help_text=_("Create a new account") + ) def clean(self) -> None: User = get_user_model() - if self.cleaned_data.get('new_user', None): - if self.cleaned_data.get('user', None): - self.add_error('new_user', _( - 'You cannot set a new username when also selecting an existing user.')) - elif User.objects.filter(username=self.cleaned_data['new_user']).exists(): - self.add_error('new_user', _('This username is already in use.')) + if self.cleaned_data.get("new_user", None): + if self.cleaned_data.get("user", None): + self.add_error( + "new_user", + _("You cannot set a new username when also selecting an existing user."), + ) + elif User.objects.filter(username=self.cleaned_data["new_user"]).exists(): + self.add_error("new_user", _("This username is already in use.")) else: - new_user_obj = User.objects.create_user(self.cleaned_data['new_user'], - self.instance.email, - first_name=self.instance.first_name, - last_name=self.instance.last_name) + new_user_obj = User.objects.create_user( + self.cleaned_data["new_user"], + self.instance.email, + first_name=self.instance.first_name, + last_name=self.instance.last_name, + ) - self.cleaned_data['user'] = new_user_obj + self.cleaned_data["user"] = new_user_obj class EditGroupForm(forms.ModelForm): class Meta: model = Group - fields = ['name', 'short_name', 'members', 'owners', 'parent_groups'] + fields = ["name", "short_name", "members", "owners", "parent_groups"] widgets = { - 'members': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']), - 'owners': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains']), - 'parent_groups': ModelSelect2MultipleWidget(search_fields=['name__icontains', 'short_name__icontains']), + "members": ModelSelect2MultipleWidget( + search_fields=[ + "first_name__icontains", + "last_name__icontains", + "short_name__icontains", + ] + ), + "owners": ModelSelect2MultipleWidget( + search_fields=[ + "first_name__icontains", + "last_name__icontains", + "short_name__icontains", + ] + ), + "parent_groups": ModelSelect2MultipleWidget( + search_fields=["name__icontains", "short_name__icontains"] + ), } + class EditSchoolForm(forms.ModelForm): class Meta: model = School - fields = ['name', 'name_official', 'logo', 'logo_cropping'] + fields = ["name", "name_official", "logo", "logo_cropping"] class EditTermForm(forms.ModelForm): class Meta: model = SchoolTerm - fields = ['caption', 'date_start', 'date_end'] + fields = ["caption", "date_start", "date_end"] diff --git a/biscuit/core/menus.py b/biscuit/core/menus.py index a230037f8305813cd5ec1a13307894af9da4bc15..cbb745d053bb6565aca7fad34f332434c5612af2 100644 --- a/biscuit/core/menus.py +++ b/biscuit/core/menus.py @@ -2,121 +2,125 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ MENUS = { - 'NAV_MENU_CORE': [ + "NAV_MENU_CORE": [ { - 'name': _('Account'), - 'url': '#', - 'root': True, - 'submenu': [ + "name": _("Account"), + "url": "#", + "root": True, + "submenu": [ { - 'name': _('Stop impersonation'), - 'url': 'impersonate-stop', - 'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.is_impersonate'] + "name": _("Stop impersonation"), + "url": "impersonate-stop", + "validators": [ + "menu_generator.validators.is_authenticated", + "biscuit.core.util.core_helpers.is_impersonate", + ], }, { - 'name': _('Login'), - 'url': settings.LOGIN_URL, - 'validators': ['menu_generator.validators.is_anonymous'] + "name": _("Login"), + "url": settings.LOGIN_URL, + "validators": ["menu_generator.validators.is_anonymous"], }, { - 'name': _('Logout'), - 'url': 'logout', - 'validators': ['menu_generator.validators.is_authenticated'] + "name": _("Logout"), + "url": "logout", + "validators": ["menu_generator.validators.is_authenticated"], }, { - 'name': _('Two factor auth'), - 'url': 'two_factor:profile', - 'validators': ['menu_generator.validators.is_authenticated', lambda request: 'two_factor' in settings.INSTALLED_APPS] - } - ] + "name": _("Two factor auth"), + "url": "two_factor:profile", + "validators": [ + "menu_generator.validators.is_authenticated", + lambda request: "two_factor" in settings.INSTALLED_APPS, + ], + }, + ], }, { - 'name': _('Admin'), - 'url': '#', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'], - 'submenu': [ + "name": _("Admin"), + "url": "#", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + ], + "submenu": [ { - 'name': _('Data management'), - 'url': 'data_management', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'] + "name": _("Data management"), + "url": "data_management", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + ], }, { - 'name': _('System status'), - 'url': 'system_status', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'] + "name": _("System status"), + "url": "system_status", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + ], }, { - 'name': _('Impersonation'), - 'url': 'impersonate-list', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'] + "name": _("Impersonation"), + "url": "impersonate-list", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + ], }, { - 'name': _('Manage school'), - 'url': 'school_management', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'] - } - ] + "name": _("Manage school"), + "url": "school_management", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + ], + }, + ], }, { - 'name': _('People'), - 'url': '#', - 'root': True, - 'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'], - 'submenu': [ + "name": _("People"), + "url": "#", + "root": True, + "validators": [ + "menu_generator.validators.is_authenticated", + "biscuit.core.util.core_helpers.has_person", + ], + "submenu": [ { - 'name': _('Persons'), - 'url': 'persons', - 'validators': ['menu_generator.validators.is_authenticated'] + "name": _("Persons"), + "url": "persons", + "validators": ["menu_generator.validators.is_authenticated"], }, { - 'name': _('Groups'), - 'url': 'groups', - 'validators': ['menu_generator.validators.is_authenticated'] + "name": _("Groups"), + "url": "groups", + "validators": ["menu_generator.validators.is_authenticated"], }, { - 'name': _('Persons and accounts'), - 'url': 'persons_accounts', - 'validators': ['menu_generator.validators.is_authenticated', 'menu_generator.validators.is_superuser'] - } - ] - } - ], - 'FOOTER_MENU_CORE': [ - { - 'name': _('BiscuIT Software'), - 'url': '#', - 'submenu': [ - { - 'name': _('Website'), - 'url': 'https://biscuit.edugit.org/' + "name": _("Persons and accounts"), + "url": "persons_accounts", + "validators": [ + "menu_generator.validators.is_authenticated", + "menu_generator.validators.is_superuser", + ], }, - { - 'name': 'Teckids e.V.', - 'url': 'https://www.teckids.org/' - } - ] + ], }, - { - 'name': _('Support'), - 'url': '#', - 'submenu': [ - { - 'name': _('Get support'), - 'url': 'contact_form' - } - ] - } - ], - 'DATA_MANAGEMENT_MENU': [ ], - 'SCHOOL_MANAGEMENT_MENU': [ + "FOOTER_MENU_CORE": [ { - 'name': _('Edit school information'), - 'url': 'edit_school_information', + "name": _("BiscuIT Software"), + "url": "#", + "submenu": [ + {"name": _("Website"), "url": "https://biscuit.edugit.org/"}, + {"name": "Teckids e.V.", "url": "https://www.teckids.org/"}, + ], }, - { - 'name': _('Edit school term'), - 'url': 'edit_school_term', - } + ], + "DATA_MANAGEMENT_MENU": [], + "SCHOOL_MANAGEMENT_MENU": [ + {"name": _("Edit school information"), "url": "edit_school_information",}, + {"name": _("Edit school term"), "url": "edit_school_term",}, ], } diff --git a/biscuit/core/migrations/0001_initial.py b/biscuit/core/migrations/0001_initial.py index 1a88ae010c8497c6e432931ec15438feda3e0aa1..36a2f4cbe4c667ee3cf89efa29232b83293259b4 100644 --- a/biscuit/core/migrations/0001_initial.py +++ b/biscuit/core/migrations/0001_initial.py @@ -1,9 +1,10 @@ # Generated by Django 2.2.5 on 2019-09-03 18:30 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models from django.utils.translation import ugettext_lazy as _ -import django.db.models.deletion + import image_cropping.fields import phonenumber_field.modelfields diff --git a/biscuit/core/migrations/0002_school_term.py b/biscuit/core/migrations/0002_school_term.py index 026d7e3a518fe4a4d299ca01500605afbdced4ca..8b1923f5c7ee263eca740493051fb714b57de48e 100644 --- a/biscuit/core/migrations/0002_school_term.py +++ b/biscuit/core/migrations/0002_school_term.py @@ -1,7 +1,7 @@ # Generated by Django 2.2.5 on 2019-09-14 12:55 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models from django.utils.translation import ugettext_lazy as _ diff --git a/biscuit/core/migrations/0003_school_logo.py b/biscuit/core/migrations/0003_school_logo.py index c855202b5e08e69df3ee133ed5fdc5046acf80d1..09a18d44e827a32f25774867092de3ac35f3a014 100644 --- a/biscuit/core/migrations/0003_school_logo.py +++ b/biscuit/core/migrations/0003_school_logo.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.5 on 2019-09-14 13:03 from django.db import migrations + import image_cropping.fields diff --git a/biscuit/core/migrations/0006_create_superuser.py b/biscuit/core/migrations/0006_create_superuser.py new file mode 100644 index 0000000000000000000000000000000000000000..861dee94e738bccea983c7e94093d8b24923bb1e --- /dev/null +++ b/biscuit/core/migrations/0006_create_superuser.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.8 on 2019-12-09 21:04 + +from django.contrib.auth import get_user_model +from django.db import migrations + + +def create_superuser(apps, schema_editor): + User = get_user_model() + + if not User.objects.filter(is_superuser=True).exists(): + User.objects.create_superuser( + username='admin', + email='root@example.com', + password='admin' + ).save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_unlink_school'), + ] + + operations = [ + migrations.RunPython(create_superuser) + ] diff --git a/biscuit/core/migrations/0007_unlink_school_schoolterm.py b/biscuit/core/migrations/0007_unlink_school_schoolterm.py new file mode 100644 index 0000000000000000000000000000000000000000..4e26febd83153a971022c950f77929b91efa462b --- /dev/null +++ b/biscuit/core/migrations/0007_unlink_school_schoolterm.py @@ -0,0 +1,31 @@ +# Generated by Django 2.2.8 on 2019-12-11 23:27 + +from django.db import migrations, models + + +def mark_current_term(apps, schema_editor): + db_alias = schema_editor.connection.alias + + SchoolTerm = apps.get_model('core', 'SchoolTerm') # noqa + + if not SchoolTerm.objects.filter(current=True).exists(): + SchoolTerm.objects.using(db_alias).latest('date_start').update(current=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_create_superuser'), + ] + + operations = [ + migrations.RemoveField( + model_name='school', + name='current_term', + ), + migrations.AddField( + model_name='schoolterm', + name='current', + field=models.NullBooleanField(default=None, unique=True), + ), + ] diff --git a/biscuit/core/mixins.py b/biscuit/core/mixins.py index 9ec842a5ea99b793c688a63a9a2e8c1d5d9203b4..6db9ffe40229c36ffb51292925cfe62ad10f2408 100644 --- a/biscuit/core/mixins.py +++ b/biscuit/core/mixins.py @@ -46,11 +46,11 @@ class ExtensibleModel(object): if name.isidentifier(): prop_name = name else: - raise ValueError('%s is not a valid name.' % name) + raise ValueError("%s is not a valid name." % name) # Verify that property name does not clash with other names in the class if hasattr(cls, prop_name): - raise ValueError('%s already used.' % prop_name) + raise ValueError("%s already used." % prop_name) # Add function wrapped in property decorator if we got here setattr(cls, prop_name, obj) @@ -67,6 +67,7 @@ class ExtensibleModel(object): cls._safe_add(func, func.__name__) + class CRUDMixin(models.Model): class Meta: abstract = True @@ -78,8 +79,5 @@ class CRUDMixin(models.Model): content_type = ContentType.objects.get_for_model(self) return CRUDEvent.objects.filter( - object_id=self.pk, - content_type=content_type - ).select_related( - 'user' - ) + object_id=self.pk, content_type=content_type + ).select_related("user") diff --git a/biscuit/core/models.py b/biscuit/core/models.py index ffd0189e1a901c1fa7349b8cf164b9da8f00b49d..eab46395fcea5d7681ae5fa1a2c90ba99437c72e 100644 --- a/biscuit/core/models.py +++ b/biscuit/core/models.py @@ -12,16 +12,17 @@ from .mixins import ExtensibleModel class ThemeSettings(dbsettings.Group): - colour_primary = dbsettings.StringValue(default='#007bff') - colour_secondary = dbsettings.StringValue(default='#6c757d') - colour_success = dbsettings.StringValue(default='#28a745') - colour_info = dbsettings.StringValue(default='#17a2b8') - colour_warning = dbsettings.StringValue(default='#ffc107') - colour_danger = dbsettings.StringValue(default='#dc3545') - colour_light = dbsettings.StringValue(default='#f8f9fa') - colour_dark = dbsettings.StringValue(default='#343a40') + colour_primary = dbsettings.StringValue(default="#007bff") + colour_secondary = dbsettings.StringValue(default="#6c757d") + colour_success = dbsettings.StringValue(default="#28a745") + colour_info = dbsettings.StringValue(default="#17a2b8") + colour_warning = dbsettings.StringValue(default="#ffc107") + colour_danger = dbsettings.StringValue(default="#dc3545") + colour_light = dbsettings.StringValue(default="#f8f9fa") + colour_dark = dbsettings.StringValue(default="#343a40") -theme_settings = ThemeSettings('Global theme settings') + +theme_settings = ThemeSettings("Global theme settings") class School(models.Model): @@ -31,17 +32,22 @@ class School(models.Model): currently logged-in user. """ - name = models.CharField(verbose_name=_('Name'), max_length=30) - name_official = models.CharField(verbose_name=_('Official name'), max_length=200, help_text=_( - 'Official name of the school, e.g. as given by supervisory authority')) + name = models.CharField(verbose_name=_("Name"), max_length=30) + name_official = models.CharField( + verbose_name=_("Official name"), + max_length=200, + help_text=_("Official name of the school, e.g. as given by supervisory authority"), + ) - logo = ImageCropField(verbose_name=_('School logo'), blank=True, null=True) - logo_cropping = ImageRatioField('logo', '600x600', size_warning=True) + logo = ImageCropField(verbose_name=_("School logo"), blank=True, null=True) + logo_cropping = ImageRatioField("logo", "600x600", size_warning=True) - current_term = models.ForeignKey('SchoolTerm', models.CASCADE, related_name='+') + @property + def current_term(self): + return SchoolTerm.objects.get(current=True) class Meta: - ordering = ['name', 'name_official'] + ordering = ["name", "name_official"] class SchoolTerm(models.Model): @@ -49,13 +55,17 @@ class SchoolTerm(models.Model): be linked to. """ - caption = models.CharField(verbose_name=_('Visible caption of the term'), - max_length=30) + caption = models.CharField(verbose_name=_("Visible caption of the term"), max_length=30) + + date_start = models.DateField(verbose_name=_("Effective start date of term"), null=True) + date_end = models.DateField(verbose_name=_("Effective end date of term"), null=True) + + current = models.NullBooleanField(default=None, unique=True) - date_start = models.DateField(verbose_name=_( - 'Effective start date of term'), null=True) - date_end = models.DateField(verbose_name=_( - 'Effective end date of term'), null=True) + def save(self, *args, **kwargs): + if self.current is False: + self.current = None + super().save(*args, **kwargs) class Person(models.Model, ExtensibleModel): @@ -64,58 +74,55 @@ class Person(models.Model, ExtensibleModel): """ class Meta: - ordering = ['last_name', 'first_name'] + ordering = ["last_name", "first_name"] - SEX_CHOICES = [ - ('f', _('female')), - ('m', _('male')) - ] + SEX_CHOICES = [("f", _("female")), ("m", _("male"))] user = models.OneToOneField( - get_user_model(), on_delete=models.SET_NULL, blank=True, null=True, - related_name='person') - is_active = models.BooleanField( - verbose_name=_('Is person active?'), default=True) + get_user_model(), on_delete=models.SET_NULL, blank=True, null=True, related_name="person" + ) + is_active = models.BooleanField(verbose_name=_("Is person active?"), default=True) - first_name = models.CharField(verbose_name=_('First name'), max_length=30) - last_name = models.CharField(verbose_name=_('Last name'), max_length=30) - additional_name = models.CharField(verbose_name=_( - 'Additional name(s)'), max_length=30, blank=True) + first_name = models.CharField(verbose_name=_("First name"), max_length=30) + last_name = models.CharField(verbose_name=_("Last name"), max_length=30) + additional_name = models.CharField( + verbose_name=_("Additional name(s)"), max_length=30, blank=True + ) - short_name = models.CharField(verbose_name=_( - 'Short name'), max_length=5, blank=True, null=True, unique=True) + short_name = models.CharField( + verbose_name=_("Short name"), max_length=5, blank=True, null=True, unique=True + ) - street = models.CharField(verbose_name=_( - 'Street'), max_length=30, blank=True) - housenumber = models.CharField(verbose_name=_( - 'Street number'), max_length=10, blank=True) - postal_code = models.CharField(verbose_name=_( - 'Postal code'), max_length=5, blank=True) - place = models.CharField(verbose_name=_( - 'Place'), max_length=30, blank=True) + street = models.CharField(verbose_name=_("Street"), max_length=30, blank=True) + housenumber = models.CharField(verbose_name=_("Street number"), max_length=10, blank=True) + postal_code = models.CharField(verbose_name=_("Postal code"), max_length=5, blank=True) + place = models.CharField(verbose_name=_("Place"), max_length=30, blank=True) - phone_number = PhoneNumberField(verbose_name=_('Home phone'), blank=True) - mobile_number = PhoneNumberField( - verbose_name=_('Mobile phone'), blank=True) + phone_number = PhoneNumberField(verbose_name=_("Home phone"), blank=True) + mobile_number = PhoneNumberField(verbose_name=_("Mobile phone"), blank=True) - email = models.EmailField(verbose_name=_('E-mail address'), blank=True) + email = models.EmailField(verbose_name=_("E-mail address"), blank=True) - date_of_birth = models.DateField( - verbose_name=_('Date of birth'), blank=True, null=True) - sex = models.CharField(verbose_name=_( - 'Sex'), max_length=1, choices=SEX_CHOICES, blank=True) + date_of_birth = models.DateField(verbose_name=_("Date of birth"), blank=True, null=True) + sex = models.CharField(verbose_name=_("Sex"), max_length=1, choices=SEX_CHOICES, blank=True) - photo = ImageCropField(verbose_name=_('Photo'), blank=True, null=True) - photo_cropping = ImageRatioField('photo', '600x800', size_warning=True) + photo = ImageCropField(verbose_name=_("Photo"), blank=True, null=True) + photo_cropping = ImageRatioField("photo", "600x800", size_warning=True) - import_ref = models.CharField(verbose_name=_( - 'Reference ID of import source'), max_length=64, - blank=True, null=True, editable=False, unique=True) + import_ref = models.CharField( + verbose_name=_("Reference ID of import source"), + max_length=64, + blank=True, + null=True, + editable=False, + unique=True, + ) - guardians = models.ManyToManyField('self', verbose_name=_('Guardians / Parents'), - symmetrical=False, related_name='children') + guardians = models.ManyToManyField( + "self", verbose_name=_("Guardians / Parents"), symmetrical=False, related_name="children" + ) - primary_group = models.ForeignKey('Group', models.SET_NULL, null=True) + primary_group = models.ForeignKey("Group", models.SET_NULL, null=True) @property def primary_group_short_name(self) -> Optional[str]: @@ -134,13 +141,12 @@ class Person(models.Model, ExtensibleModel): if it can't find one. """ - group, created = Group.objects.get_or_create(short_name=value, - defaults={'name': value}) + group, created = Group.objects.get_or_create(short_name=value, defaults={"name": value}) self.primary_group = group @property def full_name(self) -> str: - return '%s, %s' % (self.last_name, self.first_name) + return "%s, %s" % (self.last_name, self.first_name) def __str__(self) -> str: return self.full_name @@ -152,18 +158,21 @@ class Group(models.Model, ExtensibleModel): """ class Meta: - ordering = ['short_name', 'name'] + ordering = ["short_name", "name"] - name = models.CharField(verbose_name=_( - 'Long name of group'), max_length=60, unique=True) - short_name = models.CharField(verbose_name=_( - 'Short name of group'), max_length=16, unique=True) + name = models.CharField(verbose_name=_("Long name of group"), max_length=60, unique=True) + short_name = models.CharField(verbose_name=_("Short name of group"), max_length=16, unique=True) - members = models.ManyToManyField('Person', related_name='member_of') - owners = models.ManyToManyField('Person', related_name='owner_of') + members = models.ManyToManyField("Person", related_name="member_of") + owners = models.ManyToManyField("Person", related_name="owner_of") - parent_groups = models.ManyToManyField('self', related_name='child_groups', - symmetrical=False, verbose_name=_('Parent groups'), blank=True) + parent_groups = models.ManyToManyField( + "self", + related_name="child_groups", + symmetrical=False, + verbose_name=_("Parent groups"), + blank=True, + ) def __str__(self) -> str: - return '%s (%s)' % (self.name, self.short_name) + return "%s (%s)" % (self.name, self.short_name) diff --git a/biscuit/core/settings.py b/biscuit/core/settings.py index 61c92d3ea50a109873dbdb3e13f8e1bbd4100f9c..65ab18f801923660f2c34c2facdd3ec7dc067aef 100644 --- a/biscuit/core/settings.py +++ b/biscuit/core/settings.py @@ -1,6 +1,6 @@ -from glob import glob import os import sys +from glob import glob from django.utils.translation import ugettext_lazy as _ @@ -9,153 +9,153 @@ from easy_thumbnails.conf import Settings as thumbnail_settings from .util.core_helpers import get_app_packages -ENVVAR_PREFIX_FOR_DYNACONF = 'BISCUIT' -DIRS_FOR_DYNACONF = ['/etc/biscuit'] +ENVVAR_PREFIX_FOR_DYNACONF = "BISCUIT" +DIRS_FOR_DYNACONF = ["/etc/biscuit"] SETTINGS_FILE_FOR_DYNACONF = [] for directory in DIRS_FOR_DYNACONF: - SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, '*.ini')) - SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, '*.yaml')) - SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, '*.toml')) + SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.ini")) + SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.yaml")) + SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.toml")) _settings = LazySettings( ENVVAR_PREFIX_FOR_DYNACONF=ENVVAR_PREFIX_FOR_DYNACONF, - SETTINGS_FILE_FOR_DYNACONF=SETTINGS_FILE_FOR_DYNACONF + SETTINGS_FILE_FOR_DYNACONF=SETTINGS_FILE_FOR_DYNACONF, ) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = _settings.get('secret_key', 'DoNotUseInProduction') +SECRET_KEY = _settings.get("secret_key", "DoNotUseInProduction") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = _settings.get('maintenance.debug', False) -INTERNAL_IPS = _settings.get('maintenance.internal_ips', []) +DEBUG = _settings.get("maintenance.debug", False) +INTERNAL_IPS = _settings.get("maintenance.internal_ips", []) DEBUG_TOOLBAR_CONFIG = { - 'RENDER_PANELS': True, - 'SHOW_COLLAPSED': True + "RENDER_PANELS": True, + "SHOW_COLLAPSED": True, + "JQUERY_URL": "", + "SHOW_TOOLBAR_CALLBACK": "biscuit.core.util.core_helpers.dt_show_toolbar", } -ALLOWED_HOSTS = _settings.get('http.allowed_hosts', []) +ALLOWED_HOSTS = _settings.get("http.allowed_hosts", []) # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django_global_request', - 'settings_context_processor', - 'sass_processor', - 'easyaudit', - 'dbbackup', - 'dbsettings', - 'django_cron', - 'bootstrap4', - 'fa', - 'django_any_js', - 'django_yarnpkg', - 'django_tables2', - 'easy_thumbnails', - 'image_cropping', - 'maintenance_mode', - 'menu_generator', - 'phonenumber_field', - 'debug_toolbar', - 'contact_form', - 'django_select2', - 'hattori', - 'django_otp.plugins.otp_totp', - 'django_otp.plugins.otp_static', - 'django_otp', - 'otp_yubikey', - 'biscuit.core', - 'impersonate', - 'two_factor' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_global_request", + "settings_context_processor", + "sass_processor", + "easyaudit", + "dbbackup", + "dbsettings", + "django_cron", + "bootstrap4", + "django_any_js", + "django_yarnpkg", + "django_tables2", + "easy_thumbnails", + "image_cropping", + "maintenance_mode", + "menu_generator", + "phonenumber_field", + "debug_toolbar", + "django_select2", + "hattori", + "django_otp.plugins.otp_totp", + "django_otp.plugins.otp_static", + "django_otp", + "otp_yubikey", + "biscuit.core", + "impersonate", + "two_factor", ] INSTALLED_APPS += get_app_packages() STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'django_yarnpkg.finders.NodeModulesFinder', - 'sass_processor.finders.CssFinder' + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "django_yarnpkg.finders.NodeModulesFinder", + "sass_processor.finders.CssFinder", ] MIDDLEWARE = [ # 'django.middleware.cache.UpdateCacheMiddleware', - 'debug_toolbar.middleware.DebugToolbarMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django_global_request.middleware.GlobalRequestMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django_otp.middleware.OTPMiddleware', - 'impersonate.middleware.ImpersonateMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'easyaudit.middleware.easyaudit.EasyAuditMiddleware', - 'maintenance_mode.middleware.MaintenanceModeMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django_global_request.middleware.GlobalRequestMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", + "django_otp.middleware.OTPMiddleware", + "impersonate.middleware.ImpersonateMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "easyaudit.middleware.easyaudit.EasyAuditMiddleware", + "maintenance_mode.middleware.MaintenanceModeMiddleware", # 'django.middleware.cache.FetchFromCacheMiddleware' ] -ROOT_URLCONF = 'biscuit.core.urls' +ROOT_URLCONF = "biscuit.core.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'maintenance_mode.context_processors.maintenance_mode', - 'settings_context_processor.context_processors.settings' + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "maintenance_mode.context_processors.maintenance_mode", + "settings_context_processor.context_processors.settings", ], }, }, ] THUMBNAIL_PROCESSORS = ( - 'image_cropping.thumbnail_processors.crop_corners', + "image_cropping.thumbnail_processors.crop_corners", ) + thumbnail_settings.THUMBNAIL_PROCESSORS # Already included by base template / Bootstrap IMAGE_CROPPING_JQUERY_URL = None -WSGI_APPLICATION = 'biscuit.core.wsgi.application' +WSGI_APPLICATION = "biscuit.core.wsgi.application" # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': _settings.get('database.name', 'biscuit'), - 'USER': _settings.get('database.username', 'biscuit'), - 'PASSWORD': _settings.get('database.password', None), - 'HOST': _settings.get('database.host', '127.0.0.1'), - 'PORT': _settings.get('database.port', '5432'), - 'ATOMIC_REQUESTS': True + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": _settings.get("database.name", "biscuit"), + "USER": _settings.get("database.username", "biscuit"), + "PASSWORD": _settings.get("database.password", None), + "HOST": _settings.get("database.host", "127.0.0.1"), + "PORT": _settings.get("database.port", "5432"), + "ATOMIC_REQUESTS": True, } } -if _settings.get('caching.memcached.enabled', True): +if _settings.get("caching.memcached.enabled", True): CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': _settings.get('caching.memcached.address', '127.0.0.1:11211') + "default": { + "BACKEND": "django.core.cache.backends.memcached.MemcachedCache", + "LOCATION": _settings.get("caching.memcached.address", "127.0.0.1:11211"), } } @@ -163,65 +163,57 @@ if _settings.get('caching.memcached.enabled', True): # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",}, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, ] # Authentication backends are dynamically populated AUTHENTICATION_BACKENDS = [] -if _settings.get('ldap.uri', None): +if _settings.get("ldap.uri", None): # LDAP dependencies are not necessarily installed, so import them here import ldap # noqa from django_auth_ldap.config import LDAPSearch, GroupOfNamesType # noqa # Enable Django's integration to LDAP - AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend') + AUTHENTICATION_BACKENDS.append("django_auth_ldap.backend.LDAPBackend") - AUTH_LDAP_SERVER_URI = _settings.get('ldap.uri') + AUTH_LDAP_SERVER_URI = _settings.get("ldap.uri") # Optional: non-anonymous bind - if _settings.get('ldap.bind.dn', None): - AUTH_LDAP_BIND_DN = _settings.get('ldap.bind.dn') - AUTH_LDAP_BIND_PASSWORD = _settings.get('ldap.bind.password') + if _settings.get("ldap.bind.dn", None): + AUTH_LDAP_BIND_DN = _settings.get("ldap.bind.dn") + AUTH_LDAP_BIND_PASSWORD = _settings.get("ldap.bind.password") # Search attributes to find users by username AUTH_LDAP_USER_SEARCH = LDAPSearch( - _settings.get('ldap.users.base'), + _settings.get("ldap.users.base"), ldap.SCOPE_SUBTREE, - _settings.get('ldap.users.filter', '(uid=%(user)s)') + _settings.get("ldap.users.filter", "(uid=%(user)s)"), ) # Mapping of LDAP attributes to Django model fields AUTH_LDAP_USER_ATTR_MAP = { - 'first_name': _settings.get('ldap.map.first_name', 'givenName'), - 'last_name': _settings.get('ldap.map.first_name', 'sn'), - 'email': _settings.get('ldap.map.email', 'mail'), + "first_name": _settings.get("ldap.map.first_name", "givenName"), + "last_name": _settings.get("ldap.map.first_name", "sn"), + "email": _settings.get("ldap.map.email", "mail"), } # Add ModelBckend last so all other backends get a chance # to verify passwords first -AUTHENTICATION_BACKENDS.append('django.contrib.auth.backends.ModelBackend') +AUTHENTICATION_BACKENDS.append("django.contrib.auth.backends.ModelBackend") # Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ LANGUAGES = [ - ('de', _('German')), - ('en', _('English')), + ("de", _("German")), + ("en", _("English")), ] -LANGUAGE_CODE = _settings.get('l10n.lang', 'en') -TIME_ZONE = _settings.get('l10n.tz', 'UTC') +LANGUAGE_CODE = _settings.get("l10n.lang", "en") +TIME_ZONE = _settings.get("l10n.tz", "UTC") USE_I18N = True USE_L10N = True USE_TZ = True @@ -230,121 +222,107 @@ USE_TZ = True # https://docs.djangoproject.com/en/2.1/howto/static-files/ -STATIC_URL = _settings.get('static.url', '/static/') -MEDIA_URL = _settings.get('media.url', '/media/') - -LOGIN_REDIRECT_URL = 'index' -LOGOUT_REDIRECT_URL = 'index' +STATIC_URL = _settings.get("static.url", "/static/") +MEDIA_URL = _settings.get("media.url", "/media/") -STATIC_ROOT = _settings.get('static.root', os.path.join(BASE_DIR, 'static')) -MEDIA_ROOT = _settings.get('media.root', os.path.join(BASE_DIR, 'media')) -NODE_MODULES_ROOT = _settings.get('node_modules.root', os.path.join(BASE_DIR, 'node_modules')) +LOGIN_REDIRECT_URL = "index" +LOGOUT_REDIRECT_URL = "index" -YARN_INSTALLED_APPS = [ - 'bootstrap', - 'font-awesome', - 'jquery', - 'popper.js', - 'datatables', - 'select2' -] +STATIC_ROOT = _settings.get("static.root", os.path.join(BASE_DIR, "static")) +MEDIA_ROOT = _settings.get("media.root", os.path.join(BASE_DIR, "media")) +NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "node_modules")) -JS_URL = _settings.get('js_assets.url', STATIC_URL) -JS_ROOT = _settings.get('js_assets.root', NODE_MODULES_ROOT+'/node_modules') +YARN_INSTALLED_APPS = ["bootstrap", "@mdi/font", "jquery", "popper.js", "datatables", "select2"] -FONT_AWESOME = {'url': JS_URL+'/font-awesome/css/font-awesome.min.css'} +JS_URL = _settings.get("js_assets.url", STATIC_URL) +JS_ROOT = _settings.get("js_assets.root", NODE_MODULES_ROOT + "/node_modules") BOOTSTRAP4 = { - 'css_url': JS_URL+'/bootstrap/dist//css/bootstrap.min.css', - 'javascript_url': JS_URL+'/bootstrap/dist/js/bootstrap.min.js', - 'jquery_url': JS_URL+'/jquery/dist/jquery.min.js', - 'popper_url': JS_URL+'/popper.js/dist/umd/popper.min.js', - 'include_jquery': True, - 'include_popper': True, - 'javascript_in_head': True + "css_url": JS_URL + "/bootstrap/dist//css/bootstrap.min.css", + "javascript_url": JS_URL + "/bootstrap/dist/js/bootstrap.min.js", + "jquery_url": JS_URL + "/jquery/dist/jquery.min.js", + "popper_url": JS_URL + "/popper.js/dist/umd/popper.min.js", + "include_jquery": True, + "include_popper": True, + "javascript_in_head": True, } -SELECT2_CSS = JS_URL+'/select2/dist/css/select2.min.css' -SELECT2_JS = JS_URL+'/select2/dist/js/select2.min.js' -SELECT2_I18N_PATH = JS_URL+'/select2/dist/js/i18n' +SELECT2_CSS = JS_URL + "/select2/dist/css/select2.min.css" +SELECT2_JS = JS_URL + "/select2/dist/js/select2.min.js" +SELECT2_I18N_PATH = JS_URL + "/select2/dist/js/i18n" ANY_JS = { - 'DataTables': { - 'js_url': JS_URL+'/datatables/media/js/jquery.dataTables.min.js' + "DataTables": {"js_url": JS_URL + "/datatables/media/js/jquery.dataTables.min.js"}, + "DataTables-Bootstrap4": { + "css_url": JS_URL + "/datatables/media/css/dataTables.bootstrap4.min.css", + "js_url": JS_URL + "/datatables/media/js/dataTables.bootstrap4.min.js", }, - 'DataTables-Bootstrap4': { - 'css_url': JS_URL+'/datatables/media/css/dataTables.bootstrap4.min.css', - 'js_url': JS_URL+'/datatables/media/js/dataTables.bootstrap4.min.js' - } + "material-design-icons": {"css_url": JS_URL + "@mdi/font/css/materialdesignicons.css"}, } SASS_PROCESSOR_AUTO_INCLUDE = False SASS_PROCESSOR_CUSTOM_FUNCTIONS = { - 'get-colour': 'biscuit.core.util.sass_helpers.get_colour', - 'get-theme-setting': 'biscuit.core.util.sass_helpers.get_theme_setting', + "get-colour": "biscuit.core.util.sass_helpers.get_colour", + "get-theme-setting": "biscuit.core.util.sass_helpers.get_theme_setting", } -SASS_PROCESSOR_INCLUDE_DIRS = [ - _settings.get('bootstrap.sass_path', JS_ROOT+'/bootstrap/scss/') -] - -ADMINS = _settings.get('contact.admins', []) -SERVER_EMAIL = _settings.get('contact.from', 'root@localhost') -DEFAULT_FROM_EMAIL = _settings.get('contact.from', 'root@localhost') -MANAGERS = _settings.get('contact.admins', []) - -if _settings.get('mail.server.host', None): - EMAIL_HOST = _settings.get('mail.server.host') - EMAIL_USE_TLS = _settings.get('mail.server.tls', False) - EMAIL_USE_SSL = _settings.get('mail.server.ssl', False) - if _settings.get('mail.server.port', None): - EMAIL_PORT = _settings.get('mail.server.port') - if _settings.get('mail.server.user', None): - EMAIL_HOST_USER = _settings.get('mail.server.user') - EMAIL_HOST_PASSWORD = _settings.get('mail.server.password') - -TEMPLATE_VISIBLE_SETTINGS = ['ADMINS', 'DEBUG'] - -MAINTENANCE_MODE = _settings.get('maintenance.enabled', None) +SASS_PROCESSOR_INCLUDE_DIRS = [_settings.get("bootstrap.sass_path", JS_ROOT + "/bootstrap/scss/")] + +ADMINS = _settings.get("contact.admins", []) +SERVER_EMAIL = _settings.get("contact.from", "root@localhost") +DEFAULT_FROM_EMAIL = _settings.get("contact.from", "root@localhost") +MANAGERS = _settings.get("contact.admins", []) + +if _settings.get("mail.server.host", None): + EMAIL_HOST = _settings.get("mail.server.host") + EMAIL_USE_TLS = _settings.get("mail.server.tls", False) + EMAIL_USE_SSL = _settings.get("mail.server.ssl", False) + if _settings.get("mail.server.port", None): + EMAIL_PORT = _settings.get("mail.server.port") + if _settings.get("mail.server.user", None): + EMAIL_HOST_USER = _settings.get("mail.server.user") + EMAIL_HOST_PASSWORD = _settings.get("mail.server.password") + +TEMPLATE_VISIBLE_SETTINGS = ["ADMINS", "DEBUG"] + +MAINTENANCE_MODE = _settings.get("maintenance.enabled", None) MAINTENANCE_MODE_IGNORE_IP_ADDRESSES = _settings.get( - 'maintenance.ignore_ips', _settings.get('maintenance.internal_ips', [])) -MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = 'ipware.ip.get_ip' + "maintenance.ignore_ips", _settings.get("maintenance.internal_ips", []) +) +MAINTENANCE_MODE_GET_CLIENT_IP_ADDRESS = "ipware.ip.get_ip" MAINTENANCE_MODE_IGNORE_SUPERUSER = True -MAINTENANCE_MODE_STATE_FILE_PATH = _settings.get('maintenance.statefile', 'maintenance_mode_state.txt') +MAINTENANCE_MODE_STATE_FILE_PATH = _settings.get( + "maintenance.statefile", "maintenance_mode_state.txt" +) -IMPERSONATE = { - 'USE_HTTP_REFERER': True, - 'REQUIRE_SUPERUSER': True, - 'ALLOW_SUPERUSER': True -} +IMPERSONATE = {"USE_HTTP_REFERER": True, "REQUIRE_SUPERUSER": True, "ALLOW_SUPERUSER": True} DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap4.html" -DBBACKUP_STORAGE = _settings.get('backup.storage', 'django.core.files.storage.FileSystemStorage') -DBBACKUP_STORAGE_OPTIONS = { - 'location': _settings.get('backup.location', '/var/backups/biscuit') -} -DBBACKUP_CLEANUP_KEEP = _settings.get('backup.keep.database', 10) -DBBACKUP_CLEANUP_KEEP_MEDIA = _settings.get('backup.keep.media', 10) -DBBACKUP_CRON_TIMES = _settings.get('backup.times', None) or ['03:57'] +DBBACKUP_STORAGE = _settings.get("backup.storage", "django.core.files.storage.FileSystemStorage") +DBBACKUP_STORAGE_OPTIONS = {"location": _settings.get("backup.location", "/var/backups/biscuit")} +DBBACKUP_CLEANUP_KEEP = _settings.get("backup.keep.database", 10) +DBBACKUP_CLEANUP_KEEP_MEDIA = _settings.get("backup.keep.media", 10) +DBBACKUP_CRON_TIMES = _settings.get("backup.times", None) or ["03:57"] -CRON_CLASSES = [ - 'biscuit.core.cronjobs.Backup' -] +CRON_CLASSES = ["biscuit.core.cronjobs.Backup"] -ANONYMIZE_ENABLED = _settings.get('maintenance.anonymisable', True) +ANONYMIZE_ENABLED = _settings.get("maintenance.anonymisable", True) -LOGIN_URL = 'two_factor:login' +LOGIN_URL = "two_factor:login" -if _settings.get('2fa.call.enabled', False): - TWO_FACTOR_CALL_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio' +if _settings.get("2fa.call.enabled", False): + TWO_FACTOR_CALL_GATEWAY = "two_factor.gateways.twilio.gateway.Twilio" -if _settings.get('2fa.sms.enabled', False): - TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio' +if _settings.get("2fa.sms.enabled", False): + TWO_FACTOR_SMS_GATEWAY = "two_factor.gateways.twilio.gateway.Twilio" -if _settings.get('2fa.twilio.sid', None): - MIDDLEWARE.insert(MIDDLEWARE.index('django_otp.middleware.OTPMiddleware')+1, 'two_factor.middleware.threadlocals.ThreadLocals') - TWILIO_SID = _settings.get('2fa.twilio.sid') - TWILIO_TOKEN = _settings.get('2fa.twilio.token') - TWILIO_CALLER_ID = _settings.get('2fa.twilio.callerid') +if _settings.get("2fa.twilio.sid", None): + MIDDLEWARE.insert( + MIDDLEWARE.index("django_otp.middleware.OTPMiddleware") + 1, + "two_factor.middleware.threadlocals.ThreadLocals", + ) + TWILIO_SID = _settings.get("2fa.twilio.sid") + TWILIO_TOKEN = _settings.get("2fa.twilio.token") + TWILIO_CALLER_ID = _settings.get("2fa.twilio.callerid") _settings.populate_obj(sys.modules[__name__]) diff --git a/biscuit/core/signals.py b/biscuit/core/signals.py index e531c5b9ed0158c3986144ab68e6c0eaeea9eda9..bcd55fe623bc3f3952a0853e8f8688630978194a 100644 --- a/biscuit/core/signals.py +++ b/biscuit/core/signals.py @@ -1,11 +1,11 @@ -from glob import glob import os +from glob import glob from django.conf import settings def clean_scss(*args, **kwargs) -> None: - for source_map in glob(os.path.join(settings.STATIC_ROOT, '*.css.map')): + for source_map in glob(os.path.join(settings.STATIC_ROOT, "*.css.map")): try: os.unlink(source_map) except OSError: diff --git a/biscuit/core/tables.py b/biscuit/core/tables.py index 39edd10b77bea1d75d1ad532ba387383bc093513..b8190272916e32cc67007d31409963d42bd02b8d 100644 --- a/biscuit/core/tables.py +++ b/biscuit/core/tables.py @@ -4,15 +4,15 @@ from django_tables2.utils import A class PersonsTable(tables.Table): class Meta: - attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'} + attrs = {"class": "table table-striped table-bordered table-hover table-responsive-xl"} - first_name = tables.LinkColumn('person_by_id', args=[A('id')]) - last_name = tables.LinkColumn('person_by_id', args=[A('id')]) + first_name = tables.LinkColumn("person_by_id", args=[A("id")]) + last_name = tables.LinkColumn("person_by_id", args=[A("id")]) class GroupsTable(tables.Table): class Meta: - attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'} + attrs = {"class": "table table-striped table-bordered table-hover table-responsive-xl"} - name = tables.LinkColumn('group_by_id', args=[A('id')]) - short_name = tables.LinkColumn('group_by_id', args=[A('id')]) + name = tables.LinkColumn("group_by_id", args=[A("id")]) + short_name = tables.LinkColumn("group_by_id", args=[A("id")]) diff --git a/biscuit/core/templates/contact_form/contact_form.html b/biscuit/core/templates/contact_form/contact_form.html deleted file mode 100644 index 00a6caad94102e477bea534a638846baffcb2fe9..0000000000000000000000000000000000000000 --- a/biscuit/core/templates/contact_form/contact_form.html +++ /dev/null @@ -1,17 +0,0 @@ -{# -*- engine:django -*- #} -{% extends "core/base.html" %} -{% load bootstrap4 i18n %} - -{% block page_title %}BiscuIT SIS support{% endblock %} - -{% block content %} - <h1>{% blocktrans %}Get support{% endblocktrans %}</h1> - - <form method="post"> - {% csrf_token %} - {% bootstrap_form form %} - <button type="submit" class="btn btn-dark"> - {% blocktrans %}Send{% endblocktrans %} - </button> - </form> -{% endblock %} diff --git a/biscuit/core/templates/contact_form/contact_form.txt b/biscuit/core/templates/contact_form/contact_form.txt deleted file mode 100644 index 9a8f66d94083d6ab9dd9abb4a1ade3ffc0ee150c..0000000000000000000000000000000000000000 --- a/biscuit/core/templates/contact_form/contact_form.txt +++ /dev/null @@ -1,3 +0,0 @@ -From: {{ name }} <{{ email }}> - -{{ body }} diff --git a/biscuit/core/templates/contact_form/contact_form_sent.html b/biscuit/core/templates/contact_form/contact_form_sent.html deleted file mode 100644 index 86d69b579fb81370e4842f88edf2f7263986de45..0000000000000000000000000000000000000000 --- a/biscuit/core/templates/contact_form/contact_form_sent.html +++ /dev/null @@ -1,13 +0,0 @@ -{# -*- engine:django -*- #} -{% extends "core/base.html" %} -{% load bootstrap4 i18n %} - -{% block page_title %}BiscuIT School Information System (SIS){% endblock %} - -{% block content %} - <div class="alert alert-success" role="alert"> - {% blocktrans %} - The message was successfully submitted. - {% endblocktrans %} - </div> -{% endblock %} diff --git a/biscuit/core/templates/contact_form/contact_form_subject.txt b/biscuit/core/templates/contact_form/contact_form_subject.txt deleted file mode 100644 index 5fcb23b2da374cb55dc32f3795caf0b3579334ec..0000000000000000000000000000000000000000 --- a/biscuit/core/templates/contact_form/contact_form_subject.txt +++ /dev/null @@ -1 +0,0 @@ -[BiscuIT Support] diff --git a/biscuit/core/templates/core/base.html b/biscuit/core/templates/core/base.html index 9b440b9ab6ac3ab7a41682f7819861a75ba74348..c87fe544e7a25d15dada319a14cffaca5b817936 100644 --- a/biscuit/core/templates/core/base.html +++ b/biscuit/core/templates/core/base.html @@ -2,11 +2,11 @@ {% extends "bootstrap4/bootstrap4.html" %} -{% load bootstrap4 i18n menu_generator static font_awesome any_js sass_tags %} +{% load bootstrap4 i18n menu_generator static any_js sass_tags %} {% block bootstrap4_extra_head %} - {% fa_css %} {% include_css "DataTables-Bootstrap4" %} + {% include_css "material-design-icons"%} {% include 'core/icons.html' %} <link rel="stylesheet" href="{% sass_src 'bootstrap_modified.scss' %}" /> <link rel="stylesheet" href="{% static 'css/style.css' %}" /> @@ -24,10 +24,10 @@ <header> <nav class="navbar navbar-dark bg-dark navbar-expand-md fixed-top"> <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-main"> - {% fa 'fa-align-justify' %} + <span class="mdi mdi-menu"></span> </button> <a class="navbar-brand" href="{% url "index" %}"> - {% fa 'fa-briefcase' %} BiscuIT SIS + <span class="mdi mdi-briefcase"></span> BiscuIT SIS </a> <div class="collapse navbar-collapse" id="navbar-main"> diff --git a/biscuit/core/templates/core/crud_events_ul.html b/biscuit/core/templates/core/crud_events_ul.html index dbe27d8fc25d94e2e1899600be8e3d06dbfd4ae8..8eef4639e7fc56f3ac8a563f2dd7488f8c922479 100644 --- a/biscuit/core/templates/core/crud_events_ul.html +++ b/biscuit/core/templates/core/crud_events_ul.html @@ -1,5 +1,3 @@ -{% load font_awesome %} - <ul class="{{ class_ul }}"> {% for event in obj.crud_events %} <li class="{{ class_li }}"> @@ -9,15 +7,15 @@ </div> <span class="badge badge-light text-dark"> {% if event.event_type == event.CREATE %} - {% fa 'plus' %} + <span class="mdi mdi-plus"></span> {% elif event.event_type == event.UPDATE %} - {% fa 'pencil' %} + <span class="mdi mdi-pencil"></span> {% elif event.event_type == event.DELETE %} - {% fa 'trash' %} + <span class="mdi mdi-delete"></span> {% elif event.event_type == event.M2M_CHANGE %} - {% fa 'pencil' %} + <span class="mdi mdi-pencil"></span> {% elif event.event_type == event.M2M_CHANGE_REV %} - {% fa 'pencil' %} + <span class="mdi mdi-pencil"></span> {% endif %} </span> </li> diff --git a/biscuit/core/templates/core/edit_group.html b/biscuit/core/templates/core/edit_group.html index 0eb63393e8c7326012cb6fe6209f4c7e33218126..33739a47f4fcfd94002c862de66a1a553cc1576b 100644 --- a/biscuit/core/templates/core/edit_group.html +++ b/biscuit/core/templates/core/edit_group.html @@ -23,7 +23,7 @@ {% csrf_token %} {% bootstrap_form edit_group_form %} <button type="submit" class="btn btn-dark"> - {% blocktrans %}Edit{% endblocktrans %} + <span class="mdi mdi-pencil"></span> </button> </form> diff --git a/biscuit/core/templates/core/edit_person.html b/biscuit/core/templates/core/edit_person.html index c1b4f8e22115bca0f821c7dcc04b67c8e88b3bf6..e698f01c6cf4c3154ae3c9cd4002d9be28be7b5f 100644 --- a/biscuit/core/templates/core/edit_person.html +++ b/biscuit/core/templates/core/edit_person.html @@ -25,7 +25,7 @@ {% csrf_token %} {% bootstrap_form edit_person_form %} <button type="submit" class="btn btn-dark"> - {% blocktrans %}Edit{% endblocktrans %} + <span class="mdi mdi-pencil"></span> </button> </form> diff --git a/biscuit/core/templates/core/edit_school.html b/biscuit/core/templates/core/edit_school.html index f26ffce4c61c9ccaa9df5205098a74d5ea78e227..8e1b03db0aeef2c080e37387752fc931d84c5b87 100644 --- a/biscuit/core/templates/core/edit_school.html +++ b/biscuit/core/templates/core/edit_school.html @@ -19,7 +19,7 @@ {% csrf_token %} {% bootstrap_form edit_school_form %} <button type="submit" class="btn btn-dark"> - {% blocktrans %}Edit{% endblocktrans %} + <span class="mdi mdi-pencil"></span> </button> </form> diff --git a/biscuit/core/templates/core/edit_schoolterm.html b/biscuit/core/templates/core/edit_schoolterm.html index 1ba2931b24a80b13eff22b6bd9900da00f312132..0a5f083b22c227d95348e89c84d8f589fb4db988 100644 --- a/biscuit/core/templates/core/edit_schoolterm.html +++ b/biscuit/core/templates/core/edit_schoolterm.html @@ -19,7 +19,7 @@ {% csrf_token %} {% bootstrap_form edit_term_form %} <button type="submit" class="btn btn-dark"> - {% blocktrans %}Edit{% endblocktrans %} + <span class="mdi mdi-pencil"></span> </button> </form> diff --git a/biscuit/core/templates/core/footer-menu.html b/biscuit/core/templates/core/footer-menu.html index 7f03b85514fab560ee776f9edc02fa2222bec2b1..6866c019fc2c410993d7df279a434a57567389ea 100644 --- a/biscuit/core/templates/core/footer-menu.html +++ b/biscuit/core/templates/core/footer-menu.html @@ -1,6 +1,6 @@ {# -*- engine:django -*- #} -{% load menu_generator font_awesome i18n %} +{% load menu_generator i18n %} {% get_menu "FOOTER_MENU_CORE" as footer_menu %} {% regroup footer_menu by submenu|length_is:"0" as footer_menus %} @@ -12,7 +12,7 @@ <h5>{{ item.name }}</h5> <ul class="list-unstyled quick-links"> {% for menu in item.submenu %} - <li><a class="font-weight-bold text-dark" href="{{ menu.url }}">{% fa 'fa-angle-double-right' %} {{ menu.name }}</a></li> + <li><a class="font-weight-bold text-dark" href="{{ menu.url }}"><span class="mdi mdi-chevron-double-right"></span> {{ menu.name }}</a></li> {% endfor %} </ul> </div> @@ -26,7 +26,7 @@ <h5>{% blocktrans %}Assorted{% endblocktrans %}</h5> <ul class="list-unstyled quick-links"> {% for item in menu.list %} - <li><a class="font-weight-bold text-dark" href="{{ item.url }}">{% fa 'fa-angle-double-right' %} {{ item.name }}</a></li> + <li><a class="font-weight-bold text-dark" href="{{ item.url }}"><span class="mdi mdi-chevron-double-right"></span> {{ item.name }}</a></li> {% endfor %} </ul> </div> diff --git a/biscuit/core/templates/core/group_full.html b/biscuit/core/templates/core/group_full.html index cb3b8e83fd232312ccef2033ef23f057f15136ed..9cb841e18b5f9d9e68e238b8f246202e4ba97980 100644 --- a/biscuit/core/templates/core/group_full.html +++ b/biscuit/core/templates/core/group_full.html @@ -2,7 +2,7 @@ {% extends "core/base.html" %} -{% load bootstrap4 font_awesome i18n static %} +{% load bootstrap4 i18n static %} {% load render_table from django_tables2 %} {% block bootstrap4_title %}{% blocktrans %}Group{% endblocktrans %} - {{ block.super }}{% endblock %} @@ -13,13 +13,13 @@ <h2>{{ group.name }} <small>{{ group.short_name }}</small></h2> <p> <a href="{% url 'edit_group_by_id' group.id %}"> - {% blocktrans %}Edit group{% endblocktrans %} + <span class="mdi mdi-pencil"></span> </a> </p> <h3>{% blocktrans %}Details{% endblocktrans %}</h3> <table class="table table-responsive-xl table-border table-striped"> <tr> - <td>{% fa 'users' %}</td> + <td><span class="mdi mdi-account-group"></span></td> <td>{{ group.name }}</td> <td>{{ group.short_name }}</td> </tr> diff --git a/biscuit/core/templates/core/groups.html b/biscuit/core/templates/core/groups.html index 72b7ead8d5b3286756ca65e6bb5009d80037f846..6534a8e384dbf48b4820eac1590ebdbcf923b06c 100644 --- a/biscuit/core/templates/core/groups.html +++ b/biscuit/core/templates/core/groups.html @@ -2,7 +2,7 @@ {% extends "core/base.html" %} -{% load bootstrap4 font_awesome i18n %} +{% load bootstrap4 i18n %} {% load render_table from django_tables2 %} {% block bootstrap4_title %}{% blocktrans %}Groups{% endblocktrans %} - {{ block.super }}{% endblock %} @@ -12,7 +12,7 @@ {% block content %} <div class="btn-group" role="group" aria-lable="Group actions"> <a href="{% url 'create_group' %}" class="btn btn-dark"> - {% fa 'plus' %} + <span class="mdi mdi-plus"></span> </a> </div> diff --git a/biscuit/core/templates/core/person_full.html b/biscuit/core/templates/core/person_full.html index 684a72f40351f4551d154a32db8b5b5ec3656a21..b412765539e921c1ed18d7a7d55c0acad1812f8f 100644 --- a/biscuit/core/templates/core/person_full.html +++ b/biscuit/core/templates/core/person_full.html @@ -2,7 +2,7 @@ {% extends "core/base.html" %} -{% load bootstrap4 font_awesome i18n static cropping %} +{% load bootstrap4 i18n static cropping %} {% load render_table from django_tables2 %} {% block bootstrap4_title %}{% blocktrans %}Person{% endblocktrans %} - {{ block.super }}{% endblock %} @@ -13,7 +13,7 @@ <h2>{{ person.first_name }} {{ person.last_name }}</h2> <p> <a href="{% url 'edit_person_by_id' person.id %}"> - {% blocktrans %}Edit person{% endblocktrans %} + <span class="mdi mdi-pencil"></span> </a> </p> <h3>{% blocktrans %}Contact details{% endblocktrans %}</h3> @@ -26,36 +26,36 @@ <img class="person-img" src="{% static 'img/fallback.png' %}" alt="{{ person.first_name }} {{ person.last_name }}" /> {% endif %} </td> - <td>{% fa 'user' %}</td> + <td><span class="mdi mdi-account"></span></td> <td>{{ person.first_name }}</td> <td>{{ person.additional_name }}</td> <td>{{ person.last_name }}</td> </tr> <tr> - <td>{% fa 'venus-mars' %}</td> + <td><span class="mdi mdi-gender-male-female"></td> <td colspan="3">{{ person.get_sex_display }}</td> </tr> <tr> - <td>{% fa 'fa-home' %}</td> + <td><span class="mdi mdi-home"></span></td> <td colspan="2">{{ person.street }} {{ person.housenumber }}</td> <td colspan="2">{{ person.postal_code }} {{ person.place }}</td> </tr> <tr> - <td>{% fa 'phone-square' %}</td> + <td><span class="mdi mdi-phone"></span></td> <td>{{ person.phone_number }}</td> <td>{{ person.mobile_number }}</td> </tr> <tr> - <td>{% fa 'envelope' %}</td> + <td><span class="mdi mdi-email"></span></td> <td colspan="3">{{ person.email }}</td> </tr> <tr> - <td>{% fa 'gift' %}</td> + <td><span class="mdi mdi-cake"></span></td> <td colspan="3">{{ person.date_of_birth|date }}</td> </tr> {% comment %} <tr> - <td>{% fa 'graduation-cap' %}</td> + <td><span class="mdi mdi-school"></span></td> <td>Class</td> <td>Teacher</td> </tr> diff --git a/biscuit/core/templates/core/system_status.html b/biscuit/core/templates/core/system_status.html index 881a244811787bd157ea45add6a93fce8319e1cd..7b8006dd28717c04e82c206756e7027f20b45111 100644 --- a/biscuit/core/templates/core/system_status.html +++ b/biscuit/core/templates/core/system_status.html @@ -1,6 +1,6 @@ {# -*- engine:django -*- #} {% extends "core/base.html" %} -{% load i18n font_awesome %} +{% load i18n %} {% block bootstrap4_title %}{% blocktrans %}System status{% endblocktrans %} - {{ block.super }}{% endblock %} @@ -13,18 +13,18 @@ </div> <ul class="list-group list-group-flush"> <li class="list-group-item d-flex justify-content-between align-items-center"> - {% if maintenance_mode %} + {% if maintenance_mode %} <div> <p>{% blocktrans %}Maintenance mode enabled{% endblocktrans %}</p> <p class="small">{% blocktrans%}Only admin and visitors from internal IPs can access the site.{% endblocktrans %}</p> </div> - <span class="badge badge-danger"><a href="{% url 'maintenance_mode_off' %}">{% fa 'power-off' %}</a></span> + <span class="badge badge-danger mdi mdi-power"><a href="{% url 'maintenance_mode_off' %}"></a></span> {% else %} <div> <p>{% blocktrans %}Maintenance mode disabled{% endblocktrans %}</p> <p class="small">{% blocktrans%}Everyone can access the site.{% endblocktrans %}</p> </div> - <span class="badge badge-success"><a href="{% url 'maintenance_mode_on' %}">{% fa 'power-off' %}</a></span> + <span class="badge badge-success mdi mdi-power"><a href="{% url 'maintenance_mode_on' %}"></a></span> {% endif %} </li> <li class="list-group-item d-flex justify-content-between align-items-center"> @@ -33,13 +33,13 @@ <p>{% blocktrans %}Debug mode enabled{% endblocktrans %}</p> <p class="small">{% blocktrans%}The web server throws back debug information on errors. Do not use in production!{% endblocktrans %}</p> </div> - <span class="badge badge-danger">{% fa 'power-off' %}</span> + <span class="badge badge-danger mdi mdi-power"> </span> {% else %} <div> <p>{% blocktrans %}Debug mode disabled{% endblocktrans %}</p> <p class="small">{% blocktrans%}Debug mode is disabled. Default error pages are displayed on errors.{% endblocktrans %}</p> </div> - <span class="badge badge-success">{% fa 'power-off' %}</span> + <span class="badge badge-success mdi mdi-power"> </span> {% endif %} </li> </ul> @@ -56,9 +56,9 @@ <p>{{ backup.end_time }}</p> </div> {% if backup.is_success %} - <span class="badge badge-success">{% fa 'check' %}</span> + <span class="badge badge-success mdi mdi-check"> </span> {% else %} - <span class="badge badge-danger">{% fa 'times' %}</span> + <span class="badge badge-danger mdi mdi-error"> </span> {% endif %} </li> {% endfor %} diff --git a/biscuit/core/templates/core/turnable.html b/biscuit/core/templates/core/turnable.html index 61e4b3764b6fb439cdcf0221cc6974461a168ce5..237f7576f8a250bbe9ba1de42becbc093ffe6b96 100644 --- a/biscuit/core/templates/core/turnable.html +++ b/biscuit/core/templates/core/turnable.html @@ -2,7 +2,6 @@ {% extends "core/base.html" %} -{% load font_awesome %} {% block content %} @@ -12,10 +11,10 @@ </div> <div class="btn-group" role="group" aria-label="URL actions"> <a href="{{ url_prev }}" class="btn btn-dark"> - {% fa 'arrow-left' %} + <span class="mdi mdi-chevron-left"></span> </a> <a href="{{ url_next }}" class="btn btn-dark"> - {% fa 'arrow-right' %} + <span class="mdi mdi-chevron-right"></span> </a> </div> </div> diff --git a/biscuit/core/templatetags/data_helpers.py b/biscuit/core/templatetags/data_helpers.py index 66b41efd37e7ddbae179dd2d6dfef619f0ff3c98..0b195d136d86587f892635d6d0e41be1aebada68 100644 --- a/biscuit/core/templatetags/data_helpers.py +++ b/biscuit/core/templatetags/data_helpers.py @@ -11,7 +11,7 @@ def get_dict(value: Any, arg: Any) -> Any: if hasattr(value, str(arg)): return getattr(value, arg) - elif hasattr(value, 'keys') and arg in value.keys(): + elif hasattr(value, "keys") and arg in value.keys(): return value[arg] elif str(arg).isnumeric() and len(value) > int(arg): return value[int(arg)] diff --git a/biscuit/core/tests/browser/test_selenium.py b/biscuit/core/tests/browser/test_selenium.py new file mode 100644 index 0000000000000000000000000000000000000000..4fff999cab330c75c6166b55307919f682970d4f --- /dev/null +++ b/biscuit/core/tests/browser/test_selenium.py @@ -0,0 +1,60 @@ +import os + +from django.conf import settings +from django.test.selenium import SeleniumTestCase, SeleniumTestCaseBase +from django.urls import reverse + +import pytest + +pytestmark = pytest.mark.django_db + +SeleniumTestCaseBase.external_host = os.environ.get("TEST_HOST", "") or None +SeleniumTestCaseBase.browsers = list( + filter(bool, os.environ.get("TEST_SELENIUM_BROWSERS", "").split(",")) +) +SeleniumTestCaseBase.selenium_hub = os.environ.get("TEST_SELENIUM_HUB", "") or None + + +class SeleniumTests(SeleniumTestCase): + serialized_rollback = True + + @classmethod + def _screenshot(cls, filename): + screenshot_path = os.environ.get("TEST_SCREENSHOT_PATH", None) + if screenshot_path: + os.makedirs(os.path.join(screenshot_path, cls.browser), exist_ok=True) + return cls.selenium.save_screenshot( + os.path.join(screenshot_path, cls.browser, filename) + ) + else: + return False + + def test_index(self): + self.selenium.get(self.live_server_url + "/") + assert "BiscuIT" in self.selenium.title + self._screenshot("index.png") + + def test_login_default_superuser(self): + username = "admin" + password = "admin" + + # Navigate to configured login page + self.selenium.get(self.live_server_url + reverse(settings.LOGIN_URL)) + self._screenshot("login_default_superuser_blank.png") + + # Find login form input fields and enter defined credentials + self.selenium.find_element_by_xpath( + '//label[contains(text(), "Username")]/../input' + ).send_keys(username) + self.selenium.find_element_by_xpath( + '//label[contains(text(), "Password")]/../input' + ).send_keys(password) + self._screenshot("login_default_superuser_filled.png") + + # Submit form by clicking django-two-factor-auth's Next button + self.selenium.find_element_by_xpath('//button[contains(text(), "Next")]').click() + self._screenshot("login_default_superuser_submitted.png") + + # Should redirect away from login page and not put up an alert about wrong credentials + assert "Please enter a correct username and password." not in self.selenium.page_source + assert reverse(settings.LOGIN_URL) not in self.selenium.current_url diff --git a/biscuit/core/tests/models/test_person.py b/biscuit/core/tests/models/test_person.py index e454d3b60709498564c1c005242694b08ad805bb..62861ae1d82b7359080b653049038c9e142a7a27 100644 --- a/biscuit/core/tests/models/test_person.py +++ b/biscuit/core/tests/models/test_person.py @@ -2,12 +2,10 @@ import pytest from biscuit.core.models import Person +pytestmark = pytest.mark.django_db + -@pytest.mark.django_db def test_full_name(): - _person = Person.objects.create( - first_name='Jane', - last_name='Doe' - ) + _person = Person.objects.create(first_name="Jane", last_name="Doe") - assert _person.full_name == 'Doe, Jane' + assert _person.full_name == "Doe, Jane" diff --git a/biscuit/core/tests/templatetags/test_data_helpers.py b/biscuit/core/tests/templatetags/test_data_helpers.py index 096903947c8c21870ad5c05fcffb5b0a8e522a12..5f8e975ffdffb5b522ee94927add0a38d742045f 100644 --- a/biscuit/core/tests/templatetags/test_data_helpers.py +++ b/biscuit/core/tests/templatetags/test_data_helpers.py @@ -1,22 +1,26 @@ from biscuit.core.templatetags.data_helpers import get_dict + def test_get_dict_object(): class _Foo(object): bar = 12 - assert _Foo.bar == get_dict(_Foo, 'bar') + assert _Foo.bar == get_dict(_Foo, "bar") + def test_get_dict_dict(): - _foo = {'bar': 12} + _foo = {"bar": 12} + + assert _foo["bar"] == get_dict(_foo, "bar") - assert _foo['bar'] == get_dict(_foo, 'bar') def test_get_dict_list(): _foo = [10, 11, 12] assert _foo[2] == get_dict(_foo, 2) + def test_get_dict_invalid(): _foo = 12 - assert get_dict(_foo, 'bar') is None + assert get_dict(_foo, "bar") is None diff --git a/biscuit/core/tests/views/test_account.py b/biscuit/core/tests/views/test_account.py index 3bffcf17e9e226ac04230d367219e3cc5942a835..48cf07028fb2ae232a6b1018550a23e8edb04ff4 100644 --- a/biscuit/core/tests/views/test_account.py +++ b/biscuit/core/tests/views/test_account.py @@ -1,53 +1,55 @@ -import pytest - from django.conf import settings from django.urls import reverse -@pytest.mark.django_db +import pytest + +pytestmark = pytest.mark.django_db + + def test_index_not_logged_in(client): - response = client.get('/') + response = client.get("/") assert response.status_code == 200 - assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8') + assert reverse(settings.LOGIN_URL) in response.content.decode("utf-8") + -@pytest.mark.django_db def test_login(client, django_user_model): - username = 'foo' - password = 'bar' + username = "foo" + password = "bar" django_user_model.objects.create_user(username=username, password=password) client.login(username=username, password=password) - response = client.get('/') + response = client.get("/") assert response.status_code == 200 - assert reverse(settings.LOGIN_URL) not in response.content.decode('utf-8') + assert reverse(settings.LOGIN_URL) not in response.content.decode("utf-8") + -@pytest.mark.django_db def test_index_not_linked_to_person(client, django_user_model): - username = 'foo' - password = 'bar' + username = "foo" + password = "bar" django_user_model.objects.create_user(username=username, password=password) client.login(username=username, password=password) - response = client.get('/') + response = client.get("/") assert response.status_code == 200 - assert 'You are not linked to a person' in response.content.decode('utf-8') + assert "You are not linked to a person" in response.content.decode("utf-8") + -@pytest.mark.django_db def test_logout(client, django_user_model): - username = 'foo' - password = 'bar' + username = "foo" + password = "bar" django_user_model.objects.create_user(username=username, password=password) client.login(username=username, password=password) - response = client.get('/') + response = client.get("/") assert response.status_code == 200 - response = client.get(reverse('logout'), follow=True) + response = client.get(reverse("logout"), follow=True) assert response.status_code == 200 - assert reverse(settings.LOGIN_URL) in response.content.decode('utf-8') + assert reverse(settings.LOGIN_URL) in response.content.decode("utf-8") diff --git a/biscuit/core/urls.py b/biscuit/core/urls.py index dd98dc0175b1e6a13605208615f6fa2ca1482342..1654982eb007266be149bdbfceb119aa177fdea9 100644 --- a/biscuit/core/urls.py +++ b/biscuit/core/urls.py @@ -1,7 +1,7 @@ from django.apps import apps -from django.contrib import admin from django.conf import settings from django.conf.urls.static import static +from django.contrib import admin from django.contrib.auth import views as auth_views from django.urls import include, path @@ -11,34 +11,30 @@ from two_factor.urls import urlpatterns as tf_urls from . import views urlpatterns = [ - path('admin/', admin.site.urls), - path('data_management/', views.data_management, name='data_management'), - path('status/', views.system_status, name='system_status'), - path('school_management', views.school_management, name='school_management'), - path('school/information/edit', views.edit_school, name='edit_school_information'), - path('school/term/edit', views.edit_schoolterm, name='edit_school_term'), - path('', include(tf_urls)), - path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'), - path('persons', views.persons, name='persons'), - path('persons/accounts', views.persons_accounts, name='persons_accounts'), - path('person', views.person, name='person'), - path('person/<int:id_>', views.person, - {'template': 'full'}, name='person_by_id'), - path('person/<int:id_>/card', views.person, - {'template': 'card'}, name='person_by_id_card'), - path('person/<int:id_>/edit', views.edit_person, name='edit_person_by_id'), - path('groups', views.groups, name='groups'), - path('group/create', views.edit_group, name='create_group'), - path('group/<int:id_>', views.group, - {'template': 'full'}, name='group_by_id'), - path('group/<int:id_>/edit', views.edit_group, name='edit_group_by_id'), - path('', views.index, name='index'), - path('maintenance-mode/', include('maintenance_mode.urls')), - path('contact/', include('contact_form.urls')), - path('impersonate/', include('impersonate.urls')), - path('__i18n__/', include('django.conf.urls.i18n')), - path('select2/', include('django_select2.urls')), - path('settings/', include('dbsettings.urls')) + path("admin/", admin.site.urls), + path("data_management/", views.data_management, name="data_management"), + path("status/", views.system_status, name="system_status"), + path("school_management", views.school_management, name="school_management"), + path("school/information/edit", views.edit_school, name="edit_school_information"), + path("school/term/edit", views.edit_schoolterm, name="edit_school_term"), + path("", include(tf_urls)), + path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"), + path("persons", views.persons, name="persons"), + path("persons/accounts", views.persons_accounts, name="persons_accounts"), + path("person", views.person, name="person"), + path("person/<int:id_>", views.person, {"template": "full"}, name="person_by_id"), + path("person/<int:id_>/card", views.person, {"template": "card"}, name="person_by_id_card"), + path("person/<int:id_>/edit", views.edit_person, name="edit_person_by_id"), + path("groups", views.groups, name="groups"), + path("group/create", views.edit_group, name="create_group"), + path("group/<int:id_>", views.group, {"template": "full"}, name="group_by_id"), + path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"), + path("", views.index, name="index"), + path("maintenance-mode/", include("maintenance_mode.urls")), + path("impersonate/", include("impersonate.urls")), + path("__i18n__/", include("django.conf.urls.i18n")), + path("select2/", include("django_select2.urls")), + path("settings/", include("dbsettings.urls")), ] # Serve static files from STATIC_ROOT to make it work with runserver @@ -46,18 +42,18 @@ urlpatterns = [ urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # Add URLs for optional features -if hasattr(settings, 'TWILIO_ACCOUNT_SID'): +if hasattr(settings, "TWILIO_ACCOUNT_SID"): from two_factor.gateways.twilio.urls import urlpatterns as tf_twilio_urls # noqa - urlpatterns += [path('', include(tf_twilio_urls))] + + urlpatterns += [path("", include(tf_twilio_urls))] # Serve javascript-common if in development if settings.DEBUG: - urlpatterns.append(path('__debug__/', include(debug_toolbar.urls))) + urlpatterns.append(path("__debug__/", include(debug_toolbar.urls))) # Automatically mount URLs from all installed BiscuIT apps for app_config in apps.app_configs.values(): - if not app_config.name.startswith('biscuit.apps.'): + if not app_config.name.startswith("biscuit.apps."): continue - urlpatterns.append(path('app/%s/' % app_config.label, - include('%s.urls' % app_config.name))) + urlpatterns.append(path("app/%s/" % app_config.label, include("%s.urls" % app_config.name))) diff --git a/biscuit/core/util/apps.py b/biscuit/core/util/apps.py index 306bb1f701c0419f29610d84766d06ce777f6cd9..f98649cdc261b21443cfc1696af2453c119b6f79 100644 --- a/biscuit/core/util/apps.py +++ b/biscuit/core/util/apps.py @@ -11,7 +11,9 @@ class AppConfig(django.apps.AppConfig): # Run model extension code try: - import_module('.'.join(self.__class__.__module__.split('.')[:-1] + ['model_extensions'])) + import_module( + ".".join(self.__class__.__module__.split(".")[:-1] + ["model_extensions"]) + ) except ImportError: # ImportErrors are non-fatal because model extensions are optional. pass diff --git a/biscuit/core/util/core_helpers.py b/biscuit/core/util/core_helpers.py index e8c0b1fe28c167b25f39eae946077c2d28832723..232890ce4e1789204d8d6c30ed7638ee2499b73c 100644 --- a/biscuit/core/util/core_helpers.py +++ b/biscuit/core/util/core_helpers.py @@ -1,10 +1,25 @@ -from importlib import import_module import pkgutil +from importlib import import_module from typing import Sequence +from django.conf import settings from django.http import HttpRequest +def dt_show_toolbar(request: HttpRequest) -> bool: + from debug_toolbar.middleware import show_toolbar # noqa + + if not settings.DEBUG: + return False + + if show_toolbar(request): + return True + elif hasattr(request, "user") and request.user.is_superuser: + return True + + return False + + def get_app_packages() -> Sequence[str]: """ Find all packages within the biscuit.apps namespace. """ @@ -16,28 +31,28 @@ def get_app_packages() -> Sequence[str]: pkgs = [] for pkg in pkgutil.iter_modules(biscuit.apps.__path__): - mod = import_module('biscuit.apps.%s' % pkg[1]) + mod = import_module("biscuit.apps.%s" % pkg[1]) # Add additional apps defined in module's INSTALLED_APPS constant - additional_apps = getattr(mod, 'INSTALLED_APPS', []) + additional_apps = getattr(mod, "INSTALLED_APPS", []) for app in additional_apps: if app not in pkgs: pkgs.append(app) - pkgs.append('biscuit.apps.%s' % pkg[1]) + pkgs.append("biscuit.apps.%s" % pkg[1]) return pkgs def is_impersonate(request: HttpRequest) -> bool: - if hasattr(request, 'user'): - return getattr(request.user, 'is_impersonate', False) + if hasattr(request, "user"): + return getattr(request.user, "is_impersonate", False) else: return False def has_person(request: HttpRequest) -> bool: - if hasattr(request, 'user'): - return getattr(request.user, 'person', None) is not None + if hasattr(request, "user"): + return getattr(request.user, "person", None) is not None else: return False diff --git a/biscuit/core/util/messages.py b/biscuit/core/util/messages.py index 7b847885567b989100b89de4c830d73f4346bb70..e3c93dbb0301ea3eff1c2c7f4b18556d33c09789 100644 --- a/biscuit/core/util/messages.py +++ b/biscuit/core/util/messages.py @@ -5,7 +5,9 @@ from django.contrib import messages from django.http import HttpRequest -def add_message(request: Optional[HttpRequest], level: int, message: str, **kwargs) -> Optional[Any]: +def add_message( + request: Optional[HttpRequest], level: int, message: str, **kwargs +) -> Optional[Any]: if request: return messages.add_message(request, level, message, **kwargs) else: diff --git a/biscuit/core/util/sass_helpers.py b/biscuit/core/util/sass_helpers.py index c609c0ed9941c1485edfb1d7e8111c1a06b414eb..9dbb56bf2652dbd385b1faa2e21c5575642e459a 100644 --- a/biscuit/core/util/sass_helpers.py +++ b/biscuit/core/util/sass_helpers.py @@ -12,4 +12,4 @@ def get_colour(html_colour: str) -> SassColor: def get_theme_setting(setting: str) -> str: - return getattr(theme_settings, setting, '') + return getattr(theme_settings, setting, "") diff --git a/biscuit/core/views.py b/biscuit/core/views.py index 585d049ae72bd65c0a8ffd16bba88056850f2f04..509860c76b462b8efa3f86c9b45dfa103d8f4a0b 100644 --- a/biscuit/core/views.py +++ b/biscuit/core/views.py @@ -2,22 +2,28 @@ from typing import Optional from django.contrib.auth.decorators import login_required from django.http import Http404, HttpRequest, HttpResponse -from django.shortcuts import get_object_or_404, render, redirect -from django_tables2 import RequestConfig +from django.shortcuts import get_object_or_404, redirect, render from django.utils.translation import ugettext_lazy as _ from django_cron.models import CronJobLog +from django_tables2 import RequestConfig from .decorators import admin_required -from .forms import PersonsAccountsFormSet, EditPersonForm, EditGroupForm, EditSchoolForm, EditTermForm -from .models import Person, Group, School -from .tables import PersonsTable, GroupsTable +from .forms import ( + EditGroupForm, + EditPersonForm, + EditSchoolForm, + EditTermForm, + PersonsAccountsFormSet, +) +from .models import Group, Person, School +from .tables import GroupsTable, PersonsTable from .util import messages def index(request: HttpRequest) -> HttpResponse: context = {} - return render(request, 'core/index.html', context) + return render(request, "core/index.html", context) @login_required @@ -30,9 +36,9 @@ def persons(request: HttpRequest) -> HttpResponse: # Build table persons_table = PersonsTable(persons) RequestConfig(request).configure(persons_table) - context['persons_table'] = persons_table + context["persons_table"] = persons_table - return render(request, 'core/persons.html', context) + return render(request, "core/persons.html", context) @login_required @@ -46,7 +52,7 @@ def person(request: HttpRequest, id_: int, template: str) -> HttpResponse: # Turn not-found object into a 404 error raise Http404 from e - context['person'] = person + context["person"] = person # Get groups where person is member of groups = Group.objects.filter(members=id_) @@ -54,9 +60,9 @@ def person(request: HttpRequest, id_: int, template: str) -> HttpResponse: # Build table groups_table = GroupsTable(groups) RequestConfig(request).configure(groups_table) - context['groups_table'] = groups_table + context["groups_table"] = groups_table - return render(request, 'core/person_%s.html' % template, context) + return render(request, "core/person_%s.html" % template, context) @login_required @@ -70,7 +76,7 @@ def group(request: HttpRequest, id_: int, template: str) -> HttpResponse: # Turn not-found object into a 404 error raise Http404 from e - context['group'] = group + context["group"] = group # Get group group = Group.objects.get(pk=id_) @@ -81,7 +87,7 @@ def group(request: HttpRequest, id_: int, template: str) -> HttpResponse: # Build table members_table = PersonsTable(members) RequestConfig(request).configure(members_table) - context['members_table'] = members_table + context["members_table"] = members_table # Get owners owners = group.owners.filter(is_active=True) @@ -89,9 +95,9 @@ def group(request: HttpRequest, id_: int, template: str) -> HttpResponse: # Build table owners_table = PersonsTable(owners) RequestConfig(request).configure(owners_table) - context['owners_table'] = owners_table + context["owners_table"] = owners_table - return render(request, 'core/group_%s.html' % template, context) + return render(request, "core/group_%s.html" % template, context) @login_required @@ -104,9 +110,9 @@ def groups(request: HttpRequest) -> HttpResponse: # Build table groups_table = GroupsTable(groups) RequestConfig(request).configure(groups_table) - context['groups_table'] = groups_table + context["groups_table"] = groups_table - return render(request, 'core/groups.html', context) + return render(request, "core/groups.html", context) @admin_required @@ -116,13 +122,13 @@ def persons_accounts(request: HttpRequest) -> HttpResponse: persons_qs = Person.objects.all() persons_accounts_formset = PersonsAccountsFormSet(request.POST or None, queryset=persons_qs) - if request.method == 'POST': + if request.method == "POST": if persons_accounts_formset.is_valid(): persons_accounts_formset.save() - context['persons_accounts_formset'] = persons_accounts_formset + context["persons_accounts_formset"] = persons_accounts_formset - return render(request, 'core/persons_accounts.html', context) + return render(request, "core/persons_accounts.html", context) @admin_required @@ -133,18 +139,18 @@ def edit_person(request: HttpRequest, id_: int) -> HttpResponse: edit_person_form = EditPersonForm(request.POST or None, request.FILES or None, instance=person) - context['person'] = person + context["person"] = person - if request.method == 'POST': + if request.method == "POST": if edit_person_form.is_valid(): edit_person_form.save(commit=True) - messages.success(request, _('The person has been saved.')) - return redirect('edit_person_by_id', id_=person.id) + messages.success(request, _("The person has been saved.")) + return redirect("edit_person_by_id", id_=person.id) - context['edit_person_form'] = edit_person_form + context["edit_person_form"] = edit_person_form - return render(request, 'core/edit_person.html', context) + return render(request, "core/edit_person.html", context) @admin_required @@ -158,40 +164,40 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: group = None edit_group_form = EditGroupForm(request.POST or None) - if request.method == 'POST': + if request.method == "POST": if edit_group_form.is_valid(): edit_group_form.save(commit=True) - messages.success(request, _('The group has been saved.')) - return redirect('groups') + messages.success(request, _("The group has been saved.")) + return redirect("groups") - context['group'] = group - context['edit_group_form'] = edit_group_form + context["group"] = group + context["edit_group_form"] = edit_group_form - return render(request, 'core/edit_group.html', context) + return render(request, "core/edit_group.html", context) @admin_required def data_management(request: HttpRequest) -> HttpResponse: context = {} - return render(request, 'core/data_management.html', context) + return render(request, "core/data_management.html", context) @admin_required def system_status(request: HttpRequest) -> HttpResponse: context = {} - context['backups'] = CronJobLog.objects.filter( - code='biscuit.core.Backup' - ).order_by('-end_time')[:10] + context["backups"] = CronJobLog.objects.filter(code="biscuit.core.Backup").order_by( + "-end_time" + )[:10] - return render(request, 'core/system_status.html', context) + return render(request, "core/system_status.html", context) @admin_required def school_management(request: HttpRequest) -> HttpResponse: context = {} - return render(request, 'core/school_management.html', context) + return render(request, "core/school_management.html", context) @admin_required @@ -201,18 +207,18 @@ def edit_school(request: HttpRequest) -> HttpResponse: school = School.objects.first() edit_school_form = EditSchoolForm(request.POST or None, request.FILES or None, instance=school) - context['school'] = school + context["school"] = school - if request.method == 'POST': + if request.method == "POST": if edit_school_form.is_valid(): edit_school_form.save(commit=True) - messages.success(request, _('The school has been saved.')) - return redirect('index') + messages.success(request, _("The school has been saved.")) + return redirect("index") - context['edit_school_form'] = edit_school_form + context["edit_school_form"] = edit_school_form - return render(request, 'core/edit_school.html', context) + return render(request, "core/edit_school.html", context) @admin_required @@ -222,13 +228,13 @@ def edit_schoolterm(request: HttpRequest) -> HttpResponse: term = School.objects.first().current_term edit_term_form = EditTermForm(request.POST or None, instance=term) - if request.method == 'POST': + if request.method == "POST": if edit_term_form.is_valid(): edit_term_form.save(commit=True) - messages.success(request, _('The term has been saved.')) - return redirect('index') + messages.success(request, _("The term has been saved.")) + return redirect("index") - context['edit_term_form'] = edit_term_form + context["edit_term_form"] = edit_term_form - return render(request, 'core/edit_schoolterm.html', context) + return render(request, "core/edit_schoolterm.html", context) diff --git a/biscuit/core/wsgi.py b/biscuit/core/wsgi.py index afb5bc6b7711d61a5160f88bea3ccc4d2e776a0d..c364569d798e00c5b4b7c178eb1554dc853101c4 100644 --- a/biscuit/core/wsgi.py +++ b/biscuit/core/wsgi.py @@ -2,6 +2,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'biscuit.core.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "biscuit.core.settings") application = get_wsgi_application() diff --git a/dev.sh b/dev.sh index 6a183f4000d6ce6e9ccf71c186e35193ec1a34c7..1840ccf811167de60ebed47ebf6459ea30ce2efb 100755 --- a/dev.sh +++ b/dev.sh @@ -15,7 +15,6 @@ case "$1" in poetry run sh -c "cd $d; poetry install" done remove_pip_metadata - poetry run ./manage.py migrate poetry run ./manage.py compilemessages poetry run ./manage.py yarn install poetry run ./manage.py collectstatic --no-input diff --git a/docker-compose.yml b/docker-compose.yml index e71f2f1dd9fcd1b815995b4c6ca999a1ce66d715..ca09c5af38ebb0f79d60d0794d0a882ba4ceaace 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,7 @@ services: - BISCUIT_http__allowed_hosts="['*']" - BISCUIT_caching__memcached__address=memcached:11211 - BISCUIT_database__host=db + - BISCUIT_maintenance__debug=${BISCUIT_maintenance__debug:-false} depends_on: - db - memcached diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 4dcf89fce710430ead493366a36f26863dffcd69..eb1668a08a600197c2abb0b20813e8aff433b9cf 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -19,6 +19,7 @@ done python manage.py flush --no-input python manage.py migrate +python manage.py collectstatic --no-input --clear if [[ -n "$@" ]]; then exec "$@" diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile index e47ed67f547c508c1d18dc5ae6184f1fef90f873..4f5a2cd405ea5ff980d523df687de50e4835a6f3 100644 --- a/docker/nginx/Dockerfile +++ b/docker/nginx/Dockerfile @@ -1,6 +1,6 @@ FROM nginx RUN rm /etc/nginx/conf.d/default.conf -COPY nginx.conf /etc/nginx/conf.d +COPY nginx.conf /etc/nginx/conf.d/default.conf RUN mkdir /var/lib/biscuit diff --git a/docs/dev/01_setup.rst b/docs/dev/01_setup.rst index 8d4cd0d8b9cfae6c631d7803eec7565274f5149d..49d166c88dae9d11357b6439a8adc6288dad357a 100644 --- a/docs/dev/01_setup.rst +++ b/docs/dev/01_setup.rst @@ -76,6 +76,13 @@ development server against a local PostgreSQL database with password BISCUIT_debug=true BISCUIT_database__password=biscuit poetry run ./manage.py runserver +.. figure:: /screenshots/index.png + :scale: 50% + :alt: Screenshot of index page + + After installing the development environment with default settings, + you should see the index page with the BOotstrap style. + .. _Poetry: https://poetry.eustace.io/ .. _Poetry installation methods: https://poetry.eustace.io/docs/#installation .. _Yarn: https://yarnpkg.com diff --git a/docs/dev/02_install_apps.rst b/docs/dev/02_install_apps.rst index b083baf6cb5710db1a84a7cb8255ee678e9db07e..f8e1e7d8cf8b986151654a6e0a51a02579a3eaae 100644 --- a/docs/dev/02_install_apps.rst +++ b/docs/dev/02_install_apps.rst @@ -17,5 +17,5 @@ This will install the Exlibris app (library management) app by using a shell for first ``cd``'ing into the app directory and then using poetry to install the app. -DO not forget to run the maintenance tasks described earlier after -installign any app. +Do not forget to run the maintenance tasks described earlier after +installing any app. diff --git a/docs/dev/03_run_tests.rst b/docs/dev/03_run_tests.rst new file mode 100644 index 0000000000000000000000000000000000000000..ae38192ed8fb8fdaae51046839269cac1ef61af4 --- /dev/null +++ b/docs/dev/03_run_tests.rst @@ -0,0 +1,75 @@ +Running tests and reports +========================= + +Running default test suite +-------------------------- + +The test suite can be run using the `tox` tool:: + + poetry run tox + + +Enabling Selenium browser tests +------------------------------- + +The test suite contains tests that use Selenium to do browser based tests. +They need to be enabled when running the test suite, which can be done by +setting certain environment variables: + ++------------------------+------------------------------------------------------+------------------------------+ +| Variable | Meaning | Example | ++========================+======================================================+==============================+ +| TEST_SELENIUM_BROWSERS | List of webdrivers to test against, comma-separated. | chrome,firefox | ++------------------------+------------------------------------------------------+------------------------------+ +| TEST_SELENIUM_HUB | Address of Selenium hub if using remote grid | http://127.0.0.1:4444/wd/hub | ++------------------------+------------------------------------------------------+------------------------------+ +| TEST_HOST | Hostname reachable from Selenium for live server | 172.17.0.1 | ++------------------------+------------------------------------------------------+------------------------------+ +| TEST_SCREENSHOT_PATH | Path to directory to create screenshots in | ./screenshots | ++------------------------+------------------------------------------------------+------------------------------+ + +Selenium tests are enabled if `TEST_SELENIUM_BROWSERS` is non-empty. + +To set variables, use env to wrap the tox ommand:: + + poetry run env TEST_SELENIUM_BROWSERS=chrome,firefox tox + + +Using a Selenium hub on local Docker host +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One way to setup Selenium is to use the official images on the local +machine. + +First, get Selenium Hub and one or more browser nodes up and running:: + + docker run -d -p 4444:4444 --name selenium-hub selenium/hub + docker run -d --link selenium-hub:hub selenium/node-chrome + docker run -d --link selenium-hub:hub selenium/node-firefox + +After that, you can run the test suite, setting the needed variables to use +Docker Hub:: + + poetry run env \ + TEST_SELENIUM_BROWSERS=chrome,firefox \ + TEST_SELENIUM_HUB=http://127.0.0.1:4444/wd/hub \ + TEST_HOST=172.17.0.1 \ + tox + +The `TEST_HOST` variable is set to the Docker host's IP address, where the +Selenium nodes can access Django's live server. Django automatically +configures the live server to be reachable if a Selenium hub is used. + + +Taking screenshots +~~~~~~~~~~~~~~~~~~ + +The browser test suites automatically take screenshots at certain steps if +enabled in the test run. This can be used to visually verify that views +look like they should or for documentation purposes. + +To enable screenshots, add the `TEST_SCREENSHOT_PATH` environment variable +when running the tests. + +If runnin multiple browsers, screenshots are placed in separate directories +per browser. diff --git a/poetry.lock b/poetry.lock index c3f515ad8164aed668ea043acac62ddc859dbf77..283d8064e726d2667a8175c219580bb7ded76b5d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,6 +6,14 @@ optional = false python-versions = "*" version = "0.7.12" +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.3" + [[package]] category = "main" description = "ASGI specs, helper code, and adapters" @@ -14,6 +22,9 @@ optional = false python-versions = "*" version = "3.2.3" +[package.extras] +tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"] + [[package]] category = "dev" description = "Atomic file writes." @@ -31,16 +42,11 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "19.3.0" -[[package]] -category = "dev" -description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" -name = "autopep8" -optional = false -python-versions = "*" -version = "1.4.4" - -[package.dependencies] -pycodestyle = ">=2.4.0" +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] category = "main" @@ -53,17 +59,56 @@ version = "2.7.0" [package.dependencies] pytz = ">=2015.7" +[[package]] +category = "dev" +description = "Security oriented static analyser for python code." +name = "bandit" +optional = false +python-versions = "*" +version = "1.6.2" + +[package.dependencies] +GitPython = ">=1.0.1" +PyYAML = ">=3.13" +colorama = ">=0.3.9" +six = ">=1.10.0" +stevedore = ">=1.20.0" + [[package]] category = "main" description = "Screen-scraping library" name = "beautifulsoup4" optional = false python-versions = "*" -version = "4.8.1" +version = "4.8.2" [package.dependencies] soupsieve = ">=1.2" +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "19.10b0" + +[package.dependencies] +appdirs = "*" +attrs = ">=18.1.0" +click = ">=6.5" +pathspec = ">=0.6,<1" +regex = "*" +toml = ">=0.9.4" +typed-ast = ">=1.4.0" + +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + [[package]] category = "main" description = "Python package for providing Mozilla's CA Bundle." @@ -91,6 +136,7 @@ version = "7.0" [[package]] category = "main" description = "Cross-platform colored terminal text." +marker = "platform_system == \"Windows\" or sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -104,10 +150,12 @@ optional = false python-versions = "*" version = "0.1.5" +[package.extras] +test = ["nose"] + [[package]] category = "main" description = "Config file reading, writing and validation." -marker = "extra == \"ini\"" name = "configobj" optional = false python-versions = "*" @@ -121,8 +169,11 @@ category = "dev" description = "Code coverage measurement for Python" name = "coverage" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" -version = "4.5.4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "5.0.1" + +[package.extras] +toml = ["toml"] [[package]] category = "dev" @@ -138,13 +189,17 @@ description = "A high-level Python Web framework that encourages rapid developme name = "django" optional = false python-versions = ">=3.6" -version = "3.0" +version = "3.0.1" [package.dependencies] asgiref = ">=3.2,<4.0" pytz = "*" sqlparse = ">=0.2.2" +[package.extras] +argon2 = ["argon2-cffi (>=16.1.0)"] +bcrypt = ["bcrypt"] + [[package]] category = "main" description = "Include JavaScript libraries with readable template tags" @@ -186,7 +241,7 @@ description = "Bootstrap support for Django projects" name = "django-bootstrap4" optional = false python-versions = "*" -version = "1.1.0" +version = "1.1.1" [package.dependencies] beautifulsoup4 = "*" @@ -213,17 +268,6 @@ version = "0.9.2" [package.dependencies] Django = ">=1.8.0" -[[package]] -category = "main" -description = "A generic contact-form application for Django" -name = "django-contact-form" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.7" - -[package.dependencies] -Django = ">=1.11" - [[package]] category = "main" description = "Running python crons in a Django project" @@ -255,7 +299,7 @@ description = "Application settings whose values can be updated while a project name = "django-dbsettings" optional = false python-versions = "*" -version = "0.11.0" +version = "1.0.0" [[package]] category = "main" @@ -277,17 +321,6 @@ optional = false python-versions = "*" version = "1.1.1" -[[package]] -category = "main" -description = "Font Awesome for Django" -name = "django-fa" -optional = false -python-versions = "*" -version = "1.0.0" - -[package.dependencies] -Django = ">1.4" - [[package]] category = "main" description = "A set of high-level abstractions for Django forms" @@ -320,11 +353,10 @@ description = "A reusable app for cropping images easily and non-destructively i name = "django-image-cropping" optional = false python-versions = "*" -version = "1.2.0" +version = "1.3.0" [package.dependencies] django-appconf = ">=1.0.2" -six = "*" [[package]] category = "main" @@ -348,7 +380,7 @@ description = "django-maintenance-mode shows a 503 error page when maintenance-m name = "django-maintenance-mode" optional = false python-versions = "*" -version = "0.13.3" +version = "0.14.0" [[package]] category = "main" @@ -381,10 +413,12 @@ version = "0.7.4" django = ">=1.11" six = ">=1.10.0" +[package.extras] +qrcode = ["qrcode"] + [[package]] category = "main" description = "A django-otp plugin that verifies YubiKey OTP tokens." -marker = "extra == \"YubiKey\"" name = "django-otp-yubikey" optional = false python-versions = "*" @@ -407,6 +441,14 @@ version = "3.0.1" Django = ">=1.11.3" babel = "*" +[package.dependencies.phonenumbers] +optional = true +version = ">=7.0.2" + +[package.extras] +phonenumbers = ["phonenumbers (>=7.0.2)"] +phonenumberslite = ["phonenumberslite (>=7.0.2)"] + [[package]] category = "main" description = "SASS processor to compile SCSS files into *.css, while rendering, or offline." @@ -415,6 +457,9 @@ optional = false python-versions = "*" version = "0.7.5" +[package.extras] +dev = ["libsass (>=0.13)"] + [[package]] category = "main" description = "Select2 option fields for Django" @@ -441,11 +486,11 @@ description = "Mypy stubs for Django" name = "django-stubs" optional = false python-versions = ">=3.6" -version = "1.1.0" +version = "1.4.0" [package.dependencies] django = "*" -mypy = ">=0.720,<0.730" +mypy = ">=0.760,<0.770" typing-extensions = "*" [[package]] @@ -459,28 +504,43 @@ version = "2.2.1" [package.dependencies] Django = ">=1.11" +[package.extras] +tablib = ["tablib"] + [[package]] category = "main" description = "Complete Two-Factor Authentication for Django" name = "django-two-factor-auth" optional = false python-versions = "*" -version = "1.9.1" +version = "1.10.0" [package.dependencies] Django = ">=1.11" django-formtools = "*" -django-otp-yubikey = "*" +django-otp = ">=0.6.0,<0.99" django-phonenumber-field = ">=1.1.0,<3.99" -django_otp = ">=0.6.0,<0.99" -phonenumbers = ">=7.0.9,<8.99" qrcode = ">=4.0.0,<6.99" -twilio = ">=6.0" -[package.source] -reference = "bf9d0812ab11320a6cadc6709c382a03184f2e31" -type = "git" -url = "https://github.com/Bouke/django-two-factor-auth" +[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" + +[package.extras] +Call = ["twilio (>=6.0)"] +SMS = ["twilio (>=6.0)"] +YubiKey = ["django-otp-yubikey"] +phonenumbers = ["phonenumbers (>=7.0.9,<8.99)"] +phonenumberslite = ["phonenumberslite (>=7.0.9,<8.99)"] + [[package]] category = "main" description = "Integrate django with yarnpkg" @@ -501,38 +561,70 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "0.15.2" +[[package]] +category = "dev" +description = "A parser for Python dependency files" +name = "dparse" +optional = false +python-versions = "*" +version = "0.4.1" + +[package.dependencies] +packaging = "*" +pyyaml = "*" +six = "*" + +[package.extras] +pipenv = ["pipenv"] + [[package]] category = "main" description = "The dynamic configurator for your Python Project" name = "dynaconf" optional = false python-versions = "*" -version = "2.2.1" +version = "2.2.2" [package.dependencies] -PyYAML = "*" -click = "*" -configobj = "*" -python-box = "*" -python-dotenv = "*" -toml = "*" +click = "<=7.0" +python-box = "<4.0.0" +python-dotenv = "<=0.10.3" + +[[package.dependencies.toml]] +version = "<=0.10.0" + +[[package.dependencies.toml]] +optional = true +version = "*" + +[package.dependencies.PyYAML] +optional = true +version = "*" + +[package.dependencies.configobj] +optional = true +version = "*" + +[package.extras] +all = ["redis", "pyyaml", "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 = "*" -version = "2.6" +python-versions = ">=3.5" +version = "2.7" [package.dependencies] -[package.dependencies.django] -python = ">=3" -version = ">=1.8" - -[package.dependencies.pillow] -python = ">=2.7" -version = "*" +django = ">=1.11,<4.0" +pillow = "*" [[package]] category = "dev" @@ -555,14 +647,6 @@ python-dateutil = ">=2.4" six = ">=1.10" text-unidecode = "1.3" -[[package]] -category = "dev" -description = "A platform independent file lock." -name = "filelock" -optional = false -python-versions = "*" -version = "3.0.12" - [[package]] category = "dev" description = "the modular source code checker: pep8, pyflakes and co" @@ -577,6 +661,109 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.5.0,<2.6.0" pyflakes = ">=2.1.0,<2.2.0" +[[package]] +category = "dev" +description = "Automated security testing with bandit and flake8." +name = "flake8-bandit" +optional = false +python-versions = "*" +version = "2.1.2" + +[package.dependencies] +bandit = "*" +flake8 = "*" +flake8-polyfill = "*" +pycodestyle = "*" + +[[package]] +category = "dev" +description = "flake8 plugin to call black as a code style validator" +name = "flake8-black" +optional = false +python-versions = "*" +version = "0.1.1" + +[package.dependencies] +black = ">=19.3b0" +flake8 = ">=3.0.0" + +[[package]] +category = "dev" +description = "Check for python builtins being used as variables or parameters." +name = "flake8-builtins" +optional = false +python-versions = "*" +version = "1.4.2" + +[package.dependencies] +flake8 = "*" + +[package.extras] +test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] + +[[package]] +category = "dev" +description = "Plugin to catch bad style specific to Django Projects" +name = "flake8-django" +optional = false +python-versions = "*" +version = "0.0.4" + +[package.dependencies] +flake8 = "*" + +[[package]] +category = "dev" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +name = "flake8-docstrings" +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" +optional = false +python-versions = "*" +version = "1.1.1" + +[[package]] +category = "dev" +description = "flake8 plugin that integrates isort ." +name = "flake8-isort" +optional = false +python-versions = "*" +version = "2.8.0" + +[package.dependencies] +flake8 = ">=3.2.1" +testfixtures = "*" + +[package.dependencies.isort] +extras = ["pyproject"] +version = ">=4.3.0" + +[package.extras] +test = ["pytest"] + +[[package]] +category = "dev" +description = "A plugin for flake8 integrating mypy." +name = "flake8-mypy" +optional = false +python-versions = "*" +version = "17.8.0" + +[package.dependencies] +attrs = "*" +flake8 = ">=3.0.0" +mypy = "*" + [[package]] category = "dev" description = "Polyfill package for Flake8 plugins" @@ -590,11 +777,37 @@ flake8 = "*" [[package]] category = "dev" -description = "Clean single-source support for Python 3 and 2" -name = "future" +description = "Python docstring reStructuredText (RST) validator" +name = "flake8-rst-docstrings" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.18.2" +python-versions = "*" +version = "0.0.12" + +[package.dependencies] +flake8 = ">=3.0.0" +restructuredtext_lint = "*" + +[[package]] +category = "dev" +description = "Git Object Database" +name = "gitdb2" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.0.6" + +[package.dependencies] +smmap2 = ">=2.0.0" + +[[package]] +category = "dev" +description = "Python Git Library" +name = "gitpython" +optional = false +python-versions = ">=3.0, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.0.5" + +[package.dependencies] +gitdb2 = ">=2.0.0" [[package]] category = "main" @@ -610,7 +823,7 @@ description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" +version = "1.2.0" [[package]] category = "dev" @@ -619,11 +832,29 @@ marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.2.0" +version = "1.3.0" [package.dependencies] zipp = ">=0.5" +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + +[[package]] +category = "dev" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "4.3.21" + +[package.extras] +pipfile = ["pipreqs", "requirementslib"] +pyproject = ["toml"] +requirements = ["pipreqs", "pip-api"] +xdg_home = ["appdirs (>=1.4.0)"] + [[package]] category = "dev" description = "A very fast and expressive template engine." @@ -635,6 +866,9 @@ version = "2.10.3" [package.dependencies] MarkupSafe = ">=0.23" +[package.extras] +i18n = ["Babel (>=0.8)"] + [[package]] category = "main" description = "Sass for Python: A straightforward binding of libsass for Python." @@ -646,17 +880,6 @@ version = "0.19.4" [package.dependencies] six = "*" -[[package]] -category = "dev" -description = "Create Python CLI apps with little to no effort at all!" -name = "mando" -optional = false -python-versions = "*" -version = "0.6.4" - -[package.dependencies] -six = "*" - [[package]] category = "dev" description = "Safely add untrusted strings to HTML/XML markup." @@ -686,14 +909,17 @@ category = "dev" description = "Optional static typing for Python" name = "mypy" optional = false -python-versions = "*" -version = "0.720" +python-versions = ">=3.5" +version = "0.761" [package.dependencies] -mypy-extensions = ">=0.4.0,<0.5.0" +mypy-extensions = ">=0.4.3,<0.5.0" typed-ast = ">=1.4.0,<1.5.0" typing-extensions = ">=3.7.4" +[package.extras] +dmypy = ["psutil (>=4.0)"] + [[package]] category = "dev" description = "Experimental type system extensions for programs checked with the mypy typechecker." @@ -714,6 +940,22 @@ version = "19.2" pyparsing = ">=2.0.2" six = "*" +[[package]] +category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.6.0" + +[[package]] +category = "dev" +description = "Python Build Reasonableness" +name = "pbr" +optional = false +python-versions = "*" +version = "5.4.4" + [[package]] category = "dev" description = "PostgreSQL interface library" @@ -728,11 +970,10 @@ scramp = "1.1.0" [[package]] category = "main" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -marker = "extra == \"phonenumbers\"" name = "phonenumbers" optional = false python-versions = "*" -version = "8.11.0" +version = "8.11.1" [[package]] category = "main" @@ -755,6 +996,9 @@ version = "0.13.1" python = "<3.8" version = ">=0.12" +[package.extras] +dev = ["pre-commit", "tox"] + [[package]] category = "main" description = "psycopg2 - Python-PostgreSQL Database Adapter" @@ -801,7 +1045,6 @@ version = "2.5.0" [[package]] category = "main" description = "Cryptographic library for Python" -marker = "extra == \"YubiKey\"" name = "pycryptodome" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -813,7 +1056,7 @@ description = "Python docstring style checker" name = "pydocstyle" optional = false python-versions = ">=3.5" -version = "5.0.0" +version = "5.0.1" [package.dependencies] snowballstemmer = "*" @@ -837,25 +1080,15 @@ version = "2.5.2" [[package]] category = "main" description = "JSON Web Token implementation in Python" -marker = "extra == \"Call\"" name = "pyjwt" optional = false python-versions = "*" version = "1.7.1" -[[package]] -category = "dev" -description = "pylama -- Code audit tool for python" -name = "pylama" -optional = false -python-versions = "*" -version = "7.7.1" - -[package.dependencies] -mccabe = ">=0.5.2" -pycodestyle = ">=2.3.1" -pydocstyle = ">=2.0.0" -pyflakes = ">=1.5.0" +[package.extras] +crypto = ["cryptography (>=1.4)"] +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 = "dev" @@ -863,7 +1096,7 @@ description = "Python parsing module" name = "pyparsing" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.5" +version = "2.4.6" [[package]] category = "dev" @@ -871,7 +1104,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.3.1" +version = "5.3.2" [package.dependencies] atomicwrites = ">=1.0" @@ -887,6 +1120,9 @@ wcwidth = "*" python = "<3.8" version = ">=0.12" +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + [[package]] category = "dev" description = "Pytest plugin for measuring coverage." @@ -899,6 +1135,9 @@ version = "2.8.1" coverage = ">=4.4" pytest = ">=3.6" +[package.extras] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] + [[package]] category = "dev" description = "A Django plugin for pytest." @@ -910,6 +1149,10 @@ version = "3.7.0" [package.dependencies] pytest = ">=3.6" +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["django", "django-configurations (>=2.0)", "six"] + [[package]] category = "dev" description = "Use a temporary PostgreSQL database with pytest-django" @@ -922,6 +1165,19 @@ version = "0.1.post0" 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" +optional = false +python-versions = "*" +version = "0.9.2" + +[package.dependencies] +packaging = ">=14.1" +pytest = ">=2.9" +termcolor = ">=1.1.0" + [[package]] category = "main" description = "Advanced Python dictionaries with dot notation access" @@ -930,6 +1186,9 @@ optional = false python-versions = "*" version = "3.4.6" +[package.extras] +testing = ["pytest", "coverage (>=3.6)", "pytest-cov"] + [[package]] category = "main" description = "Extensions to the standard Python datetime module" @@ -949,6 +1208,9 @@ optional = false python-versions = "*" version = "0.10.3" +[package.extras] +cli = ["click (>=5.0)"] + [[package]] category = "main" description = "Python modules for implementing LDAP clients" @@ -983,7 +1245,6 @@ version = "2019.3" [[package]] category = "main" description = "YAML parser and emitter for Python" -marker = "extra == \"yaml\"" name = "pyyaml" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -1001,19 +1262,19 @@ version = "6.1" colorama = "*" six = "*" +[package.extras] +dev = ["tox", "pytest", "mock"] +maintainer = ["zest.releaser"] +pil = ["pillow"] +test = ["pytest", "pytest-cov", "mock"] + [[package]] category = "dev" -description = "Code Metrics in Python" -name = "radon" +description = "Alternative regular expression module, to replace re." +name = "regex" optional = false python-versions = "*" -version = "3.0.3" - -[package.dependencies] -colorama = ">=0.4,<0.5" -flake8-polyfill = "*" -future = "*" -mando = ">=0.6,<0.7" +version = "2019.12.20" [[package]] category = "main" @@ -1029,6 +1290,36 @@ chardet = ">=3.0.2,<3.1.0" idna = ">=2.5,<2.9" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] + +[[package]] +category = "dev" +description = "reStructuredText linter" +name = "restructuredtext-lint" +optional = false +python-versions = "*" +version = "1.3.0" + +[package.dependencies] +docutils = ">=0.11,<1.0" + +[[package]] +category = "dev" +description = "Safety checks your installed dependencies for known security vulnerabilities." +name = "safety" +optional = false +python-versions = "*" +version = "1.8.5" + +[package.dependencies] +Click = ">=6.0" +dparse = ">=0.4.1" +packaging = "*" +requests = "*" +setuptools = "*" + [[package]] category = "dev" description = "An implementation of the SCRAM protocol." @@ -1037,6 +1328,17 @@ optional = false python-versions = ">=3.5" version = "1.1.0" +[[package]] +category = "dev" +description = "Python bindings for Selenium" +name = "selenium" +optional = false +python-versions = "*" +version = "3.141.0" + +[package.dependencies] +urllib3 = "*" + [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" @@ -1045,6 +1347,14 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" version = "1.13.0" +[[package]] +category = "dev" +description = "A pure Python implementation of a sliding window memory map manager" +name = "smmap2" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.0.5" + [[package]] category = "dev" description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." @@ -1067,7 +1377,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.2.2" +version = "2.3.1" [package.dependencies] Jinja2 = ">=2.3" @@ -1088,6 +1398,10 @@ sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = "*" +[package.extras] +docs = ["sphinxcontrib-websupport"] +test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"] + [[package]] category = "dev" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" @@ -1099,6 +1413,10 @@ version = "1.10.3" [package.dependencies] Sphinx = ">=2.1" +[package.extras] +test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "dataclasses"] +type_comments = ["typed-ast (>=1.4.0)"] + [[package]] category = "dev" description = "" @@ -1107,6 +1425,9 @@ optional = false python-versions = "*" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -1115,6 +1436,9 @@ optional = false python-versions = "*" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "Improve the Sphinx autodoc for Django classes." @@ -1131,6 +1455,9 @@ optional = false python-versions = "*" version = "1.0.2" +[package.extras] +test = ["pytest", "flake8", "mypy", "html5lib"] + [[package]] category = "dev" description = "A sphinx extension which renders display math in HTML via JavaScript" @@ -1139,6 +1466,9 @@ optional = false python-versions = ">=3.5" version = "1.0.1" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -1147,6 +1477,9 @@ optional = false python-versions = "*" version = "1.0.2" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "dev" description = "" @@ -1155,6 +1488,9 @@ optional = false python-versions = "*" version = "1.1.3" +[package.extras] +test = ["pytest", "flake8", "mypy"] + [[package]] category = "main" description = "Non-validating SQL parser" @@ -1163,6 +1499,39 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.3.0" +[[package]] +category = "dev" +description = "Manage dynamic plugins for Python applications" +name = "stevedore" +optional = false +python-versions = "*" +version = "1.31.0" + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" +six = ">=1.10.0" + +[[package]] +category = "dev" +description = "ANSII Color formatting for output in terminal." +name = "termcolor" +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" +optional = false +python-versions = "*" +version = "6.10.3" + +[package.extras] +build = ["setuptools-git", "wheel", "twine"] +docs = ["sphinx"] +test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "sybil", "zope.component", "twisted", "mock", "django (<2)", "django"] + [[package]] category = "dev" description = "utilities for testing.* packages" @@ -1171,6 +1540,9 @@ 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" @@ -1183,6 +1555,9 @@ version = "1.3.0" pg8000 = ">=1.10" "testing.common.database" = "*" +[package.extras] +testing = ["sqlalchemy", "nose", "psycopg2"] + [[package]] category = "main" description = "The most basic Text::Unidecode port" @@ -1199,44 +1574,24 @@ optional = false python-versions = "*" version = "0.10.0" -[[package]] -category = "dev" -description = "tox is a generic virtualenv management and test command line tool" -name = "tox" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.14.2" - -[package.dependencies] -colorama = ">=0.4.1" -filelock = ">=3.0.0,<4" -packaging = ">=14" -pluggy = ">=0.12.0,<1" -py = ">=1.4.17,<2" -six = ">=1.0.0,<2" -toml = ">=0.9.4" -virtualenv = ">=16.0.0" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=1.1.0,<2" - [[package]] category = "main" description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.40.1" +version = "4.41.0" + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] [[package]] category = "main" description = "Twilio API client and TwiML generator" -marker = "extra == \"Call\"" name = "twilio" optional = false python-versions = "*" -version = "6.34.0" +version = "6.35.1" [package.dependencies] PyJWT = ">=1.4.2" @@ -1271,13 +1626,10 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" version = "1.25.7" -[[package]] -category = "dev" -description = "Virtual Python Environment builder" -name = "virtualenv" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "16.7.8" +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] category = "dev" @@ -1290,7 +1642,6 @@ version = "0.1.7" [[package]] category = "main" description = "A library for verifying YubiKey OTP tokens, both locally and through a Yubico web service." -marker = "extra == \"YubiKey\"" name = "yubiotp" optional = false python-versions = "*" @@ -1312,137 +1663,791 @@ version = "0.6.0" [package.dependencies] more-itertools = "*" +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pathlib2", "contextlib2", "unittest2"] + [extras] +app-alsijil = [] +app-chronos = [] +app-exlibris = [] +app-schild-nrw = [] +app-untis = [] ldap = ["django-auth-ldap"] [metadata] -content-hash = "d5e0af51d66f1c4adc471e23109035555529e083d777a3c5b655629da32c1a4f" +content-hash = "0f1ffee31adf4e8caaf64df3f5feda1d455f9349f6b59338084ceb5bcc3e6617" python-versions = "^3.7" -[metadata.hashes] -alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"] -asgiref = ["7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", "ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"] -atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] -attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] -autopep8 = ["4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee"] -babel = ["af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", "e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"] -beautifulsoup4 = ["5279c36b4b2ec2cb4298d723791467e3000e5384a43ea0cdf5d45207c7e97169", "6135db2ba678168c07950f9a16c4031822c6f4aec75a65e0a97bc5ca09789931", "dcdef580e18a76d54002088602eba453eec38ebbcafafeaabd8cab12b6155d57"] -certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"] -chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] -click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] -colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] -colour = ["33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c", "af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"] -configobj = ["a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"] -coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"] -dj-database-url = ["4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163", "851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"] -django = ["6f857bd4e574442ba35a7172f1397b303167dae964cf18e53db5e85fe248d000", "d98c9b6e5eed147bc51f47c014ff6826bd1ab50b166956776ee13db5a58804ae"] -django-any-js = ["1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"] -django-appconf = ["35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3", "c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"] -django-auth-ldap = ["4d68d21058bd57a316a9e1fcd7a36d0f25d054d4d9d9ec85f766a4991176b454", "5f48232c85ddfa33e3573153e6080526ac2eef5e7ec9cf42b5c4ba3c62afb96d"] -django-bootstrap4 = ["180edf28d8dcdcee56ca8d7919147c3c98031ecc898c063b228f9855746de80a", "e56d752bd208f703ed04ca9695b4381314220e708ae9036db3ffc1ed5c7361e0"] -django-bulk-update = ["49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985", "5ab7ce8a65eac26d19143cc189c0f041d5c03b9d1b290ca240dc4f3d6aaeb337"] -django-common-helpers = ["2d56be6fa261d829a6a224f189bf276267b9082a17d613fe5f015dd4d65c17b4"] -django-contact-form = ["b42b7e04d6af3318b8427c1eaf62385ec66da252aa79b607ee55d956c7af4a2d", "c31f73faa13f52efa81ac95f41007f3a84eca617f92773a1bed7ca90c61cb3ed"] -django-cron = ["08d22708c8b2ecab8cda989019a66c7e1e2424c59d822796fd45abf7731d261d"] -django-dbbackup = ["9470e5d8bdaee4feb878b1b66c59eb9b27a131cccd648bf7cbfe70930acd4fc0"] -django-dbsettings = ["e3147ced54b7db3371df10df8845e4514aeae96720000bca1a01d0a6490a1404"] -django-debug-toolbar = ["24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8", "77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"] -django-easy-audit = ["1c5d5e6d6a33f50f696ed53cdaf51de0a4ae2f110ef8c41b33bc139b737729a6", "4b40a30599fe721eb0a9946f5023254fa0904d531c9f4adb23ee52601efaf89b"] -django-fa = ["e3ebf97b90e374b5ccb5b8a70e4a932c8787f2ee995c09a97a63bf9a1366c3ff"] -django-formtools = ["304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f", "c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"] -django-hattori = ["6953d40881317252f19f62c4e7fe8058924b852c7498bc42beb7bc4d268c252c", "e529ed7af8fc34a0169c797c477672b687a205a56f3f5206f90c260acb83b7ac"] -django-image-cropping = ["157c6f96b2bbe485bde00108cbf379ea0fcb6d7a7252648f7548aa795108dde0"] -django-impersonate = ["63b62d06f93b0318698c68f7314c78473914c262d4164eb66ad860bb83e04771"] -django-ipware = ["a7c7a8fd019dbdc9c357e6e582f65034e897572fc79a7e467674efa8aef9d00b"] -django-maintenance-mode = ["0afcfa6ff4a87348e40c44f58f8a8c4cd3e8eca40ddcdbeb620b68ca78ecbf9c", "473850f80e7762ae586f8347129e73e0d23b89a36b98a70e0c06f1778cacff7c"] -django-menu-generator = ["ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"] -django-middleware-global-request = ["f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"] -django-otp = ["1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50", "76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"] -django-otp-yubikey = ["26b12c763b37e99b95b8b8a54d06d8d54c3774eb26133a452f54558033de732b", "f0b1881562fb42ee9f12c28d284cbdb90d1f0383f2d53a595373b080a19bc261"] -django-phonenumber-field = ["1ab19f723928582fed412bd9844221fa4ff466276d8526b8b4a9913ee1487c5e", "794ebbc3068a7af75aa72a80cb0cec67e714ff8409a965968040f1fd210b2d97"] -django-sass-processor = ["9267e5fcc7fcde2ec0c7d6ad045b4c6c7e9aea92498ed1725312035a5469b410"] -django-select2 = ["4c8c4c7c4afee12c17e64d1eabf77af4cd2666a784cba1e74455ce759dd29393", "bcd7e5fc920ceee0e63c36372d8e75bd2254d8a37ac67fc16581634766680d30"] -django-settings-context-processor = ["d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"] -django-stubs = ["cd6a7333d518b9168f001b8a31c4ea89a91dea40a9dd1535c798635f69a5f80a", "e3673348a42c7259e81a4ea141dae2b2e711220ec631a6215ba9dc23cdcabdf4"] -django-tables2 = ["0d9b17f5c030ba1b5fcaeb206d8397bf58f1fdfc6beaf56e7874841b8647aa94", "6afa0496695e15b332e98537265d09fe01a55b28c75a85323d8e6b0dc2350280"] -django-two-factor-auth = [] -django-yarnpkg = ["010af70049cca94496d4c96ca45e62f13339edd1c22653ab8bfe055acbccd41b", "0d63c7b17e4b9c6c144c4093de3877ce70152f957b36fd7a50b259dc500a4948"] -docutils = ["6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", "9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", "a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"] -dynaconf = ["52e3e41290763e405723b13a893592f8bca06f676854e59623052ffeee1a658f", "75691e9dd4093a1a2dc530d33369ae9296cfba30d29b72b00715dfb98b3f82e4"] -easy-thumbnails = ["23fbe3415c93b2369ece8ebdfb5faa05540943bef8b941b3118ce769ba95e275"] -entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] -faker = ["202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11", "92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432"] -filelock = ["18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", "929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"] -flake8 = ["45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", "49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"] -flake8-polyfill = ["12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9", "e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"] -future = ["b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"] -idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"] -imagesize = ["3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", "f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"] -importlib-metadata = ["3a8b2dfd0a2c6a3636e7c016a7e54ae04b997d30e69d5eacdca7a6c2221a1402", "41e688146d000891f32b1669e8573c57e39e5060e7f5f647aa617cd9a9568278"] -jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"] -libsass = ["003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2", "0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272", "338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08", "4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a", "50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f", "6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0", "74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32", "81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c", "845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013", "8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95", "9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c", "a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1", "c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404", "e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e", "fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a", "fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df"] -mando = ["4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c", "79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"] -markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] -mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -more-itertools = ["b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", "c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"] -mypy = ["0107bff4f46a289f0e4081d59b77cef1c48ea43da5a0dbf0005d54748b26df2a", "07957f5471b3bb768c61f08690c96d8a09be0912185a27a68700f3ede99184e4", "10af62f87b6921eac50271e667cc234162a194e742d8e02fc4ddc121e129a5b0", "11fd60d2f69f0cefbe53ce551acf5b1cec1a89e7ce2d47b4e95a84eefb2899ae", "15e43d3b1546813669bd1a6ec7e6a11d2888db938e0607f7b5eef6b976671339", "352c24ba054a89bb9a35dd064ee95ab9b12903b56c72a8d3863d882e2632dc76", "437020a39417e85e22ea8edcb709612903a9924209e10b3ec6d8c9f05b79f498", "49925f9da7cee47eebf3420d7c0e00ec662ec6abb2780eb0a16260a7ba25f9c4", "6724fcd5777aa6cebfa7e644c526888c9d639bd22edd26b2a8038c674a7c34bd", "7a17613f7ea374ab64f39f03257f22b5755335b73251d0d253687a69029701ba", "cdc1151ced496ca1496272da7fc356580e95f2682be1d32377c22ddebdf73c91"] -mypy-extensions = ["090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"] -packaging = ["28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"] -pg8000 = ["e4c2b173178ba41bba19366b9cdeced91fecec2caf31c7ece719138633213cca", "eebcb4176a7e407987e525a07454882f611985e0becb2b73f76efb93bbdc0aab"] -phonenumbers = ["964ecfcf261b98fa7a749ddabd289a484bef9ed8be42d2ed46c35c68c44683bd", "975f1638a743ef53f1b80a8440159047f79ce484f28704b61479e119acc0a503"] -pillow = ["047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", "0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", "12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", "248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", "27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", "285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", "384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", "38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", "4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", "4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", "4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", "59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", "5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", "5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", "6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", "7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", "809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", "83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", "846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", "9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", "a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", "ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", "b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", "bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", "c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", "c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", "c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", "c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", "e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", "e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"] -pluggy = ["15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"] -psycopg2 = ["4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677", "47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d", "72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b", "8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c", "893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0", "92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f", "965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4", "9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38", "b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6", "dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b", "ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151", "ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a", "f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"] -py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"] -pyasn1 = ["014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", "03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", "0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", "08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", "39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", "6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", "78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", "7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", "99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", "aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", "e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", "fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"] -pyasn1-modules = ["0c35a52e00b672f832e5846826f1fb7507907f7d52fba6faa9e3c4cbe874fe4b", "13a6955947d8a554de78fc305a4d651f20fb5580b88612a5f0661d4f189d27ac", "233f55c840e821e76828262db976ac894b285909d22d060c2bdb522e7bf28cc6", "24d54188cb7abd750e0a2cba61b7b46a75608175a0c3c1b1eee08322915d8d21", "27581362b4253b9c999882a64df974124cde12be0bf2c04148a0d68bc6bbb7b8", "33c220a2701032261a23eea6e9881404ac6fc7ff96f183b5353fea8fc8962547", "64f6aecf26e93f6a3ba3725b4eb9f532551747d7a63ca9ff43aef12f4bf11eac", "7b4edf07ca2f759d7cf693184be09f22e067c2eb52b03c770d0a2e9de1c67dfd", "9b972f81f59d896cebb9ebb1d44296f1acb28bf7869443c37551f4eed8d74f83", "9ca5e376a6d9dee35bb3a62608dfa2e6698798aa6b8db3c7afd0eb31af0d63c7", "b6ada4f840fe51abf5a6bd545b45bf537bea62221fa0dde2e8a553ed9f06a4e3", "c14b107a67ee36a7f183ae9f4803ffde4a03b67f3192eab0a62e851af71371d3", "eaf35047a0b068e3e0c2a99618b13b65c98c329661daa78c9d44a4ef0fe8139e"] -pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] -pycryptodome = ["042ae873baadd0c33b4d699a5c5b976ade3233a979d972f98ca82314632d868c", "0502876279772b1384b660ccc91563d04490d562799d8e2e06b411e2d81128a9", "2de33ed0a95855735d5a0fc0c39603314df9e78ee8bbf0baa9692fb46b3b8bbb", "319e568baf86620b419d53063b18c216abf924875966efdfe06891b987196a45", "4372ec7518727172e1605c0843cdc5375d4771e447b8148c787b860260aae151", "48821950ffb9c836858d8fa09d7840b6df52eadd387a3c5acece55cb387743f9", "4b9533d4166ca07abdd49ce9d516666b1df944997fe135d4b21ac376aa624aff", "54456cf85130e01674d21fb1ab89ffccacb138a8ade88d72fa2b0ac898d2798b", "56fdd0e425f1b8fd3a00b6d96351f86226674974814c50534864d0124d48871f", "57b1b707363490c495ad0eeb38bd1b0e1697c497af25fad78d3a1ebf0477fd5b", "5c485ed6e9718ebcaa81138fa70ace9c563d202b56a8cee119b4085b023931f5", "63c103a22cbe9752f6ea9f1a0de129995bad91c4d03a66c67cffcf6ee0c9f1e1", "68fab8455efcbfe87c5d75015476f9b606227ffe244d57bfd66269451706e899", "6c2720696b10ae356040e888bde1239b8957fe18885ccf5e7b4e8dec882f0856", "72166c2ac520a5dbd2d90208b9c279161ec0861662a621892bd52fb6ca13ab91", "7c52308ac5b834331b2f107a490b2c27de024a229b61df4cdc5c131d563dfe98", "87d8d85b4792ca5e730fb7a519fbc3ed976c59dcf79c5204589c59afd56b9926", "896e9b6fd0762aa07b203c993fbbee7a1f1a4674c6886afd7bfa86f3d1be98a8", "8a799bea3c6617736e914a2e77c409f52893d382f619f088f8a80e2e21f573c1", "9d9945ac8375d5d8e60bd2a2e1df5882eaa315522eedf3ca868b1546dfa34eba", "9ef966c727de942de3e41aa8462c4b7b4bca70f19af5a3f99e31376589c11aac", "a168e73879619b467072509a223282a02c8047d932a48b74fbd498f27224aa04", "a30f501bbb32e01a49ef9e09ca1260e5ab49bf33a257080ec553e08997acc487", "a8ca2450394d3699c9f15ef25e8de9a24b401933716a1e39d37fa01f5fe3c58b", "aec4d42deb836b8fb3ba32f2ba1ef0d33dd3dc9d430b1479ee7a914490d15b5e", "b4af098f2a50f8d048ab12cabb59456585c0acf43d90ee79782d2d6d0ed59dba", "b55c60c321ac91945c60a40ac9896ac7a3d432bb3e8c14006dfd82ad5871c331", "c53348358408d94869059e16fba5ff3bef8c52c25b18421472aba272b9bb450f", "cbfd97f9e060f0d30245cd29fa267a9a84de9da97559366fca0a3f7655acc63f", "d3fe3f33ad52bf0c19ee6344b695ba44ffbfa16f3c29ca61116b48d97bd970fb", "e3a79a30d15d9c7c284a7734036ee8abdb5ca3a6f5774d293cdc9e1358c1dc10", "eec0689509389f19875f66ae8dedd59f982240cdab31b9f78a8dc266011df93a"] -pydocstyle = ["7f2b7e70c4e6951d0f2aff445b670a336a28c56c0a40275dc8620a93ac2140c3", "d85eb88a0650756ea44d91917c1eb3a20903eeb163285774721a4807190fce24"] -pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] -pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"] -pyjwt = ["5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", "8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"] -pylama = ["9bae53ef9c1a431371d6a8dca406816a60d547147b60a4934721898f553b7d8f", "fd61c11872d6256b019ef1235be37b77c922ef37ac9797df6bd489996dddeb15"] -pyparsing = ["20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", "4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"] -pytest = ["63344a2e3bce2e4d522fd62b4fdebb647c019f1f9e4ca075debbd13219db4418", "f67403f33b2b1d25a6756184077394167fe5e2f9d8bdaab30707d19ccec35427"] -pytest-cov = ["cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b", "cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"] -pytest-django = ["17592f06d51c2ef4b7a0fb24aa32c8b6998506a03c8439606cb96db160106659", "ef3d15b35ed7e46293475e6f282e71a53bcd8c6f87bdc5d5e7ad0f4d49352127"] -pytest-django-testing-postgresql = ["78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45", "78e52e3d1b0ef5f906d5d69247dd6ac7dfb10d840bd81abab92f3f8c30872cd3"] -python-box = ["694a7555e3ff9fbbce734bbaef3aad92b8e4ed0659d3ed04d56b6a0a0eff26a9", "a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10"] -python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"] -python-dotenv = ["debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093", "f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"] -python-ldap = ["7d1c4b15375a533564aad3d3deade789221e450052b21ebb9720fb822eccdb8e"] -python-memcached = ["4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594", "a2e28637be13ee0bf1a8b6843e7490f9456fd3f2a4cb60471733c7b5d5557e4f"] -pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] -pyyaml = ["0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", "35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", "38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", "483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", "4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", "7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", "8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", "c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", "e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", "ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"] -qrcode = ["3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5", "505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"] -radon = ["0cde1953547a164d24420ed6ccdfa18b61f1457b96a2b99ff0de76b22d504a0f", "ee20308ce8bae7a89b067425b63b141a0077632ab318d5288da649c830882b3d"] -requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"] -scramp = ["475aa6296deb2737b86e9df9098e8eca0f30c8ad1cc0a8adadb99ef012a5ceba", "e09d2a9be5adeb94cbeb56fc54a61fc5f5b6e140e679b2b60d1f7a8d6478d906"] -six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"] -snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"] -soupsieve = ["bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", "e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"] -sphinx = ["3b16e48e791a322d584489ab28d8800652123d1fbfdd173e2965a31d40bf22d7", "559c1a8ed1365a982f77650720b41114414139a635692a23c2990824d0a84cf2"] -sphinx-autodoc-typehints = ["27c9e6ef4f4451766ab8d08b2d8520933b97beb21c913f3df9ab2e59b56e6c6c", "a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"] -sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"] -sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"] -sphinxcontrib-django = ["95831d5d58f780010b0255f298ea03d6bbf971c0f94f631268abd4320400b7db", "f5bb52d20b64b51087b44247789adaebfc51fa2fad71bfed42c4ca6c05723838"] -sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"] -sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"] -sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"] -sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"] -sqlparse = ["40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", "7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"] -"testing.common.database" = ["965d80b2985315325dc358c3061b174a712f4d4d5bf6a80b58b11f9a1dd86d73", "e3ed492bf480a87f271f74c53b262caf5d85c8bc09989a8f534fa2283ec52492"] -"testing.postgresql" = ["1b41daeb98dfc8cd4a584bb91e8f5f4ab182993870f95257afe5f1ba6151a598", "8e1a69760369a7a8ffe63a66b6d95a5cd82db2fb976e4a8f85ffd24fbfc447d8"] -text-unidecode = ["1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", "bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"] -toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] -tox = ["7efd010a98339209f3a8292f02909b51c58417bfc6838ab7eca14cf90f96117a", "8dd653bf0c6716a435df363c853cad1f037f9d5fddd0abc90d0f48ad06f39d03"] -tqdm = ["895796ea8df435b6f502bf122f2b2034a3d48e6d8ff52175606ac1051b0e3e12", "e405d16c98fcf30725d0c9d493ed07302a18846b5452de5253030ccd18996f87"] -twilio = ["da282a9c02bd9dfb190b798528b478833d8d28cb51464e8c45da0f0794384cde"] -typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] -typing-extensions = ["091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", "910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", "cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"] -urllib3 = ["a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"] -virtualenv = ["116655188441670978117d0ebb6451eb6a7526f9ae0796cc0dee6bd7356909b0", "b57776b44f91511866594e477dd10e76a6eb44439cdd7f06dcd30ba4c5bd854f"] -wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] -yubiotp = ["7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2", "de83b1560226e38b5923f6ab919f962c8c2abb7c722104cb45b2b6db2ac86e40"] -zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] +[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"}, +] +appdirs = [ + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, +] +asgiref = [ + {file = "asgiref-3.2.3-py2.py3-none-any.whl", hash = "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"}, + {file = "asgiref-3.2.3.tar.gz", hash = "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0"}, +] +atomicwrites = [ + {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, + {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +babel = [ + {file = "Babel-2.7.0-py2.py3-none-any.whl", hash = "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab"}, + {file = "Babel-2.7.0.tar.gz", hash = "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"}, +] +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.8.2-py2-none-any.whl", hash = "sha256:e1505eeed31b0f4ce2dbb3bc8eb256c04cc2b3b72af7d551a4ab6efd5cbe5dae"}, + {file = "beautifulsoup4-4.8.2-py3-none-any.whl", hash = "sha256:9fbb4d6e48ecd30bcacc5b63b94088192dcda178513b2ae3c394229f8911b887"}, + {file = "beautifulsoup4-4.8.2.tar.gz", hash = "sha256:05fd825eb01c290877657a56df4c6e4c311b3965bda790c613a3d6fb01a5462a"}, +] +black = [ + {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, + {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +] +certifi = [ + {file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"}, + {file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"}, +] +chardet = [ + {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, + {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +] +click = [ + {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, + {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +colour = [ + {file = "colour-0.1.5-py2.py3-none-any.whl", hash = "sha256:33f6db9d564fadc16e59921a56999b79571160ce09916303d35346dddc17978c"}, + {file = "colour-0.1.5.tar.gz", hash = "sha256:af20120fefd2afede8b001fbef2ea9da70ad7d49fafdb6489025dae8745c3aee"}, +] +configobj = [ + {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"}, +] +coverage = [ + {file = "coverage-5.0.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:c90bda74e16bcd03861b09b1d37c0a4158feda5d5a036bb2d6e58de6ff65793e"}, + {file = "coverage-5.0.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:bb3d29df5d07d5399d58a394d0ef50adf303ab4fbf66dfd25b9ef258effcb692"}, + {file = "coverage-5.0.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1ca43dbd739c0fc30b0a3637a003a0d2c7edc1dd618359d58cc1e211742f8bd1"}, + {file = "coverage-5.0.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:591506e088901bdc25620c37aec885e82cc896528f28c57e113751e3471fc314"}, + {file = "coverage-5.0.1-cp27-cp27m-win32.whl", hash = "sha256:a50b0888d8a021a3342d36a6086501e30de7d840ab68fca44913e97d14487dc1"}, + {file = "coverage-5.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c792d3707a86c01c02607ae74364854220fb3e82735f631cd0a345dea6b4cee5"}, + {file = "coverage-5.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f425f50a6dd807cb9043d15a4fcfba3b5874a54d9587ccbb748899f70dc18c47"}, + {file = "coverage-5.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:25b8f60b5c7da71e64c18888f3067d5b6f1334b9681876b2fb41eea26de881ae"}, + {file = "coverage-5.0.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7362a7f829feda10c7265b553455de596b83d1623b3d436b6d3c51c688c57bf6"}, + {file = "coverage-5.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:fcd4459fe35a400b8f416bc57906862693c9f88b66dc925e7f2a933e77f6b18b"}, + {file = "coverage-5.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:40fbfd6b044c9db13aeec1daf5887d322c710d811f944011757526ef6e323fd9"}, + {file = "coverage-5.0.1-cp35-cp35m-win32.whl", hash = "sha256:7f2675750c50151f806070ec11258edf4c328340916c53bac0adbc465abd6b1e"}, + {file = "coverage-5.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:24bcfa86fd9ce86b73a8368383c39d919c497a06eebb888b6f0c12f13e920b1a"}, + {file = "coverage-5.0.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:eeafb646f374988c22c8e6da5ab9fb81367ecfe81c70c292623373d2a021b1a1"}, + {file = "coverage-5.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:2ca2cd5264e84b2cafc73f0045437f70c6378c0d7dbcddc9ee3fe192c1e29e5d"}, + {file = "coverage-5.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2cc707fc9aad2592fc686d63ef72dc0031fc98b6fb921d2f5395d9ab84fbc3ef"}, + {file = "coverage-5.0.1-cp36-cp36m-win32.whl", hash = "sha256:04b961862334687549eb91cd5178a6fbe977ad365bddc7c60f2227f2f9880cf4"}, + {file = "coverage-5.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:232f0b52a5b978288f0bbc282a6c03fe48cd19a04202df44309919c142b3bb9c"}, + {file = "coverage-5.0.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:cfce79ce41cc1a1dc7fc85bb41eeeb32d34a4cf39a645c717c0550287e30ff06"}, + {file = "coverage-5.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c9c6a1d1190c0b75ec7c0f339088309952b82ae8d67a79ff1319eb4e749b96"}, + {file = "coverage-5.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1cbb88b34187bdb841f2599770b7e6ff8e259dc3bb64fc7893acf44998acf5f8"}, + {file = "coverage-5.0.1-cp37-cp37m-win32.whl", hash = "sha256:ff3936dd5feaefb4f91c8c1f50a06c588b5dc69fba4f7d9c79a6617ad80bb7df"}, + {file = "coverage-5.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:65bead1ac8c8930cf92a1ccaedcce19a57298547d5d1db5c9d4d068a0675c38b"}, + {file = "coverage-5.0.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:348630edea485f4228233c2f310a598abf8afa5f8c716c02a9698089687b6085"}, + {file = "coverage-5.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:960d7f42277391e8b1c0b0ae427a214e1b31a1278de6b73f8807b20c2e913bba"}, + {file = "coverage-5.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0101888bd1592a20ccadae081ba10e8b204d20235d18d05c6f7d5e904a38fc10"}, + {file = "coverage-5.0.1-cp38-cp38m-win32.whl", hash = "sha256:c0fff2733f7c2950f58a4fd09b5db257b00c6fec57bf3f68c5bae004d804b407"}, + {file = "coverage-5.0.1-cp38-cp38m-win_amd64.whl", hash = "sha256:5f622f19abda4e934938e24f1d67599249abc201844933a6f01aaa8663094489"}, + {file = "coverage-5.0.1-cp39-cp39m-win32.whl", hash = "sha256:2714160a63da18aed9340c70ed514973971ee7e665e6b336917ff4cca81a25b1"}, + {file = "coverage-5.0.1-cp39-cp39m-win_amd64.whl", hash = "sha256:b7dbc5e8c39ea3ad3db22715f1b5401cd698a621218680c6daf42c2f9d36e205"}, + {file = "coverage-5.0.1.tar.gz", hash = "sha256:5ac71bba1e07eab403b082c4428f868c1c9e26a21041436b4905c4c3d4e49b08"}, +] +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.0.1-py3-none-any.whl", hash = "sha256:b61295749be7e1c42467c55bcabdaee9fbe9496fdf9ed2e22cef44d9de2ff953"}, + {file = "Django-3.0.1.tar.gz", hash = "sha256:315b11ea265dd15348d47f2cbb044ef71da2018f6e582fed875c889758e6f844"}, +] +django-any-js = [ + {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"}, +] +django-appconf = [ + {file = "django-appconf-1.0.3.tar.gz", hash = "sha256:35f13ca4d567f132b960e2cd4c832c2d03cb6543452d34e29b7ba10371ba80e3"}, + {file = "django_appconf-1.0.3-py2.py3-none-any.whl", hash = "sha256:c98a7af40062e996b921f5962a1c4f3f0c979fa7885f7be4710cceb90ebe13a6"}, +] +django-auth-ldap = [ + {file = "django-auth-ldap-2.1.0.tar.gz", hash = "sha256:5f48232c85ddfa33e3573153e6080526ac2eef5e7ec9cf42b5c4ba3c62afb96d"}, + {file = "django_auth_ldap-2.1.0-py3-none-any.whl", hash = "sha256:4d68d21058bd57a316a9e1fcd7a36d0f25d054d4d9d9ec85f766a4991176b454"}, +] +django-bootstrap4 = [ + {file = "django-bootstrap4-1.1.1.tar.gz", hash = "sha256:39f97cbce85eb66f6d76be2029bae171bd3863d0c6932b1c2dae7f299c569b90"}, + {file = "django_bootstrap4-1.1.1-py3-none-any.whl", hash = "sha256:0fcd84f8414a58b43df0b331c00c8b2f1786ae28f75f419b4d33b06fca43e0d1"}, +] +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-common-helpers = [ + {file = "django-common-helpers-0.9.2.tar.gz", hash = "sha256:2d56be6fa261d829a6a224f189bf276267b9082a17d613fe5f015dd4d65c17b4"}, +] +django-cron = [ + {file = "django-cron-0.5.1.tar.gz", hash = "sha256:08d22708c8b2ecab8cda989019a66c7e1e2424c59d822796fd45abf7731d261d"}, +] +django-dbbackup = [ + {file = "django-dbbackup-3.2.0.tar.gz", hash = "sha256:9470e5d8bdaee4feb878b1b66c59eb9b27a131cccd648bf7cbfe70930acd4fc0"}, +] +django-dbsettings = [ + {file = "django-dbsettings-1.0.0.tar.gz", hash = "sha256:42b04dffd3bc90d91718c822f1e0212d9368e8efe340f7ef09517b5fb1cf49f5"}, +] +django-debug-toolbar = [ + {file = "django-debug-toolbar-2.1.tar.gz", hash = "sha256:24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8"}, + {file = "django_debug_toolbar-2.1-py3-none-any.whl", hash = "sha256:77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"}, +] +django-easy-audit = [ + {file = "django-easy-audit-1.1.1.tar.gz", hash = "sha256:4b40a30599fe721eb0a9946f5023254fa0904d531c9f4adb23ee52601efaf89b"}, + {file = "django_easy_audit-1.1.1-py2.py3-none-any.whl", hash = "sha256:1c5d5e6d6a33f50f696ed53cdaf51de0a4ae2f110ef8c41b33bc139b737729a6"}, +] +django-formtools = [ + {file = "django-formtools-2.2.tar.gz", hash = "sha256:c5272c03c1cd51b2375abf7397a199a3148a9fbbf2f100e186467a84025d13b2"}, + {file = "django_formtools-2.2-py2.py3-none-any.whl", hash = "sha256:304fa777b8ef9e0693ce7833f885cb89ba46b0e46fc23b01176900a93f46742f"}, +] +django-hattori = [ + {file = "django-hattori-0.2.1.tar.gz", hash = "sha256:6953d40881317252f19f62c4e7fe8058924b852c7498bc42beb7bc4d268c252c"}, + {file = "django_hattori-0.2.1-py2.py3-none-any.whl", hash = "sha256:e529ed7af8fc34a0169c797c477672b687a205a56f3f5206f90c260acb83b7ac"}, +] +django-image-cropping = [ + {file = "django-image-cropping-1.3.0.tar.gz", hash = "sha256:5c102d87bc66de025517ad06e485c100f73313ebf725e7482a728944276f6463"}, +] +django-impersonate = [ + {file = "django-impersonate-1.4.1.tar.gz", hash = "sha256:63b62d06f93b0318698c68f7314c78473914c262d4164eb66ad860bb83e04771"}, +] +django-ipware = [ + {file = "django-ipware-2.1.0.tar.gz", hash = "sha256:a7c7a8fd019dbdc9c357e6e582f65034e897572fc79a7e467674efa8aef9d00b"}, +] +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"}, +] +django-menu-generator = [ + {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"}, +] +django-middleware-global-request = [ + {file = "django-middleware-global-request-0.1.2.tar.gz", hash = "sha256:f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"}, +] +django-otp = [ + {file = "django-otp-0.7.4.tar.gz", hash = "sha256:1b6025bbbd2517b7c246828b1d11c83d53567904836ae6d57bc0058f3cd18b50"}, + {file = "django_otp-0.7.4-py2.py3-none-any.whl", hash = "sha256:76a698466178ce40473726ffd8c33f68d1c47f27c53f67fa4aeeb6fdde74d37b"}, +] +django-otp-yubikey = [ + {file = "django-otp-yubikey-0.5.2.tar.gz", hash = "sha256:f0b1881562fb42ee9f12c28d284cbdb90d1f0383f2d53a595373b080a19bc261"}, + {file = "django_otp_yubikey-0.5.2-py2.py3-none-any.whl", hash = "sha256:26b12c763b37e99b95b8b8a54d06d8d54c3774eb26133a452f54558033de732b"}, +] +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-sass-processor = [ + {file = "django-sass-processor-0.7.5.tar.gz", hash = "sha256:9267e5fcc7fcde2ec0c7d6ad045b4c6c7e9aea92498ed1725312035a5469b410"}, +] +django-select2 = [ + {file = "django-select2-7.1.2.tar.gz", hash = "sha256:bcd7e5fc920ceee0e63c36372d8e75bd2254d8a37ac67fc16581634766680d30"}, + {file = "django_select2-7.1.2-py2.py3-none-any.whl", hash = "sha256:4c8c4c7c4afee12c17e64d1eabf77af4cd2666a784cba1e74455ce759dd29393"}, +] +django-settings-context-processor = [ + {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"}, +] +django-stubs = [ + {file = "django-stubs-1.4.0.tar.gz", hash = "sha256:a84860e02d5ada1a96e4c6f6800be4729ea3df354ff1156f0616db647dc2fe9e"}, + {file = "django_stubs-1.4.0-py3-none-any.whl", hash = "sha256:9a1bd302c42b1132e1537ba2315b94de01e673a1926ac2b3e1523ed7b81411c8"}, +] +django-tables2 = [ + {file = "django-tables2-2.2.1.tar.gz", hash = "sha256:0d9b17f5c030ba1b5fcaeb206d8397bf58f1fdfc6beaf56e7874841b8647aa94"}, + {file = "django_tables2-2.2.1-py2.py3-none-any.whl", hash = "sha256:6afa0496695e15b332e98537265d09fe01a55b28c75a85323d8e6b0dc2350280"}, +] +django-two-factor-auth = [ + {file = "django-two-factor-auth-1.10.0.tar.gz", hash = "sha256:3c3af3cd747462be18e7494c4068a2bdc606d7a2d2b2914f8d4590fc80995a71"}, + {file = "django_two_factor_auth-1.10.0-py2.py3-none-any.whl", hash = "sha256:0945260fa84e4522d8fa951c35e401616579fd8564938441614399dc588a1c1f"}, +] +django-yarnpkg = [ + {file = "django-yarnpkg-6.0.0.tar.gz", hash = "sha256:010af70049cca94496d4c96ca45e62f13339edd1c22653ab8bfe055acbccd41b"}, + {file = "django_yarnpkg-6.0.0-py3-none-any.whl", hash = "sha256:0d63c7b17e4b9c6c144c4093de3877ce70152f957b36fd7a50b259dc500a4948"}, +] +docutils = [ + {file = "docutils-0.15.2-py2-none-any.whl", hash = "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827"}, + {file = "docutils-0.15.2-py3-none-any.whl", hash = "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0"}, + {file = "docutils-0.15.2.tar.gz", hash = "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"}, +] +dparse = [ + {file = "dparse-0.4.1-py2-none-any.whl", hash = "sha256:cef95156fa0adedaf042cd42f9990974bec76f25dfeca4dc01f381a243d5aa5b"}, + {file = "dparse-0.4.1.tar.gz", hash = "sha256:00a5fdfa900629e5159bf3600d44905b333f4059a3366f28e0dbd13eeab17b19"}, +] +dynaconf = [ + {file = "dynaconf-2.2.2-py2.py3-none-any.whl", hash = "sha256:3cfc1ad7efae08b9cf91d81043a3230175e74e01157d875ce5ec6709bf4e6b9d"}, + {file = "dynaconf-2.2.2.tar.gz", hash = "sha256:4bac78b432e090d8ed66f1c23fb32e03ca91a590bf0a51ac36137e0e45ac31ca"}, +] +easy-thumbnails = [ + {file = "easy-thumbnails-2.7.tar.gz", hash = "sha256:e4e7a0dd4001f56bfd4058428f2c91eafe27d33ef3b8b33ac4e013b159b9ff91"}, +] +entrypoints = [ + {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, + {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, +] +faker = [ + {file = "Faker-3.0.0-py2.py3-none-any.whl", hash = "sha256:202ad3b2ec16ae7c51c02904fb838831f8d2899e61bf18db1e91a5a582feab11"}, + {file = "Faker-3.0.0.tar.gz", hash = "sha256:92c84a10bec81217d9cb554ee12b3838c8986ce0b5d45f72f769da22e4bb5432"}, +] +flake8 = [ + {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, + {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, +] +flake8-bandit = [ + {file = "flake8_bandit-2.1.2.tar.gz", hash = "sha256:687fc8da2e4a239b206af2e54a90093572a60d0954f3054e23690739b0b0de3b"}, +] +flake8-black = [ + {file = "flake8-black-0.1.1.tar.gz", hash = "sha256:56f85aaa5a83f06a3f61e680e3b50f156b5e557ebdcb964d823d86f4c108b0c8"}, +] +flake8-builtins = [ + {file = "flake8-builtins-1.4.2.tar.gz", hash = "sha256:c44415fb19162ef3737056e700d5b99d48c3612a533943b4e16419a5d3de3a64"}, + {file = "flake8_builtins-1.4.2-py2.py3-none-any.whl", hash = "sha256:29bc0f7e68af481d088f5c96f8aeb02520abdfc900500484e3af969f42a38a5f"}, +] +flake8-django = [ + {file = "flake8-django-0.0.4.tar.gz", hash = "sha256:7329ec2e2b8b194e8109639c534359014c79df4d50b14f4b85b8395edc5d6760"}, +] +flake8-docstrings = [ + {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"}, + {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"}, +] +flake8-fixme = [ + {file = "flake8-fixme-1.1.1.tar.gz", hash = "sha256:50cade07d27a4c30d4f12351478df87339e67640c83041b664724bda6d16f33a"}, + {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"}, +] +flake8-isort = [ + {file = "flake8-isort-2.8.0.tar.gz", hash = "sha256:64454d1f154a303cfe23ee715aca37271d4f1d299b2f2663f45b73bff14e36a9"}, + {file = "flake8_isort-2.8.0-py2.py3-none-any.whl", hash = "sha256:aa0c4d004e6be47e74f122f5b7f36554d0d78ad8bf99b497a460dedccaa7cce9"}, +] +flake8-mypy = [ + {file = "flake8-mypy-17.8.0.tar.gz", hash = "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18"}, + {file = "flake8_mypy-17.8.0-py35.py36-none-any.whl", hash = "sha256:cff009f4250e8391bf48990093cff85802778c345c8449d6498b62efefeebcbc"}, +] +flake8-polyfill = [ + {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, + {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, +] +flake8-rst-docstrings = [ + {file = "flake8-rst-docstrings-0.0.12.tar.gz", hash = "sha256:01d38327801781b26c3dfeb71ae37e5a02c5ca1b774a686f63feab8824ca6f9c"}, +] +gitdb2 = [ + {file = "gitdb2-2.0.6-py2.py3-none-any.whl", hash = "sha256:96bbb507d765a7f51eb802554a9cfe194a174582f772e0d89f4e87288c288b7b"}, + {file = "gitdb2-2.0.6.tar.gz", hash = "sha256:1b6df1433567a51a4a9c1a5a0de977aa351a405cc56d7d35f3388bad1f630350"}, +] +gitpython = [ + {file = "GitPython-3.0.5-py3-none-any.whl", hash = "sha256:c155c6a2653593ccb300462f6ef533583a913e17857cfef8fc617c246b6dc245"}, + {file = "GitPython-3.0.5.tar.gz", hash = "sha256:9c2398ffc3dcb3c40b27324b316f08a4f93ad646d5a6328cafbb871aa79f5e42"}, +] +idna = [ + {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, + {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, +] +imagesize = [ + {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, + {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.3.0-py2.py3-none-any.whl", hash = "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"}, + {file = "importlib_metadata-1.3.0.tar.gz", hash = "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45"}, +] +isort = [ + {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, + {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, +] +jinja2 = [ + {file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"}, + {file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"}, +] +libsass = [ + {file = "libsass-0.19.4-cp27-cp27m-macosx_10_14_intel.whl", hash = "sha256:74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32"}, + {file = "libsass-0.19.4-cp27-cp27m-win32.whl", hash = "sha256:50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f"}, + {file = "libsass-0.19.4-cp27-cp27m-win_amd64.whl", hash = "sha256:4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a"}, + {file = "libsass-0.19.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013"}, + {file = "libsass-0.19.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c"}, + {file = "libsass-0.19.4-cp35-cp35m-win32.whl", hash = "sha256:fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df"}, + {file = "libsass-0.19.4-cp35-cp35m-win_amd64.whl", hash = "sha256:fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a"}, + {file = "libsass-0.19.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c"}, + {file = "libsass-0.19.4-cp36-cp36m-win32.whl", hash = "sha256:c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404"}, + {file = "libsass-0.19.4-cp36-cp36m-win_amd64.whl", hash = "sha256:0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272"}, + {file = "libsass-0.19.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2"}, + {file = "libsass-0.19.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08"}, + {file = "libsass-0.19.4-cp37-cp37m-win32.whl", hash = "sha256:e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e"}, + {file = "libsass-0.19.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1"}, + {file = "libsass-0.19.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0"}, + {file = "libsass-0.19.4.tar.gz", hash = "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +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.0.2.tar.gz", hash = "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d"}, + {file = "more_itertools-8.0.2-py3-none-any.whl", hash = "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"}, +] +mypy = [ + {file = "mypy-0.761-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6"}, + {file = "mypy-0.761-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36"}, + {file = "mypy-0.761-cp35-cp35m-win_amd64.whl", hash = "sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72"}, + {file = "mypy-0.761-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2"}, + {file = "mypy-0.761-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0"}, + {file = "mypy-0.761-cp36-cp36m-win_amd64.whl", hash = "sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474"}, + {file = "mypy-0.761-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a"}, + {file = "mypy-0.761-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749"}, + {file = "mypy-0.761-cp37-cp37m-win_amd64.whl", hash = "sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1"}, + {file = "mypy-0.761-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7"}, + {file = "mypy-0.761-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1"}, + {file = "mypy-0.761-cp38-cp38-win_amd64.whl", hash = "sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b"}, + {file = "mypy-0.761-py3-none-any.whl", hash = "sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217"}, + {file = "mypy-0.761.tar.gz", hash = "sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf"}, +] +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-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, + {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, +] +pathspec = [ + {file = "pathspec-0.6.0.tar.gz", hash = "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c"}, +] +pbr = [ + {file = "pbr-5.4.4-py2.py3-none-any.whl", hash = "sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"}, + {file = "pbr-5.4.4.tar.gz", hash = "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b"}, +] +pg8000 = [ + {file = "pg8000-1.13.2-py3-none-any.whl", hash = "sha256:e4c2b173178ba41bba19366b9cdeced91fecec2caf31c7ece719138633213cca"}, + {file = "pg8000-1.13.2.tar.gz", hash = "sha256:eebcb4176a7e407987e525a07454882f611985e0becb2b73f76efb93bbdc0aab"}, +] +phonenumbers = [ + {file = "phonenumbers-8.11.1-py2.py3-none-any.whl", hash = "sha256:aaa19bc1f2c7efbf7a94be33558e0c5b71620377c9271692d3e314c558962460"}, + {file = "phonenumbers-8.11.1.tar.gz", hash = "sha256:239507184ee5b1b83557005af1d5fcce70f83ae18f5dff45b94a67226db10d63"}, +] +pillow = [ + {file = "Pillow-6.2.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9"}, + {file = "Pillow-6.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab"}, + {file = "Pillow-6.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96"}, + {file = "Pillow-6.2.1-cp27-cp27m-win32.whl", hash = "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"}, + {file = "Pillow-6.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9"}, + {file = "Pillow-6.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547"}, + {file = "Pillow-6.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb"}, + {file = "Pillow-6.2.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871"}, + {file = "Pillow-6.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a"}, + {file = "Pillow-6.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573"}, + {file = "Pillow-6.2.1-cp35-cp35m-win32.whl", hash = "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340"}, + {file = "Pillow-6.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b"}, + {file = "Pillow-6.2.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5"}, + {file = "Pillow-6.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08"}, + {file = "Pillow-6.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5"}, + {file = "Pillow-6.2.1-cp36-cp36m-win32.whl", hash = "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031"}, + {file = "Pillow-6.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2"}, + {file = "Pillow-6.2.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a"}, + {file = "Pillow-6.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e"}, + {file = "Pillow-6.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71"}, + {file = "Pillow-6.2.1-cp37-cp37m-win32.whl", hash = "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291"}, + {file = "Pillow-6.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281"}, + {file = "Pillow-6.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c"}, + {file = "Pillow-6.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa"}, + {file = "Pillow-6.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41"}, + {file = "Pillow-6.2.1-cp38-cp38-win32.whl", hash = "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75"}, + {file = "Pillow-6.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e"}, + {file = "Pillow-6.2.1-pp272-pypy_41-win32.whl", hash = "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12"}, + {file = "Pillow-6.2.1-pp372-pp372-win32.whl", hash = "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132"}, + {file = "Pillow-6.2.1.tar.gz", hash = "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +psycopg2 = [ + {file = "psycopg2-2.8.4-cp27-cp27m-win32.whl", hash = "sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b"}, + {file = "psycopg2-2.8.4-cp27-cp27m-win_amd64.whl", hash = "sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0"}, + {file = "psycopg2-2.8.4-cp34-cp34m-win32.whl", hash = "sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38"}, + {file = "psycopg2-2.8.4-cp34-cp34m-win_amd64.whl", hash = "sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6"}, + {file = "psycopg2-2.8.4-cp35-cp35m-win32.whl", hash = "sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a"}, + {file = "psycopg2-2.8.4-cp35-cp35m-win_amd64.whl", hash = "sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4"}, + {file = "psycopg2-2.8.4-cp36-cp36m-win32.whl", hash = "sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151"}, + {file = "psycopg2-2.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b"}, + {file = "psycopg2-2.8.4-cp37-cp37m-win32.whl", hash = "sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c"}, + {file = "psycopg2-2.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d"}, + {file = "psycopg2-2.8.4-cp38-cp38-win32.whl", hash = "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677"}, + {file = "psycopg2-2.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f"}, + {file = "psycopg2-2.8.4.tar.gz", hash = "sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6"}, +] +py = [ + {file = "py-1.8.0-py2.py3-none-any.whl", hash = "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa"}, + {file = "py-1.8.0.tar.gz", hash = "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"}, +] +pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] +pyasn1-modules = [ + {file = "pyasn1-modules-0.2.7.tar.gz", hash = "sha256:0c35a52e00b672f832e5846826f1fb7507907f7d52fba6faa9e3c4cbe874fe4b"}, + {file = "pyasn1_modules-0.2.7-py2.4.egg", hash = "sha256:233f55c840e821e76828262db976ac894b285909d22d060c2bdb522e7bf28cc6"}, + {file = "pyasn1_modules-0.2.7-py2.5.egg", hash = "sha256:9ca5e376a6d9dee35bb3a62608dfa2e6698798aa6b8db3c7afd0eb31af0d63c7"}, + {file = "pyasn1_modules-0.2.7-py2.6.egg", hash = "sha256:27581362b4253b9c999882a64df974124cde12be0bf2c04148a0d68bc6bbb7b8"}, + {file = "pyasn1_modules-0.2.7-py2.7.egg", hash = "sha256:13a6955947d8a554de78fc305a4d651f20fb5580b88612a5f0661d4f189d27ac"}, + {file = "pyasn1_modules-0.2.7-py2.py3-none-any.whl", hash = "sha256:b6ada4f840fe51abf5a6bd545b45bf537bea62221fa0dde2e8a553ed9f06a4e3"}, + {file = "pyasn1_modules-0.2.7-py3.1.egg", hash = "sha256:9b972f81f59d896cebb9ebb1d44296f1acb28bf7869443c37551f4eed8d74f83"}, + {file = "pyasn1_modules-0.2.7-py3.2.egg", hash = "sha256:64f6aecf26e93f6a3ba3725b4eb9f532551747d7a63ca9ff43aef12f4bf11eac"}, + {file = "pyasn1_modules-0.2.7-py3.3.egg", hash = "sha256:33c220a2701032261a23eea6e9881404ac6fc7ff96f183b5353fea8fc8962547"}, + {file = "pyasn1_modules-0.2.7-py3.4.egg", hash = "sha256:24d54188cb7abd750e0a2cba61b7b46a75608175a0c3c1b1eee08322915d8d21"}, + {file = "pyasn1_modules-0.2.7-py3.5.egg", hash = "sha256:7b4edf07ca2f759d7cf693184be09f22e067c2eb52b03c770d0a2e9de1c67dfd"}, + {file = "pyasn1_modules-0.2.7-py3.6.egg", hash = "sha256:eaf35047a0b068e3e0c2a99618b13b65c98c329661daa78c9d44a4ef0fe8139e"}, + {file = "pyasn1_modules-0.2.7-py3.7.egg", hash = "sha256:c14b107a67ee36a7f183ae9f4803ffde4a03b67f3192eab0a62e851af71371d3"}, +] +pycodestyle = [ + {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, + {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, +] +pycryptodome = [ + {file = "pycryptodome-3.9.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:6c2720696b10ae356040e888bde1239b8957fe18885ccf5e7b4e8dec882f0856"}, + {file = "pycryptodome-3.9.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:5c485ed6e9718ebcaa81138fa70ace9c563d202b56a8cee119b4085b023931f5"}, + {file = "pycryptodome-3.9.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:56fdd0e425f1b8fd3a00b6d96351f86226674974814c50534864d0124d48871f"}, + {file = "pycryptodome-3.9.4-cp27-cp27m-win32.whl", hash = "sha256:2de33ed0a95855735d5a0fc0c39603314df9e78ee8bbf0baa9692fb46b3b8bbb"}, + {file = "pycryptodome-3.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:eec0689509389f19875f66ae8dedd59f982240cdab31b9f78a8dc266011df93a"}, + {file = "pycryptodome-3.9.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:68fab8455efcbfe87c5d75015476f9b606227ffe244d57bfd66269451706e899"}, + {file = "pycryptodome-3.9.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4b9533d4166ca07abdd49ce9d516666b1df944997fe135d4b21ac376aa624aff"}, + {file = "pycryptodome-3.9.4-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:d3fe3f33ad52bf0c19ee6344b695ba44ffbfa16f3c29ca61116b48d97bd970fb"}, + {file = "pycryptodome-3.9.4-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:319e568baf86620b419d53063b18c216abf924875966efdfe06891b987196a45"}, + {file = "pycryptodome-3.9.4-cp34-cp34m-win32.whl", hash = "sha256:042ae873baadd0c33b4d699a5c5b976ade3233a979d972f98ca82314632d868c"}, + {file = "pycryptodome-3.9.4-cp34-cp34m-win_amd64.whl", hash = "sha256:a30f501bbb32e01a49ef9e09ca1260e5ab49bf33a257080ec553e08997acc487"}, + {file = "pycryptodome-3.9.4-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:b55c60c321ac91945c60a40ac9896ac7a3d432bb3e8c14006dfd82ad5871c331"}, + {file = "pycryptodome-3.9.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:9d9945ac8375d5d8e60bd2a2e1df5882eaa315522eedf3ca868b1546dfa34eba"}, + {file = "pycryptodome-3.9.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:4372ec7518727172e1605c0843cdc5375d4771e447b8148c787b860260aae151"}, + {file = "pycryptodome-3.9.4-cp35-cp35m-win32.whl", hash = "sha256:0502876279772b1384b660ccc91563d04490d562799d8e2e06b411e2d81128a9"}, + {file = "pycryptodome-3.9.4-cp35-cp35m-win_amd64.whl", hash = "sha256:72166c2ac520a5dbd2d90208b9c279161ec0861662a621892bd52fb6ca13ab91"}, + {file = "pycryptodome-3.9.4-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:b4af098f2a50f8d048ab12cabb59456585c0acf43d90ee79782d2d6d0ed59dba"}, + {file = "pycryptodome-3.9.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8a799bea3c6617736e914a2e77c409f52893d382f619f088f8a80e2e21f573c1"}, + {file = "pycryptodome-3.9.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7c52308ac5b834331b2f107a490b2c27de024a229b61df4cdc5c131d563dfe98"}, + {file = "pycryptodome-3.9.4-cp36-cp36m-win32.whl", hash = "sha256:63c103a22cbe9752f6ea9f1a0de129995bad91c4d03a66c67cffcf6ee0c9f1e1"}, + {file = "pycryptodome-3.9.4-cp36-cp36m-win_amd64.whl", hash = "sha256:54456cf85130e01674d21fb1ab89ffccacb138a8ade88d72fa2b0ac898d2798b"}, + {file = "pycryptodome-3.9.4-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:aec4d42deb836b8fb3ba32f2ba1ef0d33dd3dc9d430b1479ee7a914490d15b5e"}, + {file = "pycryptodome-3.9.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:896e9b6fd0762aa07b203c993fbbee7a1f1a4674c6886afd7bfa86f3d1be98a8"}, + {file = "pycryptodome-3.9.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:57b1b707363490c495ad0eeb38bd1b0e1697c497af25fad78d3a1ebf0477fd5b"}, + {file = "pycryptodome-3.9.4-cp37-cp37m-win32.whl", hash = "sha256:87d8d85b4792ca5e730fb7a519fbc3ed976c59dcf79c5204589c59afd56b9926"}, + {file = "pycryptodome-3.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e3a79a30d15d9c7c284a7734036ee8abdb5ca3a6f5774d293cdc9e1358c1dc10"}, + {file = "pycryptodome-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:48821950ffb9c836858d8fa09d7840b6df52eadd387a3c5acece55cb387743f9"}, + {file = "pycryptodome-3.9.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cbfd97f9e060f0d30245cd29fa267a9a84de9da97559366fca0a3f7655acc63f"}, + {file = "pycryptodome-3.9.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9ef966c727de942de3e41aa8462c4b7b4bca70f19af5a3f99e31376589c11aac"}, + {file = "pycryptodome-3.9.4-cp38-cp38-win32.whl", hash = "sha256:a8ca2450394d3699c9f15ef25e8de9a24b401933716a1e39d37fa01f5fe3c58b"}, + {file = "pycryptodome-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:c53348358408d94869059e16fba5ff3bef8c52c25b18421472aba272b9bb450f"}, + {file = "pycryptodome-3.9.4.tar.gz", hash = "sha256:a168e73879619b467072509a223282a02c8047d932a48b74fbd498f27224aa04"}, +] +pydocstyle = [ + {file = "pydocstyle-5.0.1-py3-none-any.whl", hash = "sha256:4167fe954b8f27ebbbef2fbcf73c6e8ad1e7bb31488fce44a69fdfc4b0cd0fae"}, + {file = "pydocstyle-5.0.1.tar.gz", hash = "sha256:a0de36e549125d0a16a72a8c8c6c9ba267750656e72e466e994c222f1b6e92cb"}, +] +pyflakes = [ + {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, + {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, +] +pygments = [ + {file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"}, + {file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"}, +] +pyjwt = [ + {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, + {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"}, +] +pyparsing = [ + {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, + {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, +] +pytest = [ + {file = "pytest-5.3.2-py3-none-any.whl", hash = "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4"}, + {file = "pytest-5.3.2.tar.gz", hash = "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa"}, +] +pytest-cov = [ + {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, + {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, +] +pytest-django = [ + {file = "pytest-django-3.7.0.tar.gz", hash = "sha256:17592f06d51c2ef4b7a0fb24aa32c8b6998506a03c8439606cb96db160106659"}, + {file = "pytest_django-3.7.0-py2.py3-none-any.whl", hash = "sha256:ef3d15b35ed7e46293475e6f282e71a53bcd8c6f87bdc5d5e7ad0f4d49352127"}, +] +pytest-django-testing-postgresql = [ + {file = "pytest-django-testing-postgresql-0.1.post0.tar.gz", hash = "sha256:78b0c58930084cb4393407b2e5a2a3b8734c627b841ecef7d62d39bbfb8e8a45"}, + {file = "pytest_django_testing_postgresql-0.1.post0-py3-none-any.whl", hash = "sha256:78e52e3d1b0ef5f906d5d69247dd6ac7dfb10d840bd81abab92f3f8c30872cd3"}, +] +pytest-sugar = [ + {file = "pytest-sugar-0.9.2.tar.gz", hash = "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"}, + {file = "pytest_sugar-0.9.2-py2.py3-none-any.whl", hash = "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283"}, +] +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-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.10.3.tar.gz", hash = "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"}, + {file = "python_dotenv-0.10.3-py2.py3-none-any.whl", hash = "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093"}, +] +python-ldap = [ + {file = "python-ldap-3.2.0.tar.gz", hash = "sha256:7d1c4b15375a533564aad3d3deade789221e450052b21ebb9720fb822eccdb8e"}, +] +python-memcached = [ + {file = "python-memcached-1.59.tar.gz", hash = "sha256:a2e28637be13ee0bf1a8b6843e7490f9456fd3f2a4cb60471733c7b5d5557e4f"}, + {file = "python_memcached-1.59-py2.py3-none-any.whl", hash = "sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594"}, +] +pytz = [ + {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, + {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, +] +pyyaml = [ + {file = "PyYAML-5.2-cp27-cp27m-win32.whl", hash = "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc"}, + {file = "PyYAML-5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"}, + {file = "PyYAML-5.2-cp35-cp35m-win32.whl", hash = "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15"}, + {file = "PyYAML-5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075"}, + {file = "PyYAML-5.2-cp36-cp36m-win32.whl", hash = "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31"}, + {file = "PyYAML-5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc"}, + {file = "PyYAML-5.2-cp37-cp37m-win32.whl", hash = "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04"}, + {file = "PyYAML-5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd"}, + {file = "PyYAML-5.2-cp38-cp38-win32.whl", hash = "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f"}, + {file = "PyYAML-5.2-cp38-cp38-win_amd64.whl", hash = "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803"}, + {file = "PyYAML-5.2.tar.gz", hash = "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c"}, +] +qrcode = [ + {file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"}, + {file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"}, +] +regex = [ + {file = "regex-2019.12.20-cp27-cp27m-win32.whl", hash = "sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8"}, + {file = "regex-2019.12.20-cp27-cp27m-win_amd64.whl", hash = "sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716"}, + {file = "regex-2019.12.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9"}, + {file = "regex-2019.12.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588"}, + {file = "regex-2019.12.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae"}, + {file = "regex-2019.12.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea"}, + {file = "regex-2019.12.20-cp36-cp36m-win32.whl", hash = "sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e"}, + {file = "regex-2019.12.20-cp36-cp36m-win_amd64.whl", hash = "sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"}, + {file = "regex-2019.12.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1"}, + {file = "regex-2019.12.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147"}, + {file = "regex-2019.12.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8"}, + {file = "regex-2019.12.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d"}, + {file = "regex-2019.12.20-cp37-cp37m-win32.whl", hash = "sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885"}, + {file = "regex-2019.12.20-cp37-cp37m-win_amd64.whl", hash = "sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b"}, + {file = "regex-2019.12.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540"}, + {file = "regex-2019.12.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63"}, + {file = "regex-2019.12.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3"}, + {file = "regex-2019.12.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd"}, + {file = "regex-2019.12.20-cp38-cp38-win32.whl", hash = "sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b"}, + {file = "regex-2019.12.20-cp38-cp38-win_amd64.whl", hash = "sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f"}, + {file = "regex-2019.12.20.tar.gz", hash = "sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e"}, +] +requests = [ + {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, + {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, +] +restructuredtext-lint = [ + {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"}, +] +safety = [ + {file = "safety-1.8.5-py2.py3-none-any.whl", hash = "sha256:0a3a8a178a9c96242b224f033ee8d1d130c0448b0e6622d12deaf37f6c3b4e59"}, + {file = "safety-1.8.5.tar.gz", hash = "sha256:5059f3ffab3648330548ea9c7403405bbfaf085b11235770825d14c58f24cb78"}, +] +scramp = [ + {file = "scramp-1.1.0-py3-none-any.whl", hash = "sha256:e09d2a9be5adeb94cbeb56fc54a61fc5f5b6e140e679b2b60d1f7a8d6478d906"}, + {file = "scramp-1.1.0.tar.gz", hash = "sha256:475aa6296deb2737b86e9df9098e8eca0f30c8ad1cc0a8adadb99ef012a5ceba"}, +] +selenium = [ + {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"}, + {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, +] +six = [ + {file = "six-1.13.0-py2.py3-none-any.whl", hash = "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd"}, + {file = "six-1.13.0.tar.gz", hash = "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"}, +] +smmap2 = [ + {file = "smmap2-2.0.5-py2.py3-none-any.whl", hash = "sha256:0555a7bf4df71d1ef4218e4807bbf9b201f910174e6e08af2e138d4e517b4dde"}, + {file = "smmap2-2.0.5.tar.gz", hash = "sha256:29a9ffa0497e7f2be94ca0ed1ca1aa3cd4cf25a1f6b4f5f87f74b46ed91d609a"}, +] +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.5-py2.py3-none-any.whl", hash = "sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5"}, + {file = "soupsieve-1.9.5.tar.gz", hash = "sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda"}, +] +sphinx = [ + {file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"}, + {file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"}, +] +sphinx-autodoc-typehints = [ + {file = "sphinx-autodoc-typehints-1.10.3.tar.gz", hash = "sha256:a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"}, + {file = "sphinx_autodoc_typehints-1.10.3-py3-none-any.whl", hash = "sha256:27c9e6ef4f4451766ab8d08b2d8520933b97beb21c913f3df9ab2e59b56e6c6c"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, + {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"}, + {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"}, +] +sphinxcontrib-django = [ + {file = "sphinxcontrib-django-0.5.tar.gz", hash = "sha256:95831d5d58f780010b0255f298ea03d6bbf971c0f94f631268abd4320400b7db"}, + {file = "sphinxcontrib_django-0.5-py2.py3-none-any.whl", hash = "sha256:f5bb52d20b64b51087b44247789adaebfc51fa2fad71bfed42c4ca6c05723838"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"}, + {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"}, + {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"}, + {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"}, +] +sqlparse = [ + {file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"}, + {file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"}, +] +stevedore = [ + {file = "stevedore-1.31.0-py2.py3-none-any.whl", hash = "sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730"}, + {file = "stevedore-1.31.0.tar.gz", hash = "sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14"}, +] +termcolor = [ + {file = "termcolor-1.1.0.tar.gz", hash = "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"}, +] +testfixtures = [ + {file = "testfixtures-6.10.3-py2.py3-none-any.whl", hash = "sha256:9334f64d4210b734d04abff516d6ddaab7328306a0c4c1268ce4624df51c4f6d"}, + {file = "testfixtures-6.10.3.tar.gz", hash = "sha256:8f22100d4fb841b958f64e71c8820a32dc46f57d4d7e077777b932acd87b7327"}, +] +"testing.common.database" = [ + {file = "testing.common.database-2.0.3-py2.py3-none-any.whl", hash = "sha256:e3ed492bf480a87f271f74c53b262caf5d85c8bc09989a8f534fa2283ec52492"}, + {file = "testing.common.database-2.0.3.tar.gz", hash = "sha256:965d80b2985315325dc358c3061b174a712f4d4d5bf6a80b58b11f9a1dd86d73"}, +] +"testing.postgresql" = [ + {file = "testing.postgresql-1.3.0-py2.py3-none-any.whl", hash = "sha256:1b41daeb98dfc8cd4a584bb91e8f5f4ab182993870f95257afe5f1ba6151a598"}, + {file = "testing.postgresql-1.3.0.tar.gz", hash = "sha256:8e1a69760369a7a8ffe63a66b6d95a5cd82db2fb976e4a8f85ffd24fbfc447d8"}, +] +text-unidecode = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +tqdm = [ + {file = "tqdm-4.41.0-py2.py3-none-any.whl", hash = "sha256:9036904496bd2afacf836a6f206c5a766ce11d3e9319d54a4e794c0f34b111dc"}, + {file = "tqdm-4.41.0.tar.gz", hash = "sha256:166a82cdea964ae45528e0cc89436255ff2be73dc848bdf239f13c501cae5dc7"}, +] +twilio = [ + {file = "twilio-6.35.1.tar.gz", hash = "sha256:c784e55d150ebeb2ba837afbab7168edfb91db57e77a9da49f2a1892688a1930"}, +] +typed-ast = [ + {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e"}, + {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b"}, + {file = "typed_ast-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4"}, + {file = "typed_ast-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"}, + {file = "typed_ast-1.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631"}, + {file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233"}, + {file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1"}, + {file = "typed_ast-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a"}, + {file = "typed_ast-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c"}, + {file = "typed_ast-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a"}, + {file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e"}, + {file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d"}, + {file = "typed_ast-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36"}, + {file = "typed_ast-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0"}, + {file = "typed_ast-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66"}, + {file = "typed_ast-1.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2"}, + {file = "typed_ast-1.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47"}, + {file = "typed_ast-1.4.0-cp38-cp38-win32.whl", hash = "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161"}, + {file = "typed_ast-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e"}, + {file = "typed_ast-1.4.0.tar.gz", hash = "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.1-py2-none-any.whl", hash = "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d"}, + {file = "typing_extensions-3.7.4.1-py3-none-any.whl", hash = "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575"}, + {file = "typing_extensions-3.7.4.1.tar.gz", hash = "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2"}, +] +urllib3 = [ + {file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"}, + {file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"}, +] +wcwidth = [ + {file = "wcwidth-0.1.7-py2.py3-none-any.whl", hash = "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"}, + {file = "wcwidth-0.1.7.tar.gz", hash = "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e"}, +] +yubiotp = [ + {file = "YubiOTP-0.2.2.post1-py2.py3-none-any.whl", hash = "sha256:7e281801b24678f4bda855ce8ab975a7688a912f5a6cb22b6c2b16263a93cbd2"}, + {file = "YubiOTP-0.2.2.post1.tar.gz", hash = "sha256:de83b1560226e38b5923f6ab919f962c8c2abb7c722104cb45b2b6db2ac86e40"}, +] +zipp = [ + {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"}, + {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"}, +] diff --git a/pyproject.toml b/pyproject.toml index 93c9ddd9143721f55a77f4487b6abd10ebfbb6e2..4980fae106a5eeef3d033e7f6df446588920b1c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,25 +23,23 @@ python = "^3.7" Django = "^3.0" django-any-js = "^1.0" django-bootstrap4 = "^1.0" -django-fa = "^1.0" django-debug-toolbar = "^2.0" django-easy-audit = "^1.1" django-middleware-global-request = "^0.1.2" django-menu-generator = "^1.0.4" django-tables2 = "^2.1" Pillow = "^6.1" -django-phonenumber-field = {version = "^3.0", extras = ["phonenumbers"]} +django-phonenumber-field = {version = ">=3.0, <5.0", extras = ["phonenumbers"]} django-sass-processor = "^0.7.3" libsass = "^0.19.2" colour = "^0.1.5" dynaconf = {version = "^2.0", extras = ["yaml", "toml", "ini"]} django-settings-context-processor = "^0.2" django-auth-ldap = { version = "^2.0", optional = true } -django-maintenance-mode = "^0.13.3" +django-maintenance-mode = "^0.14.0" django-ipware = "^2.1" easy-thumbnails = "^2.6" django-image-cropping = "^1.2" -django-contact-form = "^1.7" django-impersonate = "^1.4" python-memcached = "^1.59" django-dbbackup = "^3.2" @@ -50,27 +48,46 @@ django-hattori = "^0.2" psycopg2 = "^2.8" django_select2 = "^7.1" requests = "^2.22" -django-two-factor-auth = { git = "https://github.com/Bouke/django-two-factor-auth", rev = "bf9d0812ab11320a6cadc6709c382a03184f2e31", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] } +django-two-factor-auth = { version = "^1.10.0", extras = [ "YubiKey", "phonenumbers", "Call", "SMS" ] } django-yarnpkg = "^6.0" -django-dbsettings = "^0.11.0" +django-dbsettings = "^1.0.0" [tool.poetry.extras] +app-alsijil = ['BiscuIT-App-Alsijil'] +app-chronos = ['BiscuIT-App-Chronos'] +app-exlibris = ['BiscuIT-App-Exlibris'] +app-schild-nrw = ['BiscuIT-App-SchILD-NRW'] +app-untis = ['BiscuIT-App-Untis'] ldap = ["django-auth-ldap"] [tool.poetry.dev-dependencies] sphinx = "^2.1" sphinxcontrib-django = "^0.5.0" sphinx-autodoc-typehints = "^1.7" -autopep8 = "^1.4" -pylama = "^7.7" -radon = "^3.0" -mypy = "^0.720.0" django-stubs = "^1.1" pytest = "^5.3" pytest-django = "^3.7" pytest-django-testing-postgresql = "^0.1" -tox = "^3.14" -pytest-cov = "^2.8" +selenium = "^3.141.0" +safety = "^1.8.5" +flake8 = "^3.7.9" +flake8-django = "^0.0.4" +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.12" +black = "^19.10b0" +flake8-black = "^0.1.1" +isort = "^4.3.21" +flake8-isort = "^2.8.0" +pytest-cov = "^2.8.1" +pytest-sugar = "^0.9.2" + +[tool.black] +line-length = 100 +exclude = "/migrations/" [build-system] requires = ["poetry>=0.12"] diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93 --- /dev/null +++ b/renovate.json @@ -0,0 +1 @@ +{} diff --git a/tox.ini b/tox.ini index defacd8dd0cd0f2a60820b8c881ecc512d804421..3671faf24d8700820474a1a41023468e076463d5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,32 +1,81 @@ [tox] skipsdist = True +skip_missing_interpreters = true +envlist = py37,py38 [testenv] whitelist_externals = poetry - pytest skip_install = true -commands = pytest --pylama --cov=biscuit biscuit/core/ +envdir = {toxworkdir}/globalenv +commands_pre = ./dev.sh install-all +commands = + poetry run python manage.py compilemessages + poetry run python manage.py yarn install + poetry run python manage.py collectstatic --no-input --clear + poetry run pytest --cov=. {posargs} biscuit/ apps/official/ -[pylama] -linters = pycodestyle,pyflakes,radon +[testenv:selenium] +setenv = + TEST_SCREENSHOT_PATH = {env:TEST_SCREENSHOT_PATH:.tox/screenshots} + TEST_SELENIUM_HUB = {env:TEST_SELENIUM_HUB:http://127.0.0.1:4444/wd/hub} + TEST_SELENIUM_BROWSERS = {env:TEST_SELENIUM_BROWSERS:chrome,firefox} + TEST_HOST = {env:TEST_HOST:172.17.0.1} -[pycodestyle] +[testenv:lint] +commands = + - poetry run black --check --diff biscuit/ apps/official/ + - poetry run isort -c --diff --stdout -rc biscuit/ apps/official/ + poetry run flake8 {posargs} biscuit/ apps/official/ + +[testenv:security] +commands = poetry run safety check --full-report + +[testenv:build] +commands_pre = +commands = poetry build + +[testenv:docs] +commands = poetry run make -C docs/ html {posargs} + +[testenv:reformat] +commands = + poetry run isort -rc biscuit/ apps/official/ + poetry run black biscuit/ apps/official/ + +[flake8] max_line_length = 100 -ignore = E501 +exclude = migrations,tests +ignore = E203,E231,W503 -[autopep8] -jobs = 0 -aggressive = true +[isort] +line_length = 100 +multi_line_output = 3 +include_trailing_comma = 1 +use_parantheses = 1 +default_section = THIRDPARTY +known_first_party = biscuit +known_django = django +sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER [mypy] plugins = mypy_django_plugin.main +python_version = 3.8 +platform = linux +show_column_numbers = True +follow_imports = skip +ignore_missing_imports = True +cache_dir = /dev/null [mypy.plugins.django-stubs] django_settings_module = biscuit.core.settings [pytest] DJANGO_SETTINGS_MODULE = biscuit.core.settings +junit_family = legacy [coverage:run] -omit = */migrations/* +omit = + */migrations/* */tests/* + .tox/* + manage.py