diff --git a/.gitignore b/.gitignore index 70d2c1202e645fc31260a0fdd6bac90fdd25e15a..a30a3fa095b66e018388937f3ffb79eed4692301 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,8 @@ htmlcov/ maintenance_mode_state.txt media/ package-lock.json + +# VSCode +.vscode/ +.history/ +*.code-workspace diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bcc9e130e2238e0a6025b73456696c8283c2c0ce..d36458971660c0b5ab354ad1e4b03068fb46ebea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,122 +1,6 @@ -image: registry.edugit.org/teckids/team-sysadmin/docker-images/python-pimped:master - -stages: - - test - - build - - deploy - -variables: - GIT_SUBMODULE_STRATEGY: recursive - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - FF_NETWORK_PER_BUILD: "true" - -cache: - key: - files: - - poetry.lock - - pyproject.toml - paths: - - .cache/pip - - .tox - -test: - stage: test - services: - - name: selenium/standalone-firefox - alias: selenium - before_script: - - adduser --disabled-password --gecos "Test User" testuser - - chown -R testuser . - script: - - sudo apt update - - sudo apt install python3-ldap libldap2-dev libssl-dev libsasl2-dev python3.7-dev -y - - sudo -u testuser - env TEST_SELENIUM_HUB=http://selenium:4444/wd/hub - TEST_SELENIUM_BROWSERS=firefox - TEST_HOST=build - tox -e selenium -- --junitxml=.tox/junit.xml - artifacts: - paths: - - .tox/screenshots - reports: - junit: .tox/junit.xml - -lint: - stage: test - script: - - tox -e lint,security - -build_dist: - stage: build - script: - - tox -e build - artifacts: - paths: - - dist/ - -pages: - stage: deploy - before_script: - - cp -r .tox/screenshots/firefox docs/screenshots - script: - - export LC_ALL=en_GB.utf8 - - tox -e docs -- BUILDDIR=../public/docs - artifacts: - paths: - - public/ - only: - - master - -build_docker: - stage: build - image: - name: gcr.io/kaniko-project/executor:v0.22.0 - entrypoint: [""] - script: - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" >/kaniko/.docker/config.json - - /kaniko/executor - --context $CI_PROJECT_DIR - --dockerfile $CI_PROJECT_DIR/Dockerfile - --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME - --cache=true - --cleanup - - /kaniko/executor - --context $CI_PROJECT_DIR/docker/nginx - --dockerfile $CI_PROJECT_DIR/docker/nginx/Dockerfile - --destination $CI_REGISTRY_IMAGE/nginx:$CI_COMMIT_REF_NAME - --cache=true - --cleanup - only: - - master - - tags - -deploy_demo-master: - stage: deploy - environment: - name: demo/master - url: http://demo-master.aleksis.org - before_script: - - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - - eval $(ssh-agent -s) - - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - - - mkdir -p ~/.ssh - - chmod 700 ~/.ssh - - echo "$SSH_KNOWN_HOSTS" >~/.ssh/known_hosts - - chmod 644 ~/.ssh/known_hosts - script: - - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org - env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME} - docker-compose - -p aleksis-${CI_ENVIRONMENT_SLUG} - -f /dev/stdin - pull - - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org - env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME} - NGINX_HTTP_PORT=80 - ALEKSIS_maintenance__debug=true - docker-compose - -p aleksis-${CI_ENVIRONMENT_SLUG} - -f /dev/stdin - up -d - only: - - master +include: + - local: "/ci/general.yml" + - local: "/ci/test.yml" + - local: "/ci/build_dist.yml" + - local: "/ci/build_docker.yml" + - local: "/ci/deploy.yml" diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2c24e62bb4d1acd60579eb0064c0a79c08748d7f..be12afc1552dc2b6d6cb398bc2a456392ef21b9d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -48,7 +48,7 @@ Working with the Git repository The Git repository shall be used as a historic documentation of development and as change management. It is important that the Git commit history -describes waht was changed, by whom and why. +describes what was changed, by whom and why. Help and information on Git for beginners are available in the `Git guide`_ diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index 0037842bc71590eb521aa67f25b5f4cc6b50fb92..aeda609326941428e483e05cabd6e5412b2832f7 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -2,6 +2,7 @@ from typing import Any, List, Optional, Tuple import django.apps from django.http import HttpRequest +from django.utils.module_loading import autodiscover_modules from dynamic_preferences.registries import preference_models @@ -36,6 +37,9 @@ class CoreConfig(AppConfig): def ready(self): super().ready() + # Autodiscover various modules defined by AlekSIS + autodiscover_modules("form_extensions", "model_extensions", "checks") + sitepreferencemodel = self.get_model("SitePreferenceModel") personpreferencemodel = self.get_model("PersonPreferenceModel") grouppreferencemodel = self.get_model("GroupPreferenceModel") diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py index 8e5233c531193f1c9bbccea011ebdc3f4b765c66..abd45aabdee3f9d933376a0fc034f59218a8523f 100644 --- a/aleksis/core/forms.py +++ b/aleksis/core/forms.py @@ -10,7 +10,7 @@ from dynamic_preferences.forms import PreferenceForm from material import Fieldset, Layout, Row from .mixins import ExtensibleForm -from .models import Announcement, Group, GroupType, Person +from .models import AdditionalField, Announcement, Group, GroupType, Person from .registries import ( group_preferences_registry, person_preferences_registry, @@ -127,6 +127,7 @@ class EditGroupForm(ExtensibleForm): layout = Layout( Fieldset(_("Common data"), "name", "short_name", "group_type"), Fieldset(_("Persons"), "members", "owners", "parent_groups"), + Fieldset(_("Additional data"), "additional_fields"), ) class Meta: @@ -150,6 +151,7 @@ class EditGroupForm(ExtensibleForm): "parent_groups": ModelSelect2MultipleWidget( search_fields=["name__icontains", "short_name__icontains"] ), + "additional_fields": ModelSelect2MultipleWidget(search_fields=["title__icontains",]), } @@ -282,6 +284,14 @@ class GroupPreferenceForm(PreferenceForm): registry = group_preferences_registry +class EditAdditionalFieldForm(forms.ModelForm): + """Form to manage additional fields.""" + + class Meta: + model = AdditionalField + exclude = [] + + class EditGroupTypeForm(forms.ModelForm): """Form to manage group types.""" diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py index 4767a8f3492c7401a22164fe25f54da4ec9fd702..c1b19bb3970eac18d756ec0eae6bdaf8ae285d07 100644 --- a/aleksis/core/menus.py +++ b/aleksis/core/menus.py @@ -186,6 +186,17 @@ MENUS = { ) ], }, + { + "name": _("Additional fields"), + "url": "additional_fields", + "icon": "style", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "core.view_additionalfield", + ) + ], + }, ], }, ], diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index f6a8c18c483d0833d296abd6310cc2103331ba18..6b52fb3820c366c1040905eed1226cdc1d4c0e3f 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -164,17 +164,17 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase): @classmethod def property_(cls, func: Callable[[], Any], name: Optional[str] = None) -> None: """Add the passed callable as a property.""" - cls._safe_add(property(func), func.__name__) + cls._safe_add(property(func), name or func.__name__) @classmethod def method(cls, func: Callable[[], Any], name: Optional[str] = None) -> None: """Add the passed callable as a method.""" - cls._safe_add(func, func.__name__) + cls._safe_add(func, name or func.__name__) @classmethod def class_method(cls, func: Callable[[], Any], name: Optional[str] = None) -> None: """Add the passed callable as a classmethod.""" - cls._safe_add(classmethod(func), func.__name__) + cls._safe_add(classmethod(func), name or func.__name__) @classmethod def field(cls, **kwargs) -> None: diff --git a/aleksis/core/models.py b/aleksis/core/models.py index b5bb204c25139c463e6d53c27b7c1ddf51b75576..fe0d63d62713fbdfa6ff9b142844d1226c28be6f 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -302,7 +302,9 @@ class Group(ExtensibleModel): null=True, blank=True, ) - additional_fields = models.ManyToManyField(AdditionalField, verbose_name=_("Additional fields")) + additional_fields = models.ManyToManyField( + AdditionalField, verbose_name=_("Additional fields"), blank=True + ) def get_absolute_url(self) -> str: return reverse("group_by_id", args=[self.id]) diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index 3325f136af03b52deb9be44f765cc82e2e832b70..4f97eafddf3cfdb9725cf0e9e0cde05ef83d1ad0 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -1,6 +1,6 @@ from rules import add_perm, always_allow -from .models import Announcement, Group, GroupType, Person +from .models import AdditionalField, Announcement, Group, GroupType, Person from .util.predicates import ( has_any_object, has_global_perm, @@ -195,6 +195,33 @@ change_group_preferences = has_person & ( ) add_perm("core.change_group_preferences", change_group_preferences) + +# Edit additional field +change_additional_field_predicate = has_person & ( + has_global_perm("core.change_additionalfield") | has_object_perm("core.change_additionalfield") +) +add_perm("core.change_additionalfield", change_additional_field_predicate) + +# Edit additional field +create_additional_field_predicate = has_person & ( + has_global_perm("core.create_additionalfield") | has_object_perm("core.create_additionalfield") +) +add_perm("core.create_additionalfield", create_additional_field_predicate) + + +# Delete additional field +delete_additional_field_predicate = has_person & ( + has_global_perm("core.delete_additionalfield") | has_object_perm("core.delete_additionalfield") +) +add_perm("core.delete_additionalfield", delete_additional_field_predicate) + +# View additional fields +view_additional_field_predicate = has_person & ( + has_global_perm("core.view_additionalfield") + | has_any_object("core.view_additionalfield", AdditionalField) +) +add_perm("core.view_additionalfield", view_additional_field_predicate) + # Edit group type change_group_type_predicate = has_person & ( has_global_perm("core.change_grouptype") | has_object_perm("core.change_grouptype") diff --git a/aleksis/core/tables.py b/aleksis/core/tables.py index ff8fd871e75e9b97453784928d65456c5f857e8b..6f7ba041457f11420cf9155ff0cc8ca5012fd9d1 100644 --- a/aleksis/core/tables.py +++ b/aleksis/core/tables.py @@ -24,6 +24,22 @@ class GroupsTable(tables.Table): short_name = tables.LinkColumn("group_by_id", args=[A("id")]) +class AdditionalFieldsTable(tables.Table): + """Table to list group types.""" + + class Meta: + attrs = {"class": "responsive-table hightlight"} + + title = tables.LinkColumn("edit_additional_field_by_id", args=[A("id")]) + delete = tables.LinkColumn( + "delete_additional_field_by_id", + args=[A("id")], + verbose_name=_("Delete"), + text=_("Delete"), + attrs={"a": {"class": "btn-flat waves-effect waves-red"}}, + ) + + class GroupTypesTable(tables.Table): """Table to list group types.""" diff --git a/aleksis/core/templates/403.html b/aleksis/core/templates/403.html index c84e7f57ed0fa82dead672fde7ffd2c20a8bdc81..00da4145121cafa2319c806884bb5637ad09a798 100644 --- a/aleksis/core/templates/403.html +++ b/aleksis/core/templates/403.html @@ -15,7 +15,7 @@ administrators: {% endblocktrans %} </p> - {% include "core/admins_list.html" %} + {% include "core/partials/admins_list.html" %} </div> </div> </div> diff --git a/aleksis/core/templates/404.html b/aleksis/core/templates/404.html index 45873cde006feaf32b27999cf357d4888d70f644..33c311fcaf4c106d44c53788b44426584d014c33 100644 --- a/aleksis/core/templates/404.html +++ b/aleksis/core/templates/404.html @@ -19,7 +19,7 @@ administrators: {% endblocktrans %} </p> - {% include "core/admins_list.html" %} + {% include "core/partials/admins_list.html" %} </div> </div> </div> diff --git a/aleksis/core/templates/500.html b/aleksis/core/templates/500.html index 621b9e424054cb321072450140c60548ebec3a0d..0759bfdd070f339fe0eec4f3d5806485e34e4664 100644 --- a/aleksis/core/templates/500.html +++ b/aleksis/core/templates/500.html @@ -15,7 +15,7 @@ error. You can also contact them directly: {% endblocktrans %} </p> - {% include "core/admins_list.html" %} + {% include "core/partials/admins_list.html" %} </div> </div> </div> diff --git a/aleksis/core/templates/503.html b/aleksis/core/templates/503.html index 9ed4fcecbaa5ef620264374fef1074288c4d7785..ac1dc4c3dea129f0798f18fd61dfdaaa5f291d17 100644 --- a/aleksis/core/templates/503.html +++ b/aleksis/core/templates/503.html @@ -14,7 +14,7 @@ This page is currently unavailable. If this error persists, contact your site administrators: {% endblocktrans %} </p> - {% include "core/admins_list.html" %} + {% include "core/partials/admins_list.html" %} </div> </div> </div> diff --git a/aleksis/core/templates/core/additional_field/edit.html b/aleksis/core/templates/core/additional_field/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..b1487eb259b44f1425c950574f7df845d4e22129 --- /dev/null +++ b/aleksis/core/templates/core/additional_field/edit.html @@ -0,0 +1,17 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Edit additional field{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit additional field{% endblocktrans %}{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=edit_additional_field_form %}{% endform %} + {% include "core/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/core/templates/core/additional_field/list.html b/aleksis/core/templates/core/additional_field/list.html new file mode 100644 index 0000000000000000000000000000000000000000..1ba2a77a32c528b8443f4dc5a4c201b5739414fe --- /dev/null +++ b/aleksis/core/templates/core/additional_field/list.html @@ -0,0 +1,18 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Additional fields{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Additional fields{% endblocktrans %}{% endblock %} + +{% block content %} + <a class="btn green waves-effect waves-light" href="{% url 'create_additional_field' %}"> + <i class="material-icons left">add</i> + {% trans "Create additional field" %} + </a> + + {% render_table additional_fields_table %} +{% endblock %} diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index e30d428f060f540cbf7d4dbbb483a1371dbddf38..2d112a4f0fe54f4f1926811fbd589b32351909cf 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -7,7 +7,7 @@ <!DOCTYPE html> <html lang="{{ LANGUAGE_CODE }}"> <head> - {% include "core/meta.html" %} + {% include "core/partials/meta.html" %} <title> {% block no_browser_title %} @@ -81,14 +81,14 @@ </li> {% endif %} <li class="no-padding"> - {% include "core/sidenav.html" %} + {% include "core/partials/sidenav.html" %} </li> </ul> </header> <main role="main"> - {% include 'core/no_person.html' %} + {% include 'core/partials/no_person.html' %} {% if messages %} {% for message in messages %} @@ -122,23 +122,23 @@ <div class="row no-margin footer-row-large"> <div class="col l6 s12 no-pad-left height-inherit"> <p class="white-text valign-bot"> - {% include 'core/language_form.html' %} + {% include 'core/partials/language_form.html' %} </p> </div> <div class="col xl15 l6 offset-xl01 s12 no-pad-right"> <ul class="no-margin right"> - {% include "core/footer-menu.html" %} + {% include "core/partials/footer-menu.html" %} </ul> </div> </div> <div class="row no-margin footer-row-small"> <span class="white-text make-it-higher"> - {% include 'core/language_form.html' %} + {% include 'core/partials/language_form.html' %} </span> <ul class="no-margin footer-ul"> - {% include "core/footer-menu.html" %} + {% include "core/partials/footer-menu.html" %} </ul> </div> </div> diff --git a/aleksis/core/templates/core/base_print.html b/aleksis/core/templates/core/base_print.html index 6059d664fccb38ce18a55eb9ea6d1a406a6e9ad4..b0739d3a2e7e0ceb6d81d21ce575d6ef967d810f 100644 --- a/aleksis/core/templates/core/base_print.html +++ b/aleksis/core/templates/core/base_print.html @@ -5,7 +5,7 @@ <!DOCTYPE html> <html lang="{{ LANGUAGE_CODE }}"> <head> - {% include "core/meta.html" %} + {% include "core/partials/meta.html" %} <title> {% block no_browser_title %} diff --git a/aleksis/core/templates/core/crud_events.html b/aleksis/core/templates/core/crud_events.html deleted file mode 100644 index 30c4ec464f29a8e99d7b6dd72c0bc824a9e9825f..0000000000000000000000000000000000000000 --- a/aleksis/core/templates/core/crud_events.html +++ /dev/null @@ -1,49 +0,0 @@ -{% load i18n %} - -<ul class="collection"> - {% for event in obj.crud_events %} - {% if no_m2m and event.event_type == event.M2M_CHANGE or event.event_type == event.M2M_CHANGE_REV %} - {% else %} - <li class="collection-item"> - {% if event.event_type == event.CREATE %} - {% blocktrans with person=event.user.person %} - Created by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.UPDATE %} - {% blocktrans with person=event.user.person %} - Updated by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.DELETE %} - {% blocktrans with person=event.user.person %} - Deleted by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.M2M_CHANGE %} - {% blocktrans with person=event.user.person %} - Updated by {{ person }} - {% endblocktrans %} - {% elif event.event_type == event.M2M_CHANGE_REV %} - {% blocktrans with person=event.user.person %} - Updated by {{ person }} - {% endblocktrans %} - {% endif %} - - <div class="left" style="margin-right: 10px;"> - {% if event.event_type == event.CREATE %} - <i class="material-icons">add_circle</i> - {% elif event.event_type == event.UPDATE %} - <i class="material-icons">edit</i> - {% elif event.event_type == event.DELETE %} - <i class="material-icons">delete</i> - {% elif event.event_type == event.M2M_CHANGE %} - <i class="material-icons">edit</i> - {% elif event.event_type == event.M2M_CHANGE_REV %} - <i class="material-icons">edit</i> - {% endif %} - </div> - <div class="right"> - {{ event.datetime }} - </div> - </li> - {% endif %} - {% endfor %} -</ul> diff --git a/aleksis/core/templates/core/groups_child_groups.html b/aleksis/core/templates/core/group/child_groups.html similarity index 100% rename from aleksis/core/templates/core/groups_child_groups.html rename to aleksis/core/templates/core/group/child_groups.html diff --git a/aleksis/core/templates/core/edit_group.html b/aleksis/core/templates/core/group/edit.html similarity index 88% rename from aleksis/core/templates/core/edit_group.html rename to aleksis/core/templates/core/group/edit.html index e7b3188076125020958105ee97ab0de00bd5f766..b26a28d1efc5ee292a257220bca00754512c1b99 100644 --- a/aleksis/core/templates/core/edit_group.html +++ b/aleksis/core/templates/core/group/edit.html @@ -11,7 +11,7 @@ <form method="post"> {% csrf_token %} {% form form=edit_group_form %}{% endform %} - {% include "core/save_button.html" %} + {% include "core/partials/save_button.html" %} </form> {% endblock %} diff --git a/aleksis/core/templates/core/group_full.html b/aleksis/core/templates/core/group/full.html similarity index 75% rename from aleksis/core/templates/core/group_full.html rename to aleksis/core/templates/core/group/full.html index 0add6f1a3041b723e4a4e3d87972e07a8daebf97..2c778f837a785d431300ea57a1a283a05b6bd367 100644 --- a/aleksis/core/templates/core/group_full.html +++ b/aleksis/core/templates/core/group/full.html @@ -31,6 +31,25 @@ </p> {% endif %} + <table> + <tr> + <th> + <i class="material-icons center" title="{% trans "Group type" %}">category</i> + </th> + <td> + {{ group.group_type }} + </td> + </tr> + <tr> + <th> + <i class="material-icons center" title="{% trans "Parent groups" %}">vertical_align_top</i> + </th> + <td> + {{ group.parent_groups.all|join:", " }} + </td> + </tr> + </table> + <h5>{% blocktrans %}Owners{% endblocktrans %}</h5> {% render_table owners_table %} diff --git a/aleksis/core/templates/core/groups.html b/aleksis/core/templates/core/group/list.html similarity index 100% rename from aleksis/core/templates/core/groups.html rename to aleksis/core/templates/core/group/list.html diff --git a/aleksis/core/templates/core/edit_group_type.html b/aleksis/core/templates/core/group_type/edit.html similarity index 89% rename from aleksis/core/templates/core/edit_group_type.html rename to aleksis/core/templates/core/group_type/edit.html index c857de98b893cf243c3c108f3c76b5aa142e6037..843975b16bb6ccbd7cf8eeed8f2d5713f5320e23 100644 --- a/aleksis/core/templates/core/edit_group_type.html +++ b/aleksis/core/templates/core/group_type/edit.html @@ -11,7 +11,7 @@ <form method="post"> {% csrf_token %} {% form form=edit_group_type_form %}{% endform %} - {% include "core/save_button.html" %} + {% include "core/partials/save_button.html" %} </form> {% endblock %} diff --git a/aleksis/core/templates/core/group_types.html b/aleksis/core/templates/core/group_type/list.html similarity index 100% rename from aleksis/core/templates/core/group_types.html rename to aleksis/core/templates/core/group_type/list.html diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html index ac5d19b71bc6ac4acd00d7d3c509e1c07f089dcc..683adca900d46a8ba50790829db4d3ecd0ba2716 100644 --- a/aleksis/core/templates/core/index.html +++ b/aleksis/core/templates/core/index.html @@ -27,7 +27,7 @@ </div> {% endfor %} - {% include "core/announcements.html" with announcements=announcements %} + {% include "core/partials/announcements.html" with announcements=announcements %} <div class="row" id="live_load"> {% for widget in widgets %} diff --git a/aleksis/core/templates/core/data_management.html b/aleksis/core/templates/core/management/data_management.html similarity index 87% rename from aleksis/core/templates/core/data_management.html rename to aleksis/core/templates/core/management/data_management.html index 520dcb6d54beb20c482158cfa538ccf5cf2dc1ab..fa8e61f5dac1d1e01e5a773b68015cc9befe8d8f 100644 --- a/aleksis/core/templates/core/data_management.html +++ b/aleksis/core/templates/core/management/data_management.html @@ -8,5 +8,5 @@ {% block content %} {% get_menu "DATA_MANAGEMENT_MENU" as menu %} - {% include "core/on_page_menu.html" %} + {% include "core/partials/on_page_menu.html" %} {% endblock %} diff --git a/aleksis/core/templates/core/about.html b/aleksis/core/templates/core/pages/about.html similarity index 100% rename from aleksis/core/templates/core/about.html rename to aleksis/core/templates/core/pages/about.html diff --git a/aleksis/core/templates/offline.html b/aleksis/core/templates/core/pages/offline.html similarity index 92% rename from aleksis/core/templates/offline.html rename to aleksis/core/templates/core/pages/offline.html index 6961e03de97ea1a51096df239ac55880729e0fe7..a6a70dc19f074e8c3f3ede50b5e9b5b80b5682f1 100644 --- a/aleksis/core/templates/offline.html +++ b/aleksis/core/templates/core/pages/offline.html @@ -13,5 +13,5 @@ administrators: {% endblocktrans %} </p> - {% include "core/admins_list.html" %} + {% include "core/partials/admins_list.html" %} {% endblock %} diff --git a/aleksis/core/templates/core/system_status.html b/aleksis/core/templates/core/pages/system_status.html similarity index 50% rename from aleksis/core/templates/core/system_status.html rename to aleksis/core/templates/core/pages/system_status.html index a1f313efd843c6fea63cbc41f5ff7921d2ad5eaa..a62d024cf9ff5875ce61c8305b84834e47393726 100644 --- a/aleksis/core/templates/core/system_status.html +++ b/aleksis/core/templates/core/pages/system_status.html @@ -62,4 +62,68 @@ </div> </div> </div> + + + {% if tasks %} + <div class="card"> + <div class="card-content"> + <span class="card-title"> {% blocktrans %}Celery task results{% endblocktrans %}</span> + + <table> + <thead> + <tr> + <th>{% trans "Task" %}</th> + <th>{% trans "ID" %}</th> + <th>{% trans "Date done" %}</th> + <th>{% trans "Status" %}</th> + </tr> + </thead> + <tbody> + {% for task in tasks %} + {% if task != None %} + <tr> + <td>{{ task.task_name }}</td> + <td>{{ task.task_id }}</td> + <td>{{ task.date_done }}</td> + <td> + {% if task.status == "PENDING" %} + <a class="tooltipped" data-position="top" + data-tooltip="{{ task.status }}"> + <i class="material-icons orange-text">hourglass_empty</i> + </a> + {% elif task.status == "STARTED" %} + <a class="tooltipped" data-position="top" + data-tooltip="{{ task.status }}"> + <i class="material-icons orange-text">directions_run</i> + </a> + {% elif task.status == "SUCCESS" %} + <a class="tooltipped" data-position="top" + data-tooltip="{{ task.status }}"> + <i class="material-icons green-text">done</i> + </a> + {% elif task.status == "FAILURE" %} + <a class="tooltipped" data-position="top" + data-tooltip="{{ task.status }}"> + <i class="material-icons red-text">error</i> + </a> + {% elif task.status == "RETRY" %} + <a class="tooltipped" data-position="top" + data-tooltip="{{ task.status }}"> + <i class="material-icons orange-text">hourglass_full</i> + </a> + {% elif task.status == "REVOKED" %} + <a class="tooltipped" data-position="top" + data-tooltip="{{ task.status }}"> + <i class="material-icons red-text">clear</i> + </a> + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} + </tbody> + </table> + </div> + </div> + {% endif %} {% endblock %} diff --git a/aleksis/core/templates/core/admins_list.html b/aleksis/core/templates/core/partials/admins_list.html similarity index 100% rename from aleksis/core/templates/core/admins_list.html rename to aleksis/core/templates/core/partials/admins_list.html diff --git a/aleksis/core/templates/core/announcements.html b/aleksis/core/templates/core/partials/announcements.html similarity index 100% rename from aleksis/core/templates/core/announcements.html rename to aleksis/core/templates/core/partials/announcements.html diff --git a/aleksis/core/templates/core/partials/crud_events.html b/aleksis/core/templates/core/partials/crud_events.html new file mode 100644 index 0000000000000000000000000000000000000000..50e4f73cab492804b163b7ddfe0dda08b9e267b7 --- /dev/null +++ b/aleksis/core/templates/core/partials/crud_events.html @@ -0,0 +1,62 @@ +{% load i18n data_helpers %} + +<ul class="collection"> + {% for event in obj.crud_events %} + {% if no_m2m and event.event_type == event.M2M_CHANGE or event.event_type == event.M2M_CHANGE_REV %} + {% else %} + <li class="collection-item"> + <strong> + {% if event.event_type == event.CREATE %} + {% blocktrans with person=event.user.person %} + Created by {{ person }} + {% endblocktrans %} + {% elif event.event_type == event.UPDATE %} + {% blocktrans with person=event.user.person %} + Updated by {{ person }} + {% endblocktrans %} + {% elif event.event_type == event.DELETE %} + {% blocktrans with person=event.user.person %} + Deleted by {{ person }} + {% endblocktrans %} + {% elif event.event_type == event.M2M_CHANGE %} + {% blocktrans with person=event.user.person %} + Updated by {{ person }} + {% endblocktrans %} + {% elif event.event_type == event.M2M_CHANGE_REV %} + {% blocktrans with person=event.user.person %} + Updated by {{ person }} + {% endblocktrans %} + {% endif %} + </strong> + + <div class="left" style="margin-right: 10px;"> + {% if event.event_type == event.CREATE %} + <i class="material-icons">add_circle</i> + {% elif event.event_type == event.UPDATE %} + <i class="material-icons">edit</i> + {% elif event.event_type == event.DELETE %} + <i class="material-icons">delete</i> + {% elif event.event_type == event.M2M_CHANGE %} + <i class="material-icons">edit</i> + {% elif event.event_type == event.M2M_CHANGE_REV %} + <i class="material-icons">edit</i> + {% endif %} + </div> + <div class="right"> + {{ event.datetime }} + </div> + {% parse_json event.changed_fields as changed_fields %} + {% if changed_fields %} + <ul> + {% for field, change in changed_fields.items %} + {% verbose_name event.content_type.app_label event.content_type.model field as verbose_name %} + <li> + {{ verbose_name }}: <s>{{ change.0 }}</s> → {{ change.1 }} + </li> + {% endfor %} + </ul> + {% endif %} + </li> + {% endif %} + {% endfor %} +</ul> diff --git a/aleksis/core/templates/core/footer-menu.html b/aleksis/core/templates/core/partials/footer-menu.html similarity index 100% rename from aleksis/core/templates/core/footer-menu.html rename to aleksis/core/templates/core/partials/footer-menu.html diff --git a/aleksis/core/templates/core/language_form.html b/aleksis/core/templates/core/partials/language_form.html similarity index 100% rename from aleksis/core/templates/core/language_form.html rename to aleksis/core/templates/core/partials/language_form.html diff --git a/aleksis/core/templates/core/meta.html b/aleksis/core/templates/core/partials/meta.html similarity index 100% rename from aleksis/core/templates/core/meta.html rename to aleksis/core/templates/core/partials/meta.html diff --git a/aleksis/core/templates/core/no_person.html b/aleksis/core/templates/core/partials/no_person.html similarity index 100% rename from aleksis/core/templates/core/no_person.html rename to aleksis/core/templates/core/partials/no_person.html diff --git a/aleksis/core/templates/core/on_page_menu.html b/aleksis/core/templates/core/partials/on_page_menu.html similarity index 100% rename from aleksis/core/templates/core/on_page_menu.html rename to aleksis/core/templates/core/partials/on_page_menu.html diff --git a/aleksis/core/templates/core/save_button.html b/aleksis/core/templates/core/partials/save_button.html similarity index 100% rename from aleksis/core/templates/core/save_button.html rename to aleksis/core/templates/core/partials/save_button.html diff --git a/aleksis/core/templates/core/sidenav.html b/aleksis/core/templates/core/partials/sidenav.html similarity index 100% rename from aleksis/core/templates/core/sidenav.html rename to aleksis/core/templates/core/partials/sidenav.html diff --git a/aleksis/core/templates/core/turnable.html b/aleksis/core/templates/core/partials/turnable.html similarity index 100% rename from aleksis/core/templates/core/turnable.html rename to aleksis/core/templates/core/partials/turnable.html diff --git a/aleksis/core/templates/core/persons_accounts.html b/aleksis/core/templates/core/person/accounts.html similarity index 100% rename from aleksis/core/templates/core/persons_accounts.html rename to aleksis/core/templates/core/person/accounts.html diff --git a/aleksis/core/templates/core/edit_person.html b/aleksis/core/templates/core/person/edit.html similarity index 89% rename from aleksis/core/templates/core/edit_person.html rename to aleksis/core/templates/core/person/edit.html index 8a5d0ca39a8fa0cbfd437519a09723ce59278c2d..8f854610e3424b9da47f142cef41b71c9e0fb097 100644 --- a/aleksis/core/templates/core/edit_person.html +++ b/aleksis/core/templates/core/person/edit.html @@ -14,7 +14,7 @@ <form method="post" enctype="multipart/form-data"> {% csrf_token %} {% form form=edit_person_form %}{% endform %} - {% include "core/save_button.html" %} + {% include "core/partials/save_button.html" %} </form> {% endblock %} diff --git a/aleksis/core/templates/core/person_full.html b/aleksis/core/templates/core/person/full.html similarity index 100% rename from aleksis/core/templates/core/person_full.html rename to aleksis/core/templates/core/person/full.html diff --git a/aleksis/core/templates/core/persons.html b/aleksis/core/templates/core/person/list.html similarity index 100% rename from aleksis/core/templates/core/persons.html rename to aleksis/core/templates/core/person/list.html diff --git a/aleksis/core/templates/dynamic_preferences/form.html b/aleksis/core/templates/dynamic_preferences/form.html index d8de37802c09a14327b0d949b5935fb69660c666..da3d608285c43673bd27932d0d19cef303b1cef7 100644 --- a/aleksis/core/templates/dynamic_preferences/form.html +++ b/aleksis/core/templates/dynamic_preferences/form.html @@ -22,7 +22,7 @@ <form action="" enctype="multipart/form-data" method="post"> {% csrf_token %} {% form form=form %}{% endform %} - {% include "core/save_button.html" with caption=_("Save preferences") %} + {% include "core/partials/save_button.html" with caption=_("Save preferences") %} </form> </div> {% endblock %} diff --git a/aleksis/core/templates/templated_email/notification.email b/aleksis/core/templates/templated_email/notification.email index bb39f5861876b1dc1c4c39639de649ec6d6590eb..8e61fd0df28b5bc6342c891921831e147e9cc8d9 100644 --- a/aleksis/core/templates/templated_email/notification.email +++ b/aleksis/core/templates/templated_email/notification.email @@ -2,21 +2,48 @@ {% block subject %} {% trans "New notification for" %} {{ notification_user }} {% endblock %} +{% block plain %} + {% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %} + + {% trans "we got a new notification for you:" %} + + {{ notification.title }} + + {{ notification.description }} + + {% if notification.link %} + {% trans "More information" %} → {{ notification.link }} + {% endif %} + + {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %} + Sent by {{ trans_sender }} at {{ trans_created_at }} + {% endblocktrans %} + + {% trans "Your AlekSIS team" %} +{% endblock %} + {% block html %} <main> - <p>{% trans "Dear" %} {{ notification_user }}, <br> - {% trans "we got a new notification for you:" %}</p> + <p>{% blocktrans with notification_user=notification_user %}Dear {{ notification_user }},{% endblocktrans %}</p> + + <p>{% trans "we got a new notification for you:" %}</p> + <blockquote> + <h5>{{ notification.title }}</h5> <p>{{ notification.description }}</p> {% if notification.link %} <a href="{{ notification.link }}">{% trans "More information" %} →</a> {% endif %} </blockquote> - {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %} - <p>By {{ trans_sender }} at {{ trans_created_at }}</p> + <p> + {% blocktrans with trans_sender=notification.sender trans_created_at=notification.created_at %} + Sent by {{ trans_sender }} at {{ trans_created_at }} + {% endblocktrans %} + </p> - <i>Your AlekSIS team</i> - {% endblocktrans %} + <p> + <i>{% trans "Your AlekSIS team" %}</i> + </p> </main> {% endblock %} diff --git a/aleksis/core/templatetags/data_helpers.py b/aleksis/core/templatetags/data_helpers.py index 19f286434db34eb9e74b88950b8d87ee854959ef..f7393c73fd86eba6f861f87e321bf8dc5cbce6fd 100644 --- a/aleksis/core/templatetags/data_helpers.py +++ b/aleksis/core/templatetags/data_helpers.py @@ -1,6 +1,8 @@ -from typing import Any +import json +from typing import Any, Optional, Union from django import template +from django.contrib.contenttypes.models import ContentType register = template.Library() @@ -16,3 +18,24 @@ def get_dict(value: Any, arg: Any) -> Any: return value[int(arg)] else: return None + + +@register.simple_tag +def verbose_name(app_label: str, model: str, field: Optional[str] = None) -> str: + """Get a verbose name of a model or a field by app label and model name.""" + ct = ContentType.objects.get(app_label=app_label, model=model).model_class() + + if field: + # Field + return ct._meta.get_field(field).verbose_name.title() + else: + # Whole model + return ct._meta.verbose_name.title() + + +@register.simple_tag +def parse_json(value: Optional[str] = None) -> Union[dict, None]: + """Template tag for parsing JSON from a string.""" + if not value: + return None + return json.loads(value) diff --git a/aleksis/core/tests/models/test_notification.py b/aleksis/core/tests/models/test_notification.py new file mode 100644 index 0000000000000000000000000000000000000000..1b1a6df054fe24f06bd363e825df3f1259af53e8 --- /dev/null +++ b/aleksis/core/tests/models/test_notification.py @@ -0,0 +1,34 @@ +import pytest + +from aleksis.core.models import Notification, Person + +pytestmark = pytest.mark.django_db + + +def test_email_notification(mailoutbox): + email = "doe@example.com" + recipient = Person.objects.create(first_name="Jane", last_name="Doe", email=email) + + sender = "Foo" + title = "There is happened something." + description = "Here you get some more information." + link = "https://aleksis.org/" + + notification = Notification( + sender=sender, recipient=recipient, title=title, description=description, link=link + ) + notification.save() + + assert notification.sent + + assert len(mailoutbox) == 1 + + mail = mailoutbox[0] + + assert email in mail.to + assert title in mail.body + assert description in mail.body + assert link in mail.body + assert sender in mail.body + assert recipient.addressing_name in mail.subject + assert recipient.addressing_name in mail.body diff --git a/aleksis/core/tests/templatetags/test_data_helpers.py b/aleksis/core/tests/templatetags/test_data_helpers.py index ce43e578dbd84f95ed96a6cf1426d615f7e9c816..176e08b22ecb252d5b0113583ab534f46e978072 100644 --- a/aleksis/core/tests/templatetags/test_data_helpers.py +++ b/aleksis/core/tests/templatetags/test_data_helpers.py @@ -1,4 +1,10 @@ -from aleksis.core.templatetags.data_helpers import get_dict +import json + +import pytest + +from aleksis.core.templatetags.data_helpers import get_dict, parse_json, verbose_name + +pytestmark = pytest.mark.django_db def test_get_dict_object(): @@ -24,3 +30,24 @@ def test_get_dict_invalid(): _foo = 12 assert get_dict(_foo, "bar") is None + + +def test_verbose_name_model(): + assert verbose_name("core", "person") == "Person" + + +def test_verbose_name_field(): + assert verbose_name("core", "person", "first_name") == "First Name" + + +def test_parse_json_json(): + foo = {"foo": 12, "bar": "12", "baz": []} + foo_json = json.dumps(foo) + + assert parse_json(foo_json) == foo + assert parse_json("{}") == {} + + +def test_parse_json_empty(): + assert parse_json(None) is None + assert parse_json("") is None diff --git a/aleksis/core/tests/views/test_account.py b/aleksis/core/tests/views/test_account.py index da1c2b90ef0f867b7239817b7ce60fc1cc2478b0..28686eabf8290a7a72444d8fe5f4b71bb74f4626 100644 --- a/aleksis/core/tests/views/test_account.py +++ b/aleksis/core/tests/views/test_account.py @@ -39,4 +39,4 @@ def test_logout(client, django_user_model): response = client.get(reverse("logout"), follow=True) assert response.status_code == 200 - assert "Enter your credentials." in response.content.decode("utf-8") + assert "Please login to see this page." in response.content.decode("utf-8") diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 4d9f84de5f69e21f70a880c6075b6df3eba46df4..373d35a8b22998f81be159292ba2ea03f64fabcf 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -28,7 +28,23 @@ urlpatterns = [ path("person/<int:id_>", views.person, name="person_by_id"), path("person/<int:id_>/edit", views.edit_person, name="edit_person_by_id"), path("groups", views.groups, name="groups"), + path("groups/additional_fields", views.additional_fields, name="additional_fields"), path("groups/child_groups/", views.groups_child_groups, name="groups_child_groups"), + path( + "groups/additional_field/<int:id_>/edit", + views.edit_additional_field, + name="edit_additional_field_by_id", + ), + path( + "groups/additional_field/create", + views.edit_additional_field, + name="create_additional_field", + ), + path( + "groups/additional_field/<int:id_>/delete", + views.delete_additional_field, + name="delete_additional_field_by_id", + ), path("group/create", views.edit_group, name="create_group"), path("group/<int:id_>", views.group, name="group_by_id"), path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"), diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py index 1500537334ebf5d339955c55a480776053b01d03..d263feb9a2ba0c9e36d821a56e12f77726a7a672 100644 --- a/aleksis/core/util/apps.py +++ b/aleksis/core/util/apps.py @@ -1,4 +1,3 @@ -from importlib import import_module from typing import Any, List, Optional, Sequence, Tuple import django.apps @@ -19,15 +18,6 @@ class AppConfig(django.apps.AppConfig): def ready(self): super().ready() - # Run model extension code - try: - import_module( - ".".join(self.__class__.__module__.split(".")[:-1] + ["model_extensions"]) - ) - except ImportError: - # ImportErrors are non-fatal because model extensions are optional. - pass - # Register default listeners pre_migrate.connect(self.pre_migrate, sender=self) post_migrate.connect(self.post_migrate, sender=self) @@ -38,13 +28,6 @@ class AppConfig(django.apps.AppConfig): # Getting an app ready means it should look at its config once self.preference_updated(self) - # Register system checks of this app - try: - import_module(".".join(self.__class__.__module__.split(".")[:-1] + ["checks"])) - except ImportError: - # ImportErrors are non-fatal because checks are optional. - pass - @classmethod def get_name(cls): """Get name of application package.""" diff --git a/aleksis/core/util/notifications.py b/aleksis/core/util/notifications.py index c9cbcb9f76700d3e26f9c448a5e02c5e103a4936..38d27057033752d6ae7cdefce9c53b4728447b80 100644 --- a/aleksis/core/util/notifications.py +++ b/aleksis/core/util/notifications.py @@ -73,13 +73,13 @@ def send_notification(notification: Union[int, "Notification"], resend: bool = F If resend is passed as True, the notification is sent even if it was previously marked as sent. """ - channels = lazy_preference("notification", "channels") - if isinstance(notification, int): - notification = apps.get_model("core", "Notification") - notification_ = notification.objects.get(pk=notification) + Notification = apps.get_model("core", "Notification") + notification = Notification.objects.get(pk=notification) + + channels = [notification.recipient.preferences["notification__channels"]] - if resend or not notification_.sent: + if resend or not notification.sent: for channel in channels: name, check, send = _CHANNELS_MAP[channel] if check(): diff --git a/aleksis/core/util/predicates.py b/aleksis/core/util/predicates.py index 0e452ddb0cb560ba4edbe174c560ade56c6261ed..975273d7d934ae41df8ecefb494b121ed69b2a94 100644 --- a/aleksis/core/util/predicates.py +++ b/aleksis/core/util/predicates.py @@ -98,6 +98,12 @@ def is_group_owner(user: User, group: Group) -> bool: return group.owners.filter(owners=user.person).exists() +@predicate +def is_group_member(user: User, group: Group) -> bool: + """Predicate which checks if the user is a member of the provided group.""" + return user.person in group.members.all() + + @predicate def is_notification_recipient(user: User, obj: Model) -> bool: """Check if is a notification recipient. diff --git a/aleksis/core/views.py b/aleksis/core/views.py index 81dc709017dac791c2e239379cc9cf262bfe3e71..92f0bf2fc5d5fd863c9427f4acac9a65081c5b5f 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -1,6 +1,7 @@ from typing import Optional from django.apps import apps +from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator @@ -20,6 +21,7 @@ from .filters import GroupFilter from .forms import ( AnnouncementForm, ChildGroupsForm, + EditAdditionalFieldForm, EditGroupForm, EditGroupTypeForm, EditPersonForm, @@ -28,13 +30,21 @@ from .forms import ( PersonsAccountsFormSet, SitePreferenceForm, ) -from .models import Announcement, DashboardWidget, Group, GroupType, Notification, Person +from .models import ( + AdditionalField, + Announcement, + DashboardWidget, + Group, + GroupType, + Notification, + Person, +) from .registries import ( group_preferences_registry, person_preferences_registry, site_preferences_registry, ) -from .tables import GroupsTable, GroupTypesTable, PersonsTable +from .tables import AdditionalFieldsTable, GroupsTable, GroupTypesTable, PersonsTable from .util import messages from .util.apps import AppConfig from .util.core_helpers import objectgetter_optional @@ -67,7 +77,7 @@ def index(request: HttpRequest) -> HttpResponse: def offline(request: HttpRequest) -> HttpResponse: """Offline message for PWA.""" - return render(request, "core/offline.html") + return render(request, "core/pages/offline.html") def about(request: HttpRequest) -> HttpResponse: @@ -78,7 +88,7 @@ def about(request: HttpRequest) -> HttpResponse: filter(lambda a: isinstance(a, AppConfig), apps.get_app_configs()) ) - return render(request, "core/about.html", context) + return render(request, "core/pages/about.html", context) @permission_required("core.view_persons") @@ -96,7 +106,7 @@ def persons(request: HttpRequest) -> HttpResponse: RequestConfig(request).configure(persons_table) context["persons_table"] = persons_table - return render(request, "core/persons.html", context) + return render(request, "core/person/list.html", context) @permission_required( @@ -117,7 +127,7 @@ def person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: RequestConfig(request).configure(groups_table) context["groups_table"] = groups_table - return render(request, "core/person_full.html", context) + return render(request, "core/person/full.html", context) @permission_required("core.view_group", fn=objectgetter_optional(Group, None, False)) @@ -147,7 +157,7 @@ def group(request: HttpRequest, id_: int) -> HttpResponse: RequestConfig(request).configure(owners_table) context["owners_table"] = owners_table - return render(request, "core/group_full.html", context) + return render(request, "core/group/full.html", context) @permission_required("core.view_groups") @@ -163,7 +173,7 @@ def groups(request: HttpRequest) -> HttpResponse: RequestConfig(request).configure(groups_table) context["groups_table"] = groups_table - return render(request, "core/groups.html", context) + return render(request, "core/group/list.html", context) @permission_required("core.link_persons_accounts") @@ -183,7 +193,7 @@ def persons_accounts(request: HttpRequest) -> HttpResponse: context["persons_accounts_formset"] = persons_accounts_formset - return render(request, "core/persons_accounts.html", context) + return render(request, "core/person/accounts.html", context) @permission_required("core.assign_child_groups_to_groups") @@ -220,7 +230,7 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse: context["group"] = group context["form"] = form - return render(request, "core/groups_child_groups.html", context) + return render(request, "core/group/child_groups.html", context) @permission_required( @@ -245,7 +255,7 @@ def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse context["edit_person_form"] = edit_person_form - return render(request, "core/edit_person.html", context) + return render(request, "core/person/edit.html", context) def get_group_by_id(request: HttpRequest, id_: Optional[int] = None): @@ -272,22 +282,22 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: if request.method == "POST": if edit_group_form.is_valid(): - edit_group_form.save(commit=True) + group = edit_group_form.save(commit=True) messages.success(request, _("The group has been saved.")) - return redirect("groups") + return redirect("group_by_id", group.pk) context["edit_group_form"] = edit_group_form - return render(request, "core/edit_group.html", context) + return render(request, "core/group/edit.html", context) @permission_required("core.manage_data") def data_management(request: HttpRequest) -> HttpResponse: """View with special menu for data management.""" context = {} - return render(request, "core/data_management.html", context) + return render(request, "core/management/data_management.html", context) @permission_required("core.view_system_status") @@ -295,7 +305,17 @@ def system_status(request: HttpRequest) -> HttpResponse: """View giving information about the system status.""" context = {} - return render(request, "core/system_status.html", context) + if "django_celery_results" in settings.INSTALLED_APPS: + from django_celery_results.models import TaskResult # noqa + from celery.task.control import inspect # noqa + if inspect().registered_tasks(): + job_list = list(inspect().registered_tasks().values())[0] + results = [] + for job in job_list: + results.append(TaskResult.objects.filter(task_name=job).last()) + context["tasks"] = results + + return render(request, "core/pages/system_status.html", context) @permission_required( @@ -447,6 +467,71 @@ def preferences( return render(request, "dynamic_preferences/form.html", context) +@permission_required( + "core.change_additionalfield", fn=objectgetter_optional(AdditionalField, None, False) +) +def edit_additional_field(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: + """View to edit or create a additional_field.""" + context = {} + + additional_field = objectgetter_optional(AdditionalField, None, False)(request, id_) + context["additional_field"] = additional_field + + if id_: + # Edit form for existing additional_field + edit_additional_field_form = EditAdditionalFieldForm( + request.POST or None, instance=additional_field + ) + else: + if request.user.has_perm("core.create_additionalfield"): + # Empty form to create a new additional_field + edit_additional_field_form = EditAdditionalFieldForm(request.POST or None) + else: + raise PermissionDenied() + + if request.method == "POST": + if edit_additional_field_form.is_valid(): + edit_additional_field_form.save(commit=True) + + messages.success(request, _("The additional_field has been saved.")) + + return redirect("additional_fields") + + context["edit_additional_field_form"] = edit_additional_field_form + + return render(request, "core/additional_field/edit.html", context) + + +@permission_required("core.view_additionalfield") +def additional_fields(request: HttpRequest) -> HttpResponse: + """List view for listing all additional fields.""" + context = {} + + # Get all additional fields + additional_fields = get_objects_for_user( + request.user, "core.view_additionalfield", AdditionalField + ) + + # Build table + additional_fields_table = AdditionalFieldsTable(additional_fields) + RequestConfig(request).configure(additional_fields_table) + context["additional_fields_table"] = additional_fields_table + + return render(request, "core/additional_field/list.html", context) + + +@permission_required( + "core.delete_additionalfield", fn=objectgetter_optional(AdditionalField, None, False) +) +def delete_additional_field(request: HttpRequest, id_: int) -> HttpResponse: + """View to delete an additional field.""" + additional_field = objectgetter_optional(AdditionalField, None, False)(request, id_) + additional_field.delete() + messages.success(request, _("The additional field has been deleted.")) + + return redirect("additional_fields") + + @permission_required("core.change_grouptype", fn=objectgetter_optional(GroupType, None, False)) def edit_group_type(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse: """View to edit or create a group_type.""" @@ -472,7 +557,7 @@ def edit_group_type(request: HttpRequest, id_: Optional[int] = None) -> HttpResp context["edit_group_type_form"] = edit_group_type_form - return render(request, "core/edit_group_type.html", context) + return render(request, "core/group_type/edit.html", context) @permission_required("core.view_grouptype") @@ -488,7 +573,7 @@ def group_types(request: HttpRequest) -> HttpResponse: RequestConfig(request).configure(group_types_table) context["group_types_table"] = group_types_table - return render(request, "core/group_types.html", context) + return render(request, "core/group_type/list.html", context) @permission_required("core.delete_grouptype", fn=objectgetter_optional(GroupType, None, False)) diff --git a/ci/build_dist.yml b/ci/build_dist.yml new file mode 100644 index 0000000000000000000000000000000000000000..939cdf326decd7642edff67a62f84ff028f30d56 --- /dev/null +++ b/ci/build_dist.yml @@ -0,0 +1,7 @@ +build_dist: + stage: build + script: + - tox -e build + artifacts: + paths: + - dist/ diff --git a/ci/build_docker.yml b/ci/build_docker.yml new file mode 100644 index 0000000000000000000000000000000000000000..92a1572c6ac4d1878110500f458262a6a7c5bdf4 --- /dev/null +++ b/ci/build_docker.yml @@ -0,0 +1,22 @@ +build_docker: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" >/kaniko/.docker/config.json + - /kaniko/executor + --context $CI_PROJECT_DIR + --dockerfile $CI_PROJECT_DIR/Dockerfile + --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME + --cache=true + --cleanup + - /kaniko/executor + --context $CI_PROJECT_DIR/docker/nginx + --dockerfile $CI_PROJECT_DIR/docker/nginx/Dockerfile + --destination $CI_REGISTRY_IMAGE/nginx:$CI_COMMIT_REF_NAME + --cache=true + --cleanup + only: + - master + - tags diff --git a/ci/deploy.yml b/ci/deploy.yml new file mode 100644 index 0000000000000000000000000000000000000000..d01e23e4f3436bc8dd4849d283038591157e9668 --- /dev/null +++ b/ci/deploy.yml @@ -0,0 +1,43 @@ +pages: + stage: deploy + before_script: + - cp -r .tox/screenshots/firefox docs/screenshots + script: + - export LC_ALL=en_GB.utf8 + - tox -e docs -- BUILDDIR=../public/docs + artifacts: + paths: + - public/ + only: + - master + +deploy_demo-master: + stage: deploy + environment: + name: demo/master + url: http://demo-master.aleksis.org + before_script: + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - echo "$SSH_KNOWN_HOSTS" >~/.ssh/known_hosts + - chmod 644 ~/.ssh/known_hosts + script: + - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org + env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME} + docker-compose + -p aleksis-${CI_ENVIRONMENT_SLUG} + -f /dev/stdin + pull + - grep -v "build:" docker-compose.yml | ssh root@demo-master.aleksis.org + env ALEKSIS_IMAGE_TAG=${CI_COMMIT_REF_NAME} + NGINX_HTTP_PORT=80 + ALEKSIS_maintenance__debug=true + docker-compose + -p aleksis-${CI_ENVIRONMENT_SLUG} + -f /dev/stdin + up -d + only: + - master diff --git a/ci/general.yml b/ci/general.yml new file mode 100644 index 0000000000000000000000000000000000000000..58891983dbeda6b9c8f49dc07e490a021a4c31dd --- /dev/null +++ b/ci/general.yml @@ -0,0 +1,20 @@ +image: registry.edugit.org/teckids/team-sysadmin/docker-images/python-pimped:latest + +stages: + - test + - build + - deploy + +variables: + GIT_SUBMODULE_STRATEGY: recursive + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + FF_NETWORK_PER_BUILD: "true" + +cache: + key: + files: + - poetry.lock + - pyproject.toml + paths: + - .cache/pip + - .tox diff --git a/ci/test.yml b/ci/test.yml new file mode 100644 index 0000000000000000000000000000000000000000..2ab64bc324536c8716c5cb8641d09d9c43dbf844 --- /dev/null +++ b/ci/test.yml @@ -0,0 +1,26 @@ +test: + stage: test + services: + - name: selenium/standalone-firefox + alias: selenium + before_script: + - adduser --disabled-password --gecos "Test User" testuser + - chown -R testuser . + script: + - sudo apt update + - sudo apt install python3-ldap libldap2-dev libssl-dev libsasl2-dev python3.7-dev -y + - sudo -u testuser + env TEST_SELENIUM_HUB=http://selenium:4444/wd/hub + TEST_SELENIUM_BROWSERS=firefox + TEST_HOST=build + tox -e selenium -- --junitxml=.tox/junit.xml + artifacts: + paths: + - .tox/screenshots + reports: + junit: .tox/junit.xml + +lint: + stage: test + script: + - tox -e lint,security diff --git a/docs/dev/01_setup.rst b/docs/dev/01_setup.rst index df0ffd11c21112912f0934c9f16aed909bd8cbb9..d61c21e9a074310ec599f8bcffb6824dabe64bac 100644 --- a/docs/dev/01_setup.rst +++ b/docs/dev/01_setup.rst @@ -28,7 +28,7 @@ Install native dependencies Some system libraries are required to install AlekSIS:: - sudo apt install build-essential libpq-dev libpq5 libssl-dev python3-dev python3-pip python3-venv yarnpkg + sudo apt install build-essential libpq-dev libpq5 libssl-dev python3-dev python3-pip python3-venv yarnpkg gettext Get Poetry diff --git a/poetry.lock b/poetry.lock index 6a1072253c76129eec46481ec04b584724013e5b..2722fb59608ca242b559e8991b00dca23df2328f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,7 +12,7 @@ description = "Low-level AMQP client for Python (fork of amqplib)." name = "amqp" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.5.2" +version = "2.6.0" [package.dependencies] vine = ">=1.1.3,<5.0.0a1" @@ -23,7 +23,7 @@ description = "A small Python module for determining appropriate platform-specif name = "appdirs" optional = false python-versions = "*" -version = "1.4.3" +version = "1.4.4" [[package]] category = "main" @@ -91,7 +91,7 @@ description = "Screen-scraping library" name = "beautifulsoup4" optional = false python-versions = "*" -version = "4.9.0" +version = "4.9.1" [package.dependencies] soupsieve = [">1.2", "<2.0"] @@ -165,12 +165,12 @@ category = "main" description = "Distributed Task Queue." name = "celery" optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*," -version = "4.4.2" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "4.4.4" [package.dependencies] billiard = ">=3.6.3.0,<4.0" -kombu = ">=4.6.8,<4.7" +kombu = ">=4.6.10,<4.7" pytz = ">0.0-dev" vine = "1.3.0" @@ -187,10 +187,10 @@ arangodb = ["pyArango (>=1.3.2)"] auth = ["cryptography"] azureblockblob = ["azure-storage (0.36.0)", "azure-common (1.1.5)", "azure-storage-common (1.1.0)"] brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"] -cassandra = ["cassandra-driver"] +cassandra = ["cassandra-driver (<3.21.0)"] consul = ["python-consul"] cosmosdbsql = ["pydocumentdb (2.3.2)"] -couchbase = ["couchbase", "couchbase-cffi"] +couchbase = ["couchbase-cffi (<3.0.0)", "couchbase (<3.0.0)"] couchdb = ["pycouchdb"] django = ["Django (>=1.11)"] dynamodb = ["boto3 (>=1.9.178)"] @@ -210,7 +210,7 @@ s3 = ["boto3 (>=1.9.125)"] slmq = ["softlayer-messaging (>=1.0.3)"] solar = ["ephem"] sqlalchemy = ["sqlalchemy"] -sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.2)"] +sqs = ["boto3 (>=1.9.125)", "pycurl (7.43.0.5)"] tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=1.3.1)"] @@ -222,11 +222,10 @@ description = "An app for integrating Celery with Haystack." name = "celery-haystack" optional = true python-versions = "*" -version = "0.7.2" +version = "0.10" [package.dependencies] django-appconf = ">=0.4.1" -django-celery-transactions = ">=0.1.2" [[package]] category = "main" @@ -308,7 +307,7 @@ description = "A high-level Python Web framework that encourages rapid developme name = "django" optional = false python-versions = ">=3.6" -version = "3.0.6" +version = "3.0.7" [package.dependencies] asgiref = ">=3.2,<4.0" @@ -347,7 +346,7 @@ description = "Django LDAP authentication backend." name = "django-auth-ldap" optional = true python-versions = ">=3.5" -version = "2.1.1" +version = "2.2.0" [package.dependencies] Django = ">=1.11" @@ -381,8 +380,8 @@ category = "main" description = "Django utility for a memoization decorator that uses the Django cache framework." name = "django-cache-memoize" optional = false -python-versions = "*" -version = "0.1.6" +python-versions = ">=3.4" +version = "0.1.7" [package.extras] dev = ["flake8", "tox", "twine", "therapist", "black"] @@ -425,18 +424,6 @@ version = "1.2.1" [package.dependencies] celery = ">=4.4,<5.0" -[[package]] -category = "main" -description = "Django transaction support for Celery tasks." -name = "django-celery-transactions" -optional = true -python-versions = "*" -version = "0.3.6" - -[package.dependencies] -Django = ">=1.2.4" -celery = ">=2.2.7" - [[package]] category = "main" description = "Django admin CKEditor integration." @@ -500,7 +487,7 @@ description = "Yet another Django audit log app, hopefully the simplest one." name = "django-easy-audit" optional = false python-versions = "*" -version = "1.2.2" +version = "1.2.3a4" [package.dependencies] beautifulsoup4 = "*" @@ -648,7 +635,7 @@ description = "Material design for django forms and admin" name = "django-material" optional = false python-versions = "*" -version = "1.6.3" +version = "1.6.7" [package.dependencies] six = "*" @@ -678,7 +665,7 @@ description = "A pluggable framework for adding two-factor authentication to Dja name = "django-otp" optional = false python-versions = "*" -version = "0.9.0" +version = "0.9.1" [package.dependencies] django = ">=1.11" @@ -732,7 +719,7 @@ description = "A Django app to include a manifest.json and Service Worker instan name = "django-pwa" optional = false python-versions = "*" -version = "1.0.8" +version = "1.0.9" [package.dependencies] django = ">=1.8" @@ -777,12 +764,15 @@ description = "Select2 option fields for Django" name = "django-select2" optional = false python-versions = "*" -version = "7.2.3" +version = "7.4.2" [package.dependencies] django = ">=2.2" django-appconf = ">=0.6.0" +[package.extras] +test = ["pytest", "pytest-cov", "pytest-django", "selenium"] + [[package]] category = "main" description = "Makes specified django settings visible in template rendering context." @@ -963,21 +953,13 @@ version = "2.7" django = ">=1.11,<4.0" pillow = "*" -[[package]] -category = "dev" -description = "Discover and load entry points from installed packages." -name = "entrypoints" -optional = false -python-versions = ">=2.7" -version = "0.3" - [[package]] category = "main" description = "Faker is a Python package that generates fake data for you." name = "faker" optional = false python-versions = ">=3.4" -version = "4.0.3" +version = "4.1.0" [package.dependencies] python-dateutil = ">=2.4" @@ -985,17 +967,20 @@ text-unidecode = "1.3" [[package]] category = "dev" -description = "the modular source code checker: pep8, pyflakes and co" +description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.7.9" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "3.8.2" [package.dependencies] -entrypoints = ">=0.3.0,<0.4.0" mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.5.0,<2.6.0" -pyflakes = ">=2.1.0,<2.2.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" [[package]] category = "dev" @@ -1017,10 +1002,10 @@ description = "flake8 plugin to call black as a code style validator" name = "flake8-black" optional = false python-versions = "*" -version = "0.1.1" +version = "0.2.0" [package.dependencies] -black = ">=19.3b0" +black = "*" flake8 = ">=3.0.0" [[package]] @@ -1029,7 +1014,7 @@ description = "Check for python builtins being used as variables or parameters." name = "flake8-builtins" optional = false python-versions = "*" -version = "1.5.2" +version = "1.5.3" [package.dependencies] flake8 = "*" @@ -1043,7 +1028,7 @@ description = "Plugin to catch bad style specific to Django Projects" name = "flake8-django" optional = false python-versions = "*" -version = "1.0.0" +version = "1.1.1" [package.dependencies] flake8 = "*" @@ -1140,7 +1125,7 @@ description = "Python Git Library" name = "gitpython" optional = false python-versions = ">=3.4" -version = "3.1.2" +version = "3.1.3" [package.dependencies] gitdb = ">=4.0.1,<5" @@ -1219,10 +1204,10 @@ description = "Messaging library for Python." name = "kombu" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.6.8" +version = "4.6.10" [package.dependencies] -amqp = ">=2.5.2,<2.6" +amqp = ">=2.6.0,<2.7" [package.dependencies.importlib-metadata] python = "<3.8" @@ -1288,7 +1273,7 @@ description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" -version = "8.2.0" +version = "8.3.0" [[package]] category = "dev" @@ -1320,7 +1305,7 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.3" +version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" @@ -1367,7 +1352,7 @@ description = "Python version of Google's common library for parsing, formatting name = "phonenumbers" optional = false python-versions = "*" -version = "8.12.2" +version = "8.12.4" [[package]] category = "main" @@ -1434,7 +1419,7 @@ description = "Python style guide checker" name = "pycodestyle" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.5.0" +version = "2.6.0" [[package]] category = "main" @@ -1461,7 +1446,7 @@ description = "passive checker of Python programs" name = "pyflakes" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.1.1" +version = "2.2.0" [[package]] category = "dev" @@ -1498,7 +1483,7 @@ description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.1" +version = "5.4.3" [package.dependencies] atomicwrites = ">=1.0" @@ -1523,15 +1508,15 @@ category = "dev" description = "Pytest plugin for measuring coverage." name = "pytest-cov" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.9.0" [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" [package.extras] -testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] [[package]] category = "dev" @@ -1590,7 +1575,7 @@ description = "Python Crontab API" name = "python-crontab" optional = true python-versions = "*" -version = "2.4.2" +version = "2.5.1" [package.dependencies] python-dateutil = "*" @@ -1684,7 +1669,7 @@ description = "Python client for Redis key-value store" name = "redis" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "3.5.0" +version = "3.5.3" [package.extras] hiredis = ["hiredis (>=0.1.3)"] @@ -1695,7 +1680,7 @@ description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" -version = "2020.4.4" +version = "2020.5.14" [[package]] category = "main" @@ -1721,7 +1706,7 @@ description = "reStructuredText linter" name = "restructuredtext-lint" optional = false python-versions = "*" -version = "1.3.0" +version = "1.3.1" [package.dependencies] docutils = ">=0.11,<1.0" @@ -1774,7 +1759,7 @@ description = "Python 2 and 3 compatibility utilities" name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.14.0" +version = "1.15.0" [[package]] category = "dev" @@ -1798,7 +1783,7 @@ description = "A modern CSS selector implementation for Beautiful Soup." name = "soupsieve" optional = false python-versions = "*" -version = "1.9.5" +version = "1.9.6" [[package]] category = "main" @@ -1806,7 +1791,7 @@ description = "A simple tool/library for working with SPDX license definitions." name = "spdx-license-list" optional = false python-versions = "*" -version = "0.4.0" +version = "0.5.0" [[package]] category = "dev" @@ -1814,7 +1799,7 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "3.0.3" +version = "3.0.4" [package.dependencies] Jinja2 = ">=2.3" @@ -2015,7 +2000,7 @@ description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" optional = false python-versions = "*" -version = "0.10.0" +version = "0.10.1" [[package]] category = "main" @@ -2023,7 +2008,7 @@ description = "Fast, Extensible Progress Meter" name = "tqdm" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "4.46.0" +version = "4.46.1" [package.extras] dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"] @@ -2034,7 +2019,7 @@ description = "Twilio API client and TwiML generator" name = "twilio" optional = false python-versions = "*" -version = "6.39.0" +version = "6.41.0" [package.dependencies] PyJWT = ">=1.4.2" @@ -2084,11 +2069,11 @@ version = "1.3.0" [[package]] category = "dev" -description = "Measures number of Terminal column cells of wide-character codes" +description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" optional = false python-versions = "*" -version = "0.1.9" +version = "0.2.3" [[package]] category = "main" @@ -2128,7 +2113,7 @@ celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celer ldap = ["django-auth-ldap"] [metadata] -content-hash = "8123606bea67a68e0df620d9760ab06f724c70eea16a651be634a832a3fb3fe2" +content-hash = "3e85d3bfff56719272c13ad591ca4ccd7a2b72324a33c2192be383536b66b5f8" python-versions = "^3.7" [metadata.files] @@ -2137,12 +2122,12 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] amqp = [ - {file = "amqp-2.5.2-py2.py3-none-any.whl", hash = "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8"}, - {file = "amqp-2.5.2.tar.gz", hash = "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"}, + {file = "amqp-2.6.0-py2.py3-none-any.whl", hash = "sha256:bb68f8d2bced8f93ccfd07d96c689b716b3227720add971be980accfc2952139"}, + {file = "amqp-2.6.0.tar.gz", hash = "sha256:24dbaff8ce4f30566bb88976b398e8c4e77637171af3af6f1b9650f48890e60b"}, ] appdirs = [ - {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, - {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] asgiref = [ {file = "asgiref-3.2.7-py2.py3-none-any.whl", hash = "sha256:9ca8b952a0a9afa61d30aa6d3d9b570bb3fd6bafcf7ec9e6bed43b936133db1c"}, @@ -2165,9 +2150,9 @@ bandit = [ {file = "bandit-1.6.2.tar.gz", hash = "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"}, ] beautifulsoup4 = [ - {file = "beautifulsoup4-4.9.0-py2-none-any.whl", hash = "sha256:a4bbe77fd30670455c5296242967a123ec28c37e9702a8a81bd2f20a4baf0368"}, - {file = "beautifulsoup4-4.9.0-py3-none-any.whl", hash = "sha256:d4e96ac9b0c3a6d3f0caae2e4124e6055c5dcafde8e2f831ff194c104f0775a0"}, - {file = "beautifulsoup4-4.9.0.tar.gz", hash = "sha256:594ca51a10d2b3443cbac41214e12dbb2a1cd57e1a7344659849e2e20ba6a8d8"}, + {file = "beautifulsoup4-4.9.1-py2-none-any.whl", hash = "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c"}, + {file = "beautifulsoup4-4.9.1-py3-none-any.whl", hash = "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8"}, + {file = "beautifulsoup4-4.9.1.tar.gz", hash = "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7"}, ] billiard = [ {file = "billiard-3.6.3.0-py3-none-any.whl", hash = "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede"}, @@ -2190,11 +2175,12 @@ calendarweek = [ {file = "calendarweek-0.4.5.tar.gz", hash = "sha256:5b1788ca435022f9348fc81a718974e51dd85d080f9aa3dad717df70a1bc6e1f"}, ] celery = [ - {file = "celery-4.4.2-py2.py3-none-any.whl", hash = "sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a"}, - {file = "celery-4.4.2.tar.gz", hash = "sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f"}, + {file = "celery-4.4.4-py2.py3-none-any.whl", hash = "sha256:9ae2e73b93cc7d6b48b56aaf49a68c91752d0ffd7dfdcc47f842ca79a6f13eae"}, + {file = "celery-4.4.4.tar.gz", hash = "sha256:c2037b6a8463da43b19969a0fc13f9023ceca6352b4dd51be01c66fbbb13647e"}, ] celery-haystack = [ - {file = "celery-haystack-0.7.2.tar.gz", hash = "sha256:5ee3dfb9d5c1b0cf13b5a8e38cf3cbbde9009fe4470042f3087485743b6971fb"}, + {file = "celery-haystack-0.10.tar.gz", hash = "sha256:b6e2a3c70071bef0838ca1a7d9f14fae6c2ecf385704092e59b82147a1ee552e"}, + {file = "celery_haystack-0.10-py2.py3-none-any.whl", hash = "sha256:ec1f39050661e033f554de99cb9393c2e94427667ff5401f16393b2a68f888fc"}, ] certifi = [ {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, @@ -2257,8 +2243,8 @@ dj-database-url = [ {file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"}, ] django = [ - {file = "Django-3.0.6-py3-none-any.whl", hash = "sha256:051ba55d42daa3eeda3944a8e4df2bc96d4c62f94316dea217248a22563c3621"}, - {file = "Django-3.0.6.tar.gz", hash = "sha256:9aaa6a09678e1b8f0d98a948c56482eac3e3dd2ddbfb8de70a868135ef3b5e01"}, + {file = "Django-3.0.7-py3-none-any.whl", hash = "sha256:e1630333248c9b3d4e38f02093a26f1e07b271ca896d73097457996e0fae12e8"}, + {file = "Django-3.0.7.tar.gz", hash = "sha256:5052b34b34b3425233c682e0e11d658fd6efd587d11335a0203d827224ada8f2"}, ] django-any-js = [ {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"}, @@ -2268,8 +2254,8 @@ django-appconf = [ {file = "django_appconf-1.0.4-py2.py3-none-any.whl", hash = "sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06"}, ] django-auth-ldap = [ - {file = "django-auth-ldap-2.1.1.tar.gz", hash = "sha256:fabbbc35a5d28ce6a12e3f6309229d82bfe6a410391914938593e4b96ce42ec8"}, - {file = "django_auth_ldap-2.1.1-py3-none-any.whl", hash = "sha256:43c47c8eac1d0b1f1ee70d28534c7cef33deefddff996f8fae11dd937cc65e82"}, + {file = "django-auth-ldap-2.2.0.tar.gz", hash = "sha256:11af1773b08613339d2c3a0cec1308a4d563518f17b1719c3759994d0b4d04bf"}, + {file = "django_auth_ldap-2.2.0-py3-none-any.whl", hash = "sha256:0ed2d88d81c39be915a9ab53b97ec0a33a3d16055518ab4c9bcffe8236d40370"}, ] django-bleach = [ {file = "django-bleach-0.6.1.tar.gz", hash = "sha256:674709c26040618aff0741ce8261fd151e5ead405bd50568c2034662d69daac3"}, @@ -2280,8 +2266,8 @@ django-bulk-update = [ {file = "django_bulk_update-2.2.0-py2.py3-none-any.whl", hash = "sha256:49a403392ae05ea872494d74fb3dfa3515f8df5c07cc277c3dc94724c0ee6985"}, ] django-cache-memoize = [ - {file = "django-cache-memoize-0.1.6.tar.gz", hash = "sha256:7f271be70b11155929ee8a4a2b5f53c9fb46b9befa1b546caffa3298e6ac8f7d"}, - {file = "django_cache_memoize-0.1.6-py2.py3-none-any.whl", hash = "sha256:d239e8c37734b0a70b74f94fa33b180b3b0c82c3784beb21209bb4ab64a3e6fb"}, + {file = "django-cache-memoize-0.1.7.tar.gz", hash = "sha256:5e96349b0159aec1eb79257199a1902ea3ed538231ce7b4fee12e563127ca657"}, + {file = "django_cache_memoize-0.1.7-py2.py3-none-any.whl", hash = "sha256:bc7f53725558244af62197d0125732d7ec88ecc1281a3a2f37d77ae1a8c269d3"}, ] django-celery-beat = [ {file = "django-celery-beat-2.0.0.tar.gz", hash = "sha256:fdf1255eecfbeb770c6521fe3e69989dfc6373cd5a7f0fe62038d37f80f47e48"}, @@ -2295,9 +2281,6 @@ django-celery-results = [ {file = "django_celery_results-1.2.1-py2.py3-none-any.whl", hash = "sha256:a29ab580f0e38c66c39f51cc426bbdbb2a391b8cc0867df9dea748db2c961db2"}, {file = "django_celery_results-1.2.1.tar.gz", hash = "sha256:e390f70cc43bbc2cd7c8e4607dc29ab6211a2ab968f93677583f0160921f670c"}, ] -django-celery-transactions = [ - {file = "django-celery-transactions-0.3.6.tar.gz", hash = "sha256:cdf966ec461e9ec736a7bedcf47cb219fc79ea86f2b39191cb258082dd4f4b33"}, -] django-ckeditor = [ {file = "django-ckeditor-5.9.0.tar.gz", hash = "sha256:e4d112851a72c5bf8b586e1c674d34084cab16d28f2553ad15cc770d1e9639c7"}, {file = "django_ckeditor-5.9.0-py2.py3-none-any.whl", hash = "sha256:71c3c7bb46b0cbfb9712ef64af0d2a406eab233f44ecd7c42c24bdfa39ae3bde"}, @@ -2318,8 +2301,8 @@ django-dynamic-preferences = [ {file = "django_dynamic_preferences-1.9-py2.py3-none-any.whl", hash = "sha256:a3c84696f0459d8d6d9c43374ff3db7daa59b46670b461bb954057d08af607e1"}, ] django-easy-audit = [ - {file = "django-easy-audit-1.2.2.tar.gz", hash = "sha256:86e84910bb21928018d8f31a0eed003bf4adfde6dff345bab1bd7a4429868d0a"}, - {file = "django_easy_audit-1.2.2-py3-none-any.whl", hash = "sha256:e9a0c88fbd802da08f84b1d4c49ae99c7cfb46b025be635f1b2ddf8abf7b3613"}, + {file = "django-easy-audit-1.2.3a4.tar.gz", hash = "sha256:55a6512c012fcffc47bca38376d775d15d44d24e823682ea59418c4edabe8f54"}, + {file = "django_easy_audit-1.2.3a4-py3-none-any.whl", hash = "sha256:37c90a273559ba003d691fa0c30ee5ff792b7739d13953f7e8923c954480240f"}, ] django-favicon-plus-reloaded = [ {file = "django-favicon-plus-reloaded-1.0.4.tar.gz", hash = "sha256:90c761c636a338e6e9fb1d086649d82095085f92cff816c9cf074607f28c85a5"}, @@ -2371,8 +2354,8 @@ django-maintenance-mode = [ {file = "django_maintenance_mode-0.14.0-py2-none-any.whl", hash = "sha256:b4cc24a469ed10897826a28f05d64e6166a58d130e4940ac124ce198cd4cc778"}, ] django-material = [ - {file = "django-material-1.6.3.tar.gz", hash = "sha256:f8758afe1beabc16a3c54f5437c7fea15946b7d068eedd89c97d57a363793950"}, - {file = "django_material-1.6.3-py2.py3-none-any.whl", hash = "sha256:502dc88c2f61f190fdc401666e83b47da00cbda98477af6ed8b7d43944ce6407"}, + {file = "django-material-1.6.7.tar.gz", hash = "sha256:3cc68b34348634f019bf529f3e0b99b1474ab36ec9b50040f5e557b5b65add1d"}, + {file = "django_material-1.6.7-py2.py3-none-any.whl", hash = "sha256:9da268532c92c270b512d9610c9723a07dbfea06db98434dac8aa1dd2910778f"}, ] django-menu-generator = [ {file = "django-menu-generator-1.0.4.tar.gz", hash = "sha256:ce71a5055c16933c8aff64fb36c21e5cf8b6d505733aceed1252f8b99369a378"}, @@ -2381,8 +2364,8 @@ django-middleware-global-request = [ {file = "django-middleware-global-request-0.1.2.tar.gz", hash = "sha256:f6490759bc9f7dbde4001709554e29ca715daf847f2222914b4e47117dca9313"}, ] django-otp = [ - {file = "django-otp-0.9.0.tar.gz", hash = "sha256:f5faa95a3e85391e70e433205509fa070ed25646f15fcafd2cd2fbd987c33262"}, - {file = "django_otp-0.9.0-py3-none-any.whl", hash = "sha256:334e2a0ece7e5d9de3263e17bd3b6aee2809d1f8d70555408d5bf8f0c33b13fb"}, + {file = "django-otp-0.9.1.tar.gz", hash = "sha256:f456639addace8b6d1eb77f9edaada1a53dbb4d6f3c19f17c476c4e3e4beb73f"}, + {file = "django_otp-0.9.1-py3-none-any.whl", hash = "sha256:0c67cf6f4bd6fca84027879ace9049309213b6ac81f88e954376a6b5535d96c4"}, ] django-otp-yubikey = [ {file = "django-otp-yubikey-0.5.2.tar.gz", hash = "sha256:f0b1881562fb42ee9f12c28d284cbdb90d1f0383f2d53a595373b080a19bc261"}, @@ -2397,8 +2380,8 @@ django-polymorphic = [ {file = "django_polymorphic-2.1.2-py2.py3-none-any.whl", hash = "sha256:0a25058e95e5e99fe0beeabb8f4734effe242d7b5b77dca416fba9fd3062da6a"}, ] django-pwa = [ - {file = "django-pwa-1.0.8.tar.gz", hash = "sha256:caf9d6e2a792def272e6cb496f594a9821c4d73cb5117d33560bc7b7b82d6132"}, - {file = "django_pwa-1.0.8-py3-none-any.whl", hash = "sha256:88a844095ec3dc38ec8edc8d1f95247eccaebefeb41484fb94c10631881b0eb7"}, + {file = "django-pwa-1.0.9.tar.gz", hash = "sha256:c11bcb40bbbb65f9037e4ae4d7233e6fa724c4410419b257cce4b6624a9542e9"}, + {file = "django_pwa-1.0.9-py3-none-any.whl", hash = "sha256:8706cbd84489fb63d3523d5037d2cdfd8ff177417292bd7845b0f177d3c4ed3f"}, ] django-render-block = [ {file = "django_render_block-0.6-py2.py3-none-any.whl", hash = "sha256:95c7dc9610378a10e0c4a10d8364ec7307210889afccd6a67a6aaa0fd599bd4d"}, @@ -2411,8 +2394,8 @@ django-sass-processor = [ {file = "django-sass-processor-0.8.tar.gz", hash = "sha256:e039551994feaaba6fcf880412b25a772dd313162a34cbb4289814988cfae340"}, ] django-select2 = [ - {file = "django-select2-7.2.3.tar.gz", hash = "sha256:b4cd3e8c42bdbdc582c38c03a23bc115a90c2f86563282329d37c567d8c34c36"}, - {file = "django_select2-7.2.3-py2.py3-none-any.whl", hash = "sha256:1482be84449c254ec944c4da0a236b00ec57445304377b783850fd95b269d1ad"}, + {file = "django-select2-7.4.2.tar.gz", hash = "sha256:9d3330fa0083a03fb69fceb5dcd2e78065cfd08e45c89d4fd727fce4673d3e08"}, + {file = "django_select2-7.4.2-py2.py3-none-any.whl", hash = "sha256:06531d563ce33c3133682ae2bb9e6d762103a863d0054ffef51bae8b4cfcca6c"}, ] django-settings-context-processor = [ {file = "django-settings-context-processor-0.2.tar.gz", hash = "sha256:d37c853d69a3069f5abbf94c7f4f6fc0fac38bbd0524190cd5a250ba800e496a"}, @@ -2458,30 +2441,27 @@ dynaconf = [ 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-4.0.3-py3-none-any.whl", hash = "sha256:53bf2c8a2de8af271466e7b9cc2f08ecf83c4c947981680eb61080779a0adace"}, - {file = "Faker-4.0.3.tar.gz", hash = "sha256:7292806948ed848f1bcea1e7b963bae6f398687d1da0ea096e156fea2787f454"}, + {file = "Faker-4.1.0-py3-none-any.whl", hash = "sha256:34ae397aef03a0a17910452f1e8430d57fa59e2d67b20e9b637218e8f7dd22b3"}, + {file = "Faker-4.1.0.tar.gz", hash = "sha256:103c46b9701a151299c5bffe6fefcd4fb5fb04c3b5d06bee4952d36255d44ea2"}, ] flake8 = [ - {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, - {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, + {file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"}, + {file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"}, ] 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"}, + {file = "flake8-black-0.2.0.tar.gz", hash = "sha256:10e7ff9f81f637a9471684e5624d6a32b11cba362b38df4e20fc8f761184440b"}, ] flake8-builtins = [ - {file = "flake8-builtins-1.5.2.tar.gz", hash = "sha256:fe7be13fe51bfb06bdae6096c6488e328c822c3aa080e24b91b77116a4fbb8b0"}, - {file = "flake8_builtins-1.5.2-py2.py3-none-any.whl", hash = "sha256:a0296d23da92a6f2494243b9f2039bfdb73f34aba20054c1b70b2a60c84745bb"}, + {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"}, + {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"}, ] flake8-django = [ - {file = "flake8-django-1.0.0.tar.gz", hash = "sha256:2c9e4b8bdcad3084d3a335a0e305cbf66cd9a2af937d66c50c859c1f966036b2"}, + {file = "flake8-django-1.1.1.tar.gz", hash = "sha256:fb4e8f669d3cf44297bb6e1c5d0a358ab0aba373cd4c69268cf2798de6bcbd9b"}, + {file = "flake8_django-1.1.1-py3-none-any.whl", hash = "sha256:c71da0e61b6119dae91cbffdbdb00f1d6ebe3f5d0c43f5bf136929997ab0b72d"}, ] flake8-docstrings = [ {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"}, @@ -2511,8 +2491,8 @@ gitdb = [ {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, ] gitpython = [ - {file = "GitPython-3.1.2-py3-none-any.whl", hash = "sha256:da3b2cf819974789da34f95ac218ef99f515a928685db141327c09b73dd69c09"}, - {file = "GitPython-3.1.2.tar.gz", hash = "sha256:864a47472548f3ba716ca202e034c1900f197c0fb3a08f641c20c3cafd15ed94"}, + {file = "GitPython-3.1.3-py3-none-any.whl", hash = "sha256:ef1d60b01b5ce0040ad3ec20bc64f783362d41fa0822a2742d3586e1f49bb8ac"}, + {file = "GitPython-3.1.3.tar.gz", hash = "sha256:e107af4d873daed64648b4f4beb89f89f0cfbe3ef558fc7821ed2331c2f8da1a"}, ] html2text = [ {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, @@ -2539,8 +2519,8 @@ jinja2 = [ {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] kombu = [ - {file = "kombu-4.6.8-py2.py3-none-any.whl", hash = "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"}, - {file = "kombu-4.6.8.tar.gz", hash = "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76"}, + {file = "kombu-4.6.10-py2.py3-none-any.whl", hash = "sha256:dc282bb277197d723bccda1a9ba30a27a28c9672d0ab93e9e51bb05a37bd29c3"}, + {file = "kombu-4.6.10.tar.gz", hash = "sha256:437b9cdea193cc2ed0b8044c85fd0f126bb3615ca2f4d4a35b39de7cacfa3c1a"}, ] libsass = [ {file = "libsass-0.20.0-cp27-cp27m-win32.whl", hash = "sha256:98f6dee9850b29e62977a963e3beb3cfeb98b128a267d59d2c3d675e298c8d57"}, @@ -2599,8 +2579,8 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] more-itertools = [ - {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, - {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, + {file = "more-itertools-8.3.0.tar.gz", hash = "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be"}, + {file = "more_itertools-8.3.0-py3-none-any.whl", hash = "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982"}, ] mypy = [ {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"}, @@ -2623,8 +2603,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, - {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] pathspec = [ {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, @@ -2642,8 +2622,8 @@ pg8000 = [ {file = "pg8000-1.15.2.tar.gz", hash = "sha256:eb42ba62fbc048c91d5cf1ac729e0ea4ee329cc526bddafed4e7a8aa6b57fbbb"}, ] phonenumbers = [ - {file = "phonenumbers-8.12.2-py2.py3-none-any.whl", hash = "sha256:eedbace07295109ce98b13b9bd1ac22dd43c1e90a3f0854c557c2298493fc731"}, - {file = "phonenumbers-8.12.2.tar.gz", hash = "sha256:61adadab01adaac571b04ddbe50f981c488ef00cfd51eef7e040ef4765871b00"}, + {file = "phonenumbers-8.12.4-py2.py3-none-any.whl", hash = "sha256:c6c43d6459aac85b646d6b7a7ab79b3b629eb168f0e9b851b331e2e5872bbd01"}, + {file = "phonenumbers-8.12.4.tar.gz", hash = "sha256:46c5997fe076026aa2d4b66d0c53eea4babae2e808e8a5f39c09e2dfa6612d08"}, ] pillow = [ {file = "Pillow-7.1.2-cp35-cp35m-macosx_10_10_intel.whl", hash = "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3"}, @@ -2724,8 +2704,8 @@ pyasn1-modules = [ {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"}, ] pycodestyle = [ - {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, - {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] pycryptodome = [ {file = "pycryptodome-3.9.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:0e10f352ccbbcb5bb2dc4ecaf106564e65702a717d72ab260f9ac4c19753cfc2"}, @@ -2764,8 +2744,8 @@ pydocstyle = [ {file = "pydocstyle-5.0.2.tar.gz", hash = "sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"}, ] pyflakes = [ - {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, - {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, @@ -2780,12 +2760,12 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"}, - {file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"}, + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] 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"}, + {file = "pytest-cov-2.9.0.tar.gz", hash = "sha256:b6a814b8ed6247bd81ff47f038511b57fe1ce7f4cc25b9106f1a4b106f1d9322"}, + {file = "pytest_cov-2.9.0-py2.py3-none-any.whl", hash = "sha256:c87dfd8465d865655a8213859f1b4749b43448b5fae465cb981e16d52a811424"}, ] pytest-django = [ {file = "pytest-django-3.9.0.tar.gz", hash = "sha256:664e5f42242e5e182519388f01b9f25d824a9feb7cd17d8f863c8d776f38baf9"}, @@ -2803,7 +2783,7 @@ python-box = [ {file = "python_box-3.4.6-py2.py3-none-any.whl", hash = "sha256:a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10"}, ] python-crontab = [ - {file = "python-crontab-2.4.2.tar.gz", hash = "sha256:ed9583cff430715d9560cbad28309429ac13f3fdb13051ac2288050e05abc392"}, + {file = "python-crontab-2.5.1.tar.gz", hash = "sha256:4bbe7e720753a132ca4ca9d4094915f40e9d9dc8a807a4564007651018ce8c31"}, ] python-dateutil = [ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, @@ -2842,38 +2822,38 @@ qrcode = [ {file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"}, ] redis = [ - {file = "redis-3.5.0-py2.py3-none-any.whl", hash = "sha256:174101a3ce04560d716616290bb40e0a2af45d5844c8bd474c23fc5c52e7a46a"}, - {file = "redis-3.5.0.tar.gz", hash = "sha256:7378105cd8ea20c4edc49f028581e830c01ad5f00be851def0f4bc616a83cd89"}, + {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, + {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, ] regex = [ - {file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"}, - {file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"}, - {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"}, - {file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"}, - {file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"}, - {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"}, - {file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"}, - {file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"}, - {file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"}, - {file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"}, - {file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"}, - {file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"}, + {file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"}, + {file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"}, + {file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"}, + {file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"}, + {file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"}, + {file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"}, + {file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"}, + {file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"}, + {file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"}, ] requests = [ {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, ] restructuredtext-lint = [ - {file = "restructuredtext_lint-1.3.0.tar.gz", hash = "sha256:97b3da356d5b3a8514d8f1f9098febd8b41463bed6a1d9f126cf0a048b6fd908"}, + {file = "restructuredtext_lint-1.3.1.tar.gz", hash = "sha256:470e53b64817211a42805c3a104d2216f6f5834b22fe7adb637d1de4d6501fb8"}, ] rules = [ {file = "rules-2.2.tar.gz", hash = "sha256:9bae429f9d4f91a375402990da1541f9e093b0ac077221d57124d06eeeca4405"}, @@ -2891,8 +2871,8 @@ selenium = [ {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"}, ] six = [ - {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, - {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] smmap = [ {file = "smmap-3.0.4-py2.py3-none-any.whl", hash = "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4"}, @@ -2903,16 +2883,16 @@ snowballstemmer = [ {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"}, + {file = "soupsieve-1.9.6-py2.py3-none-any.whl", hash = "sha256:feb1e937fa26a69e08436aad4a9037cd7e1d4c7212909502ba30701247ff8abd"}, + {file = "soupsieve-1.9.6.tar.gz", hash = "sha256:7985bacc98c34923a439967c1a602dc4f1e15f923b6fcf02344184f86cc7efaa"}, ] spdx-license-list = [ - {file = "spdx_license_list-0.4.0-py3-none-any.whl", hash = "sha256:e5c2d1efc4067ff83609a200c731db6c656fdfd26144ac8b50755d6c72515453"}, - {file = "spdx_license_list-0.4.0.tar.gz", hash = "sha256:f8b5eeda2a1c88d8ce15f6324d5a6128a462932a2e55b032f017ac9a0e61f1c7"}, + {file = "spdx_license_list-0.5.0-py3-none-any.whl", hash = "sha256:65c9f598dee3249d529300eb08800f8bf3d0d902868669146ada65192ecd0507"}, + {file = "spdx_license_list-0.5.0.tar.gz", hash = "sha256:40cd53ff16401bab7059e6d1ef61839196b12079929a2763a50145d3b6949bc1"}, ] sphinx = [ - {file = "Sphinx-3.0.3-py3-none-any.whl", hash = "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83"}, - {file = "Sphinx-3.0.3.tar.gz", hash = "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572"}, + {file = "Sphinx-3.0.4-py3-none-any.whl", hash = "sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c"}, + {file = "Sphinx-3.0.4.tar.gz", hash = "sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807"}, ] sphinx-autodoc-typehints = [ {file = "sphinx-autodoc-typehints-1.10.3.tar.gz", hash = "sha256:a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"}, @@ -2974,16 +2954,15 @@ text-unidecode = [ {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"}, + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, ] tqdm = [ - {file = "tqdm-4.46.0-py2.py3-none-any.whl", hash = "sha256:acdafb20f51637ca3954150d0405ff1a7edde0ff19e38fb99a80a66210d2a28f"}, - {file = "tqdm-4.46.0.tar.gz", hash = "sha256:4733c4a10d0f2a4d098d801464bdaf5240c7dadd2a7fde4ee93b0a0efd9fb25e"}, + {file = "tqdm-4.46.1-py2.py3-none-any.whl", hash = "sha256:07c06493f1403c1380b630ae3dcbe5ae62abcf369a93bbc052502279f189ab8c"}, + {file = "tqdm-4.46.1.tar.gz", hash = "sha256:cd140979c2bebd2311dfb14781d8f19bd5a9debb92dcab9f6ef899c987fcf71f"}, ] twilio = [ - {file = "twilio-6.39.0.tar.gz", hash = "sha256:7ef6ad19251fee6a41f1184e97b4fcb62f4a8c0e6f4b78797e40e9c92aed006d"}, + {file = "twilio-6.41.0.tar.gz", hash = "sha256:7c6329118583852bb06a2065dd2987a012310e5dfd834ef821d736b059bd1c74"}, ] typed-ast = [ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, @@ -3022,8 +3001,8 @@ vine = [ {file = "vine-1.3.0.tar.gz", hash = "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87"}, ] wcwidth = [ - {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, - {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, + {file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"}, + {file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"}, ] webencodings = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, diff --git a/pyproject.toml b/pyproject.toml index 42cb1190059bb2ee1a73f8cb02a039a1a25102ba..79e4ab7fcc7701a8553f78b4e54171fbe7999140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,16 +72,16 @@ django-celery-beat = {version="^2.0.0", optional=true} django-celery-email = {version="^3.0.0", optional=true} django-jsonstore = "^0.4.1" django-polymorphic = "^2.1.2" -django-otp = "0.9.0" +django-otp = "0.9.1" django-colorfield = "^0.3.0" django-bleach = "^0.6.1" django-guardian = "^2.2.0" rules = "^2.2" django-cache-memoize = "^0.1.6" django-haystack = {version="3.0b1", allow-prereleases = true} -celery-haystack = {version="^0.7.0", optional=true} +celery-haystack = {version="^0.10.0", optional=true} django-dbbackup = "^3.3.0" -spdx-license-list = "^0.4.0" +spdx-license-list = "^0.5.0" license-expression = "^1.2" django-reversion = "^3.0.7" django-favicon-plus-reloaded = "^1.0.4" @@ -111,7 +111,7 @@ flake8-builtins = "^1.4.1" flake8-docstrings = "^1.5.0" flake8-rst-docstrings = "^0.0.13" black = "^19.10b0" -flake8-black = "^0.1.1" +flake8-black = "^0.2.0" isort = "^4.3.21" flake8-isort = "^3.0.0" pytest-cov = "^2.8.1"