From fe9e5ce8611ebcceb78a889663e053626ec7a23d Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Mon, 28 Nov 2022 17:03:46 +0100 Subject: [PATCH] integration of 3D secure v2 for eboutic bank payment --- .gitignore | 1 + core/management/commands/populate.py | 8 +- core/static/core/style.scss | 140 ++-- core/views/user.py | 2 +- counter/admin.py | 5 + counter/forms.py | 177 +++++ counter/migrations/0019_billinginfo.py | 55 ++ counter/models.py | 82 ++- counter/tests.py | 267 ++++++- counter/urls.py | 12 +- counter/views.py | 231 ++----- eboutic/forms.py | 3 +- eboutic/models.py | 123 +++- .../static/eboutic/css/eboutic.css | 16 +- .../static/eboutic/js/eboutic.js | 0 eboutic/static/eboutic/js/makecommand.js | 73 ++ eboutic/templates/eboutic/eboutic_main.jinja | 16 +- .../eboutic/eboutic_makecommand.jinja | 170 +++-- eboutic/tests.py | 66 +- eboutic/urls.py | 3 +- eboutic/views.py | 121 ++-- launderette/views.py | 3 +- locale/fr/LC_MESSAGES/django.po | 420 ++++++----- poetry.lock | 652 ++++++++++++++---- pyproject.toml | 2 + subscription/models.py | 11 +- 26 files changed, 1896 insertions(+), 763 deletions(-) create mode 100644 counter/forms.py create mode 100644 counter/migrations/0019_billinginfo.py rename {core => eboutic}/static/eboutic/css/eboutic.css (90%) rename {core => eboutic}/static/eboutic/js/eboutic.js (100%) create mode 100644 eboutic/static/eboutic/js/makecommand.js diff --git a/.gitignore b/.gitignore index 511f5fbf..d7337889 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ db.sqlite3 pyrightconfig.json dist/ .vscode/ +.idea env/ doc/html data/ diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 130a139d..ae83008c 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -26,6 +26,7 @@ import os from datetime import date, datetime, timedelta from io import StringIO, BytesIO +from django.contrib.auth.models import Permission from django.core.management.base import BaseCommand from django.core.management import call_command from django.conf import settings @@ -73,7 +74,7 @@ class Command(BaseCommand): root_path = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ) - Group(name="Root").save() + root_group, _ = Group.objects.get_or_create(name="Root") Group(name="Public").save() Group(name="Subscribers").save() Group(name="Old subscribers").save() @@ -87,6 +88,11 @@ class Command(BaseCommand): Group(name="Forum admin").save() Group(name="Pedagogy admin").save() self.reset_index("core", "auth") + + change_billing = Permission.objects.get(codename="change_billinginfo") + add_billing = Permission.objects.get(codename="add_billinginfo") + root_group.permissions.add(change_billing, add_billing) + root = User( id=0, username="root", diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 2bbb12cd..04fac15e 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -49,36 +49,27 @@ body { font-family: sans-serif; } -input[type=button], input[type=submit], input[type=reset],input[type=file] { +button, input[type=button], input[type=submit], input[type=reset],input[type=file] { border: none; text-decoration: none; background-color: $background-button-color; padding: 0.4em; margin: 0.1em; - font-weight: bold; font-size: 1.2em; border-radius: 5px; - cursor: pointer; - box-shadow: $shadow-color 0px 0px 1px; + &:hover { + background: hsl(0, 0%, 83%); + } +} - &:hover { - background: hsl(0, 0%, 83%); - } +input[type=button], input[type=submit], input[type=reset],input[type=file] { + font-weight: bold; } -button{ - border: none; - text-decoration: none; - background-color: $background-button-color; - padding: 0.4em; - margin: 0.1em; - font-size: 1.18em; - border-radius: 5px; - box-shadow: $shadow-color 0px 0px 1px; + +button:not(:disabled), input[type=button]:not(:disabled), input[type=submit]:not(:disabled), input[type=reset]:not(:disabled),input[type=file]:not(:disabled) { cursor: pointer; - &:hover { - background: hsl(0, 0%, 83%); - } } + input,textarea[type=text],[type=number]{ border: none; text-decoration: none; @@ -123,6 +114,38 @@ a { margin: 1px; } +.collapse { + border-radius: 5px; + overflow: hidden; + + .collapse-header { + color: white; + background-color: #354a5f; + padding: 5px 10px; + display: flex; + align-items: center; + gap: 10px; + + .collapse-header-text { + flex: 2; + } + + .collapse-header-icon { + transition: all ease-in-out 150ms; + &.reverse { + transform: rotate(180deg); + } + } + } + .collapse-body { + padding: 10px; + } +} + +.shadow { + box-shadow: rgba(60, 64, 67, .3) 0 1px 3px 0, rgba(60, 64, 67, .15) 0 4px 8px 3px; +} + .w_big { width: 75%; } @@ -135,10 +158,12 @@ a { width: 23%; } -.clickable:hover { +.clickable:not(:disabled):hover { cursor: pointer; } +[x-cloak] { display: none !important; } + /*--------------------------------HEADER-------------------------------*/ #header_language_chooser { @@ -170,21 +195,11 @@ header { background-color: $primary-neutral-dark-color; border-radius: 0px 0px 10px 10px; - // PINKTOBER - // background-color: $pinktober; - // border-bottom: 5px solid $pinktober-secondary; - // margin-bottom: -5px; - // border-radius: 0 0 5px 7px; #header_logo { background-color: $white-color; padding: 0.2em; - border-radius: 0px 0px 0px 9px; - - //PINKTOBER - // border-bottom: 5px solid $shadow-color; - // border-radius: 0px 0px 0px 5px; - // margin-bottom: -5px; + border-radius: 0 0 0 9px; a { display: flex; @@ -211,14 +226,8 @@ header { width: 100%; label { display: inline; - - // PINKTOBER - // color: $pinktober-primary-text; } } - a { - display: button; - } } #header_bar { @@ -243,16 +252,6 @@ header { flex: initial; list-style-type: none; margin: 0.2em 0.2em; - - /* - PINKTOBER - & .fa.fa-times { - color: $pinktober-bar-closed !important; - } - - & .fa.fa-check { - color: $pinktober-bar-opened !important; - }*/ } #header_search { @@ -444,6 +443,36 @@ header { } } + .btn { + font-size: 15px; + font-weight: normal; + color: white; + min-width: 60px; + padding: 5px 10px; + border: none; + text-decoration: none; + + &.btn-blue { + background-color: #354a5f; + } + + &.btn-blue:disabled { + background-color: rgba(70, 90, 126, 0.4); + } + + &.btn-blue.clickable:not(:disabled):hover { + background-color: #2c3646; + } + + &.btn-grey { + background-color: grey; + } + + &.btn-grey.clickable:not(:disabled):hover { + background-color:hsl(210,5%,30%); + } + } + /*--------------------------------CONTENT------------------------------*/ #quick_notif { width: 100%; @@ -465,10 +494,7 @@ header { .alert { margin: 10px; - border: #fc8181 1px solid; - background-color: rgb(255,245,245); border-radius: 4px; - color: #c53030; padding: 12px 16px; display: flex; gap: 16px; @@ -476,6 +502,18 @@ header { align-items: center; text-align: justify; + &.alert-green { + background-color: rgb(245, 255, 245); + color: rgb(3, 84, 63); + border: rgb(14, 159, 110) 1px solid; + } + + &.alert-red { + background-color: rgb(255,245,245); + color: #c53030; + border: #fc8181 1px solid; + } + .alert-main { flex: 2; } @@ -1496,7 +1534,7 @@ textarea { margin: 10px 0; display: flex; flex-wrap: wrap; - height: 20p; + height: 20px; align-items: center; } .search_check { diff --git a/core/views/user.py b/core/views/user.py index 6737bf7d..3dc6c28b 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -67,7 +67,7 @@ from core.views.forms import ( ) from core.models import User, SithFile, Preferences, Gift from subscription.models import Subscription -from counter.views import StudentCardForm +from counter.forms import StudentCardForm from trombi.views import UserTrombiForm diff --git a/counter/admin.py b/counter/admin.py index 323e541a..9faea823 100644 --- a/counter/admin.py +++ b/counter/admin.py @@ -36,6 +36,11 @@ class CustomerAdmin(SearchModelAdmin): search_fields = ["account_id"] +@admin.register(BillingInfo) +class BillingInfoAdmin(admin.ModelAdmin): + list_display = ("first_name", "last_name", "address_1", "city", "country") + + admin.site.register(Customer, CustomerAdmin) admin.site.register(Product, ProductAdmin) admin.site.register(ProductType) diff --git a/counter/forms.py b/counter/forms.py new file mode 100644 index 00000000..a09236e7 --- /dev/null +++ b/counter/forms.py @@ -0,0 +1,177 @@ +from ajax_select import make_ajax_field +from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField +from django import forms +from django.utils.translation import gettext_lazy as _ + +from core.views.forms import TzAwareDateTimeField, SelectDate +from counter.models import ( + BillingInfo, + StudentCard, + Customer, + Refilling, + Counter, + Product, + Eticket, +) + + +class BillingInfoForm(forms.ModelForm): + class Meta: + model = BillingInfo + exclude = ["customer"] + + +class StudentCardForm(forms.ModelForm): + """ + Form for adding student cards + Only used for user profile since CounterClick is to complicated + """ + + class Meta: + model = StudentCard + fields = ["uid"] + + def clean(self): + cleaned_data = super(StudentCardForm, self).clean() + uid = cleaned_data.get("uid", None) + if not uid or not StudentCard.is_valid(uid): + raise forms.ValidationError(_("This UID is invalid"), code="invalid") + return cleaned_data + + +class GetUserForm(forms.Form): + """ + The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view, + reverse function, or any other use. + + The Form implements a nice JS widget allowing the user to type a customer account id, or search the database with + some nickname, first name, or last name (TODO) + """ + + code = forms.CharField( + label="Code", max_length=StudentCard.UID_SIZE, required=False + ) + id = AutoCompleteSelectField( + "users", required=False, label=_("Select user"), help_text=None + ) + + def as_p(self): + self.fields["code"].widget.attrs["autofocus"] = True + return super(GetUserForm, self).as_p() + + def clean(self): + cleaned_data = super(GetUserForm, self).clean() + cus = None + if cleaned_data["code"] != "": + if len(cleaned_data["code"]) == StudentCard.UID_SIZE: + card = StudentCard.objects.filter(uid=cleaned_data["code"]) + if card.exists(): + cus = card.first().customer + if cus is None: + cus = Customer.objects.filter( + account_id__iexact=cleaned_data["code"] + ).first() + elif cleaned_data["id"] is not None: + cus = Customer.objects.filter(user=cleaned_data["id"]).first() + if cus is None or not cus.can_buy: + raise forms.ValidationError(_("User not found")) + cleaned_data["user_id"] = cus.user.id + cleaned_data["user"] = cus.user + return cleaned_data + + +class RefillForm(forms.ModelForm): + error_css_class = "error" + required_css_class = "required" + amount = forms.FloatField( + min_value=0, widget=forms.NumberInput(attrs={"class": "focus"}) + ) + + class Meta: + model = Refilling + fields = ["amount", "payment_method", "bank"] + + +class CounterEditForm(forms.ModelForm): + class Meta: + model = Counter + fields = ["sellers", "products"] + + sellers = make_ajax_field(Counter, "sellers", "users", help_text="") + products = make_ajax_field(Counter, "products", "products", help_text="") + + +class ProductEditForm(forms.ModelForm): + class Meta: + model = Product + fields = [ + "name", + "description", + "product_type", + "code", + "parent_product", + "buying_groups", + "purchase_price", + "selling_price", + "special_selling_price", + "icon", + "club", + "limit_age", + "tray", + "archived", + ] + + parent_product = AutoCompleteSelectField( + "products", show_help_text=False, label=_("Parent product"), required=False + ) + buying_groups = AutoCompleteSelectMultipleField( + "groups", + show_help_text=False, + help_text="", + label=_("Buying groups"), + required=True, + ) + club = AutoCompleteSelectField("clubs", show_help_text=False) + counters = AutoCompleteSelectMultipleField( + "counters", + show_help_text=False, + help_text="", + label=_("Counters"), + required=False, + ) + + def __init__(self, *args, **kwargs): + super(ProductEditForm, self).__init__(*args, **kwargs) + if self.instance.id: + self.fields["counters"].initial = [ + str(c.id) for c in self.instance.counters.all() + ] + + def save(self, *args, **kwargs): + ret = super(ProductEditForm, self).save(*args, **kwargs) + if self.fields["counters"].initial: + for cid in self.fields["counters"].initial: + c = Counter.objects.filter(id=int(cid)).first() + c.products.remove(self.instance) + c.save() + for cid in self.cleaned_data["counters"]: + c = Counter.objects.filter(id=int(cid)).first() + c.products.add(self.instance) + c.save() + return ret + + +class CashSummaryFormBase(forms.Form): + begin_date = TzAwareDateTimeField(label=_("Begin date"), required=False) + end_date = TzAwareDateTimeField(label=_("End date"), required=False) + + +class EticketForm(forms.ModelForm): + class Meta: + model = Eticket + fields = ["product", "banner", "event_title", "event_date"] + widgets = {"event_date": SelectDate} + + product = AutoCompleteSelectField( + "products", show_help_text=False, label=_("Product"), required=True + ) diff --git a/counter/migrations/0019_billinginfo.py b/counter/migrations/0019_billinginfo.py new file mode 100644 index 00000000..d45f287e --- /dev/null +++ b/counter/migrations/0019_billinginfo.py @@ -0,0 +1,55 @@ +# Generated by Django 3.2.15 on 2022-11-14 13:26 + +from django.db import migrations, models +import django.db.models.deletion +import django_countries.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("counter", "0018_producttype_priority"), + ] + + operations = [ + migrations.CreateModel( + name="BillingInfo", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("first_name", models.CharField(max_length=30)), + ("last_name", models.CharField(max_length=30)), + ( + "address_1", + models.CharField(max_length=50, verbose_name="address line 1"), + ), + ( + "address_2", + models.CharField( + blank=True, + max_length=50, + null=True, + verbose_name="address line 2", + ), + ), + ("zip_code", models.CharField(max_length=16, verbose_name="zip code")), + ("city", models.CharField(max_length=50, verbose_name="city")), + ("country", django_countries.fields.CountryField(max_length=2)), + ( + "customer", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="billing_infos", + to="counter.customer", + ), + ), + ], + ), + ] diff --git a/counter/models.py b/counter/models.py index 354c7007..20fa77a0 100644 --- a/counter/models.py +++ b/counter/models.py @@ -21,6 +21,7 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +from django.db.models.functions import Length from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from django.db import models @@ -38,16 +39,19 @@ import string import os import base64 import datetime +from dict2xml import dict2xml from club.models import Club, Membership from accounting.models import CurrencyField from core.models import Group, User, Notification from subscription.models import Subscription +from django_countries.fields import CountryField + class Customer(models.Model): """ - This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and + This class extends a user to make a customer. It adds some basic customers' information, such as the account ID, and is used by other accounting classes as reference to the customer, rather than using User """ @@ -89,13 +93,28 @@ class Customer(models.Model): .subscription_end ) < timedelta(days=90) - @staticmethod - def generate_account_id(number): - number = str(number) - letter = random.choice(string.ascii_lowercase) - while Customer.objects.filter(account_id=number + letter).exists(): - letter = random.choice(string.ascii_lowercase) - return number + letter + @classmethod + def new_for_user(cls, user: User): + """ + Create a new Customer instance for the user given in parameter without saving it + The account if is automatically generated and the amount set at 0 + """ + # account_id are number with a letter appended + account_id = ( + Customer.objects.order_by(Length("account_id"), "account_id") + .values("account_id") + .last() + ) + if account_id is None: + # legacy from the old site + return cls(user=user, account_id="1504a", amount=0) + account_id = account_id["account_id"] + num = int(account_id[:-1]) + while Customer.objects.filter(account_id=account_id).exists(): + num += 1 + account_id = str(num) + random.choice(string.ascii_lowercase) + + return cls(user=user, account_id=account_id, amount=0) def save(self, allow_negative=False, is_selling=False, *args, **kwargs): """ @@ -122,6 +141,53 @@ class Customer(models.Model): return "".join(["https://", settings.SITH_URL, self.get_absolute_url()]) +class BillingInfo(models.Model): + """ + Represent the billing information of a user, which are required + by the 3D-Secure v2 system used by the etransaction module + """ + + customer = models.OneToOneField( + Customer, related_name="billing_infos", on_delete=models.CASCADE + ) + + # declaring surname and name even though they are already defined + # in User add some redundancy, but ensures that the billing infos + # shall stay correct, whatever shenanigans the user commits on its profile + first_name = models.CharField(_("First name"), max_length=30) + last_name = models.CharField(_("Last name"), max_length=30) + address_1 = models.CharField(_("Address 1"), max_length=50) + address_2 = models.CharField(_("Address 2"), max_length=50, blank=True, null=True) + zip_code = models.CharField(_("Zip code"), max_length=16) # code postal + city = models.CharField(_("City"), max_length=50) + country = CountryField(blank_label=_("Country")) + + def to_3dsv2_xml(self) -> str: + """ + Convert the data from this model into a xml usable + by the online paying service of the Crédit Agricole bank. + see : `https://www.ca-moncommerce.com/espace-client-mon-commerce/up2pay-e-transactions/ma-documentation/manuel-dintegration-focus-3ds-v2/principes-generaux/#boutique-cms-utilisation-des-modules-up2pay-e-transactions-mise-a-jour-module` + """ + data = { + "Billing": { + "Address": { + "FirstName": self.first_name, + "LastName": self.last_name, + "Address1": self.address_1, + "ZipCode": self.zip_code, + "City": self.city, + "CountryCode": self.country, + } + } + } + if self.address_2: + data["Billing"]["Address"]["Address2"] = self.address_2 + return dict2xml(data) + + def __str__(self): + return f"{self.first_name} {self.last_name}" + + class ProductType(models.Model): """ This describes a product type diff --git a/counter/tests.py b/counter/tests.py index ac82fcbb..0a25a9ff 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -21,7 +21,7 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +import json import re from django.test import TestCase @@ -29,7 +29,7 @@ from django.urls import reverse from django.core.management import call_command from core.models import User -from counter.models import Counter +from counter.models import Counter, Customer, BillingInfo class CounterTest(TestCase): @@ -67,7 +67,7 @@ class CounterTest(TestCase): response = self.client.get(response.get("location")) self.assertTrue(">Richard Batsbak/billing_info/create", + create_billing_info, + name="create_billing_info", + ), + path( + "customer//billing_info/edit", + edit_billing_info, + name="edit_billing_info", + ), re_path(r"^admin/(?P[0-9]+)$", CounterEditView.as_view(), name="admin"), re_path( r"^admin/(?P[0-9]+)/prop$", diff --git a/counter/views.py b/counter/views.py index 02461416..ca012688 100644 --- a/counter/views.py +++ b/counter/views.py @@ -21,10 +21,13 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +import json +from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404 from django.http import Http404 from django.core.exceptions import PermissionDenied +from django.views.decorators.http import require_POST from django.views.generic import ListView, DetailView, RedirectView, TemplateView from django.views.generic.base import View from django.views.generic.edit import ( @@ -49,12 +52,20 @@ import re import pytz from datetime import date, timedelta, datetime from http import HTTPStatus -from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField -from ajax_select import make_ajax_field from core.views import CanViewMixin, TabedViewMixin, CanEditMixin -from core.views.forms import LoginForm, SelectDate, SelectDateTime +from core.views.forms import LoginForm from core.models import User +from counter.forms import ( + BillingInfoForm, + StudentCardForm, + GetUserForm, + RefillForm, + CounterEditForm, + ProductEditForm, + CashSummaryFormBase, + EticketForm, +) from subscription.models import Subscription from counter.models import ( Counter, @@ -68,9 +79,9 @@ from counter.models import ( CashRegisterSummaryItem, Eticket, Permanency, + BillingInfo, ) from accounting.models import CurrencyField -from core.views.forms import TzAwareDateTimeField class CounterAdminMixin(View): @@ -103,24 +114,6 @@ class CounterAdminMixin(View): return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs) -class StudentCardForm(forms.ModelForm): - """ - Form for adding student cards - Only used for user profile since CounterClick is to complicated - """ - - class Meta: - model = StudentCard - fields = ["uid"] - - def clean(self): - cleaned_data = super(StudentCardForm, self).clean() - uid = cleaned_data.get("uid", None) - if not uid or not StudentCard.is_valid(uid): - raise forms.ValidationError(_("This UID is invalid"), code="invalid") - return cleaned_data - - class StudentCardDeleteView(DeleteView, CanEditMixin): """ View used to delete a card from a user @@ -140,59 +133,6 @@ class StudentCardDeleteView(DeleteView, CanEditMixin): ) -class GetUserForm(forms.Form): - """ - The Form class aims at providing a valid user_id field in its cleaned data, in order to pass it to some view, - reverse function, or any other use. - - The Form implements a nice JS widget allowing the user to type a customer account id, or search the database with - some nickname, first name, or last name (TODO) - """ - - code = forms.CharField( - label="Code", max_length=StudentCard.UID_SIZE, required=False - ) - id = AutoCompleteSelectField( - "users", required=False, label=_("Select user"), help_text=None - ) - - def as_p(self): - self.fields["code"].widget.attrs["autofocus"] = True - return super(GetUserForm, self).as_p() - - def clean(self): - cleaned_data = super(GetUserForm, self).clean() - cus = None - if cleaned_data["code"] != "": - if len(cleaned_data["code"]) == StudentCard.UID_SIZE: - card = StudentCard.objects.filter(uid=cleaned_data["code"]) - if card.exists(): - cus = card.first().customer - if cus is None: - cus = Customer.objects.filter( - account_id__iexact=cleaned_data["code"] - ).first() - elif cleaned_data["id"] is not None: - cus = Customer.objects.filter(user=cleaned_data["id"]).first() - if cus is None or not cus.can_buy: - raise forms.ValidationError(_("User not found")) - cleaned_data["user_id"] = cus.user.id - cleaned_data["user"] = cus.user - return cleaned_data - - -class RefillForm(forms.ModelForm): - error_css_class = "error" - required_css_class = "required" - amount = forms.FloatField( - min_value=0, widget=forms.NumberInput(attrs={"class": "focus"}) - ) - - class Meta: - model = Refilling - fields = ["amount", "payment_method", "bank"] - - class CounterTabsMixin(TabedViewMixin): def get_tabs_title(self): if hasattr(self.object, "stock_owner"): @@ -867,15 +807,6 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): current_tab = "counters" -class CounterEditForm(forms.ModelForm): - class Meta: - model = Counter - fields = ["sellers", "products"] - - sellers = make_ajax_field(Counter, "sellers", "users", help_text="") - products = make_ajax_field(Counter, "products", "products", help_text="") - - class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit a counter's main informations (for the counter's manager) @@ -995,66 +926,6 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): current_tab = "products" -class ProductEditForm(forms.ModelForm): - class Meta: - model = Product - fields = [ - "name", - "description", - "product_type", - "code", - "parent_product", - "buying_groups", - "purchase_price", - "selling_price", - "special_selling_price", - "icon", - "club", - "limit_age", - "tray", - "archived", - ] - - parent_product = AutoCompleteSelectField( - "products", show_help_text=False, label=_("Parent product"), required=False - ) - buying_groups = AutoCompleteSelectMultipleField( - "groups", - show_help_text=False, - help_text="", - label=_("Buying groups"), - required=True, - ) - club = AutoCompleteSelectField("clubs", show_help_text=False) - counters = AutoCompleteSelectMultipleField( - "counters", - show_help_text=False, - help_text="", - label=_("Counters"), - required=False, - ) - - def __init__(self, *args, **kwargs): - super(ProductEditForm, self).__init__(*args, **kwargs) - if self.instance.id: - self.fields["counters"].initial = [ - str(c.id) for c in self.instance.counters.all() - ] - - def save(self, *args, **kwargs): - ret = super(ProductEditForm, self).save(*args, **kwargs) - if self.fields["counters"].initial: - for cid in self.fields["counters"].initial: - c = Counter.objects.filter(id=int(cid)).first() - c.products.remove(self.instance) - c.save() - for cid in self.cleaned_data["counters"]: - c = Counter.objects.filter(id=int(cid)).first() - c.products.add(self.instance) - c.save() - return ret - - class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ A create view for the admins @@ -1482,7 +1353,7 @@ class CounterStatView(DetailView, CounterAdminMixin): def get_context_data(self, **kwargs): """Add stats to the context""" - from django.db.models import Sum, Case, When, F, DecimalField + from django.db.models import Sum, Case, When, F kwargs = super(CounterStatView, self).get_context_data(**kwargs) kwargs["Customer"] = Customer @@ -1585,11 +1456,6 @@ class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): return reverse("counter:cash_summary_list") -class CashSummaryFormBase(forms.Form): - begin_date = TzAwareDateTimeField(label=_("Begin date"), required=False) - end_date = TzAwareDateTimeField(label=_("End date"), required=False) - - class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """Display a list of cash summaries""" @@ -1669,7 +1535,7 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): end_date = (start_date + timedelta(days=32)).replace( day=1, hour=0, minute=0, microsecond=0 ) - from django.db.models import Sum, Case, When, F, DecimalField + from django.db.models import Sum, Case, When, F kwargs["sum_cb"] = sum( [ @@ -1725,17 +1591,6 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): current_tab = "etickets" -class EticketForm(forms.ModelForm): - class Meta: - model = Eticket - fields = ["product", "banner", "event_title", "event_date"] - widgets = {"event_date": SelectDate} - - product = AutoCompleteSelectField( - "products", show_help_text=False, label=_("Product"), required=True - ) - - class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ Create an eticket @@ -1895,3 +1750,55 @@ class StudentCardFormView(FormView): return reverse_lazy( "core:user_prefs", kwargs={"user_id": self.customer.user.pk} ) + + +def __manage_billing_info_req(request, user_id, delete_if_fail=False): + data = json.loads(request.body) + form = BillingInfoForm(data) + if not form.is_valid(): + if delete_if_fail: + Customer.objects.get(user__id=user_id).billing_infos.delete() + errors = [ + {"field": str(form.fields[k].label), "messages": v} + for k, v in form.errors.items() + ] + content = json.dumps({"errors": errors}) + return HttpResponse(status=400, content=content) + if form.is_valid(): + infos = Customer.objects.get(user__id=user_id).billing_infos + for field in form.fields: + infos.__dict__[field] = form[field].value() + infos.save() + content = json.dumps({"errors": None}) + return HttpResponse(status=200, content=content) + + +@login_required +@require_POST +def create_billing_info(request, user_id): + user = request.user + if user.id != user_id and not user.has_perm("counter:add_billinginfo"): + raise PermissionDenied() + user = get_object_or_404(User, pk=user_id) + if not hasattr(user, "customer"): + customer = Customer.new_for_user(user) + customer.save() + else: + customer = get_object_or_404(Customer, user_id=user_id) + BillingInfo.objects.create(customer=customer) + return __manage_billing_info_req(request, user_id, True) + + +@login_required +@require_POST +def edit_billing_info(request, user_id): + user = request.user + if user.id != user_id and not user.has_perm("counter:change_billinginfo"): + raise PermissionDenied() + user = get_object_or_404(User, pk=user_id) + if not hasattr(user, "customer"): + raise Http404 + if not hasattr(user.customer, "billing_infos"): + raise Http404 + + return __manage_billing_info_req(request, user_id) diff --git a/eboutic/forms.py b/eboutic/forms.py index 8f1ddaf1..91356b3d 100644 --- a/eboutic/forms.py +++ b/eboutic/forms.py @@ -138,7 +138,7 @@ class BasketForm: continue if type(item["quantity"]) is not int or item["quantity"] < 0: self.error_messages.add( - _("You cannot buy %(nbr)d %(name)%s.") + _("You cannot buy %(nbr)d %(name)s.") % {"nbr": item["quantity"], "name": item["name"]} ) continue @@ -166,7 +166,6 @@ class BasketForm: return True def get_error_messages(self) -> typing.List[str]: - # return [msg for msg in self.error_messages] return list(self.error_messages) def get_cleaned_cookie(self) -> str: diff --git a/eboutic/models.py b/eboutic/models.py index 696cafac..0b2014e7 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -21,9 +21,12 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +import hmac import typing +from datetime import datetime from typing import List +from dict2xml import dict2xml from django.conf import settings from django.db import models, DataError from django.db.models import Sum, F @@ -32,7 +35,7 @@ from django.utils.translation import gettext_lazy as _ from accounting.models import CurrencyField from core.models import Group, User -from counter.models import Counter, Product, Selling, Refilling +from counter.models import Counter, Product, Selling, Refilling, BillingInfo, Customer def get_eboutic_products(user: User) -> List[Product]: @@ -104,7 +107,7 @@ class Basket(models.Model): """ Remove all items from this basket without deleting the basket """ - BasketItem.objects.filter(basket=self).delete() + self.items.all().delete() @cached_property def contains_refilling_item(self) -> bool: @@ -122,7 +125,7 @@ class Basket(models.Model): def from_session(cls, session) -> typing.Union["Basket", None]: """ Given an HttpRequest django object, return the basket used in the current session - if it exists else create a new one and return it + if it exists else None """ if "basket_id" in session: try: @@ -131,6 +134,93 @@ class Basket(models.Model): return None return None + def generate_sales(self, counter, seller: User, payment_method: str): + """ + Generate a list of sold items corresponding to the items + of this basket WITHOUT saving them NOR deleting the basket + + Example: + :: + + counter = Counter.objects.get(name="Eboutic") + sales = basket.generate_sales(counter, "SITH_ACCOUNT") + # here the basket is in the same state as before the method call + + with transaction.atomic(): + for sale in sales: + sale.save() + basket.delete() + # all the basket items are deleted by the on_delete=CASCADE relation + # thus only the sales remain + """ + # I must proceed with two distinct requests instead of + # only one with a join because the AbstractBaseItem model has been + # poorly designed. If you refactor the model, please refactor this too. + items = self.items.order_by("product_id") + ids = [item.product_id for item in items] + products = Product.objects.filter(id__in=ids).order_by("id") + # items and products are sorted in the same order + sales = [] + for item, product in zip(items, products): + sales.append( + Selling( + label=product.name, + counter=counter, + club=product.club, + product=product, + seller=seller, + customer=self.user.customer, + unit_price=item.product_unit_price, + quantity=item.quantity, + payment_method=payment_method, + ) + ) + return sales + + def get_e_transaction_data(self): + user = self.user + if not hasattr(user, "customer"): + raise Customer.DoesNotExist + customer = user.customer + if not hasattr(user.customer, "billing_infos"): + raise BillingInfo.DoesNotExist + data = [ + ("PBX_SITE", settings.SITH_EBOUTIC_PBX_SITE), + ("PBX_RANG", settings.SITH_EBOUTIC_PBX_RANG), + ("PBX_IDENTIFIANT", settings.SITH_EBOUTIC_PBX_IDENTIFIANT), + ("PBX_TOTAL", str(int(self.get_total() * 100))), + ("PBX_DEVISE", "978"), # This is Euro + ("PBX_CMD", str(self.id)), + ("PBX_PORTEUR", user.email), + ("PBX_RETOUR", "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K"), + ("PBX_HASH", "SHA512"), + ("PBX_TYPEPAIEMENT", "CARTE"), + ("PBX_TYPECARTE", "CB"), + ("PBX_TIME", datetime.now().replace(microsecond=0).isoformat("T")), + ("PBX_BILLING", customer.billing_infos.to_3dsv2_xml()), + ( + "PBX_SHOPPINGCART", + dict2xml({"shoppingcart": {"total": {min(self.items.count(), 99)}}}), + ), + ] + data.append( + ( + "PBX_HMAC", + ( + hmac.new( + settings.SITH_EBOUTIC_HMAC_KEY, + bytes("&".join("=".join(d) for d in data), "utf-8"), + "sha512", + ) + .hexdigest() + .upper() + ), + ) + ) + return data + + # def validate(self, exclude=None): + def __str__(self): return "%s's basket (%d items)" % (self.user, self.items.all().count()) @@ -156,18 +246,9 @@ class Invoice(models.Model): )["total"] return float(total) if total is not None else 0 - def validate(self, *args, **kwargs): + def validate(self): if self.validated: raise DataError(_("Invoice already validated")) - from counter.models import Customer - - if not Customer.objects.filter(user=self.user).exists(): - number = Customer.objects.count() + 1 - Customer( - user=self.user, - account_id=Customer.generate_account_id(number), - amount=0, - ).save() eboutic = Counter.objects.filter(type="EBOUTIC").first() for i in self.items.all(): if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING: @@ -227,6 +308,22 @@ class BasketItem(AbstractBaseItem): Basket, related_name="items", verbose_name=_("basket"), on_delete=models.CASCADE ) + @classmethod + def from_product(cls, product: Product, quantity: int): + """ + Create a BasketItem with the same characteristics as the + product passed in parameters, with the specified quantity + WARNING : the basket field is not filled, so you must set + it yourself before saving the model + """ + return cls( + product_id=product.id, + product_name=product.name, + type_id=product.product_type.id, + quantity=quantity, + product_unit_price=product.selling_price, + ) + class InvoiceItem(AbstractBaseItem): invoice = models.ForeignKey( diff --git a/core/static/eboutic/css/eboutic.css b/eboutic/static/eboutic/css/eboutic.css similarity index 90% rename from core/static/eboutic/css/eboutic.css rename to eboutic/static/eboutic/css/eboutic.css index 538fc734..aecb67a3 100644 --- a/core/static/eboutic/css/eboutic.css +++ b/eboutic/static/eboutic/css/eboutic.css @@ -41,20 +41,6 @@ } } -#eboutic #basket .error-message { - margin-top: 5px; - background-color: #f8d7da; - border: #f5c6cb 1px solid; - border-radius: 4px; - padding: 10px; - display: flex; - flex-direction: column; - row-gap: 7px; -} -#eboutic #basket .error-message p { - margin: 0; -} - #eboutic .item-list { margin-left: 0; list-style: none; @@ -162,7 +148,7 @@ } #eboutic .catalog-buttons button { - font-size: 15px; + font-size: 15px!important; font-weight: normal; color: white; min-width: 60px; diff --git a/core/static/eboutic/js/eboutic.js b/eboutic/static/eboutic/js/eboutic.js similarity index 100% rename from core/static/eboutic/js/eboutic.js rename to eboutic/static/eboutic/js/eboutic.js diff --git a/eboutic/static/eboutic/js/makecommand.js b/eboutic/static/eboutic/js/makecommand.js new file mode 100644 index 00000000..2326cf88 --- /dev/null +++ b/eboutic/static/eboutic/js/makecommand.js @@ -0,0 +1,73 @@ +document.addEventListener('alpine:init', () => { + Alpine.store('bank_payment_enabled', false) + + Alpine.store('billing_inputs', { + data: JSON.parse(et_data)["data"], + + async fill() { + document.getElementById("bank-submit-button").disabled = true; + const request = new Request(et_data_url, { + method: "GET", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }); + const res = await fetch(request); + if (res.ok) { + const json = await res.json(); + if (json["data"]) { + this.data = json["data"]; + } + document.getElementById("bank-submit-button").disabled = false; + } + } + }) + + Alpine.data('billing_infos', () => ({ + errors: [], + successful: false, + url: billing_info_exist ? edit_billing_info_url : create_billing_info_url, + + async send_form() { + const form = document.getElementById("billing_info_form"); + const submit_button = form.querySelector("input[type=submit]") + submit_button.disabled = true; + document.getElementById("bank-submit-button").disabled = true; + this.successful = false + + let payload = {}; + for (const elem of form.querySelectorAll("input")) { + if (elem.type === "text" && elem.value) { + payload[elem.name] = elem.value; + } + } + const country = form.querySelector("select"); + if (country && country.value) { + payload[country.name] = country.value; + } + const request = new Request(this.url, { + method: "POST", + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-CSRFToken': getCSRFToken(), + }, + body: JSON.stringify(payload), + }); + const res = await fetch(request); + const json = await res.json(); + if (json["errors"]) { + this.errors = json["errors"]; + } else { + this.errors = []; + this.successful = true; + this.url = edit_billing_info_url; + Alpine.store("billing_inputs").fill(); + } + submit_button.disabled = false; + } + })) +}) + + diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 46d1a78b..e707505b 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -25,11 +25,13 @@

Panier

{% if errors %} -
- {% for error in errors %} -

{{ error }}

- {% endfor %} - {% trans %}Your basket has been cleaned accordingly to those errors.{% endtrans %} +
+
+ {% for error in errors %} +

{{ error }}

+ {% endfor %} + {% trans %}Your basket has been cleaned accordingly to those errors.{% endtrans %} +
{% endif %}
    @@ -64,7 +66,7 @@ {% trans %}Clear{% endtrans %} -
    + {% csrf_token %}
{% if not request.user.date_of_birth %} -
+
{% trans %}You have not filled in your date of birth. As a result, you may not have access to all the products in the online shop. To fill in your date of birth, you can go to{% endtrans %} diff --git a/eboutic/templates/eboutic/eboutic_makecommand.jinja b/eboutic/templates/eboutic/eboutic_makecommand.jinja index 837c1295..eb2f6def 100644 --- a/eboutic/templates/eboutic/eboutic_makecommand.jinja +++ b/eboutic/templates/eboutic/eboutic_makecommand.jinja @@ -1,69 +1,143 @@ {% extends "core/base.jinja" %} {% block title %} -{% trans %}Basket state{% endtrans %} + {% trans %}Basket state{% endtrans %} +{% endblock %} + +{% block jquery_css %} + {# Remove jquery css #} +{% endblock %} + +{% block additional_js %} + + {% endblock %} {% block content %} -

{% trans %}Eboutic{% endtrans %}

+

{% trans %}Eboutic{% endtrans %}

-
-

{% trans %}Basket: {% endtrans %}

- - +
+

{% trans %}Basket: {% endtrans %}

+
+ - - + + {% for item in basket.items.all() %} - - - - - + + + + + {% endfor %} - -
Article Quantity Unit price
{{ item.product_name }}{{ item.quantity }}{{ item.product_unit_price }} €
{{ item.product_name }}{{ item.quantity }}{{ item.product_unit_price }} €
+ + -

- {% trans %}Basket amount: {% endtrans %}{{ "%0.2f"|format(basket.get_total()) }} € - - {% if customer_amount != None %} -
- {% trans %}Current account amount: {% endtrans %}{{ "%0.2f"|format(customer_amount) }} € - - {% if not basket.contains_refilling_item %} -
- {% trans %}Remaining account amount: {% endtrans %} - {{ "%0.2f"|format(customer_amount|float - basket.get_total()) }} € - {% endif %} - {% endif %} -

- {% if settings.SITH_EBOUTIC_CB_ENABLED %} -

- {% for (field_name,field_value) in et_request.items() -%} - - {% endfor %} - + {% trans %}Basket amount: {% endtrans %}{{ "%0.2f"|format(basket.get_total()) }} € + + {% if customer_amount != None %} +
+ {% trans %}Current account amount: {% endtrans %} + {{ "%0.2f"|format(customer_amount) }} € + + {% if not basket.contains_refilling_item %} +
+ {% trans %}Remaining account amount: {% endtrans %} + {{ "%0.2f"|format(customer_amount|float - basket.get_total()) }} € + {% endif %} + {% endif %}

- - {% endif %} - {% if basket.contains_refilling_item %} -

{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}

- {% else %} -
- {% csrf_token %} - - -
- {% endif %} -
+
+ {% if settings.SITH_EBOUTIC_CB_ENABLED %} +
+
+ + {% trans %}Edit billing information{% endtrans %} + + + + +
+
+ {% csrf_token %} + {{ billing_form }} +
+
+
+
+ +
+
+ +
+
+
+
+ Informations de facturation enregistrées +
+
+ +
+
+ +
+
+
+ {% if must_fill_billing_infos %} +

+ + {% trans %}You must fill your billing infos if you want to pay with your credit + card{% endtrans %} + +

+ {% endif %} +
+ {% csrf_token %} + + +
+ {% endif %} + {% if basket.contains_refilling_item %} +

{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}

+ {% else %} +
+ {% csrf_token %} + + +
+ {% endif %} +
{% endblock %} +{% block script %} + + {{ super() }} +{% endblock %} diff --git a/eboutic/tests.py b/eboutic/tests.py index 98b73014..b5d4d6ce 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -24,7 +24,6 @@ # import base64 import json -import re import urllib from OpenSSL import crypto @@ -40,18 +39,19 @@ from eboutic.models import Basket class EbouticTest(TestCase): - def setUp(self): + @classmethod + def setUpTestData(cls): call_command("populate") - self.skia = User.objects.filter(username="skia").first() - self.subscriber = User.objects.filter(username="subscriber").first() - self.old_subscriber = User.objects.filter(username="old_subscriber").first() - self.public = User.objects.filter(username="public").first() - self.barbar = Product.objects.filter(code="BARB").first() - self.refill = Product.objects.filter(code="15REFILL").first() - self.cotis = Product.objects.filter(code="1SCOTIZ").first() - self.eboutic = Counter.objects.filter(name="Eboutic").first() + cls.barbar = Product.objects.filter(code="BARB").first() + cls.refill = Product.objects.filter(code="15REFILL").first() + cls.cotis = Product.objects.filter(code="1SCOTIZ").first() + cls.eboutic = Counter.objects.filter(name="Eboutic").first() + cls.skia = User.objects.filter(username="skia").first() + cls.subscriber = User.objects.filter(username="subscriber").first() + cls.old_subscriber = User.objects.filter(username="old_subscriber").first() + cls.public = User.objects.filter(username="public").first() - def get_busy_basket(self, user): + def get_busy_basket(self, user) -> Basket: """ Create and return a basket with 3 barbar and 1 cotis in it. Edit the client session to store the basket id in it @@ -64,11 +64,11 @@ class EbouticTest(TestCase): basket.add_product(self.cotis) return basket - def generate_bank_valid_answer_from_page_content(self, content): - content = str(content) - basket_id = re.search(r"PBX_CMD\" value=\"(\d*)\"", content).group(1) - amount = re.search(r"PBX_TOTAL\" value=\"(\d*)\"", content).group(1) - query = "Amount=%s&BasketID=%s&Auto=42&Error=00000" % (amount, basket_id) + def generate_bank_valid_answer(self) -> str: + basket = Basket.from_session(self.client.session) + basket_id = basket.id + amount = int(basket.get_total() * 100) + query = f"Amount={amount}&BasketID={basket_id}&Auto=42&Error=00000" with open("./eboutic/tests/private_key.pem") as f: PRIVKEY = f.read() with open("./eboutic/tests/public_key.pem") as f: @@ -81,8 +81,7 @@ class EbouticTest(TestCase): query, urllib.parse.quote_plus(b64sig), ) - response = self.client.get(url) - return response + return url def test_buy_with_sith_account(self): self.client.login(username="subscriber", password="plop") @@ -102,7 +101,7 @@ class EbouticTest(TestCase): def test_buy_with_sith_account_no_money(self): self.client.login(username="subscriber", password="plop") basket = self.get_busy_basket(self.subscriber) - initial = basket.get_total() - 1 + initial = basket.get_total() - 1 # just not enough to complete the sale self.subscriber.customer.amount = initial self.subscriber.customer.save() response = self.client.post(reverse("eboutic:pay_with_sith")) @@ -122,7 +121,7 @@ class EbouticTest(TestCase): {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28}, {"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertEqual(response.status_code, 200) self.assertInHTML( "Cotis 2 semestres128.00 €", @@ -146,7 +145,7 @@ class EbouticTest(TestCase): def test_submit_empty_basket(self): self.client.login(username="subscriber", password="plop") self.client.cookies["basket_items"] = "[]" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertRedirects(response, "/eboutic/") def test_submit_invalid_basket(self): @@ -157,7 +156,7 @@ class EbouticTest(TestCase): ] = f"""[ {{"id": {max_id + 1}, "name": "", "quantity": 1, "unit_price": 28}} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertIn( 'basket_items=""', self.client.cookies["basket_items"].OutputString(), @@ -175,7 +174,7 @@ class EbouticTest(TestCase): ] = """[ {"id": 4, "name": "Barbar", "quantity": -1, "unit_price": 1.7} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertRedirects(response, "/eboutic/") def test_buy_subscribe_product_with_credit_card(self): @@ -189,14 +188,14 @@ class EbouticTest(TestCase): ] = """[ {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertInHTML( "Cotis 2 semestres128.00 €", response.content.decode(), ) basket = Basket.objects.get(id=self.client.session["basket_id"]) self.assertEqual(basket.items.count(), 1) - response = self.generate_bank_valid_answer_from_page_content(response.content) + response = self.client.get(self.generate_bank_valid_answer()) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode("utf-8") == "Payment successful") @@ -215,9 +214,10 @@ class EbouticTest(TestCase): [{"id": 3, "name": "Rechargement 15 €", "quantity": 1, "unit_price": 15}] ) initial_balance = self.subscriber.customer.amount - response = self.client.post(reverse("eboutic:command")) + self.client.get(reverse("eboutic:command")) - response = self.generate_bank_valid_answer_from_page_content(response.content) + url = self.generate_bank_valid_answer() + response = self.client.get(url) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode() == "Payment successful") new_balance = Customer.objects.get(user=self.subscriber).amount @@ -228,14 +228,15 @@ class EbouticTest(TestCase): self.client.cookies["basket_items"] = json.dumps( [{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}] ) - response = self.client.post(reverse("eboutic:command")) + self.client.get(reverse("eboutic:command")) + et_answer_url = self.generate_bank_valid_answer() self.client.cookies["basket_items"] = json.dumps( [ # alter basket {"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7} ] ) - self.client.post(reverse("eboutic:command")) - response = self.generate_bank_valid_answer_from_page_content(response.content) + self.client.get(reverse("eboutic:command")) + response = self.client.get(et_answer_url) self.assertEqual(response.status_code, 500) self.assertIn( "Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'", @@ -247,8 +248,9 @@ class EbouticTest(TestCase): self.client.cookies["basket_items"] = json.dumps( [{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}] ) - response = self.client.post(reverse("eboutic:command")) - response = self.generate_bank_valid_answer_from_page_content(response.content) + self.client.get(reverse("eboutic:command")) + et_answer_url = self.generate_bank_valid_answer() + response = self.client.get(et_answer_url) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode("utf-8") == "Payment successful") diff --git a/eboutic/urls.py b/eboutic/urls.py index 417a8b85..a1bd6ecd 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -34,8 +34,9 @@ urlpatterns = [ # Subscription views path("", eboutic_main, name="main"), path("command/", EbouticCommand.as_view(), name="command"), - path("pay/", pay_with_sith, name="pay_with_sith"), + path("pay/sith/", pay_with_sith, name="pay_with_sith"), path("pay//", payment_result, name="payment_result"), + path("et_data/", e_transaction_data, name="et_data"), path( "et_autoanswer", EtransactionAutoAnswer.as_view(), diff --git a/eboutic/views.py b/eboutic/views.py index b478397a..811872ec 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -23,13 +23,10 @@ # import base64 -import hmac import json -from collections import OrderedDict from datetime import datetime + import sentry_sdk - - from OpenSSL import crypto from django.conf import settings from django.contrib.auth.decorators import login_required @@ -41,7 +38,8 @@ from django.utils.decorators import method_decorator from django.views.decorators.http import require_GET, require_POST from django.views.generic import TemplateView, View -from counter.models import Customer, Counter, Selling +from counter.forms import BillingInfoForm +from counter.models import Customer, Counter, Product from eboutic.forms import BasketForm from eboutic.models import Basket, Invoice, InvoiceItem, get_eboutic_products @@ -85,11 +83,11 @@ class EbouticCommand(TemplateView): template_name = "eboutic/eboutic_makecommand.jinja" @method_decorator(login_required) - def get(self, request, *args, **kwargs): + def post(self, request, *args, **kwargs): return redirect("eboutic:main") @method_decorator(login_required) - def post(self, request: HttpRequest, *args, **kwargs): + def get(self, request: HttpRequest, *args, **kwargs): form = BasketForm(request) if not form.is_valid(): request.session["errors"] = form.get_error_messages() @@ -98,65 +96,56 @@ class EbouticCommand(TemplateView): res.set_cookie("basket_items", form.get_cleaned_cookie(), path="/eboutic") return res - if "basket_id" in request.session: - basket, _ = Basket.objects.get_or_create( - id=request.session["basket_id"], user=request.user - ) + basket = Basket.from_session(request.session) + if basket is not None: basket.clear() else: basket = Basket.objects.create(user=request.user) + request.session["basket_id"] = basket.id + request.session.modified = True - basket.save() - eboutique = Counter.objects.get(type="EBOUTIC") - for item in json.loads(request.COOKIES["basket_items"]): - basket.add_product( - eboutique.products.get(id=(item["id"])), item["quantity"] - ) - request.session["basket_id"] = basket.id - request.session.modified = True + items = json.loads(request.COOKIES["basket_items"]) + items.sort(key=lambda item: item["id"]) + ids = [item["id"] for item in items] + quantities = [item["quantity"] for item in items] + products = Product.objects.filter(id__in=ids) + for product, qty in zip(products, quantities): + basket.add_product(product, qty) kwargs["basket"] = basket return self.render_to_response(self.get_context_data(**kwargs)) def get_context_data(self, **kwargs): - kwargs = super(EbouticCommand, self).get_context_data(**kwargs) + # basket is already in kwargs when the method is called + default_billing_info = None if hasattr(self.request.user, "customer"): - kwargs["customer_amount"] = self.request.user.customer.amount + customer = self.request.user.customer + kwargs["customer_amount"] = customer.amount + if hasattr(customer, "billing_infos"): + default_billing_info = customer.billing_infos else: kwargs["customer_amount"] = None - kwargs["et_request"] = OrderedDict() - kwargs["et_request"]["PBX_SITE"] = settings.SITH_EBOUTIC_PBX_SITE - kwargs["et_request"]["PBX_RANG"] = settings.SITH_EBOUTIC_PBX_RANG - kwargs["et_request"]["PBX_IDENTIFIANT"] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT - kwargs["et_request"]["PBX_TOTAL"] = int(kwargs["basket"].get_total() * 100) - kwargs["et_request"][ - "PBX_DEVISE" - ] = 978 # This is Euro. ET support only this value anyway - kwargs["et_request"]["PBX_CMD"] = kwargs["basket"].id - kwargs["et_request"]["PBX_PORTEUR"] = kwargs["basket"].user.email - kwargs["et_request"]["PBX_RETOUR"] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" - kwargs["et_request"]["PBX_HASH"] = "SHA512" - kwargs["et_request"]["PBX_TYPEPAIEMENT"] = "CARTE" - kwargs["et_request"]["PBX_TYPECARTE"] = "CB" - kwargs["et_request"]["PBX_TIME"] = str( - datetime.now().replace(microsecond=0).isoformat("T") - ) - kwargs["et_request"]["PBX_HMAC"] = ( - hmac.new( - settings.SITH_EBOUTIC_HMAC_KEY, - bytes( - "&".join( - ["%s=%s" % (k, v) for k, v in kwargs["et_request"].items()] - ), - "utf-8", - ), - "sha512", - ) - .hexdigest() - .upper() - ) + kwargs["must_fill_billing_infos"] = default_billing_info is None + if not kwargs["must_fill_billing_infos"]: + # the user has already filled its billing_infos, thus we can + # get it without expecting an error + data = kwargs["basket"].get_e_transaction_data() + data = {"data": [{"key": key, "value": val} for key, val in data]} + kwargs["billing_infos"] = json.dumps(data) + kwargs["billing_form"] = BillingInfoForm(instance=default_billing_info) return kwargs +@login_required +@require_GET +def e_transaction_data(request): + basket = Basket.from_session(request.session) + if basket is None: + return HttpResponse(status=404, content=json.dumps({"data": []})) + data = basket.get_e_transaction_data() + data = {"data": [{"key": key, "value": val} for key, val in data]} + return HttpResponse(status=200, content=json.dumps(data)) + + @login_required @require_POST def pay_with_sith(request): @@ -171,24 +160,14 @@ def pay_with_sith(request): res = redirect("eboutic:payment_result", "failure") else: eboutic = Counter.objects.filter(type="EBOUTIC").first() + sales = basket.generate_sales(eboutic, c.user, "SITH_ACCOUNT") try: with transaction.atomic(): - for it in basket.items.all(): - product = eboutic.products.get(id=it.product_id) - Selling( - label=it.product_name, - counter=eboutic, - club=product.club, - product=product, - seller=c.user, - customer=c, - unit_price=it.product_unit_price, - quantity=it.quantity, - payment_method="SITH_ACCOUNT", - ).save() + for sale in sales: + sale.save() basket.delete() - request.session.pop("basket_id", None) - res = redirect("eboutic:payment_result", "success") + request.session.pop("basket_id", None) + res = redirect("eboutic:payment_result", "success") except DatabaseError as e: with sentry_sdk.push_scope() as scope: scope.user = {"username": request.user.username} @@ -205,12 +184,8 @@ class EtransactionAutoAnswer(View): # Response documentation http://www1.paybox.com/espace-integrateur-documentation # /la-solution-paybox-system/gestion-de-la-reponse/ def get(self, request, *args, **kwargs): - if ( - not "Amount" in request.GET.keys() - or not "BasketID" in request.GET.keys() - or not "Error" in request.GET.keys() - or not "Sig" in request.GET.keys() - ): + required = {"Amount", "BasketID", "Error", "Sig"} + if not required.issubset(set(request.GET.keys())): return HttpResponse("Bad arguments", status=400) key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) cert = crypto.X509() diff --git a/launderette/views.py b/launderette/views.py index 0387c3d3..7c15dc6d 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -41,7 +41,8 @@ from club.models import Club from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin from launderette.models import Launderette, Token, Machine, Slot from counter.models import Counter, Customer, Selling -from counter.views import GetUserForm +from counter.forms import GetUserForm + # For users diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 799a4fb6..e2b442a5 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-15 20:59+0100\n" +"POT-Creation-Date: 2022-11-28 16:54+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -18,8 +18,8 @@ msgstr "" #: accounting/models.py:61 accounting/models.py:110 accounting/models.py:143 #: accounting/models.py:216 club/models.py:48 com/models.py:279 -#: com/models.py:296 counter/models.py:131 counter/models.py:159 -#: counter/models.py:243 forum/models.py:58 launderette/models.py:38 +#: com/models.py:296 counter/models.py:199 counter/models.py:232 +#: counter/models.py:316 forum/models.py:58 launderette/models.py:38 #: launderette/models.py:93 launderette/models.py:131 stock/models.py:40 #: stock/models.py:63 stock/models.py:105 stock/models.py:133 msgid "name" @@ -66,8 +66,8 @@ msgid "account number" msgstr "numero de compte" #: accounting/models.py:116 accounting/models.py:147 club/models.py:275 -#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:177 -#: counter/models.py:245 trombi/models.py:217 +#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:250 +#: counter/models.py:318 trombi/models.py:217 msgid "club" msgstr "club" @@ -88,12 +88,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:214 club/models.py:281 counter/models.py:679 +#: accounting/models.py:214 club/models.py:281 counter/models.py:752 #: election/models.py:18 launderette/models.py:194 msgid "start date" msgstr "date de début" -#: accounting/models.py:215 club/models.py:282 counter/models.py:680 +#: accounting/models.py:215 club/models.py:282 counter/models.py:753 #: election/models.py:19 msgid "end date" msgstr "date de fin" @@ -106,8 +106,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:225 accounting/models.py:289 counter/models.py:56 -#: counter/models.py:401 +#: accounting/models.py:225 accounting/models.py:289 counter/models.py:60 +#: counter/models.py:474 msgid "amount" msgstr "montant" @@ -129,18 +129,18 @@ msgstr "classeur" #: accounting/models.py:290 core/models.py:862 core/models.py:1400 #: core/models.py:1448 core/models.py:1477 core/models.py:1501 -#: counter/models.py:411 counter/models.py:504 counter/models.py:709 -#: eboutic/models.py:61 eboutic/models.py:148 forum/models.py:311 +#: counter/models.py:484 counter/models.py:577 counter/models.py:782 +#: eboutic/models.py:66 eboutic/models.py:240 forum/models.py:311 #: forum/models.py:408 stock/models.py:104 msgid "date" msgstr "date" -#: accounting/models.py:291 counter/models.py:133 counter/models.py:710 +#: accounting/models.py:291 counter/models.py:201 counter/models.py:783 #: pedagogy/models.py:219 stock/models.py:107 msgid "comment" msgstr "commentaire" -#: accounting/models.py:293 counter/models.py:413 counter/models.py:506 +#: accounting/models.py:293 counter/models.py:486 counter/models.py:579 #: subscription/models.py:66 msgid "payment method" msgstr "méthode de paiement" @@ -149,7 +149,7 @@ msgstr "méthode de paiement" msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:303 eboutic/models.py:233 +#: accounting/models.py:303 eboutic/models.py:332 msgid "invoice" msgstr "facture" @@ -167,7 +167,7 @@ msgstr "type comptable" #: accounting/models.py:328 accounting/models.py:475 accounting/models.py:510 #: accounting/models.py:545 core/models.py:1476 core/models.py:1502 -#: counter/models.py:470 +#: counter/models.py:543 msgid "label" msgstr "étiquette" @@ -266,7 +266,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:467 counter/models.py:169 pedagogy/models.py:46 +#: accounting/models.py:467 counter/models.py:242 pedagogy/models.py:46 msgid "code" msgstr "code" @@ -378,7 +378,7 @@ msgstr "Compte en banque : " #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:226 pedagogy/templates/pedagogy/guide.jinja:67 +#: launderette/views.py:227 pedagogy/templates/pedagogy/guide.jinja:67 #: pedagogy/templates/pedagogy/guide.jinja:90 #: pedagogy/templates/pedagogy/guide.jinja:126 #: pedagogy/templates/pedagogy/uv_detail.jinja:185 @@ -670,7 +670,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1201 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1064 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:138 #: trombi/templates/trombi/comment.jinja:4 @@ -930,7 +930,7 @@ msgstr "S'abonner" msgid "Remove" msgstr "Retirer" -#: club/forms.py:76 launderette/views.py:228 +#: club/forms.py:76 launderette/views.py:229 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" @@ -955,11 +955,11 @@ msgstr "Une action est requise" msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:162 counter/views.py:1589 +#: club/forms.py:162 counter/forms.py:157 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/views.py:1590 +#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/forms.py:158 #: election/views.py:172 subscription/views.py:49 msgid "End date" msgstr "Date de fin" @@ -967,15 +967,15 @@ msgstr "Date de fin" #: club/forms.py:166 club/templates/club/club_sellings.jinja:21 #: core/templates/core/user_account_detail.jinja:18 #: core/templates/core/user_account_detail.jinja:51 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:216 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:148 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:174 counter/views.py:830 +#: club/forms.py:174 counter/views.py:762 msgid "Products" msgstr "Produits" -#: club/forms.py:179 counter/views.py:835 +#: club/forms.py:179 counter/views.py:767 msgid "Archived products" msgstr "Produits archivés" @@ -1045,8 +1045,8 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs" msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:267 counter/models.py:670 counter/models.py:700 -#: eboutic/models.py:57 eboutic/models.py:144 election/models.py:192 +#: club/models.py:267 counter/models.py:743 counter/models.py:773 +#: eboutic/models.py:62 eboutic/models.py:236 election/models.py:192 #: launderette/models.py:145 launderette/models.py:213 sas/models.py:244 #: trombi/models.py:213 msgid "user" @@ -1057,8 +1057,8 @@ msgstr "nom d'utilisateur" msgid "role" msgstr "rôle" -#: club/models.py:289 core/models.py:81 counter/models.py:132 -#: counter/models.py:160 election/models.py:15 election/models.py:120 +#: club/models.py:289 core/models.py:81 counter/models.py:200 +#: counter/models.py:233 election/models.py:15 election/models.py:120 #: election/models.py:197 forum/models.py:59 forum/models.py:240 msgid "description" msgstr "description" @@ -1155,7 +1155,7 @@ msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:78 #: core/templates/core/file_detail.jinja:19 core/views/forms.py:345 -#: launderette/views.py:226 trombi/templates/trombi/detail.jinja:19 +#: launderette/views.py:227 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1560,7 +1560,7 @@ msgstr "Informations affichées" #: com/templates/com/news_admin_list.jinja:248 #: com/templates/com/news_admin_list.jinja:285 #: launderette/templates/launderette/launderette_admin.jinja:42 -#: launderette/views.py:233 +#: launderette/views.py:234 msgid "Type" msgstr "Type" @@ -1845,7 +1845,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:11 -#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:226 +#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:227 #: pedagogy/templates/pedagogy/uv_detail.jinja:12 #: pedagogy/templates/pedagogy/uv_detail.jinja:21 #: stock/templates/stock/shopping_list_items.jinja:9 @@ -2484,85 +2484,85 @@ msgstr "Forum" msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:188 counter/models.py:253 +#: core/templates/core/base.jinja:187 counter/models.py:326 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:23 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:8 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 #: sith/settings.py:366 sith/settings.py:374 msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:191 +#: core/templates/core/base.jinja:189 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:195 +#: core/templates/core/base.jinja:193 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:196 launderette/models.py:47 +#: core/templates/core/base.jinja:194 launderette/models.py:47 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:197 core/templates/core/file.jinja:20 +#: core/templates/core/base.jinja:195 core/templates/core/file.jinja:20 #: core/views/files.py:86 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:198 core/templates/core/user_tools.jinja:109 +#: core/templates/core/base.jinja:196 core/templates/core/user_tools.jinja:109 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:202 +#: core/templates/core/base.jinja:200 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:206 +#: core/templates/core/base.jinja:204 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:207 +#: core/templates/core/base.jinja:205 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:211 +#: core/templates/core/base.jinja:209 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:215 +#: core/templates/core/base.jinja:213 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:216 core/templates/core/base.jinja:258 +#: core/templates/core/base.jinja:214 core/templates/core/base.jinja:256 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:217 +#: core/templates/core/base.jinja:215 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:259 +#: core/templates/core/base.jinja:257 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:260 +#: core/templates/core/base.jinja:258 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:261 +#: core/templates/core/base.jinja:259 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:262 +#: core/templates/core/base.jinja:260 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:264 +#: core/templates/core/base.jinja:262 msgid "Site made by good people" msgstr "Site réalisé par des gens bons" @@ -3070,7 +3070,7 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:57 -#: core/templates/core/user_tools.jinja:37 counter/views.py:855 +#: core/templates/core/user_tools.jinja:37 counter/views.py:787 msgid "Etickets" msgstr "Etickets" @@ -3381,7 +3381,7 @@ msgstr "Achats" msgid "Product top 10" msgstr "Top 10 produits" -#: core/templates/core/user_stats.jinja:27 counter/views.py:1735 +#: core/templates/core/user_stats.jinja:27 counter/forms.py:168 msgid "Product" msgstr "Produit" @@ -3426,8 +3426,8 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:29 counter/views.py:825 -#: counter/views.py:1033 +#: core/templates/core/user_tools.jinja:29 counter/forms.py:131 +#: counter/views.py:757 msgid "Counters" msgstr "Comptoirs" @@ -3444,12 +3444,12 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:35 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:845 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:777 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:36 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:850 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:782 msgid "Invoices call" msgstr "Appels à facture" @@ -3678,7 +3678,7 @@ msgstr "Parrain / Marraine" msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:348 counter/views.py:156 trombi/views.py:158 +#: core/views/forms.py:348 counter/forms.py:47 trombi/views.py:158 msgid "Select user" msgstr "Choisir un utilisateur" @@ -3713,146 +3713,190 @@ msgstr "Photos" msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/app.py:31 counter/models.py:267 counter/models.py:676 -#: counter/models.py:706 launderette/models.py:41 stock/models.py:43 +#: counter/app.py:31 counter/models.py:340 counter/models.py:749 +#: counter/models.py:779 launderette/models.py:41 stock/models.py:43 msgid "counter" msgstr "comptoir" +#: counter/forms.py:30 +msgid "This UID is invalid" +msgstr "Cet UID est invalide" + +#: counter/forms.py:69 +msgid "User not found" +msgstr "Utilisateur non trouvé" + +#: counter/forms.py:117 +msgid "Parent product" +msgstr "Produit parent" + +#: counter/forms.py:123 +msgid "Buying groups" +msgstr "Groupes d'achat" + #: counter/migrations/0013_customer_recorded_products.py:26 msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:55 +#: counter/models.py:59 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:57 +#: counter/models.py:61 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:60 +#: counter/models.py:64 msgid "customer" msgstr "client" -#: counter/models.py:61 +#: counter/models.py:65 msgid "customers" msgstr "clients" -#: counter/models.py:107 counter/views.py:377 +#: counter/models.py:126 counter/views.py:309 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:137 counter/models.py:164 +#: counter/models.py:157 +msgid "First name" +msgstr "Prénom" + +#: counter/models.py:158 +msgid "Last name" +msgstr "Nom de famille" + +#: counter/models.py:159 +msgid "Address 1" +msgstr "Adresse 1" + +#: counter/models.py:161 +msgid "Address 2" +msgstr "Adresse 2" + +#: counter/models.py:163 +msgid "Zip code" +msgstr "Code postal" + +#: counter/models.py:164 +msgid "City" +msgstr "Ville" + +#: counter/models.py:165 +msgid "Country" +msgstr "Pays" + +#: counter/models.py:209 counter/models.py:237 msgid "product type" msgstr "type du produit" -#: counter/models.py:170 +#: counter/models.py:243 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:171 +#: counter/models.py:244 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:172 +#: counter/models.py:245 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:174 +#: counter/models.py:247 msgid "icon" msgstr "icône" -#: counter/models.py:179 +#: counter/models.py:252 msgid "limit age" msgstr "âge limite" -#: counter/models.py:180 +#: counter/models.py:253 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:184 +#: counter/models.py:257 msgid "parent product" msgstr "produit parent" -#: counter/models.py:190 +#: counter/models.py:263 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:192 election/models.py:52 +#: counter/models.py:265 election/models.py:52 msgid "archived" msgstr "archivé" -#: counter/models.py:195 counter/models.py:801 +#: counter/models.py:268 counter/models.py:874 msgid "product" msgstr "produit" -#: counter/models.py:248 +#: counter/models.py:321 msgid "products" msgstr "produits" -#: counter/models.py:251 +#: counter/models.py:324 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:253 +#: counter/models.py:326 msgid "Bar" msgstr "Bar" -#: counter/models.py:253 +#: counter/models.py:326 msgid "Office" msgstr "Bureau" -#: counter/models.py:256 +#: counter/models.py:329 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:264 launderette/models.py:207 +#: counter/models.py:337 launderette/models.py:207 msgid "token" msgstr "jeton" -#: counter/models.py:419 +#: counter/models.py:492 msgid "bank" msgstr "banque" -#: counter/models.py:421 counter/models.py:511 +#: counter/models.py:494 counter/models.py:584 msgid "is validated" msgstr "est validé" -#: counter/models.py:424 +#: counter/models.py:497 msgid "refilling" msgstr "rechargement" -#: counter/models.py:488 eboutic/models.py:209 +#: counter/models.py:561 eboutic/models.py:292 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:489 counter/models.py:786 eboutic/models.py:210 +#: counter/models.py:562 counter/models.py:859 eboutic/models.py:293 msgid "quantity" msgstr "quantité" -#: counter/models.py:508 +#: counter/models.py:581 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:508 sith/settings.py:359 sith/settings.py:364 +#: counter/models.py:581 sith/settings.py:359 sith/settings.py:364 #: sith/settings.py:384 msgid "Credit card" msgstr "Carte bancaire" -#: counter/models.py:514 +#: counter/models.py:587 msgid "selling" msgstr "vente" -#: counter/models.py:541 +#: counter/models.py:614 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:542 +#: counter/models.py:615 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:544 counter/models.py:567 +#: counter/models.py:617 counter/models.py:640 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3864,59 +3908,59 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:681 +#: counter/models.py:754 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:684 +#: counter/models.py:757 msgid "permanency" msgstr "permanence" -#: counter/models.py:711 +#: counter/models.py:784 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:714 +#: counter/models.py:787 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:782 +#: counter/models.py:855 msgid "cash summary" msgstr "relevé" -#: counter/models.py:785 +#: counter/models.py:858 msgid "value" msgstr "valeur" -#: counter/models.py:787 +#: counter/models.py:860 msgid "check" msgstr "chèque" -#: counter/models.py:790 +#: counter/models.py:863 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:805 +#: counter/models.py:878 msgid "banner" msgstr "bannière" -#: counter/models.py:807 +#: counter/models.py:880 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:809 +#: counter/models.py:882 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:811 +#: counter/models.py:884 msgid "secret" msgstr "secret" -#: counter/models.py:867 +#: counter/models.py:940 msgid "uid" msgstr "uid" -#: counter/models.py:872 +#: counter/models.py:945 msgid "student cards" msgstr "cartes étudiante" @@ -3968,7 +4012,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1202 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1065 msgid "Emptied" msgstr "Coffre vidé" @@ -4013,7 +4057,7 @@ msgid "Selling" msgstr "Vente" #: counter/templates/counter/counter_click.jinja:65 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:11 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:20 msgid "Basket: " msgstr "Panier : " @@ -4026,11 +4070,11 @@ msgstr "Terminer" msgid "Refilling" msgstr "Rechargement" -#: counter/templates/counter/counter_click.jinja:193 counter/views.py:646 +#: counter/templates/counter/counter_click.jinja:193 counter/views.py:578 msgid "END" msgstr "FIN" -#: counter/templates/counter/counter_click.jinja:193 counter/views.py:648 +#: counter/templates/counter/counter_click.jinja:193 counter/views.py:580 msgid "CAN" msgstr "ANN" @@ -4196,125 +4240,109 @@ msgstr "Temps" msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:120 -msgid "This UID is invalid" -msgstr "Cet UID est invalide" - -#: counter/views.py:178 -msgid "User not found" -msgstr "Utilisateur non trouvé" - -#: counter/views.py:235 +#: counter/views.py:167 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:249 +#: counter/views.py:181 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:264 +#: counter/views.py:196 msgid "Take items from stock" msgstr "Prendre des éléments du stock" -#: counter/views.py:317 +#: counter/views.py:249 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:319 +#: counter/views.py:251 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:324 +#: counter/views.py:256 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:368 +#: counter/views.py:300 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:371 +#: counter/views.py:303 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:374 +#: counter/views.py:306 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:671 +#: counter/views.py:603 msgid "You have not enough money to buy all the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" -#: counter/views.py:819 +#: counter/views.py:751 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:821 +#: counter/views.py:753 msgid "Stocks" msgstr "Stocks" -#: counter/views.py:840 +#: counter/views.py:772 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:1019 -msgid "Parent product" -msgstr "Produit parent" - -#: counter/views.py:1025 -msgid "Buying groups" -msgstr "Groupes d'achat" - -#: counter/views.py:1159 +#: counter/views.py:1022 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:1160 +#: counter/views.py:1023 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:1161 +#: counter/views.py:1024 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:1162 +#: counter/views.py:1025 msgid "1 euro" msgstr "1 €" -#: counter/views.py:1163 +#: counter/views.py:1026 msgid "2 euros" msgstr "2 €" -#: counter/views.py:1164 +#: counter/views.py:1027 msgid "5 euros" msgstr "5 €" -#: counter/views.py:1165 +#: counter/views.py:1028 msgid "10 euros" msgstr "10 €" -#: counter/views.py:1166 +#: counter/views.py:1029 msgid "20 euros" msgstr "20 €" -#: counter/views.py:1167 +#: counter/views.py:1030 msgid "50 euros" msgstr "50 €" -#: counter/views.py:1169 +#: counter/views.py:1032 msgid "100 euros" msgstr "100 €" -#: counter/views.py:1172 counter/views.py:1178 counter/views.py:1184 -#: counter/views.py:1190 counter/views.py:1196 +#: counter/views.py:1035 counter/views.py:1041 counter/views.py:1047 +#: counter/views.py:1053 counter/views.py:1059 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:1175 counter/views.py:1181 counter/views.py:1187 -#: counter/views.py:1193 counter/views.py:1199 +#: counter/views.py:1038 counter/views.py:1044 counter/views.py:1050 +#: counter/views.py:1056 counter/views.py:1062 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1829 +#: counter/views.py:1676 msgid "people(s)" msgstr "personne(s)" @@ -4340,39 +4368,36 @@ msgid "%(name)s : this product does not exist." msgstr "%(name)s : ce produit n'existe pas." #: eboutic/forms.py:134 -#, fuzzy, python-format -#| msgid "%(name)s : this product does not exist." +#, python-format msgid "%(name)s : this product does not exist or may no longer be available." -msgstr "" -"%(name)s : ce produit n'existe pas ou n'est peut-être plus disponnible." +msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible." #: eboutic/forms.py:141 -#, fuzzy, python-format -#| msgid "You cannot buy %(nbr)d %(name)%s." -msgid "You cannot buy %(nbr)d %(name)%s." +#, python-format +msgid "You cannot buy %(nbr)d %(name)s." msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s." -#: eboutic/models.py:149 +#: eboutic/models.py:241 msgid "validated" msgstr "validé" -#: eboutic/models.py:159 +#: eboutic/models.py:251 msgid "Invoice already validated" msgstr "Facture déjà validée" -#: eboutic/models.py:206 +#: eboutic/models.py:289 msgid "product id" msgstr "ID du produit" -#: eboutic/models.py:207 +#: eboutic/models.py:290 msgid "product name" msgstr "nom du produit" -#: eboutic/models.py:208 +#: eboutic/models.py:291 msgid "product type id" msgstr "id du type du produit" -#: eboutic/models.py:225 +#: eboutic/models.py:308 msgid "basket" msgstr "panier" @@ -4380,38 +4405,41 @@ msgstr "panier" msgid "Your basket has been cleaned accordingly to those errors." msgstr "Votre panier a été nettoyé en fonction de ces erreurs." -#: eboutic/templates/eboutic/eboutic_main.jinja:40 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:36 +#: eboutic/templates/eboutic/eboutic_main.jinja:41 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:45 msgid "Current account amount: " msgstr "Solde actuel : " -#: eboutic/templates/eboutic/eboutic_main.jinja:59 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:32 +#: eboutic/templates/eboutic/eboutic_main.jinja:60 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:41 msgid "Basket amount: " msgstr "Valeur du panier : " -#: eboutic/templates/eboutic/eboutic_main.jinja:66 +#: eboutic/templates/eboutic/eboutic_main.jinja:67 msgid "Clear" msgstr "Vider" -#: eboutic/templates/eboutic/eboutic_main.jinja:72 +#: eboutic/templates/eboutic/eboutic_main.jinja:73 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:93 msgid "Validate" msgstr "Valider" -#: eboutic/templates/eboutic/eboutic_main.jinja:81 +#: eboutic/templates/eboutic/eboutic_main.jinja:82 msgid "" -"You have not filled in your date of birth. As a result, you may not have access to all the products in the online shop. To fill in your date of birth, you can go to" +"You have not filled in your date of birth. As a result, you may not have " +"access to all the products in the online shop. To fill in your date of " +"birth, you can go to" msgstr "" -"Vous n'avez pas renseigné votre date de naissance. " -"Par conséquent, il est possible que vous n'ayez pas accès à l'intégralité " -"des produits de la boutique en ligne. Pour remplir votre date de naissance, " -"vous pouvez aller sur" +"Vous n'avez pas renseigné votre date de naissance. Par conséquent, il est " +"possible que vous n'ayez pas accès à l'intégralité des produits de la " +"boutique en ligne. Pour remplir votre date de naissance, vous pouvez aller " +"sur" -#: eboutic/templates/eboutic/eboutic_main.jinja:87 +#: eboutic/templates/eboutic/eboutic_main.jinja:84 msgid "this page" msgstr "cette page" -#: eboutic/templates/eboutic/eboutic_main.jinja:125 +#: eboutic/templates/eboutic/eboutic_main.jinja:124 msgid "There are no items available for sale" msgstr "Aucun article n'est disponible à la vente" @@ -4419,22 +4447,34 @@ msgstr "Aucun article n'est disponible à la vente" msgid "Basket state" msgstr "État du panier" -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:40 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:50 msgid "Remaining account amount: " msgstr "Solde restant : " -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:51 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:60 +msgid "Edit billing information" +msgstr "Éditer les informations de facturation" + +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:100 +msgid "" +"You must fill your billing infos if you want to pay with your credit\n" +" card" +msgstr "" +"Vous devez renseigner vos coordonnées de facturation si vous voulez payer " +"par carte bancaire" + +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:112 msgid "Pay with credit card" msgstr "Payer avec une carte bancaire" -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:56 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:116 msgid "" "AE account payment disabled because your basket contains refilling items." msgstr "" "Paiement par compte AE désactivé parce que votre panier contient des bons de " "rechargement." -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:61 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:121 msgid "Pay with Sith account" msgstr "Payer avec un compte AE" @@ -4854,25 +4894,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:240 +#: launderette/views.py:241 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:260 launderette/views.py:282 +#: launderette/views.py:261 launderette/views.py:283 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:271 +#: launderette/views.py:272 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:338 +#: launderette/views.py:339 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:450 +#: launderette/views.py:451 msgid "Token not found" msgstr "Jeton non trouvé" diff --git a/poetry.lock b/poetry.lock index ef60aa13..8ddf1390 100644 --- a/poetry.lock +++ b/poetry.lock @@ -23,11 +23,11 @@ optional = false python-versions = ">=3.7" [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] -name = "babel" -version = "2.10.3" +name = "Babel" +version = "2.11.0" description = "Internationalization utilities" category = "main" optional = true @@ -46,11 +46,11 @@ python-versions = "*" [[package]] name = "black" -version = "22.6.0" +version = "22.10.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" [package.dependencies] click = ">=8.0.0" @@ -68,7 +68,7 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.6.15" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -87,7 +87,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.1.0" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = true @@ -109,11 +109,11 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" @@ -138,12 +138,12 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] name = "decorator" @@ -154,8 +154,19 @@ optional = false python-versions = ">=3.5" [[package]] -name = "django" -version = "3.2.15" +name = "dict2xml" +version = "1.7.2" +description = "Small utility to convert a python dictionary into an XML string" +category = "main" +optional = false +python-versions = ">= 3.5" + +[package.extras] +tests = ["noseOfYeti (==2.3.1)", "pytest (==7.1.3)"] + +[[package]] +name = "Django" +version = "3.2.16" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -178,16 +189,33 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "django-countries" +version = "7.4.2" +description = "Provides a country field for Django models." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +dev = ["black", "django", "djangorestframework", "graphene-django", "pytest", "pytest-django", "tox"] +maintainer = ["django", "transifex-client", "zest.releaser[recommended]"] +pyuca = ["pyuca"] +test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytest-django"] + [[package]] name = "django-debug-toolbar" -version = "3.5.0" +version = "3.7.0" description = "A configurable set of panels that display various debug information about the current request/response." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -Django = ">=3.2" +Django = ">=3.2.4" sqlparse = ">=0.2.0" [[package]] @@ -226,14 +254,14 @@ python-versions = "*" [[package]] name = "django-phonenumber-field" -version = "6.3.0" +version = "6.4.0" description = "An international phone number field for django models." category = "main" optional = false python-versions = ">=3.7" [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" [package.extras] phonenumbers = ["phonenumbers (>=7.0.2)"] @@ -268,14 +296,14 @@ test = ["testfixtures"] [[package]] name = "djangorestframework" -version = "3.13.1" +version = "3.14.0" description = "Web APIs for Django, made easy." category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -django = ">=2.2" +django = ">=3.0" pytz = "*" [[package]] @@ -288,7 +316,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "idna" -version = "3.3" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = true @@ -304,7 +332,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.12.0" +version = "5.1.0" description = "Read metadata from Python packages" category = "main" optional = true @@ -314,9 +342,9 @@ python-versions = ">=3.7" zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "ipython" @@ -337,6 +365,7 @@ pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" +setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -345,14 +374,14 @@ doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] -notebook = ["notebook", "ipywidgets"] +notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] [[package]] name = "jedi" -version = "0.18.1" +version = "0.18.2" description = "An autocompletion tool for Python that can be used for text editors." category = "dev" optional = false @@ -362,11 +391,12 @@ python-versions = ">=3.6" parso = ">=0.8.0,<0.9.0" [package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] -name = "jinja2" +name = "Jinja2" version = "3.1.2" description = "A very fast and expressive template engine." category = "main" @@ -391,7 +421,7 @@ python-versions = "*" six = "*" [[package]] -name = "markupsafe" +name = "MarkupSafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" @@ -400,7 +430,7 @@ python-versions = ">=3.7" [[package]] name = "matplotlib-inline" -version = "0.1.3" +version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" category = "dev" optional = false @@ -458,11 +488,11 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.9.0" +version = "0.10.2" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" [[package]] name = "pexpect" @@ -477,7 +507,7 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.12.53" +version = "8.13.1" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." category = "main" optional = false @@ -492,8 +522,8 @@ optional = false python-versions = "*" [[package]] -name = "pillow" -version = "9.2.0" +name = "Pillow" +version = "9.3.0" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -505,19 +535,19 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "2.5.4" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "prompt-toolkit" -version = "3.0.30" +version = "3.0.33" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false @@ -551,23 +581,26 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "pygments" -version = "2.12.0" +name = "Pygments" +version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.6" +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pygraphviz" -version = "1.9" +version = "1.10" description = "Python interface to Graphviz" category = "main" optional = false python-versions = ">=3.8" [[package]] -name = "pyopenssl" +name = "pyOpenSSL" version = "21.0.0" description = "Python wrapper module around the OpenSSL library" category = "main" @@ -591,7 +624,7 @@ optional = true python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "python-dateutil" @@ -614,17 +647,18 @@ python-versions = "*" [[package]] name = "reportlab" -version = "3.6.11" +version = "3.6.12" description = "The Reportlab Toolkit" category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7,<4" [package.dependencies] pillow = ">=9.0.0" [package.extras] -rlpycairo = ["rlPyCairo (>=0.0.5)"] +fttextpath = ["freetype-py (>=2.3.0,<2.4)"] +rlpycairo = ["rlPyCairo (>=0.1.0)"] [[package]] name = "requests" @@ -646,7 +680,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sentry-sdk" -version = "1.9.0" +version = "1.11.1" description = "Python client for Sentry (https://sentry.io)" category = "main" optional = false @@ -654,7 +688,7 @@ python-versions = "*" [package.dependencies] certifi = "*" -urllib3 = ">=1.10.0" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] @@ -665,17 +699,31 @@ chalice = ["chalice (>=1.16.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] -flask = ["flask (>=0.11)", "blinker (>=1.1)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)"] httpx = ["httpx (>=0.16.0)"] -pure_eval = ["pure-eval", "executing", "asttokens"] +pure_eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] -quart = ["quart (>=0.16.1)", "blinker (>=1.1)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] tornado = ["tornado (>=5)"] +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -693,7 +741,7 @@ optional = true python-versions = "*" [[package]] -name = "sphinx" +name = "Sphinx" version = "4.5.0" description = "Python documentation generator" category = "main" @@ -721,8 +769,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] [[package]] name = "sphinx-copybutton" @@ -737,22 +785,22 @@ sphinx = ">=1.8" [package.extras] code_style = ["pre-commit (==2.12.1)"] -rtd = ["sphinx", "ipython", "sphinx-book-theme"] +rtd = ["ipython", "sphinx", "sphinx-book-theme"] [[package]] name = "sphinx-rtd-theme" -version = "1.0.0" +version = "1.1.1" description = "Read the Docs theme for Sphinx" category = "main" optional = true -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.dependencies] docutils = "<0.18" -sphinx = ">=1.6" +sphinx = ">=1.6,<6" [package.extras] -dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" @@ -763,7 +811,7 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -775,7 +823,7 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -787,8 +835,8 @@ optional = true python-versions = ">=3.6" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest", "html5lib"] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" @@ -799,7 +847,7 @@ optional = true python-versions = ">=3.5" [package.extras] -test = ["pytest", "flake8", "mypy"] +test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" @@ -810,7 +858,7 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -822,12 +870,12 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sqlparse" -version = "0.4.2" +version = "0.4.3" description = "A non-validating SQL parser." category = "main" optional = false @@ -843,34 +891,35 @@ python-versions = ">=3.7" [[package]] name = "traitlets" -version = "5.3.0" +version = "5.5.0" description = "" category = "dev" optional = false python-versions = ">=3.7" [package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest"] [[package]] name = "typing-extensions" -version = "4.3.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.11" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -903,15 +952,15 @@ django-haystack = ">=2.8.0" [[package]] name = "zipp" -version = "3.8.1" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = true python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] @@ -921,26 +970,134 @@ testing = ["coverage"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "08911593e7e5f4267de94d632be1332f4ef01a22b37a827f557eb15f0dba340b" +content-hash = "c335c796e1f2841eedbc714576a3906c267a66758ca21d46dc52dcffb2f36a16" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appnope = [] -asgiref = [] -babel = [] +appnope = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] +asgiref = [ + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, +] +Babel = [ + {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, + {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, +] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -black = [] -certifi = [] -cffi = [] -charset-normalizer = [] -click = [] -colorama = [] +black = [ + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, @@ -995,18 +1152,67 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] -cryptography = [] +cryptography = [ + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, +] decorator = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] -django = [] -django-ajax-selects = [] -django-debug-toolbar = [] -django-haystack = [] -django-jinja = [] -django-ordered-model = [] -django-phonenumber-field = [] +dict2xml = [ + {file = "dict2xml-1.7.2.tar.gz", hash = "sha256:4027330957466d14a4f692e6b499717e743bf3d8477f474d0a3cf2d31c8178af"}, +] +Django = [ + {file = "Django-3.2.16-py3-none-any.whl", hash = "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121"}, + {file = "Django-3.2.16.tar.gz", hash = "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d"}, +] +django-ajax-selects = [ + {file = "django-ajax-selects-2.2.0.tar.gz", hash = "sha256:539298874b2d26ce9e778a5173d312f55340c887a126c7e2d3460b9a5b4593a2"}, +] +django-countries = [ + {file = "django-countries-7.4.2.tar.gz", hash = "sha256:9d553531de8eaf384fec7fdd5867f3cb9b922d384984c3e613b6b29031e5c3c2"}, + {file = "django_countries-7.4.2-py3-none-any.whl", hash = "sha256:2eadbe91578fe7b5a0c7782c07487b2ceb4bde9bc5c76eb43458929969f25c62"}, +] +django-debug-toolbar = [ + {file = "django-debug-toolbar-3.7.0.tar.gz", hash = "sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465"}, + {file = "django_debug_toolbar-3.7.0-py3-none-any.whl", hash = "sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8"}, +] +django-haystack = [ + {file = "django-haystack-3.2.1.tar.gz", hash = "sha256:97e3197aefc225fe405b6f17600a2534bf827cb4d6743130c20bc1a06f7293a4"}, +] +django-jinja = [ + {file = "django-jinja-2.10.2.tar.gz", hash = "sha256:bfdfbb55c1f5a679d69ad575d550c4707d386634009152efe014089f3c4d1412"}, + {file = "django_jinja-2.10.2-py3-none-any.whl", hash = "sha256:dd003ec1c95c0989eb28a538831bced62b1b61da551cb44a5dfd708fcf75589f"}, +] +django-ordered-model = [ + {file = "django-ordered-model-3.6.tar.gz", hash = "sha256:62161a6bc51d8b402644854b257605d7b5183d01fd349826682a87e9227c05b5"}, + {file = "django_ordered_model-3.6-py3-none-any.whl", hash = "sha256:0006b111f472a2348f75554a4e77bee2b1f379a0f96726af6b1a3ebf3a950789"}, +] +django-phonenumber-field = [ + {file = "django-phonenumber-field-6.4.0.tar.gz", hash = "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed"}, + {file = "django_phonenumber_field-6.4.0-py3-none-any.whl", hash = "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58"}, +] django-ranged-response = [ {file = "django-ranged-response-0.2.0.tar.gz", hash = "sha256:f71fff352a37316b9bead717fc76e4ddd6c9b99c4680cdf4783b9755af1cf985"}, ] @@ -1015,25 +1221,37 @@ django-simple-captcha = [ {file = "django_simple_captcha-0.5.17-py2.py3-none-any.whl", hash = "sha256:f9a07e5e9de264ba4039c9eaad66bc48188a21ceda5fcdc2fa13c5512141c2c9"}, ] djangorestframework = [ - {file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"}, - {file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"}, + {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, + {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] +importlib-metadata = [ + {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, + {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, +] +ipython = [ + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, ] -imagesize = [] -importlib-metadata = [] -ipython = [] jedi = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, +] +Jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -jinja2 = [] libsass = [ {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"}, @@ -1043,9 +1261,10 @@ libsass = [ {file = "libsass-0.21.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e2b1a7d093f2e76dc694c17c0c285e846d0b0deb0e8b21dc852ba1a3a4e2f1d6"}, {file = "libsass-0.21.0-cp36-abi3-win32.whl", hash = "sha256:abc29357ee540849faf1383e1746d40d69ed5cb6d4c346df276b258f5aa8977a"}, {file = "libsass-0.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:659ae41af8708681fa3ec73f47b9735a6725e71c3b66ff570bfce78952f2314e"}, + {file = "libsass-0.21.0-cp38-abi3-macosx_12_0_arm64.whl", hash = "sha256:c9ec490609752c1d81ff6290da33485aa7cb6d7365ac665b74464c1b7d97f7da"}, {file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"}, ] -markupsafe = [ +MarkupSafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, @@ -1088,8 +1307,8 @@ markupsafe = [ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] matplotlib-inline = [ - {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, - {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, ] mistune = [ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, @@ -1099,7 +1318,15 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -mysqlclient = [] +mysqlclient = [ + {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"}, + {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"}, + {file = "mysqlclient-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c"}, + {file = "mysqlclient-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994"}, + {file = "mysqlclient-2.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855"}, + {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"}, + {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1109,24 +1336,96 @@ parso = [ {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, ] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] -phonenumbers = [] +phonenumbers = [ + {file = "phonenumbers-8.13.1-py2.py3-none-any.whl", hash = "sha256:07a95c2f178687fd1c3f722cf792b3d33e3a225ae71577e500c99b28544cd6d0"}, + {file = "phonenumbers-8.13.1.tar.gz", hash = "sha256:7cadfe900e833857500b7bafa3e5a7eddc3263eb66b66a767870b33e44665f92"}, +] pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] -pillow = [] -platformdirs = [] -prompt-toolkit = [] +Pillow = [ + {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, + {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, + {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, + {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, + {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, + {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, + {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, + {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, + {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, + {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, + {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, + {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, + {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, + {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, + {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, + {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, + {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, + {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, +] +platformdirs = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.33-py3-none-any.whl", hash = "sha256:ced598b222f6f4029c0800cefaa6a17373fb580cd093223003475ce32805c35b"}, + {file = "prompt_toolkit-3.0.33.tar.gz", hash = "sha256:535c29c31216c77302877d5120aef6c94ff573748a5b5ca5b1b1f76f5e700c73"}, +] psycopg2-binary = [ {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f2534ab7dc7e776a263b463a16e189eb30e85ec9bbe1bff9e78dae802608932"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, @@ -1160,6 +1459,7 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e6aa71ae45f952a2205377773e76f4e3f27951df38e69a4c95440c779e013560"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, @@ -1171,6 +1471,7 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b3a24a1982ae56461cc24f6680604fffa2c1b818e9dc55680da038792e004d18"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, @@ -1190,13 +1491,21 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pygments = [] -pygraphviz = [] -pyopenssl = [ +Pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pygraphviz = [ + {file = "pygraphviz-1.10.zip", hash = "sha256:457e093a888128903251a266a8cc16b4ba93f3f6334b3ebfed92c7471a74d867"}, +] +pyOpenSSL = [ {file = "pyOpenSSL-21.0.0-py2.py3-none-any.whl", hash = "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"}, {file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"}, ] -pyparsing = [] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -1205,9 +1514,65 @@ pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] -reportlab = [] -requests = [] -sentry-sdk = [] +reportlab = [ + {file = "reportlab-3.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dfcf7bd6db5d80711cbbd0996b6e7a79cc414ca81457960367df11d2860f92a"}, + {file = "reportlab-3.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0bc7a1d64fe754b62e175ba0cf47a630b529c0488ec9ac4e4c7655e295ea4d"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adf78ccb2defad5b6ecb2e2e9f2a672719b0a8e2278592a7d77f6c220a042388"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c84afd5bef6e407c80ba9f99b6abbe3ea78e8243b0f19897a871a7bcad1f749d"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fa3cdf490f3828b055381e8c7dc7819b3e5f7a442d7af7a8f90e9806a7fff51"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07fdd968df7941c2bfb67b9bb4532f424992dfafc71b72a4e4b291ff707e6b0e"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce85a204f46c871c8af6fa64b9bbed165456935c1d0bfb2f570a3194f6723ddb"}, + {file = "reportlab-3.6.12-cp310-cp310-win32.whl", hash = "sha256:090ea99ff829d918f7b6140594373b1340a34e1e6876eddae5aa06662ec10d64"}, + {file = "reportlab-3.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:4c599645af9b5b2241a23e977a82c965a59c24cd94b2600b8d34373c66cad763"}, + {file = "reportlab-3.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:236a6483210049205f6180d7a7595d0ca2e4ce343d83cc94ca719a4145809c6f"}, + {file = "reportlab-3.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:69f41295d696c822224334f0994f1f107df7efed72211d45a1118696f1427c84"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f51dcb39e910a853749250c0f82aced80bca3f7315e9c4ee14349eb7cab6a3f8"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8dddc52e0e486291be0ad39184da0607fae9cc665fdba1881211de9cfc0b332"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4863c49602722237e35cbce5aa91af4539cc63a671f59504d2b3f3767d898cf"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b1215facead57cc5325aef4229ef886e85d270b2ba02080fb5809ce9d2b81b4"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12049314497d872f6788f811e2b331654db207937f8a2fb34ff3e3cd9897faa"}, + {file = "reportlab-3.6.12-cp311-cp311-win32.whl", hash = "sha256:759495c2b8c15cb0d6b539c246896029e4cde42a896c3956f77e311c5f6b0807"}, + {file = "reportlab-3.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:666bdba4958b348460a765c48b8c0640e7085540846ed9494f47d8651604b33c"}, + {file = "reportlab-3.6.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a7c3369fa618eca79f9554ce06c618a5e738e592d61d96aa09b2457ca3ea410"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9b0861d8f40d7a24b094b8834f6a489b9e8c70bceaa7fa98237eed229671ce"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26c25ea4afa8b92a2c14f4edc41c8fc30505745ce84cae86538e80cacadd7ae2"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55a070206580e161b6bbe1a96abf816c18d4c2c225d49916654714c93d842835"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40e108072379ff83dd7442159ebc249d12eb8eec15b70614953fecd2c403792"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39e92fa4ab2a8f0f2cc051d9c1e3acb881340c07ef59c0c8b627861343d653c0"}, + {file = "reportlab-3.6.12-cp37-cp37m-win32.whl", hash = "sha256:3fd1ffdd5204301eb4c290a5752ac62f44d2d0b262e02e35a1e5234c13e14662"}, + {file = "reportlab-3.6.12-cp37-cp37m-win_amd64.whl", hash = "sha256:d4cecfb48a6cfbfe2caf0fc280cecea999699e63bc98cb02254bd87b39eff677"}, + {file = "reportlab-3.6.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b6a1b685da0b9a8000bb980e02d9d5be202d0cc539af113b661c76c051fca6f1"}, + {file = "reportlab-3.6.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f5808e1dac6b66c109d6205ce2aebf84bb89e1a1493b7e6df38932df5ebfb9cf"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb83df8f7840321d34cb5b24c972c617a8c1716c8a36e5050fff56adf5891b8c"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72ec333f089b4fce5a6d740ed0a1963a3994146be195722da0d8e14d4a7e1600"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71cf73f9907c444ef663ea653dbac24af07c307079572c3ff8f20ad1463af3b7"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cee3b6ebef5e4a8654ec5f0effeb1a2bb157ad87b0ac856871d25a805c0f2f90"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db62bed0774778fdf82c609cb9efd0062f2fdcd285be527d01f6be9fd9755888"}, + {file = "reportlab-3.6.12-cp38-cp38-win32.whl", hash = "sha256:b777ddc57b2d3366cbc540616034cdc1089ca0a31fefc907028e1dd62a6bf16c"}, + {file = "reportlab-3.6.12-cp38-cp38-win_amd64.whl", hash = "sha256:c07ec796a2a5d44bf787f2b623b6e668a389b0cafb78af34cf74554ff3bc532b"}, + {file = "reportlab-3.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cdd206883e999278d2af656f988dfcc89eb0c175ce6d75e87b713cf1e792c0c4"}, + {file = "reportlab-3.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a62e51a4a47616896bd0f1e9cc3fbfb174b713794a5031a34b84f69dbe01775"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dd0307b2b13b0482ac8314fd793fbbce263a428b189371addf0466784e1d597"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56d701f7dc662e1d3d7fe364e66fa1339eafce54a488c2d16ec0ea49dc213c2"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:109009b02fc225882ea766a5ed8be0ef473fa1356e252a3f651a6aa89b4a195f"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3648f3c340b6b6aabf9352341478c708cee6f00c5cd5c902311fcf4ce870f3c"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:907f7cd4832bb295d0c1573de15cc5aab5988282caf2ee7a2b1276fb6cdf502b"}, + {file = "reportlab-3.6.12-cp39-cp39-win32.whl", hash = "sha256:93e229519d046491b798f2c12dbbf2f3e237e89589aa5cbb5e1d8c1a978816db"}, + {file = "reportlab-3.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:498b4ec7e73426de64c6bf6ec03c5b3f10dedf5db8a9e13fdf195f95a3d065aa"}, + {file = "reportlab-3.6.12.tar.gz", hash = "sha256:b13cebf4e397bba14542bcd023338b6ff2c151a3a12aabca89eecbf972cb361a"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +sentry-sdk = [ + {file = "sentry-sdk-1.11.1.tar.gz", hash = "sha256:675f6279b6bb1fea09fd61751061f9a90dca3b5929ef631dd50dc8b3aeb245e9"}, + {file = "sentry_sdk-1.11.1-py2.py3-none-any.whl", hash = "sha256:8b4ff696c0bdcceb3f70bbb87a57ba84fd3168b1332d493fcd16c137f709578c"}, +] +setuptools = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1216,14 +1581,17 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -sphinx = [] +Sphinx = [ + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, +] sphinx-copybutton = [ {file = "sphinx-copybutton-0.4.0.tar.gz", hash = "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386"}, {file = "sphinx_copybutton-0.4.0-py3-none-any.whl", hash = "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, - {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, + {file = "sphinx_rtd_theme-1.1.1-py2.py3-none-any.whl", hash = "sha256:31faa07d3e97c8955637fc3f1423a5ab2c44b74b8cc558a51498c202ce5cbda7"}, + {file = "sphinx_rtd_theme-1.1.1.tar.gz", hash = "sha256:6146c845f1e1947b3c3dd4432c28998a1693ccc742b4f9ad7c63129f0757c103"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -1250,16 +1618,25 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] sqlparse = [ - {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, - {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, + {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, + {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, ] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -traitlets = [] -typing-extensions = [] -urllib3 = [] +traitlets = [ + {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, + {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +urllib3 = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -1267,5 +1644,10 @@ wcwidth = [ xapian-bindings = [ {file = "xapian-bindings-0.1.0.tar.gz", hash = "sha256:f2b0396082ebf4f6681ab43d6d8fd1f63b6964b18c32c91236ed067c6f62ad14"}, ] -xapian-haystack = [] -zipp = [] +xapian-haystack = [ + {file = "xapian-haystack-3.0.1.tar.gz", hash = "sha256:a5c0e1262b95008df4dfeb58d093c654acee3f2b27ea3f7d366900895cdc70f9"}, +] +zipp = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] diff --git a/pyproject.toml b/pyproject.toml index 3a88a467..99b3958f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ psycopg2-binary = "2.9.3" sentry-sdk = "^1.4.3" pygraphviz = "^1.9" Jinja2 = "^3.1" +django-countries = "^7.4.2" +dict2xml = "^1.7.2" # Extra optional dependencies mysqlclient = { version = "^2.0.3", optional = true } diff --git a/subscription/models.py b/subscription/models.py index 40eeb207..29d4224a 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -97,19 +97,12 @@ class Subscription(models.Model): # TODO see SubscriptionForm's clean method raise ValidationError(_("Subscription error")) - def save(self): + def save(self, *args, **kwargs): super(Subscription, self).save() from counter.models import Customer if not Customer.objects.filter(user=self.member).exists(): - last_id = ( - Customer.objects.count() + 1504 - ) # Number to keep a continuity with the old site - Customer( - user=self.member, - account_id=Customer.generate_account_id(last_id + 1), - amount=0, - ).save() + Customer.new_for_user(self.member).save() form = PasswordResetForm({"email": self.member.email}) if form.is_valid(): form.save(