diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 141f8e53..db32fa3a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,3 +45,21 @@ jobs: poetry run ./manage.py compilemessages sudo systemctl restart uwsgi + + sentry: + runs-on: ubuntu-latest + environment: production + timeout-minutes: 30 + needs: deployment + steps: + - uses: actions/checkout@v4 + + - name: Sentry Release + uses: getsentry/action-release@v1.7.0 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_URL: ${{ secrets.SENTRY_URL }} + with: + environment: production \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d0b57f5..e480eda0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.6.9 + rev: v0.8.3 hooks: - id: ruff # just check the code, and print the errors - id: ruff # actually fix the fixable errors, but print nothing @@ -14,7 +14,7 @@ repos: - id: biome-check additional_dependencies: ["@biomejs/biome@1.9.3"] - repo: https://github.com/rtts/djhtml - rev: 3.0.6 + rev: 3.0.7 hooks: - id: djhtml name: format templates diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index 9e261bba..7098101a 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -69,7 +69,7 @@ class Command(BaseCommand): # sqlite doesn't support this operation return sqlcmd = StringIO() - call_command("sqlsequencereset", *args, stdout=sqlcmd) + call_command("sqlsequencereset", "--no-color", *args, stdout=sqlcmd) cursor = connection.cursor() cursor.execute(sqlcmd.getvalue()) @@ -137,11 +137,10 @@ class Command(BaseCommand): ) self.reset_index("club") + for bar_id, bar_name in settings.SITH_COUNTER_BARS: + Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR").save() + self.reset_index("counter") counters = [ - *[ - Counter(id=bar_id, name=bar_name, club=bar_club, type="BAR") - for bar_id, bar_name in settings.SITH_COUNTER_BARS - ], Counter(name="Eboutic", club=main_club, type="EBOUTIC"), Counter(name="AE", club=main_club, type="OFFICE"), Counter(name="Vidage comptes AE", club=main_club, type="OFFICE"), diff --git a/core/schemas.py b/core/schemas.py index 775cd6b0..f4080c90 100644 --- a/core/schemas.py +++ b/core/schemas.py @@ -4,6 +4,7 @@ from typing import Annotated from annotated_types import MinLen from django.contrib.staticfiles.storage import staticfiles_storage from django.db.models import Q +from django.urls import reverse from django.utils.text import slugify from haystack.query import SearchQuerySet from ninja import FilterSchema, ModelSchema, Schema @@ -37,13 +38,13 @@ class UserProfileSchema(ModelSchema): @staticmethod def resolve_profile_url(obj: User) -> str: - return obj.get_absolute_url() + return reverse("core:user_profile", kwargs={"user_id": obj.pk}) @staticmethod def resolve_profile_pict(obj: User) -> str: if obj.profile_pict_id is None: return staticfiles_storage.url("core/img/unknown.jpg") - return obj.profile_pict.get_download_url() + return reverse("core:download", kwargs={"file_id": obj.profile_pict_id}) class SithFileSchema(ModelSchema): diff --git a/core/static/bundled/core/components/ajax-select-base.ts b/core/static/bundled/core/components/ajax-select-base.ts index 0befd398..674b7b73 100644 --- a/core/static/bundled/core/components/ajax-select-base.ts +++ b/core/static/bundled/core/components/ajax-select-base.ts @@ -103,6 +103,12 @@ export class AutoCompleteSelectBase extends inheritHtmlElement("select") { export abstract class AjaxSelect extends AutoCompleteSelectBase { protected filter?: (items: TomOption[]) => TomOption[] = null; protected minCharNumberForSearch = 2; + /** + * A cache of researches that have been made using this input. + * For each record, the key is the user's query and the value + * is the list of results sent back by the server. + */ + protected cache = {} as Record; protected abstract valueField: string; protected abstract labelField: string; @@ -135,7 +141,13 @@ export abstract class AjaxSelect extends AutoCompleteSelectBase { this.widget.clearOptions(); } - const resp = await this.search(query); + // Check in the cache if this query has already been typed + // and do an actual HTTP request only if the result isn't cached + let resp = this.cache[query]; + if (!resp) { + resp = await this.search(query); + this.cache[query] = resp; + } if (this.filter) { callback(this.filter(resp), []); diff --git a/core/static/bundled/htmx-index.js b/core/static/bundled/htmx-index.js index 56edea4a..474617ac 100644 --- a/core/static/bundled/htmx-index.js +++ b/core/static/bundled/htmx-index.js @@ -1,3 +1,11 @@ import htmx from "htmx.org"; +document.body.addEventListener("htmx:beforeRequest", (event) => { + event.target.ariaBusy = true; +}); + +document.body.addEventListener("htmx:afterRequest", (event) => { + event.originalTarget.ariaBusy = null; +}); + Object.assign(window, { htmx }); diff --git a/core/static/bundled/utils/web-components.ts b/core/static/bundled/utils/web-components.ts index 8bec98f9..c2b089c5 100644 --- a/core/static/bundled/utils/web-components.ts +++ b/core/static/bundled/utils/web-components.ts @@ -6,7 +6,16 @@ **/ export function registerComponent(name: string, options?: ElementDefinitionOptions) { return (component: CustomElementConstructor) => { - window.customElements.define(name, component, options); + try { + window.customElements.define(name, component, options); + } catch (e) { + if (e instanceof DOMException) { + // biome-ignore lint/suspicious/noConsole: it's handy to troobleshot + console.warn(e.message); + return; + } + throw e; + } }; } diff --git a/core/static/core/colors.scss b/core/static/core/colors.scss index 5453dd34..35dc6a69 100644 --- a/core/static/core/colors.scss +++ b/core/static/core/colors.scss @@ -29,4 +29,9 @@ $shadow-color: rgb(223, 223, 223); $background-button-color: hsl(0, 0%, 95%); -$deepblue: #354a5f; \ No newline at end of file +$deepblue: #354a5f; + +@mixin shadow { + box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0, + rgba(60, 64, 67, 0.15) 0 4px 8px 3px; +} \ No newline at end of file diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 50892df3..cbe8d326 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -42,6 +42,32 @@ body { } } +[tooltip] { + position: relative; +} + +[tooltip]::before { + @include shadow; + opacity: 0; + z-index: 1; + content: attr(tooltip); + background: hsl(219.6, 20.8%, 96%); + color: $black-color; + border: 0.5px solid hsl(0, 0%, 50%); + ; + border-radius: 5px; + padding: 5px; + top: 1em; + position: absolute; + margin-top: 5px; + white-space: nowrap; + transition: opacity 500ms ease-out; +} + +[tooltip]:hover::before { + opacity: 1; +} + .ib { display: inline-block; padding: 1px; @@ -79,8 +105,7 @@ body { } .shadow { - box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0, - rgba(60, 64, 67, 0.15) 0 4px 8px 3px; + @include shadow; } .w_big { @@ -308,6 +333,7 @@ body { font-size: 120%; background-color: unset; position: relative; + &:after { content: ''; position: absolute; @@ -318,14 +344,17 @@ body { border-radius: 2px; transition: all 0.2s ease-in-out; } + &:hover:after { border-bottom-color: darken($primary-neutral-light-color, 20%); } + &.active:after { border-bottom-color: $primary-dark-color; } } } + section { padding: 20px; } diff --git a/core/templates/core/user_preferences.jinja b/core/templates/core/user_preferences.jinja index 0cf4bd57..d20f708f 100644 --- a/core/templates/core/user_preferences.jinja +++ b/core/templates/core/user_preferences.jinja @@ -28,42 +28,20 @@ {% else %} -

{% trans trombi=user.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %} +

{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
{% trans %}Go to my Trombi tools{% endtrans %}

{% endif %} - {% if profile.customer %} -

{% trans %}Student cards{% endtrans %}

- - {% if profile.customer.student_cards.exists() %} - - {% else %} - {% trans %}No student card registered.{% endtrans %} -

- {% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually - add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %} -

- {% endif %} - -
- {% csrf_token %} - {{ student_card_form.as_p() }} - -
+ {% if student_card_fragment %} +

{% trans %}Student card{% endtrans %}

+ {{ student_card_fragment }} +

+ {% trans %}You can add a card by asking at a counter or add it yourself here. If you want to manually + add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %} +

{% endif %} {% endblock %} \ No newline at end of file diff --git a/core/utils.py b/core/utils.py index 5b6191f6..cdd72fa6 100644 --- a/core/utils.py +++ b/core/utils.py @@ -13,22 +13,41 @@ # # +from dataclasses import dataclass from datetime import date # Image utils from io import BytesIO -from typing import Optional +from typing import Any import PIL from django.conf import settings from django.core.files.base import ContentFile +from django.forms import BaseForm from django.http import HttpRequest +from django.template.loader import render_to_string +from django.utils.html import SafeString from django.utils.timezone import localdate from PIL import ExifTags from PIL.Image import Image, Resampling -def get_start_of_semester(today: Optional[date] = None) -> date: +@dataclass +class FormFragmentTemplateData[T: BaseForm]: + """Dataclass used to pre-render form fragments""" + + form: T + template: str + context: dict[str, Any] + + def render(self, request: HttpRequest) -> SafeString: + # Request is needed for csrf_tokens + return render_to_string( + self.template, context={"form": self.form, **self.context}, request=request + ) + + +def get_start_of_semester(today: date | None = None) -> date: """Return the date of the start of the semester of the given date. If no date is given, return the start date of the current semester. @@ -58,7 +77,7 @@ def get_start_of_semester(today: Optional[date] = None) -> date: return autumn.replace(year=autumn.year - 1) -def get_semester_code(d: Optional[date] = None) -> str: +def get_semester_code(d: date | None = None) -> str: """Return the semester code of the given date. If no date is given, return the semester code of the current semester. diff --git a/core/views/user.py b/core/views/user.py index e9694a92..9f724fca 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -70,8 +70,8 @@ from core.views.forms import ( UserGodfathersForm, UserProfileForm, ) -from counter.forms import StudentCardForm from counter.models import Refilling, Selling +from counter.views.student_card import StudentCardFormView from eboutic.models import Invoice from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -559,10 +559,6 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): context_object_name = "profile" current_tab = "prefs" - def get_object(self, queryset=None): - user = get_object_or_404(User, pk=self.kwargs["user_id"]) - return user - def get_form_kwargs(self): kwargs = super().get_form_kwargs() pref = self.object.preferences @@ -572,13 +568,12 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): def get_context_data(self, **kwargs): kwargs = super().get_context_data(**kwargs) - if not ( - hasattr(self.object, "trombi_user") and self.request.user.trombi_user.trombi - ): + if not hasattr(self.object, "trombi_user"): kwargs["trombi_form"] = UserTrombiForm() - if hasattr(self.object, "customer"): - kwargs["student_card_form"] = StudentCardForm() + kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( + self.object.customer + ).render(self.request) return kwargs diff --git a/counter/apps.py b/counter/apps.py index 54e7ad4c..9348cd1c 100644 --- a/counter/apps.py +++ b/counter/apps.py @@ -24,6 +24,12 @@ from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ +PAYMENT_METHOD = [ + ("CHECK", _("Check")), + ("CASH", _("Cash")), + ("CARD", _("Credit card")), +] + class CounterConfig(AppConfig): name = "counter" diff --git a/counter/forms.py b/counter/forms.py index 84a92512..80a0e0ad 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -45,16 +45,12 @@ class BillingInfoForm(forms.ModelForm): class StudentCardForm(forms.ModelForm): - """Form for adding student cards - Only used for user profile since CounterClick is to complicated. - """ + """Form for adding student cards""" class Meta: model = StudentCard fields = ["uid"] - widgets = { - "uid": NFCTextInput, - } + widgets = {"uid": NFCTextInput} def clean(self): cleaned_data = super().clean() @@ -114,15 +110,9 @@ class GetUserForm(forms.Form): return cleaned_data -class NFCCardForm(forms.Form): - student_card_uid = forms.CharField( - max_length=StudentCard.UID_SIZE, - required=False, - widget=NFCTextInput, - ) - - class RefillForm(forms.ModelForm): + allowed_refilling_methods = ["CASH", "CARD"] + error_css_class = "error" required_css_class = "required" amount = forms.FloatField( @@ -132,6 +122,21 @@ class RefillForm(forms.ModelForm): class Meta: model = Refilling fields = ["amount", "payment_method", "bank"] + widgets = {"payment_method": forms.RadioSelect} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields["payment_method"].choices = ( + method + for method in self.fields["payment_method"].choices + if method[0] in self.allowed_refilling_methods + ) + if self.fields["payment_method"].initial not in self.allowed_refilling_methods: + self.fields["payment_method"].initial = self.allowed_refilling_methods[0] + + if "CHECK" not in self.allowed_refilling_methods: + del self.fields["bank"] class CounterEditForm(forms.ModelForm): @@ -153,7 +158,6 @@ class ProductEditForm(forms.ModelForm): "description", "product_type", "code", - "parent_product", "buying_groups", "purchase_price", "selling_price", @@ -165,7 +169,6 @@ class ProductEditForm(forms.ModelForm): "archived", ] widgets = { - "parent_product": AutoCompleteSelectMultipleProduct, "product_type": AutoCompleteSelect, "buying_groups": AutoCompleteSelectMultipleGroup, "club": AutoCompleteSelectClub, diff --git a/counter/management/commands/dump_accounts.py b/counter/management/commands/dump_accounts.py index 01ba35cd..9897f2c8 100644 --- a/counter/management/commands/dump_accounts.py +++ b/counter/management/commands/dump_accounts.py @@ -55,7 +55,9 @@ class Command(BaseCommand): customer__user__in=reactivated_users ).delete() self._dump_accounts({u.customer for u in users_to_dump}) - self._send_mails(users_to_dump) + self.stdout.write("Accounts dumped") + nb_successful_mails = self._send_mails(users_to_dump) + self.stdout.write(f"{nb_successful_mails} were successfuly sent.") self.stdout.write("Finished !") @staticmethod @@ -103,13 +105,14 @@ class Command(BaseCommand): if len(pending_dumps) != len(customer_ids): raise ValueError("One or more accounts were not engaged in a dump process") counter = Counter.objects.get(pk=settings.SITH_COUNTER_ACCOUNT_DUMP_ID) + seller = User.objects.get(pk=settings.SITH_ROOT_USER_ID) sales = Selling.objects.bulk_create( [ Selling( label="Vidange compte inactif", club=counter.club, counter=counter, - seller=None, + seller=seller, product=None, customer=account, quantity=1, @@ -134,8 +137,12 @@ class Command(BaseCommand): Customer.objects.filter(pk__in=customer_ids).update(amount=0) @staticmethod - def _send_mails(users: Iterable[User]): - """Send the mails informing users that their account has been dumped.""" + def _send_mails(users: Iterable[User]) -> int: + """Send the mails informing users that their account has been dumped. + + Returns: + The number of emails successfully sent. + """ mails = [ ( _("Your AE account has been emptied"), @@ -145,4 +152,4 @@ class Command(BaseCommand): ) for user in users ] - send_mass_mail(mails) + return send_mass_mail(mails, fail_silently=True) diff --git a/counter/migrations/0025_remove_product_parent_product_and_more.py b/counter/migrations/0025_remove_product_parent_product_and_more.py new file mode 100644 index 00000000..64a2129c --- /dev/null +++ b/counter/migrations/0025_remove_product_parent_product_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.17 on 2024-12-09 11:07 + +from django.db import migrations, models + +import accounting.models + + +class Migration(migrations.Migration): + dependencies = [("counter", "0024_accountdump_accountdump_unique_ongoing_dump")] + + operations = [ + migrations.RemoveField(model_name="product", name="parent_product"), + migrations.AlterField( + model_name="product", + name="description", + field=models.TextField(default="", verbose_name="description"), + ), + migrations.AlterField( + model_name="product", + name="purchase_price", + field=accounting.models.CurrencyField( + decimal_places=2, + help_text="Initial cost of purchasing the product", + max_digits=12, + verbose_name="purchase price", + ), + ), + migrations.AlterField( + model_name="product", + name="special_selling_price", + field=accounting.models.CurrencyField( + decimal_places=2, + help_text="Price for barmen during their permanence", + max_digits=12, + verbose_name="special selling price", + ), + ), + ] diff --git a/counter/migrations/0026_alter_studentcard_customer.py b/counter/migrations/0026_alter_studentcard_customer.py new file mode 100644 index 00000000..f1f5cd49 --- /dev/null +++ b/counter/migrations/0026_alter_studentcard_customer.py @@ -0,0 +1,53 @@ +# Generated by Django 4.2.17 on 2024-12-08 13:30 +from operator import attrgetter + +import django.db.models.deletion +from django.db import migrations, models +from django.db.migrations.state import StateApps +from django.db.models import Count + + +def delete_duplicates(apps: StateApps, schema_editor): + """Delete cards of users with more than one student cards. + + For all users who have more than one registered student card, all + the cards except the last one are deleted. + """ + Customer = apps.get_model("counter", "Customer") + StudentCard = apps.get_model("counter", "StudentCard") + customers = ( + Customer.objects.annotate(nb_cards=Count("student_cards")) + .filter(nb_cards__gt=1) + .prefetch_related("student_cards") + ) + to_delete = [ + card.id + for customer in customers + for card in sorted(customer.student_cards.all(), key=attrgetter("id"))[:-1] + ] + StudentCard.objects.filter(id__in=to_delete).delete() + + +class Migration(migrations.Migration): + dependencies = [("counter", "0025_remove_product_parent_product_and_more")] + + operations = [ + migrations.RunPython(delete_duplicates, migrations.RunPython.noop), + migrations.AlterField( + model_name="studentcard", + name="customer", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="student_card", + to="counter.customer", + verbose_name="student card", + ), + ), + migrations.AlterModelOptions( + name="studentcard", + options={ + "verbose_name": "student card", + "verbose_name_plural": "student cards", + }, + ), + ] diff --git a/counter/migrations/0027_alter_refilling_payment_method.py b/counter/migrations/0027_alter_refilling_payment_method.py new file mode 100644 index 00000000..af9dd54a --- /dev/null +++ b/counter/migrations/0027_alter_refilling_payment_method.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.17 on 2024-12-15 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("counter", "0026_alter_studentcard_customer"), + ] + + operations = [ + migrations.AlterField( + model_name="refilling", + name="payment_method", + field=models.CharField( + choices=[("CHECK", "Check"), ("CASH", "Cash"), ("CARD", "Credit card")], + default="CARD", + max_length=255, + verbose_name="payment method", + ), + ), + ] diff --git a/counter/models.py b/counter/models.py index 9c83b6c1..087baffc 100644 --- a/counter/models.py +++ b/counter/models.py @@ -42,7 +42,8 @@ from club.models import Club from core.fields import ResizedImageField from core.models import Group, Notification, User from core.utils import get_start_of_semester -from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB +from counter.apps import PAYMENT_METHOD +from sith.settings import SITH_MAIN_CLUB from subscription.models import Subscription @@ -326,7 +327,7 @@ class Product(models.Model): """A product, with all its related information.""" name = models.CharField(_("name"), max_length=64) - description = models.TextField(_("description"), blank=True) + description = models.TextField(_("description"), default="") product_type = models.ForeignKey( ProductType, related_name="products", @@ -336,9 +337,15 @@ class Product(models.Model): on_delete=models.SET_NULL, ) code = models.CharField(_("code"), max_length=16, blank=True) - purchase_price = CurrencyField(_("purchase price")) + purchase_price = CurrencyField( + _("purchase price"), + help_text=_("Initial cost of purchasing the product"), + ) selling_price = CurrencyField(_("selling price")) - special_selling_price = CurrencyField(_("special selling price")) + special_selling_price = CurrencyField( + _("special selling price"), + help_text=_("Price for barmen during their permanence"), + ) icon = ResizedImageField( height=70, force_format="WEBP", @@ -352,14 +359,6 @@ class Product(models.Model): ) limit_age = models.IntegerField(_("limit age"), default=0) tray = models.BooleanField(_("tray price"), default=False) - parent_product = models.ForeignKey( - "self", - related_name="children_products", - verbose_name=_("parent product"), - null=True, - blank=True, - on_delete=models.SET_NULL, - ) buying_groups = models.ManyToManyField( Group, related_name="products", verbose_name=_("buying groups"), blank=True ) @@ -369,7 +368,7 @@ class Product(models.Model): verbose_name = _("product") def __str__(self): - return "%s (%s)" % (self.name, self.code) + return f"{self.name} ({self.code})" def get_absolute_url(self): return reverse("counter:product_list") @@ -560,9 +559,6 @@ class Counter(models.Model): """Show if the counter authorize the refilling with physic money.""" if self.type != "BAR": return False - if self.id in SITH_COUNTER_OFFICES: - # If the counter is either 'AE' or 'BdF', refills are authorized - return True # at least one of the barmen is in the AE board ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"]) return any(ae.get_membership_for(barman) for barman in self.barmen_list) @@ -652,6 +648,14 @@ class Counter(models.Model): ) )["total"] + def customer_is_barman(self, customer: Customer | User) -> bool: + """Check if this counter is a `bar` and if the customer is currently logged in. + This is useful to compute special prices.""" + + # Customer and User are two different tables, + # but they share the same primary key + return self.type == "BAR" and any(b.pk == customer.pk for b in self.barmen_list) + class RefillingQuerySet(models.QuerySet): def annotate_total(self) -> Self: @@ -690,8 +694,8 @@ class Refilling(models.Model): payment_method = models.CharField( _("payment method"), max_length=255, - choices=settings.SITH_COUNTER_PAYMENT_METHOD, - default="CASH", + choices=PAYMENT_METHOD, + default="CARD", ) bank = models.CharField( _("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER" @@ -1140,20 +1144,22 @@ class StudentCard(models.Model): uid = models.CharField( _("uid"), max_length=UID_SIZE, unique=True, validators=[MinLengthValidator(4)] ) - customer = models.ForeignKey( + customer = models.OneToOneField( Customer, - related_name="student_cards", - verbose_name=_("student cards"), - null=False, - blank=False, + related_name="student_card", + verbose_name=_("student card"), on_delete=models.CASCADE, ) + class Meta: + verbose_name = _("student card") + verbose_name_plural = _("student cards") + def __str__(self): return self.uid @staticmethod - def is_valid(uid): + def is_valid(uid: str) -> bool: return ( (uid.isupper() or uid.isnumeric()) and len(uid) == StudentCard.UID_SIZE diff --git a/counter/static/bundled/counter/counter-click-index.ts b/counter/static/bundled/counter/counter-click-index.ts new file mode 100644 index 00000000..b8de64be --- /dev/null +++ b/counter/static/bundled/counter/counter-click-index.ts @@ -0,0 +1,129 @@ +import { exportToHtml } from "#core:utils/globals"; + +interface CounterConfig { + csrfToken: string; + clickApiUrl: string; + sessionBasket: Record; + customerBalance: number; + customerId: number; +} +interface BasketItem { + // biome-ignore lint/style/useNamingConvention: talking with python + bonus_qty: number; + price: number; + qty: number; +} + +exportToHtml("loadCounter", (config: CounterConfig) => { + document.addEventListener("alpine:init", () => { + Alpine.data("counter", () => ({ + basket: config.sessionBasket, + errors: [], + customerBalance: config.customerBalance, + + sumBasket() { + if (!this.basket || Object.keys(this.basket).length === 0) { + return 0; + } + const total = Object.values(this.basket).reduce( + (acc: number, cur: BasketItem) => acc + cur.qty * cur.price, + 0, + ) as number; + return total / 100; + }, + + onRefillingSuccess(event: CustomEvent) { + if (event.type !== "htmx:after-request" || event.detail.failed) { + return; + } + this.customerBalance += Number.parseFloat( + (event.detail.target.querySelector("#id_amount") as HTMLInputElement).value, + ); + document.getElementById("selling-accordion").click(); + }, + + async handleCode(event: SubmitEvent) { + const code = ( + $(event.target).find("#code_field").val() as string + ).toUpperCase(); + if (["FIN", "ANN"].includes(code)) { + $(event.target).submit(); + } else { + await this.handleAction(event); + } + }, + + async handleAction(event: SubmitEvent) { + const payload = $(event.target).serialize(); + const request = new Request(config.clickApiUrl, { + method: "POST", + body: payload, + headers: { + // biome-ignore lint/style/useNamingConvention: this goes into http headers + Accept: "application/json", + "X-CSRFToken": config.csrfToken, + }, + }); + const response = await fetch(request); + const json = await response.json(); + this.basket = json.basket; + this.errors = json.errors; + $("form.code_form #code_field").val("").focus(); + }, + })); + }); +}); + +interface Product { + value: string; + label: string; + tags: string; +} +declare global { + const productsAutocomplete: Product[]; +} + +$(() => { + /* Autocompletion in the code field */ + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + const codeField: any = $("#code_field"); + + let quantity = ""; + codeField.autocomplete({ + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + select: (event: any, ui: any) => { + event.preventDefault(); + codeField.val(quantity + ui.item.value); + }, + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + focus: (event: any, ui: any) => { + event.preventDefault(); + codeField.val(quantity + ui.item.value); + }, + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + source: (request: any, response: any) => { + // biome-ignore lint/performance/useTopLevelRegex: performance impact is minimal + const res = /^(\d+x)?(.*)/i.exec(request.term); + quantity = res[1] || ""; + const search = res[2]; + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + const matcher = new RegExp(($ as any).ui.autocomplete.escapeRegex(search), "i"); + response( + $.grep(productsAutocomplete, (value: Product) => { + return matcher.test(value.tags); + }), + ); + }, + }); + + /* Accordion UI between basket and refills */ + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + ($("#click_form") as any).accordion({ + heightStyle: "content", + activate: () => $(".focus").focus(), + }); + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + ($("#products") as any).tabs(); + + codeField.focus(); +}); diff --git a/counter/static/counter/js/counter_click.js b/counter/static/counter/js/counter_click.js deleted file mode 100644 index b0ddb42c..00000000 --- a/counter/static/counter/js/counter_click.js +++ /dev/null @@ -1,86 +0,0 @@ -document.addEventListener("alpine:init", () => { - Alpine.data("counter", () => ({ - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - basket: sessionBasket, - errors: [], - - sumBasket() { - if (!this.basket || Object.keys(this.basket).length === 0) { - return 0; - } - const total = Object.values(this.basket).reduce( - (acc, cur) => acc + cur.qty * cur.price, - 0, - ); - return total / 100; - }, - - async handleCode(event) { - const code = $(event.target).find("#code_field").val().toUpperCase(); - if (["FIN", "ANN"].includes(code)) { - $(event.target).submit(); - } else { - await this.handleAction(event); - } - }, - - async handleAction(event) { - const payload = $(event.target).serialize(); - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - const request = new Request(clickApiUrl, { - method: "POST", - body: payload, - headers: { - // biome-ignore lint/style/useNamingConvention: this goes into http headers - Accept: "application/json", - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - "X-CSRFToken": csrfToken, - }, - }); - const response = await fetch(request); - const json = await response.json(); - this.basket = json.basket; - this.errors = json.errors; - $("form.code_form #code_field").val("").focus(); - }, - })); -}); - -$(() => { - /* Autocompletion in the code field */ - const codeField = $("#code_field"); - - let quantity = ""; - codeField.autocomplete({ - select: (event, ui) => { - event.preventDefault(); - codeField.val(quantity + ui.item.value); - }, - focus: (event, ui) => { - event.preventDefault(); - codeField.val(quantity + ui.item.value); - }, - source: (request, response) => { - // biome-ignore lint/performance/useTopLevelRegex: performance impact is minimal - const res = /^(\d+x)?(.*)/i.exec(request.term); - quantity = res[1] || ""; - const search = res[2]; - const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i"); - response( - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - $.grep(productsAutocomplete, (value) => { - return matcher.test(value.tags); - }), - ); - }, - }); - - /* Accordion UI between basket and refills */ - $("#click_form").accordion({ - heightStyle: "content", - activate: () => $(".focus").focus(), - }); - $("#products").tabs(); - - codeField.focus(); -}); diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index cb6bb9cf..6de57147 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -6,7 +6,7 @@ {% endblock %} {% block additional_js %} - + {% endblock %} {% block info_boxes %} @@ -28,32 +28,11 @@
{% trans %}Customer{% endtrans %}
{{ user_mini_profile(customer.user) }} {{ user_subscription(customer.user) }} -

{% trans %}Amount: {% endtrans %}{{ customer.amount }} €

-
- {% csrf_token %} - - {% trans %}Add a student card{% endtrans %} - {{ student_card_input.student_card_uid }} - {% if request.session['not_valid_student_card_uid'] %} -

{% trans %}This is not a valid student card UID{% endtrans %}

- {% endif %} - -
-
{% trans %}Registered cards{% endtrans %}
- {% if student_cards %} - - - {% else %} - {% trans %}No card registered{% endtrans %} - {% endif %} +

{% trans %}Amount: {% endtrans %}

-
-
{% trans %}Selling{% endtrans %}
+
+
{% trans %}Selling{% endtrans %}
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %} @@ -121,17 +100,32 @@
- {% if (counter.type == 'BAR' and barmens_can_refill) %} + {% if object.type == "BAR" %}
{% trans %}Refilling{% endtrans %}
-
-
- {% csrf_token %} - {{ refill_form.as_p() }} - - -
-
+ {% if refilling_fragment %} +
+ {{ refilling_fragment }} +
+ {% else %} +
+

+ {% trans trimmed %} + As a barman, you are not able to refill any account on your own. + An admin should be connected on this counter for that. + The customer can refill by using the eboutic. + {% endtrans %} +

+
+ {% endif %} + {% if student_card_fragment %} +
{% trans %}Student card{% endtrans %}
+
+ {{ student_card_fragment }} +
+ {% endif %} + {% endif %}
@@ -171,9 +165,6 @@ {% block script %} {{ super() }} {% endblock script %} \ No newline at end of file diff --git a/counter/templates/counter/fragments/create_refill.jinja b/counter/templates/counter/fragments/create_refill.jinja new file mode 100644 index 00000000..7de612a3 --- /dev/null +++ b/counter/templates/counter/fragments/create_refill.jinja @@ -0,0 +1,9 @@ +
+ {% csrf_token %} + {{ form.as_p() }} + +
diff --git a/counter/templates/counter/fragments/create_student_card.jinja b/counter/templates/counter/fragments/create_student_card.jinja new file mode 100644 index 00000000..f15716e4 --- /dev/null +++ b/counter/templates/counter/fragments/create_student_card.jinja @@ -0,0 +1,29 @@ +
+ {% if not customer.student_card %} +
+ {% csrf_token %} + {{ form.as_p() }} + +
+ {% trans %}No student card registered.{% endtrans %} + {% else %} +

+ + {% trans %}Card registered{% endtrans %} + + +   -   + +

+ {% endif %} +
diff --git a/counter/templates/counter/fragments/delete_student_card.jinja b/counter/templates/counter/fragments/delete_student_card.jinja new file mode 100644 index 00000000..94be1c47 --- /dev/null +++ b/counter/templates/counter/fragments/delete_student_card.jinja @@ -0,0 +1,15 @@ +
+
+ {% csrf_token %} +

{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}

+ + +
+
\ No newline at end of file diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index 855b43f6..154578c6 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -67,17 +67,22 @@ class TestCounter(TestCase): {"code": self.richard.customer.account_id, "counter_token": counter_token}, ) counter_url = response.get("location") - response = self.client.get(response.get("location")) + refill_url = reverse( + "counter:refilling_create", + kwargs={"customer_id": self.richard.customer.pk}, + ) + + response = self.client.get(counter_url) assert ">Richard Batsbak list[tuple[str, str]]: + """Return a list of invalid uids, with the associated error message""" + return [ + ("8B90734A802A8", ""), # too short + ( + "8B90734A802A8FA", + "Assurez-vous que cette valeur comporte au plus 14 caractères (actuellement 15).", + ), # too long + ("8b90734a802a9f", ""), # has lowercases + (" " * 14, "Ce champ est obligatoire."), # empty + ( + self.customer.customer.student_card.uid, + "Un objet Carte étudiante avec ce champ Uid existe déjà.", + ), + ] + def test_search_user_with_student_card(self): + self.login_in_counter() response = self.client.post( reverse("counter:details", args=[self.counter.id]), - {"code": "9A89B82018B0A0"}, + {"code": self.valid_card.uid}, ) assert response.url == reverse( "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, + kwargs={"counter_id": self.counter.id, "user_id": self.customer.pk}, ) def test_add_student_card_from_counter(self): - # Test card with mixed letters and numbers - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8B90734A802A8F", "action": "add_student_card"}, - ) - self.assertContains(response, text="8B90734A802A8F") - - # Test card with only numbers - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "04786547890123", "action": "add_student_card"}, - ) - self.assertContains(response, text="04786547890123") - - # Test card with only letters - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "ABCAAAFAAFAAAB", "action": "add_student_card"}, - ) - self.assertContains(response, text="ABCAAAFAAFAAAB") + self.login_in_counter() + for uid in ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"]: + customer = subscriber_user.make().customer + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": customer.pk} + ), + {"uid": uid}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": customer.pk}, + ), + ) + assert response.status_code == 302 + customer.refresh_from_db() + assert hasattr(customer, "student_card") + assert customer.student_card.uid == uid def test_add_student_card_from_counter_fail(self): - # UID too short - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8B90734A802A8", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) + self.login_in_counter() + customer = subscriber_user.make().customer + for uid, error_msg in self.invalid_uids(): + response = self.client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": customer.pk} + ), + {"uid": uid}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": customer.pk}, + ), + ) + self.assertContains(response, text="Cet UID est invalide") + self.assertContains(response, text=error_msg) + customer.refresh_from_db() + assert not hasattr(customer, "student_card") - # UID too long - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8B90734A802A8FA", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) + def test_add_student_card_from_counter_unauthorized(self): + def send_valid_request(client, counter_id): + return client.post( + reverse( + "counter:add_student_card", kwargs={"customer_id": self.customer.pk} + ), + {"uid": "8B90734A802A8F"}, + HTTP_REFERER=reverse( + "counter:click", + kwargs={"counter_id": counter_id, "user_id": self.customer.pk}, + ), + ) - # Test with already existing card - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "9A89B82018B0A0", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) + # Send to a counter where you aren't logged in + assert send_valid_request(self.client, self.counter.id).status_code == 403 - # Test with lowercase - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": "8b90734a802a9f", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) + self.login_in_counter() + barman = subscriber_user.make() + self.counter.sellers.add(barman) + # We want to test sending requests from another counter while + # we are currently registered to another counter + # so we connect to a counter and + # we create a new client, in order to check + # that using a client not logged to a counter + # where another client is logged still isn't authorized. + client = Client() + # Send to a counter where you aren't logged in + assert send_valid_request(client, self.counter.id).status_code == 403 - # Test with white spaces - response = self.client.post( - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), - {"student_card_uid": " ", "action": "add_student_card"}, - ) - self.assertContains( - response, text="Ce n'est pas un UID de carte étudiante valide" - ) + # Send to a non bar counter + client.force_login(self.club_admin) + assert send_valid_request(client, self.club_counter.id).status_code == 403 def test_delete_student_card_with_owner(self): - self.client.force_login(self.sli) + self.client.force_login(self.customer) self.client.post( reverse( "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, + kwargs={"customer_id": self.customer.customer.pk}, ) ) - assert not self.sli.customer.student_cards.exists() + self.customer.customer.refresh_from_db() + assert not hasattr(self.customer.customer, "student_card") - def test_delete_student_card_with_board_member(self): - self.client.force_login(self.skia) - self.client.post( - reverse( - "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, + def test_delete_student_card_with_admin_user(self): + """Test that AE board members and root users can delete student cards""" + for user in self.board_admin, self.root: + self.client.force_login(user) + self.client.post( + reverse( + "counter:delete_student_card", + kwargs={"customer_id": self.customer.customer.pk}, + ) ) - ) - assert not self.sli.customer.student_cards.exists() + self.customer.customer.refresh_from_db() + assert not hasattr(self.customer.customer, "student_card") - def test_delete_student_card_with_root(self): - self.client.force_login(self.root) + def test_delete_student_card_from_counter(self): + self.login_in_counter() self.client.post( reverse( "counter:delete_student_card", + kwargs={"customer_id": self.customer.customer.pk}, + ), + http_referer=reverse( + "counter:click", kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, + "counter_id": self.counter.id, + "user_id": self.customer.customer.pk, }, - ) + ), ) - assert not self.sli.customer.student_cards.exists() + self.customer.customer.refresh_from_db() + assert not hasattr(self.customer.customer, "student_card") def test_delete_student_card_fail(self): - self.client.force_login(self.krophil) + """Test that non-admin users cannot delete student cards""" + self.client.force_login(self.subscriber) response = self.client.post( reverse( "counter:delete_student_card", - kwargs={ - "customer_id": self.sli.customer.pk, - "card_id": self.sli.customer.student_cards.first().id, - }, + kwargs={"customer_id": self.customer.customer.pk}, ) ) assert response.status_code == 403 - assert self.sli.customer.student_cards.exists() + self.subscriber.customer.refresh_from_db() + assert not hasattr(self.subscriber.customer, "student_card") def test_add_student_card_from_user_preferences(self): - # Test with owner of the card - self.client.force_login(self.sli) - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8F"}, - ) + users = [self.customer, self.board_admin, self.root] + uids = ["8B90734A802A8F", "ABCAAAFAAFAAAB", "15248196326518"] + for user, uid in itertools.product(users, uids): + self.customer.customer.student_card.delete() + self.client.force_login(user) + response = self.client.post( + reverse( + "counter:add_student_card", + kwargs={"customer_id": self.customer.customer.pk}, + ), + {"uid": uid}, + ) + assert response.status_code == 302 + response = self.client.get(response.url) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="8B90734A802A8F") - - # Test with board member - self.client.force_login(self.skia) - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8A"}, - ) - - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="8B90734A802A8A") - - # Test card with only numbers - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "04786547890123"}, - ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="04786547890123") - - # Test card with only letters - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "ABCAAAFAAFAAAB"}, - ) - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="ABCAAAFAAFAAAB") - - # Test with root - self.client.force_login(self.root) - self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8B"}, - ) - - response = self.client.get( - reverse("core:user_prefs", kwargs={"user_id": self.sli.id}) - ) - self.assertContains(response, text="8B90734A802A8B") + self.customer.customer.refresh_from_db() + assert self.customer.customer.student_card.uid == uid + self.assertContains(response, text="Carte enregistrée") def test_add_student_card_from_user_preferences_fail(self): - self.client.force_login(self.sli) - # UID too short - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8"}, - ) - - self.assertContains(response, text="Cet UID est invalide") - - # UID too long - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8FA"}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with already existing card - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "9A89B82018B0A0"}, - ) - self.assertContains( - response, text="Un objet Student card avec ce champ Uid existe déjà." - ) - - # Test with lowercase - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8b90734a802a9f"}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with white spaces - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": " " * 14}, - ) - self.assertContains(response, text="Cet UID est invalide") - - # Test with unauthorized user - self.client.force_login(self.krophil) - response = self.client.post( - reverse( - "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} - ), - {"uid": "8B90734A802A8F"}, - ) - assert response.status_code == 403 + customer = subscriber_user.make() + self.client.force_login(customer) + for uid, error_msg in self.invalid_uids(): + url = reverse( + "counter:add_student_card", kwargs={"customer_id": customer.customer.pk} + ) + response = self.client.post(url, {"uid": uid}) + self.assertContains(response, text="Cet UID est invalide") + self.assertContains(response, text=error_msg) + customer.refresh_from_db() + assert not hasattr(customer.customer, "student_card") class TestCustomerAccountId(TestCase): diff --git a/counter/urls.py b/counter/urls.py index a2732925..0ca77f73 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -15,28 +15,16 @@ from django.urls import path -from counter.views import ( +from counter.views.admin import ( ActiveProductListView, ArchivedProductListView, - CashSummaryEditView, - CashSummaryListView, - CounterActivityView, - CounterCashSummaryView, - CounterClick, CounterCreateView, CounterDeleteView, CounterEditPropView, CounterEditView, - CounterLastOperationsView, CounterListView, - CounterMain, CounterRefillingListView, CounterStatView, - EticketCreateView, - EticketEditView, - EticketListView, - EticketPDFView, - InvoiceCallView, ProductCreateView, ProductEditView, ProductTypeCreateView, @@ -44,15 +32,39 @@ from counter.views import ( ProductTypeListView, RefillingDeleteView, SellingDeleteView, +) +from counter.views.auth import counter_login, counter_logout +from counter.views.cash import ( + CashSummaryEditView, + CashSummaryListView, + CounterCashSummaryView, +) +from counter.views.click import CounterClick, RefillingCreateView +from counter.views.eticket import ( + EticketCreateView, + EticketEditView, + EticketListView, + EticketPDFView, +) +from counter.views.home import ( + CounterActivityView, + CounterLastOperationsView, + CounterMain, +) +from counter.views.invoice import InvoiceCallView +from counter.views.student_card import ( StudentCardDeleteView, StudentCardFormView, - counter_login, - counter_logout, ) urlpatterns = [ path("/", CounterMain.as_view(), name="details"), path("/click//", CounterClick.as_view(), name="click"), + path( + "refill//", + RefillingCreateView.as_view(), + name="refilling_create", + ), path( "/last_ops/", CounterLastOperationsView.as_view(), @@ -74,7 +86,7 @@ urlpatterns = [ name="add_student_card", ), path( - "customer//card/delete//", + "customer//card/delete/", StudentCardDeleteView.as_view(), name="delete_student_card", ), diff --git a/counter/utils.py b/counter/utils.py index 2b9b6fd6..499b2d8e 100644 --- a/counter/utils.py +++ b/counter/utils.py @@ -22,14 +22,22 @@ def is_logged_in_counter(request: HttpRequest) -> bool: to the counter) - The current session has a counter token associated with it. - A counter with this token exists. + - The counter is open """ referer_ok = ( "HTTP_REFERER" in request.META and resolve(urlparse(request.META["HTTP_REFERER"]).path).app_name == "counter" ) - return ( + has_token = ( (referer_ok or request.resolver_match.app_name == "counter") and "counter_token" in request.session and request.session["counter_token"] - and Counter.objects.filter(token=request.session["counter_token"]).exists() + ) + if not has_token: + return False + + return ( + Counter.objects.annotate_is_open() + .filter(token=request.session["counter_token"], is_open=True) + .exists() ) diff --git a/counter/views.py b/counter/views.py deleted file mode 100644 index 9483d335..00000000 --- a/counter/views.py +++ /dev/null @@ -1,1537 +0,0 @@ -# -# Copyright 2023 © AE UTBM -# ae@utbm.fr / ae.info@utbm.fr -# -# This file is part of the website of the UTBM Student Association (AE UTBM), -# https://ae.utbm.fr. -# -# You can find the source code of the website at https://github.com/ae-utbm/sith -# -# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) -# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE -# OR WITHIN THE LOCAL FILE "LICENSE" -# -# -import itertools -import re -from datetime import datetime, timedelta -from datetime import timezone as tz -from http import HTTPStatus -from operator import itemgetter -from typing import TYPE_CHECKING -from urllib.parse import parse_qs - -from django import forms -from django.conf import settings -from django.core.exceptions import PermissionDenied -from django.db import DataError, transaction -from django.db.models import F -from django.forms import CheckboxSelectMultiple -from django.forms.models import modelform_factory -from django.http import ( - Http404, - HttpRequest, - HttpResponse, - HttpResponseRedirect, - JsonResponse, -) -from django.shortcuts import get_object_or_404, redirect -from django.urls import reverse, reverse_lazy -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ -from django.views.decorators.http import require_POST -from django.views.generic import DetailView, ListView, TemplateView -from django.views.generic.base import View -from django.views.generic.edit import ( - CreateView, - DeleteView, - FormMixin, - FormView, - ProcessFormView, - UpdateView, -) - -from accounting.models import CurrencyField -from core.utils import get_semester_code, get_start_of_semester -from core.views import CanEditMixin, CanViewMixin, TabedViewMixin -from core.views.forms import LoginForm -from counter.forms import ( - CashSummaryFormBase, - CounterEditForm, - EticketForm, - GetUserForm, - NFCCardForm, - ProductEditForm, - RefillForm, - StudentCardForm, -) -from counter.models import ( - CashRegisterSummary, - CashRegisterSummaryItem, - Counter, - Customer, - Eticket, - Permanency, - Product, - ProductType, - Refilling, - Selling, - StudentCard, -) -from counter.utils import is_logged_in_counter - -if TYPE_CHECKING: - from core.models import User - - -class CounterAdminMixin(View): - """Protect counter admin section.""" - - edit_group = [settings.SITH_GROUP_COUNTER_ADMIN_ID] - edit_club = [] - - def _test_group(self, user): - return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group) - - def _test_club(self, user): - return any(c.can_be_edited_by(user) for c in self.edit_club) - - def dispatch(self, request, *args, **kwargs): - if not ( - request.user.is_root - or self._test_group(request.user) - or self._test_club(request.user) - ): - raise PermissionDenied - return super().dispatch(request, *args, **kwargs) - - -class StudentCardDeleteView(DeleteView, CanEditMixin): - """View used to delete a card from a user.""" - - model = StudentCard - template_name = "core/delete_confirm.jinja" - pk_url_kwarg = "card_id" - - def dispatch(self, request, *args, **kwargs): - self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) - return super().dispatch(request, *args, **kwargs) - - def get_success_url(self, **kwargs): - return reverse_lazy( - "core:user_prefs", kwargs={"user_id": self.customer.user.pk} - ) - - -class CounterTabsMixin(TabedViewMixin): - def get_tabs_title(self): - return self.object - - def get_list_of_tabs(self): - tab_list = [ - { - "url": reverse_lazy( - "counter:details", kwargs={"counter_id": self.object.id} - ), - "slug": "counter", - "name": _("Counter"), - } - ] - if self.object.type == "BAR": - tab_list.append( - { - "url": reverse_lazy( - "counter:cash_summary", kwargs={"counter_id": self.object.id} - ), - "slug": "cash_summary", - "name": _("Cash summary"), - } - ) - tab_list.append( - { - "url": reverse_lazy( - "counter:last_ops", kwargs={"counter_id": self.object.id} - ), - "slug": "last_ops", - "name": _("Last operations"), - } - ) - return tab_list - - -class CounterMain( - CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin -): - """The public (barman) view.""" - - model = Counter - template_name = "counter/counter_main.jinja" - pk_url_kwarg = "counter_id" - form_class = ( - GetUserForm # Form to enter a client code and get the corresponding user id - ) - current_tab = "counter" - - def post(self, request, *args, **kwargs): - self.object = self.get_object() - if self.object.type == "BAR" and not ( - "counter_token" in self.request.session - and self.request.session["counter_token"] == self.object.token - ): # Check the token to avoid the bar to be stolen - return HttpResponseRedirect( - reverse_lazy( - "counter:details", - args=self.args, - kwargs={"counter_id": self.object.id}, - ) - + "?bad_location" - ) - return super().post(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - """We handle here the login form for the barman.""" - if self.request.method == "POST": - self.object = self.get_object() - self.object.update_activity() - kwargs = super().get_context_data(**kwargs) - kwargs["login_form"] = LoginForm() - kwargs["login_form"].fields["username"].widget.attrs["autofocus"] = True - kwargs[ - "login_form" - ].cleaned_data = {} # add_error fails if there are no cleaned_data - if "credentials" in self.request.GET: - kwargs["login_form"].add_error(None, _("Bad credentials")) - if "sellers" in self.request.GET: - kwargs["login_form"].add_error(None, _("User is not barman")) - kwargs["form"] = self.get_form() - kwargs["form"].cleaned_data = {} # same as above - if "bad_location" in self.request.GET: - kwargs["form"].add_error( - None, _("Bad location, someone is already logged in somewhere else") - ) - if self.object.type == "BAR": - kwargs["barmen"] = self.object.barmen_list - elif self.request.user.is_authenticated: - kwargs["barmen"] = [self.request.user] - if "last_basket" in self.request.session: - kwargs["last_basket"] = self.request.session.pop("last_basket") - kwargs["last_customer"] = self.request.session.pop("last_customer") - kwargs["last_total"] = self.request.session.pop("last_total") - kwargs["new_customer_amount"] = self.request.session.pop( - "new_customer_amount" - ) - return kwargs - - def form_valid(self, form): - """We handle here the redirection, passing the user id of the asked customer.""" - self.kwargs["user_id"] = form.cleaned_data["user_id"] - return super().form_valid(form) - - def get_success_url(self): - return reverse_lazy("counter:click", args=self.args, kwargs=self.kwargs) - - -class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): - """The click view - This is a detail view not to have to worry about loading the counter - Everything is made by hand in the post method. - """ - - model = Counter - queryset = Counter.objects.annotate_is_open() - template_name = "counter/counter_click.jinja" - pk_url_kwarg = "counter_id" - current_tab = "counter" - - def render_to_response(self, *args, **kwargs): - if self.is_ajax(self.request): - response = {"errors": []} - status = HTTPStatus.OK - - if self.request.session["too_young"]: - response["errors"].append(_("Too young for that product")) - status = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS - if self.request.session["not_allowed"]: - response["errors"].append(_("Not allowed for that product")) - status = HTTPStatus.FORBIDDEN - if self.request.session["no_age"]: - response["errors"].append(_("No date of birth provided")) - status = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS - if self.request.session["not_enough"]: - response["errors"].append(_("Not enough money")) - status = HTTPStatus.PAYMENT_REQUIRED - - if len(response["errors"]) > 1: - status = HTTPStatus.BAD_REQUEST - - response["basket"] = self.request.session["basket"] - - return JsonResponse(response, status=status) - - else: # Standard HTML page - return super().render_to_response(*args, **kwargs) - - def dispatch(self, request, *args, **kwargs): - self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"]) - obj: Counter = self.get_object() - if not self.customer.can_buy: - raise Http404 - if obj.type != "BAR" and not request.user.is_authenticated: - raise PermissionDenied - if obj.type == "BAR" and ( - "counter_token" not in request.session - or request.session["counter_token"] != obj.token - or len(obj.barmen_list) == 0 - ): - return redirect(obj) - return super().dispatch(request, *args, **kwargs) - - def get(self, request, *args, **kwargs): - """Simple get view.""" - if "basket" not in request.session: # Init the basket session entry - request.session["basket"] = {} - request.session["basket_total"] = 0 - request.session["not_enough"] = False # Reset every variable - request.session["too_young"] = False - request.session["not_allowed"] = False - request.session["no_age"] = False - self.refill_form = None - ret = super().get(request, *args, **kwargs) - if (self.object.type != "BAR" and not request.user.is_authenticated) or ( - self.object.type == "BAR" and len(self.object.barmen_list) == 0 - ): # Check that at least one barman is logged in - ret = self.cancel(request) # Otherwise, go to main view - return ret - - def post(self, request, *args, **kwargs): - """Handle the many possibilities of the post request.""" - self.object = self.get_object() - self.refill_form = None - if (self.object.type != "BAR" and not request.user.is_authenticated) or ( - self.object.type == "BAR" and len(self.object.barmen_list) < 1 - ): # Check that at least one barman is logged in - return self.cancel(request) - if self.object.type == "BAR" and not ( - "counter_token" in self.request.session - and self.request.session["counter_token"] == self.object.token - ): # Also check the token to avoid the bar to be stolen - return HttpResponseRedirect( - reverse_lazy( - "counter:details", - args=self.args, - kwargs={"counter_id": self.object.id}, - ) - + "?bad_location" - ) - if "basket" not in request.session: - request.session["basket"] = {} - request.session["basket_total"] = 0 - request.session["not_enough"] = False # Reset every variable - request.session["too_young"] = False - request.session["not_allowed"] = False - request.session["no_age"] = False - request.session["not_valid_student_card_uid"] = False - if self.object.type != "BAR": - self.operator = request.user - elif self.customer_is_barman(): - self.operator = self.customer.user - else: - self.operator = self.object.get_random_barman() - action = self.request.POST.get("action", None) - if action is None: - action = parse_qs(request.body.decode()).get("action", [""])[0] - if action == "add_product": - self.add_product(request) - elif action == "add_student_card": - self.add_student_card(request) - elif action == "del_product": - self.del_product(request) - elif action == "refill": - self.refill(request) - elif action == "code": - return self.parse_code(request) - elif action == "cancel": - return self.cancel(request) - elif action == "finish": - return self.finish(request) - context = self.get_context_data(object=self.object) - return self.render_to_response(context) - - def customer_is_barman(self) -> bool: - barmen = self.object.barmen_list - return self.object.type == "BAR" and self.customer.user in barmen - - def get_product(self, pid): - return Product.objects.filter(pk=int(pid)).first() - - def get_price(self, pid): - p = self.get_product(pid) - if self.customer_is_barman(): - price = p.special_selling_price - else: - price = p.selling_price - return price - - def sum_basket(self, request): - total = 0 - for infos in request.session["basket"].values(): - total += infos["price"] * infos["qty"] - return total / 100 - - def get_total_quantity_for_pid(self, request, pid): - pid = str(pid) - if pid not in request.session["basket"]: - return 0 - return ( - request.session["basket"][pid]["qty"] - + request.session["basket"][pid]["bonus_qty"] - ) - - def compute_record_product(self, request, product=None): - recorded = 0 - basket = request.session["basket"] - - if product: - if product.is_record_product: - recorded -= 1 - elif product.is_unrecord_product: - recorded += 1 - - for p in basket: - bproduct = self.get_product(str(p)) - if bproduct.is_record_product: - recorded -= basket[p]["qty"] - elif bproduct.is_unrecord_product: - recorded += basket[p]["qty"] - return recorded - - def is_record_product_ok(self, request, product): - return self.customer.can_record_more( - self.compute_record_product(request, product) - ) - - @staticmethod - def is_ajax(request): - # when using the fetch API, the django request.POST dict is empty - # this is but a wretched contrivance which strive to replace - # the deprecated django is_ajax() method - # and which must be replaced as soon as possible - # by a proper separation between the api endpoints of the counter - return len(request.POST) == 0 and len(request.body) != 0 - - def add_product(self, request, q=1, p=None): - """Add a product to the basket - q is the quantity passed as integer - p is the product id, passed as an integer. - """ - pid = p or parse_qs(request.body.decode())["product_id"][0] - pid = str(pid) - price = self.get_price(pid) - total = self.sum_basket(request) - product: Product = self.get_product(pid) - user: User = self.customer.user - buying_groups = list(product.buying_groups.values_list("pk", flat=True)) - can_buy = len(buying_groups) == 0 or any( - user.is_in_group(pk=group_id) for group_id in buying_groups - ) - if not can_buy: - request.session["not_allowed"] = True - return False - bq = 0 # Bonus quantity, for trays - if ( - product.tray - ): # Handle the tray to adjust the quantity q to add and the bonus quantity bq - total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6 - bq = int((total_qty_mod_6 + q) / 6) # Integer division - q -= bq - if self.customer.amount < ( - total + round(q * float(price), 2) - ): # Check for enough money - request.session["not_enough"] = True - return False - if product.is_unrecord_product and not self.is_record_product_ok( - request, product - ): - request.session["not_allowed"] = True - return False - if product.limit_age >= 18 and not user.date_of_birth: - request.session["no_age"] = True - return False - if product.limit_age >= 18 and user.is_banned_alcohol: - request.session["not_allowed"] = True - return False - if user.is_banned_counter: - request.session["not_allowed"] = True - return False - if ( - user.date_of_birth and self.customer.user.get_age() < product.limit_age - ): # Check if affordable - request.session["too_young"] = True - return False - if pid in request.session["basket"]: # Add if already in basket - request.session["basket"][pid]["qty"] += q - request.session["basket"][pid]["bonus_qty"] += bq - else: # or create if not - request.session["basket"][pid] = { - "qty": q, - "price": int(price * 100), - "bonus_qty": bq, - } - request.session.modified = True - return True - - def add_student_card(self, request): - """Add a new student card on the customer account.""" - uid = str(request.POST["student_card_uid"]) - if not StudentCard.is_valid(uid): - request.session["not_valid_student_card_uid"] = True - return False - - if not ( - self.object.type == "BAR" - and "counter_token" in request.session - and request.session["counter_token"] == self.object.token - and self.object.is_open - ): - raise PermissionDenied - StudentCard(customer=self.customer, uid=uid).save() - return True - - def del_product(self, request): - """Delete a product from the basket.""" - pid = parse_qs(request.body.decode())["product_id"][0] - product = self.get_product(pid) - if pid in request.session["basket"]: - if ( - product.tray - and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) - and request.session["basket"][pid]["bonus_qty"] - ): - request.session["basket"][pid]["bonus_qty"] -= 1 - else: - request.session["basket"][pid]["qty"] -= 1 - if request.session["basket"][pid]["qty"] <= 0: - del request.session["basket"][pid] - request.session.modified = True - - def parse_code(self, request): - """Parse the string entered by the barman. - - This can be of two forms : - - ``, where the string is the code of the product - - `X`, where the integer is the quantity and str the code. - """ - string = parse_qs(request.body.decode()).get("code", [""])[0].upper() - if string == "FIN": - return self.finish(request) - elif string == "ANN": - return self.cancel(request) - regex = re.compile(r"^((?P[0-9]+)X)?(?P[A-Z0-9]+)$") - m = regex.match(string) - if m is not None: - nb = m.group("nb") - code = m.group("code") - nb = int(nb) if nb is not None else 1 - p = self.object.products.filter(code=code).first() - if p is not None: - self.add_product(request, nb, p.id) - context = self.get_context_data(object=self.object) - return self.render_to_response(context) - - def finish(self, request): - """Finish the click session, and validate the basket.""" - with transaction.atomic(): - request.session["last_basket"] = [] - if self.sum_basket(request) > self.customer.amount: - raise DataError(_("You have not enough money to buy all the basket")) - - for pid, infos in request.session["basket"].items(): - # This duplicates code for DB optimization (prevent to load many times the same object) - p = Product.objects.filter(pk=pid).first() - if self.customer_is_barman(): - uprice = p.special_selling_price - else: - uprice = p.selling_price - request.session["last_basket"].append( - "%d x %s" % (infos["qty"] + infos["bonus_qty"], p.name) - ) - s = Selling( - label=p.name, - product=p, - club=p.club, - counter=self.object, - unit_price=uprice, - quantity=infos["qty"], - seller=self.operator, - customer=self.customer, - ) - s.save() - if infos["bonus_qty"]: - s = Selling( - label=p.name + " (Plateau)", - product=p, - club=p.club, - counter=self.object, - unit_price=0, - quantity=infos["bonus_qty"], - seller=self.operator, - customer=self.customer, - ) - s.save() - self.customer.recorded_products -= self.compute_record_product(request) - self.customer.save() - request.session["last_customer"] = self.customer.user.get_display_name() - request.session["last_total"] = "%0.2f" % self.sum_basket(request) - request.session["new_customer_amount"] = str(self.customer.amount) - del request.session["basket"] - request.session.modified = True - kwargs = {"counter_id": self.object.id} - return HttpResponseRedirect( - reverse_lazy("counter:details", args=self.args, kwargs=kwargs) - ) - - def cancel(self, request): - """Cancel the click session.""" - kwargs = {"counter_id": self.object.id} - request.session.pop("basket", None) - return HttpResponseRedirect( - reverse_lazy("counter:details", args=self.args, kwargs=kwargs) - ) - - def refill(self, request): - """Refill the customer's account.""" - if not self.object.can_refill(): - raise PermissionDenied - form = RefillForm(request.POST) - if form.is_valid(): - form.instance.counter = self.object - form.instance.operator = self.operator - form.instance.customer = self.customer - form.instance.save() - else: - self.refill_form = form - - def get_context_data(self, **kwargs): - """Add customer to the context.""" - kwargs = super().get_context_data(**kwargs) - products = self.object.products.select_related("product_type") - if self.customer_is_barman(): - products = products.annotate(price=F("special_selling_price")) - else: - products = products.annotate(price=F("selling_price")) - kwargs["products"] = products - kwargs["categories"] = {} - for product in kwargs["products"]: - if product.product_type: - kwargs["categories"].setdefault(product.product_type, []).append( - product - ) - kwargs["customer"] = self.customer - kwargs["student_cards"] = self.customer.student_cards.all() - kwargs["student_card_input"] = NFCCardForm() - kwargs["basket_total"] = self.sum_basket(self.request) - kwargs["refill_form"] = self.refill_form or RefillForm() - kwargs["student_card_max_uid_size"] = StudentCard.UID_SIZE - kwargs["barmens_can_refill"] = self.object.can_refill() - return kwargs - - -@require_POST -def counter_login(request: HttpRequest, counter_id: int) -> HttpResponseRedirect: - """Log a user in a counter. - - A successful login will result in the beginning of a counter duty - for the user. - """ - counter = get_object_or_404(Counter, pk=counter_id) - form = LoginForm(request, data=request.POST) - if not form.is_valid(): - return redirect(counter.get_absolute_url() + "?credentials") - user = form.get_user() - if not counter.sellers.contains(user) or user in counter.barmen_list: - return redirect(counter.get_absolute_url() + "?sellers") - if len(counter.barmen_list) == 0: - counter.gen_token() - request.session["counter_token"] = counter.token - counter.permanencies.create(user=user, start=timezone.now()) - return redirect(counter) - - -@require_POST -def counter_logout(request: HttpRequest, counter_id: int) -> HttpResponseRedirect: - """End the permanency of a user in this counter.""" - Permanency.objects.filter(counter=counter_id, user=request.POST["user_id"]).update( - end=F("activity") - ) - return redirect("counter:details", counter_id=counter_id) - - -# Counter admin views - - -class CounterAdminTabsMixin(TabedViewMixin): - tabs_title = _("Counter administration") - list_of_tabs = [ - { - "url": reverse_lazy("counter:admin_list"), - "slug": "counters", - "name": _("Counters"), - }, - { - "url": reverse_lazy("counter:product_list"), - "slug": "products", - "name": _("Products"), - }, - { - "url": reverse_lazy("counter:product_list_archived"), - "slug": "archive", - "name": _("Archived products"), - }, - { - "url": reverse_lazy("counter:producttype_list"), - "slug": "product_types", - "name": _("Product types"), - }, - { - "url": reverse_lazy("counter:cash_summary_list"), - "slug": "cash_summary", - "name": _("Cash register summaries"), - }, - { - "url": reverse_lazy("counter:invoices_call"), - "slug": "invoices_call", - "name": _("Invoices call"), - }, - { - "url": reverse_lazy("counter:eticket_list"), - "slug": "etickets", - "name": _("Etickets"), - }, - ] - - -class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): - """A list view for the admins.""" - - model = Counter - template_name = "counter/counter_list.jinja" - current_tab = "counters" - - -class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): - """Edit a counter's main informations (for the counter's manager).""" - - model = Counter - form_class = CounterEditForm - pk_url_kwarg = "counter_id" - template_name = "core/edit.jinja" - current_tab = "counters" - - def dispatch(self, request, *args, **kwargs): - obj = self.get_object() - self.edit_club.append(obj.club) - return super().dispatch(request, *args, **kwargs) - - def get_success_url(self): - return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) - - -class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): - """Edit a counter's main informations (for the counter's admin).""" - - model = Counter - form_class = modelform_factory(Counter, fields=["name", "club", "type"]) - pk_url_kwarg = "counter_id" - template_name = "core/edit.jinja" - current_tab = "counters" - - -class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): - """Create a counter (for the admins).""" - - model = Counter - form_class = modelform_factory( - Counter, - fields=["name", "club", "type", "products"], - widgets={"products": CheckboxSelectMultiple}, - ) - template_name = "core/create.jinja" - current_tab = "counters" - - -class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): - """Delete a counter (for the admins).""" - - model = Counter - pk_url_kwarg = "counter_id" - template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy("counter:admin_list") - current_tab = "counters" - - -# Product management - - -class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): - """A list view for the admins.""" - - model = ProductType - template_name = "counter/producttype_list.jinja" - current_tab = "product_types" - - -class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): - """A create view for the admins.""" - - model = ProductType - fields = ["name", "description", "comment", "icon", "priority"] - template_name = "core/create.jinja" - current_tab = "products" - - -class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): - """An edit view for the admins.""" - - model = ProductType - template_name = "core/edit.jinja" - fields = ["name", "description", "comment", "icon", "priority"] - pk_url_kwarg = "type_id" - current_tab = "products" - - -class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): - model = Product - queryset = Product.objects.values("id", "name", "code", "product_type__name") - template_name = "counter/product_list.jinja" - ordering = [ - F("product_type__priority").desc(nulls_last=True), - "product_type", - "name", - ] - - def get_context_data(self, **kwargs): - res = super().get_context_data(**kwargs) - res["object_list"] = itertools.groupby( - res["object_list"], key=itemgetter("product_type__name") - ) - return res - - -class ArchivedProductListView(ProductListView): - """A list view for the admins.""" - - current_tab = "archive" - - def get_queryset(self): - return super().get_queryset().filter(archived=True) - - -class ActiveProductListView(ProductListView): - """A list view for the admins.""" - - current_tab = "products" - - def get_queryset(self): - return super().get_queryset().filter(archived=False) - - -class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): - """A create view for the admins.""" - - model = Product - form_class = ProductEditForm - template_name = "core/create.jinja" - current_tab = "products" - - -class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): - """An edit view for the admins.""" - - model = Product - form_class = ProductEditForm - pk_url_kwarg = "product_id" - template_name = "core/edit.jinja" - current_tab = "products" - - -class RefillingDeleteView(DeleteView): - """Delete a refilling (for the admins).""" - - model = Refilling - pk_url_kwarg = "refilling_id" - template_name = "core/delete_confirm.jinja" - - def dispatch(self, request, *args, **kwargs): - """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" - self.object = self.get_object() - if timezone.now() - self.object.date <= timedelta( - minutes=settings.SITH_LAST_OPERATIONS_LIMIT - ) and is_logged_in_counter(request): - self.success_url = reverse( - "counter:details", kwargs={"counter_id": self.object.counter.id} - ) - return super().dispatch(request, *args, **kwargs) - elif self.object.is_owned_by(request.user): - self.success_url = reverse( - "core:user_account", kwargs={"user_id": self.object.customer.user.id} - ) - return super().dispatch(request, *args, **kwargs) - raise PermissionDenied - - -class SellingDeleteView(DeleteView): - """Delete a selling (for the admins).""" - - model = Selling - pk_url_kwarg = "selling_id" - template_name = "core/delete_confirm.jinja" - - def dispatch(self, request, *args, **kwargs): - """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" - self.object = self.get_object() - if timezone.now() - self.object.date <= timedelta( - minutes=settings.SITH_LAST_OPERATIONS_LIMIT - ) and is_logged_in_counter(request): - self.success_url = reverse( - "counter:details", kwargs={"counter_id": self.object.counter.id} - ) - return super().dispatch(request, *args, **kwargs) - elif self.object.is_owned_by(request.user): - self.success_url = reverse( - "core:user_account", kwargs={"user_id": self.object.customer.user.id} - ) - return super().dispatch(request, *args, **kwargs) - raise PermissionDenied - - -# Cash register summaries - - -class CashRegisterSummaryForm(forms.Form): - """Provide the cash summary form.""" - - ten_cents = forms.IntegerField(label=_("10 cents"), required=False, min_value=0) - twenty_cents = forms.IntegerField(label=_("20 cents"), required=False, min_value=0) - fifty_cents = forms.IntegerField(label=_("50 cents"), required=False, min_value=0) - one_euro = forms.IntegerField(label=_("1 euro"), required=False, min_value=0) - two_euros = forms.IntegerField(label=_("2 euros"), required=False, min_value=0) - five_euros = forms.IntegerField(label=_("5 euros"), required=False, min_value=0) - ten_euros = forms.IntegerField(label=_("10 euros"), required=False, min_value=0) - twenty_euros = forms.IntegerField(label=_("20 euros"), required=False, min_value=0) - fifty_euros = forms.IntegerField(label=_("50 euros"), required=False, min_value=0) - hundred_euros = forms.IntegerField( - label=_("100 euros"), required=False, min_value=0 - ) - check_1_value = forms.DecimalField( - label=_("Check amount"), required=False, min_value=0 - ) - check_1_quantity = forms.IntegerField( - label=_("Check quantity"), required=False, min_value=0 - ) - check_2_value = forms.DecimalField( - label=_("Check amount"), required=False, min_value=0 - ) - check_2_quantity = forms.IntegerField( - label=_("Check quantity"), required=False, min_value=0 - ) - check_3_value = forms.DecimalField( - label=_("Check amount"), required=False, min_value=0 - ) - check_3_quantity = forms.IntegerField( - label=_("Check quantity"), required=False, min_value=0 - ) - check_4_value = forms.DecimalField( - label=_("Check amount"), required=False, min_value=0 - ) - check_4_quantity = forms.IntegerField( - label=_("Check quantity"), required=False, min_value=0 - ) - check_5_value = forms.DecimalField( - label=_("Check amount"), required=False, min_value=0 - ) - check_5_quantity = forms.IntegerField( - label=_("Check quantity"), required=False, min_value=0 - ) - comment = forms.CharField(label=_("Comment"), required=False) - emptied = forms.BooleanField(label=_("Emptied"), required=False) - - def __init__(self, *args, **kwargs): - instance = kwargs.pop("instance", None) - super().__init__(*args, **kwargs) - if instance: - self.fields["ten_cents"].initial = ( - instance.ten_cents.quantity if instance.ten_cents else 0 - ) - self.fields["twenty_cents"].initial = ( - instance.twenty_cents.quantity if instance.twenty_cents else 0 - ) - self.fields["fifty_cents"].initial = ( - instance.fifty_cents.quantity if instance.fifty_cents else 0 - ) - self.fields["one_euro"].initial = ( - instance.one_euro.quantity if instance.one_euro else 0 - ) - self.fields["two_euros"].initial = ( - instance.two_euros.quantity if instance.two_euros else 0 - ) - self.fields["five_euros"].initial = ( - instance.five_euros.quantity if instance.five_euros else 0 - ) - self.fields["ten_euros"].initial = ( - instance.ten_euros.quantity if instance.ten_euros else 0 - ) - self.fields["twenty_euros"].initial = ( - instance.twenty_euros.quantity if instance.twenty_euros else 0 - ) - self.fields["fifty_euros"].initial = ( - instance.fifty_euros.quantity if instance.fifty_euros else 0 - ) - self.fields["hundred_euros"].initial = ( - instance.hundred_euros.quantity if instance.hundred_euros else 0 - ) - self.fields["check_1_quantity"].initial = ( - instance.check_1.quantity if instance.check_1 else 0 - ) - self.fields["check_2_quantity"].initial = ( - instance.check_2.quantity if instance.check_2 else 0 - ) - self.fields["check_3_quantity"].initial = ( - instance.check_3.quantity if instance.check_3 else 0 - ) - self.fields["check_4_quantity"].initial = ( - instance.check_4.quantity if instance.check_4 else 0 - ) - self.fields["check_5_quantity"].initial = ( - instance.check_5.quantity if instance.check_5 else 0 - ) - self.fields["check_1_value"].initial = ( - instance.check_1.value if instance.check_1 else 0 - ) - self.fields["check_2_value"].initial = ( - instance.check_2.value if instance.check_2 else 0 - ) - self.fields["check_3_value"].initial = ( - instance.check_3.value if instance.check_3 else 0 - ) - self.fields["check_4_value"].initial = ( - instance.check_4.value if instance.check_4 else 0 - ) - self.fields["check_5_value"].initial = ( - instance.check_5.value if instance.check_5 else 0 - ) - self.fields["comment"].initial = instance.comment - self.fields["emptied"].initial = instance.emptied - self.instance = instance - else: - self.instance = None - - def save(self, counter=None): - cd = self.cleaned_data - summary = self.instance or CashRegisterSummary( - counter=counter, user=counter.get_random_barman() - ) - summary.comment = cd["comment"] - summary.emptied = cd["emptied"] - summary.save() - summary.items.all().delete() - # Cash - if cd["ten_cents"]: - CashRegisterSummaryItem( - cash_summary=summary, value=0.1, quantity=cd["ten_cents"] - ).save() - if cd["twenty_cents"]: - CashRegisterSummaryItem( - cash_summary=summary, value=0.2, quantity=cd["twenty_cents"] - ).save() - if cd["fifty_cents"]: - CashRegisterSummaryItem( - cash_summary=summary, value=0.5, quantity=cd["fifty_cents"] - ).save() - if cd["one_euro"]: - CashRegisterSummaryItem( - cash_summary=summary, value=1, quantity=cd["one_euro"] - ).save() - if cd["two_euros"]: - CashRegisterSummaryItem( - cash_summary=summary, value=2, quantity=cd["two_euros"] - ).save() - if cd["five_euros"]: - CashRegisterSummaryItem( - cash_summary=summary, value=5, quantity=cd["five_euros"] - ).save() - if cd["ten_euros"]: - CashRegisterSummaryItem( - cash_summary=summary, value=10, quantity=cd["ten_euros"] - ).save() - if cd["twenty_euros"]: - CashRegisterSummaryItem( - cash_summary=summary, value=20, quantity=cd["twenty_euros"] - ).save() - if cd["fifty_euros"]: - CashRegisterSummaryItem( - cash_summary=summary, value=50, quantity=cd["fifty_euros"] - ).save() - if cd["hundred_euros"]: - CashRegisterSummaryItem( - cash_summary=summary, value=100, quantity=cd["hundred_euros"] - ).save() - # Checks - if cd["check_1_quantity"]: - CashRegisterSummaryItem( - cash_summary=summary, - value=cd["check_1_value"], - quantity=cd["check_1_quantity"], - is_check=True, - ).save() - if cd["check_2_quantity"]: - CashRegisterSummaryItem( - cash_summary=summary, - value=cd["check_2_value"], - quantity=cd["check_2_quantity"], - is_check=True, - ).save() - if cd["check_3_quantity"]: - CashRegisterSummaryItem( - cash_summary=summary, - value=cd["check_3_value"], - quantity=cd["check_3_quantity"], - is_check=True, - ).save() - if cd["check_4_quantity"]: - CashRegisterSummaryItem( - cash_summary=summary, - value=cd["check_4_value"], - quantity=cd["check_4_quantity"], - is_check=True, - ).save() - if cd["check_5_quantity"]: - CashRegisterSummaryItem( - cash_summary=summary, - value=cd["check_5_value"], - quantity=cd["check_5_quantity"], - is_check=True, - ).save() - if summary.items.count() < 1: - summary.delete() - - -class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): - """Provide the last operations to allow barmen to delete them.""" - - model = Counter - pk_url_kwarg = "counter_id" - template_name = "counter/last_ops.jinja" - current_tab = "last_ops" - - def dispatch(self, request, *args, **kwargs): - """We have here again a very particular right handling.""" - self.object = self.get_object() - if is_logged_in_counter(request) and self.object.barmen_list: - return super().dispatch(request, *args, **kwargs) - return HttpResponseRedirect( - reverse("counter:details", kwargs={"counter_id": self.object.id}) - + "?bad_location" - ) - - def get_context_data(self, **kwargs): - """Add form to the context.""" - kwargs = super().get_context_data(**kwargs) - threshold = timezone.now() - timedelta( - minutes=settings.SITH_LAST_OPERATIONS_LIMIT - ) - kwargs["last_refillings"] = ( - self.object.refillings.filter(date__gte=threshold) - .select_related("operator", "customer__user") - .order_by("-id")[:20] - ) - kwargs["last_sellings"] = ( - self.object.sellings.filter(date__gte=threshold) - .select_related("seller", "customer__user") - .order_by("-id")[:20] - ) - return kwargs - - -class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): - """Provide the cash summary form.""" - - model = Counter - pk_url_kwarg = "counter_id" - template_name = "counter/cash_register_summary.jinja" - current_tab = "cash_summary" - - def dispatch(self, request, *args, **kwargs): - """We have here again a very particular right handling.""" - self.object = self.get_object() - if is_logged_in_counter(request) and self.object.barmen_list: - return super().dispatch(request, *args, **kwargs) - return HttpResponseRedirect( - reverse("counter:details", kwargs={"counter_id": self.object.id}) - + "?bad_location" - ) - - def get(self, request, *args, **kwargs): - self.object = self.get_object() - self.form = CashRegisterSummaryForm() - return super().get(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - self.object = self.get_object() - self.form = CashRegisterSummaryForm(request.POST) - if self.form.is_valid(): - self.form.save(self.object) - return HttpResponseRedirect(self.get_success_url()) - return super().get(request, *args, **kwargs) - - def get_success_url(self): - return reverse_lazy("counter:details", kwargs={"counter_id": self.object.id}) - - def get_context_data(self, **kwargs): - """Add form to the context.""" - kwargs = super().get_context_data(**kwargs) - kwargs["form"] = self.form - return kwargs - - -class CounterActivityView(DetailView): - """Show the bar activity.""" - - model = Counter - pk_url_kwarg = "counter_id" - template_name = "counter/activity.jinja" - - -class CounterStatView(DetailView, CounterAdminMixin): - """Show the bar stats.""" - - model = Counter - pk_url_kwarg = "counter_id" - template_name = "counter/stats.jinja" - - def get_context_data(self, **kwargs): - """Add stats to the context.""" - counter: Counter = self.object - semester_start = get_start_of_semester() - office_hours = counter.get_top_barmen() - kwargs = super().get_context_data(**kwargs) - kwargs.update( - { - "counter": counter, - "current_semester": get_semester_code(), - "total_sellings": counter.get_total_sales(since=semester_start), - "top_customers": counter.get_top_customers(since=semester_start)[:100], - "top_barman": office_hours[:100], - "top_barman_semester": ( - office_hours.filter(start__gt=semester_start)[:100] - ), - } - ) - return kwargs - - def dispatch(self, request, *args, **kwargs): - try: - return super().dispatch(request, *args, **kwargs) - except PermissionDenied: - if ( - request.user.is_root - or request.user.is_board_member - or self.get_object().is_owned_by(request.user) - ): - return super(CanEditMixin, self).dispatch(request, *args, **kwargs) - raise PermissionDenied - - -class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): - """Edit cash summaries.""" - - model = CashRegisterSummary - template_name = "counter/cash_register_summary.jinja" - context_object_name = "cashsummary" - pk_url_kwarg = "cashsummary_id" - form_class = CashRegisterSummaryForm - current_tab = "cash_summary" - - def get_success_url(self): - return reverse("counter:cash_summary_list") - - -class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): - """Display a list of cash summaries.""" - - model = CashRegisterSummary - template_name = "counter/cash_summary_list.jinja" - context_object_name = "cashsummary_list" - current_tab = "cash_summary" - queryset = CashRegisterSummary.objects.all().order_by("-date") - paginate_by = settings.SITH_COUNTER_CASH_SUMMARY_LENGTH - - def get_context_data(self, **kwargs): - """Add sums to the context.""" - kwargs = super().get_context_data(**kwargs) - form = CashSummaryFormBase(self.request.GET) - kwargs["form"] = form - kwargs["summaries_sums"] = {} - kwargs["refilling_sums"] = {} - for c in Counter.objects.filter(type="BAR").all(): - refillings = Refilling.objects.filter(counter=c) - cashredistersummaries = CashRegisterSummary.objects.filter(counter=c) - if form.is_valid() and form.cleaned_data["begin_date"]: - refillings = refillings.filter( - date__gte=form.cleaned_data["begin_date"] - ) - cashredistersummaries = cashredistersummaries.filter( - date__gte=form.cleaned_data["begin_date"] - ) - else: - last_summary = ( - CashRegisterSummary.objects.filter(counter=c, emptied=True) - .order_by("-date") - .first() - ) - if last_summary: - refillings = refillings.filter(date__gt=last_summary.date) - cashredistersummaries = cashredistersummaries.filter( - date__gt=last_summary.date - ) - else: - refillings = refillings.filter( - date__gte=datetime(year=1994, month=5, day=17, tzinfo=tz.utc) - ) # My birth date should be old enough - cashredistersummaries = cashredistersummaries.filter( - date__gte=datetime(year=1994, month=5, day=17, tzinfo=tz.utc) - ) - if form.is_valid() and form.cleaned_data["end_date"]: - refillings = refillings.filter(date__lte=form.cleaned_data["end_date"]) - cashredistersummaries = cashredistersummaries.filter( - date__lte=form.cleaned_data["end_date"] - ) - kwargs["summaries_sums"][c.name] = sum( - [s.get_total() for s in cashredistersummaries.all()] - ) - kwargs["refilling_sums"][c.name] = sum([s.amount for s in refillings.all()]) - return kwargs - - -class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): - template_name = "counter/invoices_call.jinja" - current_tab = "invoices_call" - - def get_context_data(self, **kwargs): - """Add sums to the context.""" - kwargs = super().get_context_data(**kwargs) - kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") - if "month" in self.request.GET: - start_date = datetime.strptime(self.request.GET["month"], "%Y-%m") - else: - start_date = datetime( - year=timezone.now().year, - month=(timezone.now().month + 10) % 12 + 1, - day=1, - ) - start_date = start_date.replace(tzinfo=tz.utc) - end_date = (start_date + timedelta(days=32)).replace( - day=1, hour=0, minute=0, microsecond=0 - ) - from django.db.models import Case, F, Sum, When - - kwargs["sum_cb"] = sum( - [ - r.amount - for r in Refilling.objects.filter( - payment_method="CARD", - is_validated=True, - date__gte=start_date, - date__lte=end_date, - ) - ] - ) - kwargs["sum_cb"] += sum( - [ - s.quantity * s.unit_price - for s in Selling.objects.filter( - payment_method="CARD", - is_validated=True, - date__gte=start_date, - date__lte=end_date, - ) - ] - ) - kwargs["start_date"] = start_date - kwargs["sums"] = ( - Selling.objects.values("club__name") - .annotate( - selling_sum=Sum( - Case( - When( - date__gte=start_date, - date__lt=end_date, - then=F("unit_price") * F("quantity"), - ), - output_field=CurrencyField(), - ) - ) - ) - .exclude(selling_sum=None) - .order_by("-selling_sum") - ) - return kwargs - - -class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): - """A list view for the admins.""" - - model = Eticket - template_name = "counter/eticket_list.jinja" - ordering = ["id"] - current_tab = "etickets" - - -class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): - """Create an eticket.""" - - model = Eticket - template_name = "core/create.jinja" - form_class = EticketForm - current_tab = "etickets" - - -class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): - """Edit an eticket.""" - - model = Eticket - template_name = "core/edit.jinja" - form_class = EticketForm - pk_url_kwarg = "eticket_id" - current_tab = "etickets" - - -class EticketPDFView(CanViewMixin, DetailView): - """Display the PDF of an eticket.""" - - model = Selling - pk_url_kwarg = "selling_id" - - def get(self, request, *args, **kwargs): - from reportlab.graphics import renderPDF - from reportlab.graphics.barcode.qr import QrCodeWidget - from reportlab.graphics.shapes import Drawing - from reportlab.lib.units import cm - from reportlab.lib.utils import ImageReader - from reportlab.pdfgen import canvas - - if not ( - hasattr(self.object, "product") and hasattr(self.object.product, "eticket") - ): - raise Http404 - - eticket = self.object.product.eticket - user = self.object.customer.user - code = "%s %s %s %s" % ( - self.object.customer.user.id, - self.object.product.id, - self.object.id, - self.object.quantity, - ) - code += " " + eticket.get_hash(code)[:8].upper() - response = HttpResponse(content_type="application/pdf") - response["Content-Disposition"] = 'filename="eticket.pdf"' - p = canvas.Canvas(response) - p.setTitle("Eticket") - im = ImageReader("core/static/core/img/eticket.jpg") - width, height = im.getSize() - size = max(width, height) - width = 8 * cm * width / size - height = 8 * cm * height / size - p.drawImage(im, 10 * cm, 25 * cm, width, height) - if eticket.banner: - im = ImageReader(eticket.banner) - width, height = im.getSize() - size = max(width, height) - width = 6 * cm * width / size - height = 6 * cm * height / size - p.drawImage(im, 1 * cm, 25 * cm, width, height) - if user.profile_pict: - im = ImageReader(user.profile_pict.file) - width, height = im.getSize() - size = max(width, height) - width = 150 * width / size - height = 150 * height / size - p.drawImage(im, 10.5 * cm - width / 2, 16 * cm, width, height) - if eticket.event_title: - p.setFont("Helvetica-Bold", 20) - p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title) - if eticket.event_date: - p.setFont("Helvetica-Bold", 16) - p.drawCentredString( - 10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y") - ) # FIXME with a locale - p.setFont("Helvetica-Bold", 14) - p.drawCentredString( - 10.5 * cm, - 15 * cm, - "%s : %d %s" - % (user.get_display_name(), self.object.quantity, str(_("people(s)"))), - ) - p.setFont("Courier-Bold", 14) - qrcode = QrCodeWidget(code) - bounds = qrcode.getBounds() - width = bounds[2] - bounds[0] - height = bounds[3] - bounds[1] - d = Drawing(260, 260, transform=[260.0 / width, 0, 0, 260.0 / height, 0, 0]) - d.add(qrcode) - renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm) - p.drawCentredString(10.5 * cm, 6 * cm, code) - - partners = ImageReader("core/static/core/img/partners.png") - width, height = partners.getSize() - size = max(width, height) - width = width * 2 / 3 - height = height * 2 / 3 - p.drawImage(partners, 0 * cm, 0 * cm, width, height) - - p.showPage() - p.save() - return response - - -class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): - """List of refillings on a counter.""" - - model = Refilling - template_name = "counter/refilling_list.jinja" - current_tab = "counters" - paginate_by = 30 - - def dispatch(self, request, *args, **kwargs): - self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"]) - self.queryset = Refilling.objects.filter(counter__id=self.counter.id) - return super().dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - kwargs = super().get_context_data(**kwargs) - kwargs["counter"] = self.counter - return kwargs - - -class StudentCardFormView(FormView): - """Add a new student card.""" - - form_class = StudentCardForm - template_name = "core/create.jinja" - - def dispatch(self, request, *args, **kwargs): - self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) - if not StudentCard.can_create(self.customer, request.user): - raise PermissionDenied - return super().dispatch(request, *args, **kwargs) - - def form_valid(self, form): - data = form.clean() - res = super(FormView, self).form_valid(form) - StudentCard(customer=self.customer, uid=data["uid"]).save() - return res - - def get_success_url(self, **kwargs): - return reverse_lazy( - "core:user_prefs", kwargs={"user_id": self.customer.user.pk} - ) diff --git a/counter/views/__init__.py b/counter/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/counter/views/admin.py b/counter/views/admin.py new file mode 100644 index 00000000..fbf466b3 --- /dev/null +++ b/counter/views/admin.py @@ -0,0 +1,288 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# +import itertools +from datetime import timedelta +from operator import itemgetter + +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.db.models import F +from django.forms import CheckboxSelectMultiple +from django.forms.models import modelform_factory +from django.shortcuts import get_object_or_404 +from django.urls import reverse, reverse_lazy +from django.utils import timezone +from django.views.generic import DetailView, ListView +from django.views.generic.edit import CreateView, DeleteView, UpdateView + +from core.utils import get_semester_code, get_start_of_semester +from core.views import CanEditMixin, CanViewMixin +from counter.forms import CounterEditForm, ProductEditForm +from counter.models import Counter, Product, ProductType, Refilling, Selling +from counter.utils import is_logged_in_counter +from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin + + +class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): + """A list view for the admins.""" + + model = Counter + template_name = "counter/counter_list.jinja" + current_tab = "counters" + + +class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): + """Edit a counter's main informations (for the counter's manager).""" + + model = Counter + form_class = CounterEditForm + pk_url_kwarg = "counter_id" + template_name = "core/edit.jinja" + current_tab = "counters" + + def dispatch(self, request, *args, **kwargs): + obj = self.get_object() + self.edit_club.append(obj.club) + return super().dispatch(request, *args, **kwargs) + + def get_success_url(self): + return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) + + +class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): + """Edit a counter's main informations (for the counter's admin).""" + + model = Counter + form_class = modelform_factory(Counter, fields=["name", "club", "type"]) + pk_url_kwarg = "counter_id" + template_name = "core/edit.jinja" + current_tab = "counters" + + +class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): + """Create a counter (for the admins).""" + + model = Counter + form_class = modelform_factory( + Counter, + fields=["name", "club", "type", "products"], + widgets={"products": CheckboxSelectMultiple}, + ) + template_name = "core/create.jinja" + current_tab = "counters" + + +class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): + """Delete a counter (for the admins).""" + + model = Counter + pk_url_kwarg = "counter_id" + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("counter:admin_list") + current_tab = "counters" + + +# Product management + + +class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): + """A list view for the admins.""" + + model = ProductType + template_name = "counter/producttype_list.jinja" + current_tab = "product_types" + + +class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): + """A create view for the admins.""" + + model = ProductType + fields = ["name", "description", "comment", "icon", "priority"] + template_name = "core/create.jinja" + current_tab = "products" + + +class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): + """An edit view for the admins.""" + + model = ProductType + template_name = "core/edit.jinja" + fields = ["name", "description", "comment", "icon", "priority"] + pk_url_kwarg = "type_id" + current_tab = "products" + + +class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): + model = Product + queryset = Product.objects.values("id", "name", "code", "product_type__name") + template_name = "counter/product_list.jinja" + ordering = [ + F("product_type__priority").desc(nulls_last=True), + "product_type", + "name", + ] + + def get_context_data(self, **kwargs): + res = super().get_context_data(**kwargs) + res["object_list"] = itertools.groupby( + res["object_list"], key=itemgetter("product_type__name") + ) + return res + + +class ArchivedProductListView(ProductListView): + """A list view for the admins.""" + + current_tab = "archive" + + def get_queryset(self): + return super().get_queryset().filter(archived=True) + + +class ActiveProductListView(ProductListView): + """A list view for the admins.""" + + current_tab = "products" + + def get_queryset(self): + return super().get_queryset().filter(archived=False) + + +class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): + """A create view for the admins.""" + + model = Product + form_class = ProductEditForm + template_name = "core/create.jinja" + current_tab = "products" + + +class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): + """An edit view for the admins.""" + + model = Product + form_class = ProductEditForm + pk_url_kwarg = "product_id" + template_name = "core/edit.jinja" + current_tab = "products" + + +class RefillingDeleteView(DeleteView): + """Delete a refilling (for the admins).""" + + model = Refilling + pk_url_kwarg = "refilling_id" + template_name = "core/delete_confirm.jinja" + + def dispatch(self, request, *args, **kwargs): + """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" + self.object = self.get_object() + if timezone.now() - self.object.date <= timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) and is_logged_in_counter(request): + self.success_url = reverse( + "counter:details", kwargs={"counter_id": self.object.counter.id} + ) + return super().dispatch(request, *args, **kwargs) + elif self.object.is_owned_by(request.user): + self.success_url = reverse( + "core:user_account", kwargs={"user_id": self.object.customer.user.id} + ) + return super().dispatch(request, *args, **kwargs) + raise PermissionDenied + + +class SellingDeleteView(DeleteView): + """Delete a selling (for the admins).""" + + model = Selling + pk_url_kwarg = "selling_id" + template_name = "core/delete_confirm.jinja" + + def dispatch(self, request, *args, **kwargs): + """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" + self.object = self.get_object() + if timezone.now() - self.object.date <= timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) and is_logged_in_counter(request): + self.success_url = reverse( + "counter:details", kwargs={"counter_id": self.object.counter.id} + ) + return super().dispatch(request, *args, **kwargs) + elif self.object.is_owned_by(request.user): + self.success_url = reverse( + "core:user_account", kwargs={"user_id": self.object.customer.user.id} + ) + return super().dispatch(request, *args, **kwargs) + raise PermissionDenied + + +class CounterStatView(DetailView, CounterAdminMixin): + """Show the bar stats.""" + + model = Counter + pk_url_kwarg = "counter_id" + template_name = "counter/stats.jinja" + + def get_context_data(self, **kwargs): + """Add stats to the context.""" + counter: Counter = self.object + semester_start = get_start_of_semester() + office_hours = counter.get_top_barmen() + kwargs = super().get_context_data(**kwargs) + kwargs.update( + { + "counter": counter, + "current_semester": get_semester_code(), + "total_sellings": counter.get_total_sales(since=semester_start), + "top_customers": counter.get_top_customers(since=semester_start)[:100], + "top_barman": office_hours[:100], + "top_barman_semester": ( + office_hours.filter(start__gt=semester_start)[:100] + ), + } + ) + return kwargs + + def dispatch(self, request, *args, **kwargs): + try: + return super().dispatch(request, *args, **kwargs) + except PermissionDenied: + if ( + request.user.is_root + or request.user.is_board_member + or self.get_object().is_owned_by(request.user) + ): + return super(CanEditMixin, self).dispatch(request, *args, **kwargs) + raise PermissionDenied + + +class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): + """List of refillings on a counter.""" + + model = Refilling + template_name = "counter/refilling_list.jinja" + current_tab = "counters" + paginate_by = 30 + + def dispatch(self, request, *args, **kwargs): + self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"]) + self.queryset = Refilling.objects.filter(counter__id=self.counter.id) + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + kwargs = super().get_context_data(**kwargs) + kwargs["counter"] = self.counter + return kwargs diff --git a/counter/views/auth.py b/counter/views/auth.py new file mode 100644 index 00000000..87cce72c --- /dev/null +++ b/counter/views/auth.py @@ -0,0 +1,53 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# + +from django.db.models import F +from django.http import HttpRequest, HttpResponseRedirect +from django.shortcuts import get_object_or_404, redirect +from django.utils import timezone +from django.views.decorators.http import require_POST + +from core.views.forms import LoginForm +from counter.models import Counter, Permanency + + +@require_POST +def counter_login(request: HttpRequest, counter_id: int) -> HttpResponseRedirect: + """Log a user in a counter. + + A successful login will result in the beginning of a counter duty + for the user. + """ + counter = get_object_or_404(Counter, pk=counter_id) + form = LoginForm(request, data=request.POST) + if not form.is_valid(): + return redirect(counter.get_absolute_url() + "?credentials") + user = form.get_user() + if not counter.sellers.contains(user) or user in counter.barmen_list: + return redirect(counter.get_absolute_url() + "?sellers") + if len(counter.barmen_list) == 0: + counter.gen_token() + request.session["counter_token"] = counter.token + counter.permanencies.create(user=user, start=timezone.now()) + return redirect(counter) + + +@require_POST +def counter_logout(request: HttpRequest, counter_id: int) -> HttpResponseRedirect: + """End the permanency of a user in this counter.""" + Permanency.objects.filter(counter=counter_id, user=request.POST["user_id"]).update( + end=F("activity") + ) + return redirect("counter:details", counter_id=counter_id) diff --git a/counter/views/cash.py b/counter/views/cash.py new file mode 100644 index 00000000..d4a03af1 --- /dev/null +++ b/counter/views/cash.py @@ -0,0 +1,359 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# +from datetime import datetime +from datetime import timezone as tz + +from django import forms +from django.conf import settings +from django.http import HttpResponseRedirect +from django.urls import reverse, reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, ListView +from django.views.generic.edit import UpdateView + +from core.views import CanViewMixin +from counter.forms import CashSummaryFormBase +from counter.models import ( + CashRegisterSummary, + CashRegisterSummaryItem, + Counter, + Refilling, +) +from counter.utils import is_logged_in_counter +from counter.views.mixins import ( + CounterAdminMixin, + CounterAdminTabsMixin, + CounterTabsMixin, +) + + +class CashRegisterSummaryForm(forms.Form): + """Provide the cash summary form.""" + + ten_cents = forms.IntegerField(label=_("10 cents"), required=False, min_value=0) + twenty_cents = forms.IntegerField(label=_("20 cents"), required=False, min_value=0) + fifty_cents = forms.IntegerField(label=_("50 cents"), required=False, min_value=0) + one_euro = forms.IntegerField(label=_("1 euro"), required=False, min_value=0) + two_euros = forms.IntegerField(label=_("2 euros"), required=False, min_value=0) + five_euros = forms.IntegerField(label=_("5 euros"), required=False, min_value=0) + ten_euros = forms.IntegerField(label=_("10 euros"), required=False, min_value=0) + twenty_euros = forms.IntegerField(label=_("20 euros"), required=False, min_value=0) + fifty_euros = forms.IntegerField(label=_("50 euros"), required=False, min_value=0) + hundred_euros = forms.IntegerField( + label=_("100 euros"), required=False, min_value=0 + ) + check_1_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_1_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_2_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_2_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_3_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_3_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_4_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_4_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_5_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_5_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + comment = forms.CharField(label=_("Comment"), required=False) + emptied = forms.BooleanField(label=_("Emptied"), required=False) + + def __init__(self, *args, **kwargs): + instance = kwargs.pop("instance", None) + super().__init__(*args, **kwargs) + if instance: + self.fields["ten_cents"].initial = ( + instance.ten_cents.quantity if instance.ten_cents else 0 + ) + self.fields["twenty_cents"].initial = ( + instance.twenty_cents.quantity if instance.twenty_cents else 0 + ) + self.fields["fifty_cents"].initial = ( + instance.fifty_cents.quantity if instance.fifty_cents else 0 + ) + self.fields["one_euro"].initial = ( + instance.one_euro.quantity if instance.one_euro else 0 + ) + self.fields["two_euros"].initial = ( + instance.two_euros.quantity if instance.two_euros else 0 + ) + self.fields["five_euros"].initial = ( + instance.five_euros.quantity if instance.five_euros else 0 + ) + self.fields["ten_euros"].initial = ( + instance.ten_euros.quantity if instance.ten_euros else 0 + ) + self.fields["twenty_euros"].initial = ( + instance.twenty_euros.quantity if instance.twenty_euros else 0 + ) + self.fields["fifty_euros"].initial = ( + instance.fifty_euros.quantity if instance.fifty_euros else 0 + ) + self.fields["hundred_euros"].initial = ( + instance.hundred_euros.quantity if instance.hundred_euros else 0 + ) + self.fields["check_1_quantity"].initial = ( + instance.check_1.quantity if instance.check_1 else 0 + ) + self.fields["check_2_quantity"].initial = ( + instance.check_2.quantity if instance.check_2 else 0 + ) + self.fields["check_3_quantity"].initial = ( + instance.check_3.quantity if instance.check_3 else 0 + ) + self.fields["check_4_quantity"].initial = ( + instance.check_4.quantity if instance.check_4 else 0 + ) + self.fields["check_5_quantity"].initial = ( + instance.check_5.quantity if instance.check_5 else 0 + ) + self.fields["check_1_value"].initial = ( + instance.check_1.value if instance.check_1 else 0 + ) + self.fields["check_2_value"].initial = ( + instance.check_2.value if instance.check_2 else 0 + ) + self.fields["check_3_value"].initial = ( + instance.check_3.value if instance.check_3 else 0 + ) + self.fields["check_4_value"].initial = ( + instance.check_4.value if instance.check_4 else 0 + ) + self.fields["check_5_value"].initial = ( + instance.check_5.value if instance.check_5 else 0 + ) + self.fields["comment"].initial = instance.comment + self.fields["emptied"].initial = instance.emptied + self.instance = instance + else: + self.instance = None + + def save(self, counter=None): + cd = self.cleaned_data + summary = self.instance or CashRegisterSummary( + counter=counter, user=counter.get_random_barman() + ) + summary.comment = cd["comment"] + summary.emptied = cd["emptied"] + summary.save() + summary.items.all().delete() + # Cash + if cd["ten_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.1, quantity=cd["ten_cents"] + ).save() + if cd["twenty_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.2, quantity=cd["twenty_cents"] + ).save() + if cd["fifty_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.5, quantity=cd["fifty_cents"] + ).save() + if cd["one_euro"]: + CashRegisterSummaryItem( + cash_summary=summary, value=1, quantity=cd["one_euro"] + ).save() + if cd["two_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=2, quantity=cd["two_euros"] + ).save() + if cd["five_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=5, quantity=cd["five_euros"] + ).save() + if cd["ten_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=10, quantity=cd["ten_euros"] + ).save() + if cd["twenty_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=20, quantity=cd["twenty_euros"] + ).save() + if cd["fifty_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=50, quantity=cd["fifty_euros"] + ).save() + if cd["hundred_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=100, quantity=cd["hundred_euros"] + ).save() + # Checks + if cd["check_1_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_1_value"], + quantity=cd["check_1_quantity"], + is_check=True, + ).save() + if cd["check_2_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_2_value"], + quantity=cd["check_2_quantity"], + is_check=True, + ).save() + if cd["check_3_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_3_value"], + quantity=cd["check_3_quantity"], + is_check=True, + ).save() + if cd["check_4_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_4_value"], + quantity=cd["check_4_quantity"], + is_check=True, + ).save() + if cd["check_5_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_5_value"], + quantity=cd["check_5_quantity"], + is_check=True, + ).save() + if summary.items.count() < 1: + summary.delete() + + +class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): + """Provide the cash summary form.""" + + model = Counter + pk_url_kwarg = "counter_id" + template_name = "counter/cash_register_summary.jinja" + current_tab = "cash_summary" + + def dispatch(self, request, *args, **kwargs): + """We have here again a very particular right handling.""" + self.object = self.get_object() + if is_logged_in_counter(request) and self.object.barmen_list: + return super().dispatch(request, *args, **kwargs) + return HttpResponseRedirect( + reverse("counter:details", kwargs={"counter_id": self.object.id}) + + "?bad_location" + ) + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + self.form = CashRegisterSummaryForm() + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + self.form = CashRegisterSummaryForm(request.POST) + if self.form.is_valid(): + self.form.save(self.object) + return HttpResponseRedirect(self.get_success_url()) + return super().get(request, *args, **kwargs) + + def get_success_url(self): + return reverse_lazy("counter:details", kwargs={"counter_id": self.object.id}) + + def get_context_data(self, **kwargs): + """Add form to the context.""" + kwargs = super().get_context_data(**kwargs) + kwargs["form"] = self.form + return kwargs + + +class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): + """Edit cash summaries.""" + + model = CashRegisterSummary + template_name = "counter/cash_register_summary.jinja" + context_object_name = "cashsummary" + pk_url_kwarg = "cashsummary_id" + form_class = CashRegisterSummaryForm + current_tab = "cash_summary" + + def get_success_url(self): + return reverse("counter:cash_summary_list") + + +class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): + """Display a list of cash summaries.""" + + model = CashRegisterSummary + template_name = "counter/cash_summary_list.jinja" + context_object_name = "cashsummary_list" + current_tab = "cash_summary" + queryset = CashRegisterSummary.objects.all().order_by("-date") + paginate_by = settings.SITH_COUNTER_CASH_SUMMARY_LENGTH + + def get_context_data(self, **kwargs): + """Add sums to the context.""" + kwargs = super().get_context_data(**kwargs) + form = CashSummaryFormBase(self.request.GET) + kwargs["form"] = form + kwargs["summaries_sums"] = {} + kwargs["refilling_sums"] = {} + for c in Counter.objects.filter(type="BAR").all(): + refillings = Refilling.objects.filter(counter=c) + cashredistersummaries = CashRegisterSummary.objects.filter(counter=c) + if form.is_valid() and form.cleaned_data["begin_date"]: + refillings = refillings.filter( + date__gte=form.cleaned_data["begin_date"] + ) + cashredistersummaries = cashredistersummaries.filter( + date__gte=form.cleaned_data["begin_date"] + ) + else: + last_summary = ( + CashRegisterSummary.objects.filter(counter=c, emptied=True) + .order_by("-date") + .first() + ) + if last_summary: + refillings = refillings.filter(date__gt=last_summary.date) + cashredistersummaries = cashredistersummaries.filter( + date__gt=last_summary.date + ) + else: + refillings = refillings.filter( + date__gte=datetime(year=1994, month=5, day=17, tzinfo=tz.utc) + ) # My birth date should be old enough + cashredistersummaries = cashredistersummaries.filter( + date__gte=datetime(year=1994, month=5, day=17, tzinfo=tz.utc) + ) + if form.is_valid() and form.cleaned_data["end_date"]: + refillings = refillings.filter(date__lte=form.cleaned_data["end_date"]) + cashredistersummaries = cashredistersummaries.filter( + date__lte=form.cleaned_data["end_date"] + ) + kwargs["summaries_sums"][c.name] = sum( + [s.get_total() for s in cashredistersummaries.all()] + ) + kwargs["refilling_sums"][c.name] = sum([s.amount for s in refillings.all()]) + return kwargs diff --git a/counter/views/click.py b/counter/views/click.py new file mode 100644 index 00000000..9542b467 --- /dev/null +++ b/counter/views/click.py @@ -0,0 +1,468 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# +import re +from http import HTTPStatus +from typing import TYPE_CHECKING +from urllib.parse import parse_qs + +from django.core.exceptions import PermissionDenied +from django.db import DataError, transaction +from django.db.models import F +from django.http import Http404, HttpResponseRedirect, JsonResponse +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, FormView + +from core.utils import FormFragmentTemplateData +from core.views import CanViewMixin +from counter.forms import RefillForm +from counter.models import Counter, Customer, Product, Selling +from counter.utils import is_logged_in_counter +from counter.views.mixins import CounterTabsMixin +from counter.views.student_card import StudentCardFormView + +if TYPE_CHECKING: + from core.models import User + + +class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): + """The click view + This is a detail view not to have to worry about loading the counter + Everything is made by hand in the post method. + """ + + model = Counter + queryset = Counter.objects.annotate_is_open() + template_name = "counter/counter_click.jinja" + pk_url_kwarg = "counter_id" + current_tab = "counter" + + def render_to_response(self, *args, **kwargs): + if self.is_ajax(self.request): + response = {"errors": []} + status = HTTPStatus.OK + + if self.request.session["too_young"]: + response["errors"].append(_("Too young for that product")) + status = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS + if self.request.session["not_allowed"]: + response["errors"].append(_("Not allowed for that product")) + status = HTTPStatus.FORBIDDEN + if self.request.session["no_age"]: + response["errors"].append(_("No date of birth provided")) + status = HTTPStatus.UNAVAILABLE_FOR_LEGAL_REASONS + if self.request.session["not_enough"]: + response["errors"].append(_("Not enough money")) + status = HTTPStatus.PAYMENT_REQUIRED + + if len(response["errors"]) > 1: + status = HTTPStatus.BAD_REQUEST + + response["basket"] = self.request.session["basket"] + + return JsonResponse(response, status=status) + + else: # Standard HTML page + return super().render_to_response(*args, **kwargs) + + def dispatch(self, request, *args, **kwargs): + self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"]) + obj: Counter = self.get_object() + if not self.customer.can_buy: + raise Http404 + if obj.type != "BAR" and not request.user.is_authenticated: + raise PermissionDenied + if obj.type == "BAR" and ( + "counter_token" not in request.session + or request.session["counter_token"] != obj.token + or len(obj.barmen_list) == 0 + ): + return redirect(obj) + return super().dispatch(request, *args, **kwargs) + + def get(self, request, *args, **kwargs): + """Simple get view.""" + if "basket" not in request.session: # Init the basket session entry + request.session["basket"] = {} + request.session["basket_total"] = 0 + request.session["not_enough"] = False # Reset every variable + request.session["too_young"] = False + request.session["not_allowed"] = False + request.session["no_age"] = False + ret = super().get(request, *args, **kwargs) + if (self.object.type != "BAR" and not request.user.is_authenticated) or ( + self.object.type == "BAR" and len(self.object.barmen_list) == 0 + ): # Check that at least one barman is logged in + ret = self.cancel(request) # Otherwise, go to main view + return ret + + def post(self, request, *args, **kwargs): + """Handle the many possibilities of the post request.""" + self.object = self.get_object() + if (self.object.type != "BAR" and not request.user.is_authenticated) or ( + self.object.type == "BAR" and len(self.object.barmen_list) < 1 + ): # Check that at least one barman is logged in + return self.cancel(request) + if self.object.type == "BAR" and not ( + "counter_token" in self.request.session + and self.request.session["counter_token"] == self.object.token + ): # Also check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.object.id}, + ) + + "?bad_location" + ) + if "basket" not in request.session: + request.session["basket"] = {} + request.session["basket_total"] = 0 + request.session["not_enough"] = False # Reset every variable + request.session["too_young"] = False + request.session["not_allowed"] = False + request.session["no_age"] = False + if self.object.type != "BAR": + self.operator = request.user + elif self.object.customer_is_barman(self.customer): + self.operator = self.customer.user + else: + self.operator = self.object.get_random_barman() + action = self.request.POST.get("action", None) + if action is None: + action = parse_qs(request.body.decode()).get("action", [""])[0] + if action == "add_product": + self.add_product(request) + elif action == "del_product": + self.del_product(request) + elif action == "code": + return self.parse_code(request) + elif action == "cancel": + return self.cancel(request) + elif action == "finish": + return self.finish(request) + context = self.get_context_data(object=self.object) + return self.render_to_response(context) + + def get_product(self, pid): + return Product.objects.filter(pk=int(pid)).first() + + def get_price(self, pid): + p = self.get_product(pid) + if self.object.customer_is_barman(self.customer): + price = p.special_selling_price + else: + price = p.selling_price + return price + + def sum_basket(self, request): + total = 0 + for infos in request.session["basket"].values(): + total += infos["price"] * infos["qty"] + return total / 100 + + def get_total_quantity_for_pid(self, request, pid): + pid = str(pid) + if pid not in request.session["basket"]: + return 0 + return ( + request.session["basket"][pid]["qty"] + + request.session["basket"][pid]["bonus_qty"] + ) + + def compute_record_product(self, request, product=None): + recorded = 0 + basket = request.session["basket"] + + if product: + if product.is_record_product: + recorded -= 1 + elif product.is_unrecord_product: + recorded += 1 + + for p in basket: + bproduct = self.get_product(str(p)) + if bproduct.is_record_product: + recorded -= basket[p]["qty"] + elif bproduct.is_unrecord_product: + recorded += basket[p]["qty"] + return recorded + + def is_record_product_ok(self, request, product): + return self.customer.can_record_more( + self.compute_record_product(request, product) + ) + + @staticmethod + def is_ajax(request): + # when using the fetch API, the django request.POST dict is empty + # this is but a wretched contrivance which strive to replace + # the deprecated django is_ajax() method + # and which must be replaced as soon as possible + # by a proper separation between the api endpoints of the counter + return len(request.POST) == 0 and len(request.body) != 0 + + def add_product(self, request, q=1, p=None): + """Add a product to the basket + q is the quantity passed as integer + p is the product id, passed as an integer. + """ + pid = p or parse_qs(request.body.decode())["product_id"][0] + pid = str(pid) + price = self.get_price(pid) + total = self.sum_basket(request) + product: Product = self.get_product(pid) + user: User = self.customer.user + buying_groups = list(product.buying_groups.values_list("pk", flat=True)) + can_buy = len(buying_groups) == 0 or any( + user.is_in_group(pk=group_id) for group_id in buying_groups + ) + if not can_buy: + request.session["not_allowed"] = True + return False + bq = 0 # Bonus quantity, for trays + if ( + product.tray + ): # Handle the tray to adjust the quantity q to add and the bonus quantity bq + total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6 + bq = int((total_qty_mod_6 + q) / 6) # Integer division + q -= bq + if self.customer.amount < ( + total + round(q * float(price), 2) + ): # Check for enough money + request.session["not_enough"] = True + return False + if product.is_unrecord_product and not self.is_record_product_ok( + request, product + ): + request.session["not_allowed"] = True + return False + if product.limit_age >= 18 and not user.date_of_birth: + request.session["no_age"] = True + return False + if product.limit_age >= 18 and user.is_banned_alcohol: + request.session["not_allowed"] = True + return False + if user.is_banned_counter: + request.session["not_allowed"] = True + return False + if ( + user.date_of_birth and self.customer.user.get_age() < product.limit_age + ): # Check if affordable + request.session["too_young"] = True + return False + if pid in request.session["basket"]: # Add if already in basket + request.session["basket"][pid]["qty"] += q + request.session["basket"][pid]["bonus_qty"] += bq + else: # or create if not + request.session["basket"][pid] = { + "qty": q, + "price": int(price * 100), + "bonus_qty": bq, + } + request.session.modified = True + return True + + def del_product(self, request): + """Delete a product from the basket.""" + pid = parse_qs(request.body.decode())["product_id"][0] + product = self.get_product(pid) + if pid in request.session["basket"]: + if ( + product.tray + and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) + and request.session["basket"][pid]["bonus_qty"] + ): + request.session["basket"][pid]["bonus_qty"] -= 1 + else: + request.session["basket"][pid]["qty"] -= 1 + if request.session["basket"][pid]["qty"] <= 0: + del request.session["basket"][pid] + request.session.modified = True + + def parse_code(self, request): + """Parse the string entered by the barman. + + This can be of two forms : + - ``, where the string is the code of the product + - `X`, where the integer is the quantity and str the code. + """ + string = parse_qs(request.body.decode()).get("code", [""])[0].upper() + if string == "FIN": + return self.finish(request) + elif string == "ANN": + return self.cancel(request) + regex = re.compile(r"^((?P[0-9]+)X)?(?P[A-Z0-9]+)$") + m = regex.match(string) + if m is not None: + nb = m.group("nb") + code = m.group("code") + nb = int(nb) if nb is not None else 1 + p = self.object.products.filter(code=code).first() + if p is not None: + self.add_product(request, nb, p.id) + context = self.get_context_data(object=self.object) + return self.render_to_response(context) + + def finish(self, request): + """Finish the click session, and validate the basket.""" + with transaction.atomic(): + request.session["last_basket"] = [] + if self.sum_basket(request) > self.customer.amount: + raise DataError(_("You have not enough money to buy all the basket")) + + for pid, infos in request.session["basket"].items(): + # This duplicates code for DB optimization (prevent to load many times the same object) + p = Product.objects.filter(pk=pid).first() + if self.object.customer_is_barman(self.customer): + uprice = p.special_selling_price + else: + uprice = p.selling_price + request.session["last_basket"].append( + "%d x %s" % (infos["qty"] + infos["bonus_qty"], p.name) + ) + s = Selling( + label=p.name, + product=p, + club=p.club, + counter=self.object, + unit_price=uprice, + quantity=infos["qty"], + seller=self.operator, + customer=self.customer, + ) + s.save() + if infos["bonus_qty"]: + s = Selling( + label=p.name + " (Plateau)", + product=p, + club=p.club, + counter=self.object, + unit_price=0, + quantity=infos["bonus_qty"], + seller=self.operator, + customer=self.customer, + ) + s.save() + self.customer.recorded_products -= self.compute_record_product(request) + self.customer.save() + request.session["last_customer"] = self.customer.user.get_display_name() + request.session["last_total"] = "%0.2f" % self.sum_basket(request) + request.session["new_customer_amount"] = str(self.customer.amount) + del request.session["basket"] + request.session.modified = True + kwargs = {"counter_id": self.object.id} + return HttpResponseRedirect( + reverse_lazy("counter:details", args=self.args, kwargs=kwargs) + ) + + def cancel(self, request): + """Cancel the click session.""" + kwargs = {"counter_id": self.object.id} + request.session.pop("basket", None) + return HttpResponseRedirect( + reverse_lazy("counter:details", args=self.args, kwargs=kwargs) + ) + + def get_context_data(self, **kwargs): + """Add customer to the context.""" + kwargs = super().get_context_data(**kwargs) + products = self.object.products.select_related("product_type") + if self.object.customer_is_barman(self.customer): + products = products.annotate(price=F("special_selling_price")) + else: + products = products.annotate(price=F("selling_price")) + kwargs["products"] = products + kwargs["categories"] = {} + for product in kwargs["products"]: + if product.product_type: + kwargs["categories"].setdefault(product.product_type, []).append( + product + ) + kwargs["customer"] = self.customer + kwargs["basket_total"] = self.sum_basket(self.request) + + if self.object.type == "BAR": + kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( + self.customer + ).render(self.request) + + if self.object.can_refill(): + kwargs["refilling_fragment"] = RefillingCreateView.get_template_data( + self.customer + ).render(self.request) + return kwargs + + +class RefillingCreateView(FormView): + """This is a fragment only view which integrates with counter_click.jinja""" + + form_class = RefillForm + template_name = "counter/fragments/create_refill.jinja" + + @classmethod + def get_template_data( + cls, customer: Customer, *, form_instance: form_class | None = None + ) -> FormFragmentTemplateData[form_class]: + return FormFragmentTemplateData( + form=form_instance if form_instance else cls.form_class(), + template=cls.template_name, + context={ + "action": reverse_lazy( + "counter:refilling_create", kwargs={"customer_id": customer.pk} + ), + }, + ) + + def dispatch(self, request, *args, **kwargs): + self.customer: Customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) + if not self.customer.can_buy: + raise Http404 + + if not is_logged_in_counter(request): + raise PermissionDenied + + self.counter: Counter = get_object_or_404( + Counter, token=request.session["counter_token"] + ) + + if not self.counter.can_refill(): + raise PermissionDenied + + if self.counter.customer_is_barman(self.customer): + self.operator = self.customer.user + else: + self.operator = self.counter.get_random_barman() + + return super().dispatch(request, *args, **kwargs) + + def form_valid(self, form): + res = super().form_valid(form) + form.clean() + form.instance.counter = self.counter + form.instance.operator = self.operator + form.instance.customer = self.customer + form.instance.save() + return res + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + data = self.get_template_data(self.customer, form_instance=context["form"]) + context.update(data.context) + return context + + def get_success_url(self, **kwargs): + return self.request.path diff --git a/counter/views/eticket.py b/counter/views/eticket.py new file mode 100644 index 00000000..a05020d6 --- /dev/null +++ b/counter/views/eticket.py @@ -0,0 +1,141 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# + +from django.http import Http404, HttpResponse +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, ListView +from django.views.generic.edit import CreateView, UpdateView + +from core.views import CanViewMixin +from counter.forms import EticketForm +from counter.models import Eticket, Selling +from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin + + +class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): + """A list view for the admins.""" + + model = Eticket + template_name = "counter/eticket_list.jinja" + ordering = ["id"] + current_tab = "etickets" + + +class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): + """Create an eticket.""" + + model = Eticket + template_name = "core/create.jinja" + form_class = EticketForm + current_tab = "etickets" + + +class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): + """Edit an eticket.""" + + model = Eticket + template_name = "core/edit.jinja" + form_class = EticketForm + pk_url_kwarg = "eticket_id" + current_tab = "etickets" + + +class EticketPDFView(CanViewMixin, DetailView): + """Display the PDF of an eticket.""" + + model = Selling + pk_url_kwarg = "selling_id" + + def get(self, request, *args, **kwargs): + from reportlab.graphics import renderPDF + from reportlab.graphics.barcode.qr import QrCodeWidget + from reportlab.graphics.shapes import Drawing + from reportlab.lib.units import cm + from reportlab.lib.utils import ImageReader + from reportlab.pdfgen import canvas + + if not ( + hasattr(self.object, "product") and hasattr(self.object.product, "eticket") + ): + raise Http404 + + eticket = self.object.product.eticket + user = self.object.customer.user + code = "%s %s %s %s" % ( + self.object.customer.user.id, + self.object.product.id, + self.object.id, + self.object.quantity, + ) + code += " " + eticket.get_hash(code)[:8].upper() + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = 'filename="eticket.pdf"' + p = canvas.Canvas(response) + p.setTitle("Eticket") + im = ImageReader("core/static/core/img/eticket.jpg") + width, height = im.getSize() + size = max(width, height) + width = 8 * cm * width / size + height = 8 * cm * height / size + p.drawImage(im, 10 * cm, 25 * cm, width, height) + if eticket.banner: + im = ImageReader(eticket.banner) + width, height = im.getSize() + size = max(width, height) + width = 6 * cm * width / size + height = 6 * cm * height / size + p.drawImage(im, 1 * cm, 25 * cm, width, height) + if user.profile_pict: + im = ImageReader(user.profile_pict.file) + width, height = im.getSize() + size = max(width, height) + width = 150 * width / size + height = 150 * height / size + p.drawImage(im, 10.5 * cm - width / 2, 16 * cm, width, height) + if eticket.event_title: + p.setFont("Helvetica-Bold", 20) + p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title) + if eticket.event_date: + p.setFont("Helvetica-Bold", 16) + p.drawCentredString( + 10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y") + ) # FIXME with a locale + p.setFont("Helvetica-Bold", 14) + p.drawCentredString( + 10.5 * cm, + 15 * cm, + "%s : %d %s" + % (user.get_display_name(), self.object.quantity, str(_("people(s)"))), + ) + p.setFont("Courier-Bold", 14) + qrcode = QrCodeWidget(code) + bounds = qrcode.getBounds() + width = bounds[2] - bounds[0] + height = bounds[3] - bounds[1] + d = Drawing(260, 260, transform=[260.0 / width, 0, 0, 260.0 / height, 0, 0]) + d.add(qrcode) + renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm) + p.drawCentredString(10.5 * cm, 6 * cm, code) + + partners = ImageReader("core/static/core/img/partners.png") + width, height = partners.getSize() + size = max(width, height) + width = width * 2 / 3 + height = height * 2 / 3 + p.drawImage(partners, 0 * cm, 0 * cm, width, height) + + p.showPage() + p.save() + return response diff --git a/counter/views/home.py b/counter/views/home.py new file mode 100644 index 00000000..60cc5a5a --- /dev/null +++ b/counter/views/home.py @@ -0,0 +1,147 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# +from datetime import timedelta + +from django.conf import settings +from django.http import HttpResponseRedirect +from django.urls import reverse, reverse_lazy +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView +from django.views.generic.edit import FormMixin, ProcessFormView + +from core.views import CanViewMixin +from core.views.forms import LoginForm +from counter.forms import GetUserForm +from counter.models import Counter +from counter.utils import is_logged_in_counter +from counter.views.mixins import CounterTabsMixin + + +class CounterMain( + CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin +): + """The public (barman) view.""" + + model = Counter + template_name = "counter/counter_main.jinja" + pk_url_kwarg = "counter_id" + form_class = ( + GetUserForm # Form to enter a client code and get the corresponding user id + ) + current_tab = "counter" + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + if self.object.type == "BAR" and not ( + "counter_token" in self.request.session + and self.request.session["counter_token"] == self.object.token + ): # Check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.object.id}, + ) + + "?bad_location" + ) + return super().post(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + """We handle here the login form for the barman.""" + if self.request.method == "POST": + self.object = self.get_object() + self.object.update_activity() + kwargs = super().get_context_data(**kwargs) + kwargs["login_form"] = LoginForm() + kwargs["login_form"].fields["username"].widget.attrs["autofocus"] = True + kwargs[ + "login_form" + ].cleaned_data = {} # add_error fails if there are no cleaned_data + if "credentials" in self.request.GET: + kwargs["login_form"].add_error(None, _("Bad credentials")) + if "sellers" in self.request.GET: + kwargs["login_form"].add_error(None, _("User is not barman")) + kwargs["form"] = self.get_form() + kwargs["form"].cleaned_data = {} # same as above + if "bad_location" in self.request.GET: + kwargs["form"].add_error( + None, _("Bad location, someone is already logged in somewhere else") + ) + if self.object.type == "BAR": + kwargs["barmen"] = self.object.barmen_list + elif self.request.user.is_authenticated: + kwargs["barmen"] = [self.request.user] + if "last_basket" in self.request.session: + kwargs["last_basket"] = self.request.session.pop("last_basket") + kwargs["last_customer"] = self.request.session.pop("last_customer") + kwargs["last_total"] = self.request.session.pop("last_total") + kwargs["new_customer_amount"] = self.request.session.pop( + "new_customer_amount" + ) + return kwargs + + def form_valid(self, form): + """We handle here the redirection, passing the user id of the asked customer.""" + self.kwargs["user_id"] = form.cleaned_data["user_id"] + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy("counter:click", args=self.args, kwargs=self.kwargs) + + +class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): + """Provide the last operations to allow barmen to delete them.""" + + model = Counter + pk_url_kwarg = "counter_id" + template_name = "counter/last_ops.jinja" + current_tab = "last_ops" + + def dispatch(self, request, *args, **kwargs): + """We have here again a very particular right handling.""" + self.object = self.get_object() + if is_logged_in_counter(request) and self.object.barmen_list: + return super().dispatch(request, *args, **kwargs) + return HttpResponseRedirect( + reverse("counter:details", kwargs={"counter_id": self.object.id}) + + "?bad_location" + ) + + def get_context_data(self, **kwargs): + """Add form to the context.""" + kwargs = super().get_context_data(**kwargs) + threshold = timezone.now() - timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) + kwargs["last_refillings"] = ( + self.object.refillings.filter(date__gte=threshold) + .select_related("operator", "customer__user") + .order_by("-id")[:20] + ) + kwargs["last_sellings"] = ( + self.object.sellings.filter(date__gte=threshold) + .select_related("seller", "customer__user") + .order_by("-id")[:20] + ) + return kwargs + + +class CounterActivityView(DetailView): + """Show the bar activity.""" + + model = Counter + pk_url_kwarg = "counter_id" + template_name = "counter/activity.jinja" diff --git a/counter/views/invoice.py b/counter/views/invoice.py new file mode 100644 index 00000000..dbd6e7cb --- /dev/null +++ b/counter/views/invoice.py @@ -0,0 +1,89 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# +from datetime import datetime, timedelta +from datetime import timezone as tz + +from django.db.models import F +from django.utils import timezone +from django.views.generic import TemplateView + +from accounting.models import CurrencyField +from counter.models import Refilling, Selling +from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin + + +class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): + template_name = "counter/invoices_call.jinja" + current_tab = "invoices_call" + + def get_context_data(self, **kwargs): + """Add sums to the context.""" + kwargs = super().get_context_data(**kwargs) + kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") + if "month" in self.request.GET: + start_date = datetime.strptime(self.request.GET["month"], "%Y-%m") + else: + start_date = datetime( + year=timezone.now().year, + month=(timezone.now().month + 10) % 12 + 1, + day=1, + ) + start_date = start_date.replace(tzinfo=tz.utc) + end_date = (start_date + timedelta(days=32)).replace( + day=1, hour=0, minute=0, microsecond=0 + ) + from django.db.models import Case, Sum, When + + kwargs["sum_cb"] = sum( + [ + r.amount + for r in Refilling.objects.filter( + payment_method="CARD", + is_validated=True, + date__gte=start_date, + date__lte=end_date, + ) + ] + ) + kwargs["sum_cb"] += sum( + [ + s.quantity * s.unit_price + for s in Selling.objects.filter( + payment_method="CARD", + is_validated=True, + date__gte=start_date, + date__lte=end_date, + ) + ] + ) + kwargs["start_date"] = start_date + kwargs["sums"] = ( + Selling.objects.values("club__name") + .annotate( + selling_sum=Sum( + Case( + When( + date__gte=start_date, + date__lt=end_date, + then=F("unit_price") * F("quantity"), + ), + output_field=CurrencyField(), + ) + ) + ) + .exclude(selling_sum=None) + .order_by("-selling_sum") + ) + return kwargs diff --git a/counter/views/mixins.py b/counter/views/mixins.py new file mode 100644 index 00000000..4a07d848 --- /dev/null +++ b/counter/views/mixins.py @@ -0,0 +1,121 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# + +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic.base import View + +from core.views import TabedViewMixin + + +class CounterAdminMixin(View): + """Protect counter admin section.""" + + edit_group = [settings.SITH_GROUP_COUNTER_ADMIN_ID] + edit_club = [] + + def _test_group(self, user): + return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group) + + def _test_club(self, user): + return any(c.can_be_edited_by(user) for c in self.edit_club) + + def dispatch(self, request, *args, **kwargs): + if not ( + request.user.is_root + or self._test_group(request.user) + or self._test_club(request.user) + ): + raise PermissionDenied + return super().dispatch(request, *args, **kwargs) + + +class CounterTabsMixin(TabedViewMixin): + def get_tabs_title(self): + return self.object + + def get_list_of_tabs(self): + tab_list = [ + { + "url": reverse_lazy( + "counter:details", kwargs={"counter_id": self.object.id} + ), + "slug": "counter", + "name": _("Counter"), + } + ] + if self.object.type == "BAR": + tab_list.append( + { + "url": reverse_lazy( + "counter:cash_summary", kwargs={"counter_id": self.object.id} + ), + "slug": "cash_summary", + "name": _("Cash summary"), + } + ) + tab_list.append( + { + "url": reverse_lazy( + "counter:last_ops", kwargs={"counter_id": self.object.id} + ), + "slug": "last_ops", + "name": _("Last operations"), + } + ) + return tab_list + + +class CounterAdminTabsMixin(TabedViewMixin): + tabs_title = _("Counter administration") + list_of_tabs = [ + { + "url": reverse_lazy("counter:admin_list"), + "slug": "counters", + "name": _("Counters"), + }, + { + "url": reverse_lazy("counter:product_list"), + "slug": "products", + "name": _("Products"), + }, + { + "url": reverse_lazy("counter:product_list_archived"), + "slug": "archive", + "name": _("Archived products"), + }, + { + "url": reverse_lazy("counter:producttype_list"), + "slug": "product_types", + "name": _("Product types"), + }, + { + "url": reverse_lazy("counter:cash_summary_list"), + "slug": "cash_summary", + "name": _("Cash register summaries"), + }, + { + "url": reverse_lazy("counter:invoices_call"), + "slug": "invoices_call", + "name": _("Invoices call"), + }, + { + "url": reverse_lazy("counter:eticket_list"), + "slug": "etickets", + "name": _("Etickets"), + }, + ] diff --git a/counter/views/student_card.py b/counter/views/student_card.py new file mode 100644 index 00000000..f916260b --- /dev/null +++ b/counter/views/student_card.py @@ -0,0 +1,113 @@ +# +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr +# +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. +# +# You can find the source code of the website at https://github.com/ae-utbm/sith +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# + + +from django.core.exceptions import PermissionDenied +from django.http import Http404, HttpRequest, HttpResponse +from django.shortcuts import get_object_or_404 +from django.urls import reverse +from django.utils.translation import gettext as _ +from django.views.generic.edit import DeleteView, FormView + +from core.utils import FormFragmentTemplateData +from core.views import can_edit +from counter.forms import StudentCardForm +from counter.models import Customer, StudentCard +from counter.utils import is_logged_in_counter + + +class StudentCardDeleteView(DeleteView): + """View used to delete a card from a user. This is a fragment view !""" + + model = StudentCard + template_name = "counter/fragments/delete_student_card.jinja" + + def dispatch(self, request: HttpRequest, *args, **kwargs): + self.customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) + if not is_logged_in_counter(request) and not can_edit( + self.get_object(), request.user + ): + raise PermissionDenied() + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["action"] = self.request.path + context["action_cancel"] = self.get_success_url() + return context + + def get_object(self, queryset=None): + if not hasattr(self.customer, "student_card"): + raise Http404( + _("%(name)s has no registered student card") + % {"name": self.customer.user.get_full_name()} + ) + return self.customer.student_card + + def get_success_url(self, **kwargs): + return reverse( + "counter:add_student_card", kwargs={"customer_id": self.customer.pk} + ) + + +class StudentCardFormView(FormView): + """Add a new student card. This is a fragment view !""" + + form_class = StudentCardForm + template_name = "counter/fragments/create_student_card.jinja" + + @classmethod + def get_template_data( + cls, customer: Customer, *, form_instance: form_class | None = None + ) -> FormFragmentTemplateData[form_class]: + """Get necessary data to pre-render the fragment""" + return FormFragmentTemplateData( + form=form_instance if form_instance else cls.form_class(), + template=cls.template_name, + context={ + "action": reverse( + "counter:add_student_card", kwargs={"customer_id": customer.pk} + ), + "customer": customer, + }, + ) + + def dispatch(self, request: HttpRequest, *args, **kwargs): + self.customer = get_object_or_404( + Customer.objects.select_related("student_card"), pk=kwargs["customer_id"] + ) + + if not is_logged_in_counter(request) and not StudentCard.can_create( + self.customer, request.user + ): + raise PermissionDenied + + return super().dispatch(request, *args, **kwargs) + + def form_valid(self, form: StudentCardForm) -> HttpResponse: + data = form.clean() + StudentCard.objects.update_or_create( + customer=self.customer, defaults={"uid": data["uid"]} + ) + return super().form_valid(form) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + data = self.get_template_data(self.customer, form_instance=context["form"]) + context.update(data.context) + return context + + def get_success_url(self, **kwargs): + return self.request.path diff --git a/launderette/views.py b/launderette/views.py index efb001e0..7886d1e7 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -145,10 +145,8 @@ class LaunderetteBookView(CanViewMixin, DetailView): and self.check_slot("WASHING", h) and self.check_slot("DRYING", h + timedelta(hours=1)) ) - or self.slot_type == "WASHING" - and self.check_slot("WASHING", h) - or self.slot_type == "DRYING" - and self.check_slot("DRYING", h) + or (self.slot_type == "WASHING" and self.check_slot("WASHING", h)) + or (self.slot_type == "DRYING" and self.check_slot("DRYING", h)) ): free = True if free and datetime.now().replace(tzinfo=tz.utc) < h: diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 0a7ce5fe..1f74ddaa 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: 2024-11-29 18:04+0100\n" +"POT-Creation-Date: 2024-12-17 10:53+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -18,8 +18,8 @@ msgstr "" #: accounting/models.py:62 accounting/models.py:101 accounting/models.py:132 #: accounting/models.py:190 club/models.py:55 com/models.py:274 -#: com/models.py:293 counter/models.py:297 counter/models.py:328 -#: counter/models.py:481 forum/models.py:60 launderette/models.py:29 +#: com/models.py:293 counter/models.py:298 counter/models.py:329 +#: counter/models.py:480 forum/models.py:60 launderette/models.py:29 #: launderette/models.py:80 launderette/models.py:116 msgid "name" msgstr "nom" @@ -65,8 +65,8 @@ msgid "account number" msgstr "numéro de compte" #: accounting/models.py:107 accounting/models.py:136 club/models.py:345 -#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:351 -#: counter/models.py:483 trombi/models.py:209 +#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:358 +#: counter/models.py:482 trombi/models.py:209 msgid "club" msgstr "club" @@ -87,12 +87,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:188 club/models.py:351 counter/models.py:961 +#: accounting/models.py:188 club/models.py:351 counter/models.py:965 #: election/models.py:16 launderette/models.py:165 msgid "start date" msgstr "date de début" -#: accounting/models.py:189 club/models.py:352 counter/models.py:962 +#: accounting/models.py:189 club/models.py:352 counter/models.py:966 #: election/models.py:17 msgid "end date" msgstr "date de fin" @@ -105,8 +105,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:199 accounting/models.py:255 counter/models.py:91 -#: counter/models.py:679 +#: accounting/models.py:199 accounting/models.py:255 counter/models.py:92 +#: counter/models.py:683 msgid "amount" msgstr "montant" @@ -128,18 +128,18 @@ msgstr "classeur" #: accounting/models.py:256 core/models.py:956 core/models.py:1467 #: core/models.py:1512 core/models.py:1541 core/models.py:1565 -#: counter/models.py:689 counter/models.py:793 counter/models.py:997 +#: counter/models.py:693 counter/models.py:797 counter/models.py:1001 #: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:312 #: forum/models.py:413 msgid "date" msgstr "date" -#: accounting/models.py:257 counter/models.py:299 counter/models.py:998 +#: accounting/models.py:257 counter/models.py:300 counter/models.py:1002 #: pedagogy/models.py:208 msgid "comment" msgstr "commentaire" -#: accounting/models.py:259 counter/models.py:691 counter/models.py:795 +#: accounting/models.py:259 counter/models.py:695 counter/models.py:799 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" @@ -166,7 +166,7 @@ msgstr "type comptable" #: accounting/models.py:294 accounting/models.py:429 accounting/models.py:460 #: accounting/models.py:492 core/models.py:1540 core/models.py:1566 -#: counter/models.py:759 +#: counter/models.py:763 msgid "label" msgstr "étiquette" @@ -218,7 +218,7 @@ msgstr "Compte" msgid "Company" msgstr "Entreprise" -#: accounting/models.py:307 core/models.py:338 sith/settings.py:421 +#: accounting/models.py:307 core/models.py:338 sith/settings.py:423 msgid "Other" msgstr "Autre" @@ -264,7 +264,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:421 counter/models.py:338 pedagogy/models.py:41 +#: accounting/models.py:421 counter/models.py:339 pedagogy/models.py:41 msgid "code" msgstr "code" @@ -369,13 +369,13 @@ msgstr "Compte en banque : " #: core/templates/core/user_clubs.jinja:34 #: core/templates/core/user_clubs.jinja:63 #: core/templates/core/user_edit.jinja:62 -#: core/templates/core/user_preferences.jinja:48 +#: counter/templates/counter/fragments/create_student_card.jinja:25 #: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:65 #: election/templates/election/election_detail.jinja:191 #: forum/templates/forum/macros.jinja:21 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:210 pedagogy/templates/pedagogy/guide.jinja:99 +#: launderette/views.py:208 pedagogy/templates/pedagogy/guide.jinja:99 #: pedagogy/templates/pedagogy/guide.jinja:114 #: pedagogy/templates/pedagogy/uv_detail.jinja:189 #: sas/templates/sas/album.jinja:36 sas/templates/sas/moderation.jinja:18 @@ -517,7 +517,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:467 +#: sith/settings.py:461 msgid "Closed" msgstr "Fermé" @@ -650,8 +650,8 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:955 -#: pedagogy/templates/pedagogy/moderation.jinja:13 +#: counter/templates/counter/cash_summary_list.jinja:37 +#: counter/views/cash.py:87 pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:142 #: trombi/templates/trombi/comment.jinja:4 #: trombi/templates/trombi/comment.jinja:8 @@ -771,7 +771,6 @@ msgstr "Opération liée : " #: core/templates/core/user_godfathers_tree.jinja:85 #: core/templates/core/user_preferences.jinja:18 #: core/templates/core/user_preferences.jinja:27 -#: core/templates/core/user_preferences.jinja:65 #: counter/templates/counter/cash_register_summary.jinja:28 #: forum/templates/forum/reply.jinja:39 #: subscription/templates/subscription/fragments/creation_form.jinja:9 @@ -930,7 +929,7 @@ msgstr "S'abonner" msgid "Remove" msgstr "Retirer" -#: club/forms.py:71 launderette/views.py:212 +#: club/forms.py:71 launderette/views.py:210 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" @@ -951,11 +950,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:149 counter/forms.py:203 +#: club/forms.py:149 counter/forms.py:206 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:206 +#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:209 #: election/views.py:170 subscription/forms.py:21 msgid "End date" msgstr "Date de fin" @@ -963,15 +962,16 @@ msgstr "Date de fin" #: club/forms.py:156 club/templates/club/club_sellings.jinja:49 #: core/templates/core/user_account_detail.jinja:17 #: core/templates/core/user_account_detail.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:137 +#: counter/templates/counter/cash_summary_list.jinja:33 +#: counter/views/mixins.py:58 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:163 counter/views.py:683 +#: club/forms.py:163 counter/views/mixins.py:94 msgid "Products" msgstr "Produits" -#: club/forms.py:168 counter/views.py:688 +#: club/forms.py:168 counter/views/mixins.py:99 msgid "Archived products" msgstr "Produits archivés" @@ -1041,7 +1041,7 @@ 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:337 counter/models.py:952 counter/models.py:988 +#: club/models.py:337 counter/models.py:956 counter/models.py:992 #: eboutic/models.py:53 eboutic/models.py:189 election/models.py:183 #: launderette/models.py:130 launderette/models.py:184 sas/models.py:273 #: trombi/models.py:205 @@ -1053,8 +1053,8 @@ msgstr "nom d'utilisateur" msgid "role" msgstr "rôle" -#: club/models.py:359 core/models.py:90 counter/models.py:298 -#: counter/models.py:329 election/models.py:13 election/models.py:115 +#: club/models.py:359 core/models.py:90 counter/models.py:299 +#: counter/models.py:330 election/models.py:13 election/models.py:115 #: election/models.py:188 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" @@ -1147,7 +1147,7 @@ msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:80 #: core/templates/core/file_detail.jinja:19 core/views/forms.py:307 -#: launderette/views.py:210 trombi/templates/trombi/detail.jinja:19 +#: launderette/views.py:208 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1334,7 +1334,7 @@ msgid "No mailing list existing for this club" msgstr "Aucune mailing liste n'existe pour ce club" #: club/templates/club/mailing.jinja:72 -#: subscription/templates/subscription/subscription.jinja:39 +#: subscription/templates/subscription/subscription.jinja:38 msgid "New member" msgstr "Nouveau membre" @@ -1426,7 +1426,8 @@ msgstr "Hebdomadaire" msgid "Call" msgstr "Appel" -#: com/models.py:67 com/models.py:174 com/models.py:248 election/models.py:12 +#: com/models.py:67 com/models.py:174 com/models.py:248 +#: core/templates/core/macros.jinja:301 election/models.py:12 #: election/models.py:114 election/models.py:152 forum/models.py:256 #: forum/models.py:310 pedagogy/models.py:97 msgid "title" @@ -1572,7 +1573,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:217 +#: launderette/views.py:215 msgid "Type" msgstr "Type" @@ -1835,6 +1836,7 @@ msgid "Articles in no weekmail yet" msgstr "Articles dans aucun weekmail" #: com/templates/com/weekmail.jinja:20 com/templates/com/weekmail.jinja:49 +#: core/templates/core/macros.jinja:301 msgid "Content" msgstr "Contenu" @@ -1860,7 +1862,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:10 -#: core/templates/core/user_account_detail.jinja:116 launderette/views.py:210 +#: core/templates/core/user_account_detail.jinja:116 launderette/views.py:208 #: pedagogy/templates/pedagogy/uv_detail.jinja:16 #: pedagogy/templates/pedagogy/uv_detail.jinja:25 #: trombi/templates/trombi/comment_moderation.jinja:10 @@ -2499,13 +2501,13 @@ msgstr "Forum" msgid "Gallery" msgstr "Photos" -#: core/templates/core/base/navbar.jinja:22 counter/models.py:491 +#: core/templates/core/base/navbar.jinja:22 counter/models.py:490 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:22 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:16 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:420 sith/settings.py:428 +#: sith/settings.py:422 sith/settings.py:430 msgid "Eboutic" msgstr "Eboutic" @@ -2572,18 +2574,21 @@ msgstr "Confirmation de suppression" #: core/templates/core/delete_confirm.jinja:16 #: core/templates/core/file_delete_confirm.jinja:29 +#: counter/templates/counter/fragments/delete_student_card.jinja:4 #, python-format msgid "Are you sure you want to delete \"%(obj)s\"?" msgstr "Êtes-vous sûr de vouloir supprimer \"%(obj)s\" ?" #: core/templates/core/delete_confirm.jinja:17 #: core/templates/core/file_delete_confirm.jinja:36 +#: counter/templates/counter/fragments/delete_student_card.jinja:5 msgid "Confirm" msgstr "Confirmation" #: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/file_delete_confirm.jinja:46 -#: counter/templates/counter/counter_click.jinja:121 +#: counter/templates/counter/counter_click.jinja:100 +#: counter/templates/counter/fragments/delete_student_card.jinja:12 #: sas/templates/sas/ask_picture_removal.jinja:20 msgid "Cancel" msgstr "Annuler" @@ -3042,11 +3047,11 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:54 -#: core/templates/core/user_tools.jinja:58 counter/views.py:708 +#: core/templates/core/user_tools.jinja:58 counter/views/mixins.py:119 msgid "Etickets" msgstr "Etickets" -#: core/templates/core/user_account.jinja:69 core/views/user.py:638 +#: core/templates/core/user_account.jinja:69 core/views/user.py:633 msgid "User has no account" msgstr "L'utilisateur n'a pas de compte" @@ -3137,7 +3142,7 @@ msgstr "Non cotisant" #: core/templates/core/user_detail.jinja:162 #: subscription/templates/subscription/subscription.jinja:6 -#: subscription/templates/subscription/subscription.jinja:37 +#: subscription/templates/subscription/subscription.jinja:36 msgid "New subscription" msgstr "Nouvelle cotisation" @@ -3296,18 +3301,15 @@ msgid "Go to my Trombi tools" msgstr "Allez à mes outils de Trombi" #: core/templates/core/user_preferences.jinja:39 -msgid "Student cards" -msgstr "Cartes étudiante" +#: counter/templates/counter/counter_click.jinja:123 +msgid "Student card" +msgstr "Carte étudiante" -#: core/templates/core/user_preferences.jinja:54 -msgid "No student card registered." -msgstr "Aucune carte étudiante enregistrée." - -#: core/templates/core/user_preferences.jinja:56 +#: core/templates/core/user_preferences.jinja:42 msgid "" "You can add a card by asking at a counter or add it yourself here. If you " "want to manually\n" -" add a student card yourself, you'll need a NFC reader. We store " +" add a student card yourself, you'll need a NFC reader. We store " "the UID of the card which is 14 characters long." msgstr "" "Vous pouvez ajouter une carte en demandant à un comptoir ou en l'ajoutant " @@ -3377,8 +3379,8 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:48 counter/forms.py:176 -#: counter/views.py:678 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:179 +#: counter/views/mixins.py:89 msgid "Counters" msgstr "Comptoirs" @@ -3395,12 +3397,13 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:56 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:698 +#: counter/templates/counter/cash_summary_list.jinja:23 +#: counter/views/mixins.py:109 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:57 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:703 +#: counter/templates/counter/invoices_call.jinja:4 counter/views/mixins.py:114 msgid "Invoices call" msgstr "Appels à facture" @@ -3548,7 +3551,7 @@ msgstr "Parrain / Marraine" msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:310 counter/forms.py:82 trombi/views.py:151 +#: core/views/forms.py:310 counter/forms.py:78 trombi/views.py:151 msgid "Select user" msgstr "Choisir un utilisateur" @@ -3596,20 +3599,33 @@ msgstr "Photos" msgid "Galaxy" msgstr "Galaxie" -#: counter/apps.py:30 counter/models.py:507 counter/models.py:958 -#: counter/models.py:994 launderette/models.py:32 +#: counter/apps.py:28 sith/settings.py:412 sith/settings.py:419 +msgid "Check" +msgstr "Chèque" + +#: counter/apps.py:29 sith/settings.py:413 sith/settings.py:421 +msgid "Cash" +msgstr "Espèces" + +#: counter/apps.py:30 counter/models.py:801 sith/settings.py:415 +#: sith/settings.py:420 +msgid "Credit card" +msgstr "Carte bancaire" + +#: counter/apps.py:36 counter/models.py:506 counter/models.py:962 +#: counter/models.py:998 launderette/models.py:32 msgid "counter" msgstr "comptoir" -#: counter/forms.py:63 +#: counter/forms.py:59 msgid "This UID is invalid" msgstr "Cet UID est invalide" -#: counter/forms.py:111 +#: counter/forms.py:107 msgid "User not found" msgstr "Utilisateur non trouvé" -#: counter/management/commands/dump_accounts.py:141 +#: counter/management/commands/dump_accounts.py:148 msgid "Your AE account has been emptied" msgstr "Votre compte AE a été vidé" @@ -3621,181 +3637,180 @@ msgstr "Vidange de votre compte AE" msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:90 +#: counter/models.py:91 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:92 +#: counter/models.py:93 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:97 +#: counter/models.py:98 msgid "customer" msgstr "client" -#: counter/models.py:98 +#: counter/models.py:99 msgid "customers" msgstr "clients" -#: counter/models.py:110 counter/views.py:261 +#: counter/models.py:111 counter/views/click.py:68 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:196 +#: counter/models.py:197 msgid "First name" msgstr "Prénom" -#: counter/models.py:197 +#: counter/models.py:198 msgid "Last name" msgstr "Nom de famille" -#: counter/models.py:198 +#: counter/models.py:199 msgid "Address 1" msgstr "Adresse 1" -#: counter/models.py:199 +#: counter/models.py:200 msgid "Address 2" msgstr "Adresse 2" -#: counter/models.py:200 +#: counter/models.py:201 msgid "Zip code" msgstr "Code postal" -#: counter/models.py:201 +#: counter/models.py:202 msgid "City" msgstr "Ville" -#: counter/models.py:202 +#: counter/models.py:203 msgid "Country" msgstr "Pays" -#: counter/models.py:210 +#: counter/models.py:211 msgid "Phone number" msgstr "Numéro de téléphone" -#: counter/models.py:252 +#: counter/models.py:253 msgid "When the mail warning that the account was about to be dumped was sent." msgstr "Quand le mail d'avertissement de la vidange du compte a été envoyé." -#: counter/models.py:257 +#: counter/models.py:258 msgid "Set this to True if the warning mail received an error" msgstr "Mettre à True si le mail a reçu une erreur" -#: counter/models.py:264 +#: counter/models.py:265 msgid "The operation that emptied the account." msgstr "L'opération qui a vidé le compte." -#: counter/models.py:309 counter/models.py:333 +#: counter/models.py:310 counter/models.py:334 msgid "product type" msgstr "type du produit" -#: counter/models.py:339 +#: counter/models.py:341 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:340 +#: counter/models.py:342 +msgid "Initial cost of purchasing the product" +msgstr "Coût initial d'achat du produit" + +#: counter/models.py:344 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:341 +#: counter/models.py:346 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:348 +#: counter/models.py:347 +msgid "Price for barmen during their permanence" +msgstr "Prix pour les barmen durant leur permanence" + +#: counter/models.py:355 msgid "icon" msgstr "icône" -#: counter/models.py:353 +#: counter/models.py:360 msgid "limit age" msgstr "âge limite" -#: counter/models.py:354 +#: counter/models.py:361 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:358 -msgid "parent product" -msgstr "produit parent" - -#: counter/models.py:364 +#: counter/models.py:363 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:366 election/models.py:50 +#: counter/models.py:365 election/models.py:50 msgid "archived" msgstr "archivé" -#: counter/models.py:369 counter/models.py:1092 +#: counter/models.py:368 counter/models.py:1096 msgid "product" msgstr "produit" -#: counter/models.py:486 +#: counter/models.py:485 msgid "products" msgstr "produits" -#: counter/models.py:489 +#: counter/models.py:488 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:491 +#: counter/models.py:490 msgid "Bar" msgstr "Bar" -#: counter/models.py:491 +#: counter/models.py:490 msgid "Office" msgstr "Bureau" -#: counter/models.py:494 +#: counter/models.py:493 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:502 launderette/models.py:178 +#: counter/models.py:501 launderette/models.py:178 msgid "token" msgstr "jeton" -#: counter/models.py:697 +#: counter/models.py:701 msgid "bank" msgstr "banque" -#: counter/models.py:699 counter/models.py:800 +#: counter/models.py:703 counter/models.py:804 msgid "is validated" msgstr "est validé" -#: counter/models.py:704 +#: counter/models.py:708 msgid "refilling" msgstr "rechargement" -#: counter/models.py:777 eboutic/models.py:249 +#: counter/models.py:781 eboutic/models.py:249 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:778 counter/models.py:1072 eboutic/models.py:250 +#: counter/models.py:782 counter/models.py:1076 eboutic/models.py:250 msgid "quantity" msgstr "quantité" -#: counter/models.py:797 +#: counter/models.py:801 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:797 sith/settings.py:413 sith/settings.py:418 -#: sith/settings.py:438 -msgid "Credit card" -msgstr "Carte bancaire" - -#: counter/models.py:805 +#: counter/models.py:809 msgid "selling" msgstr "vente" -#: counter/models.py:909 +#: counter/models.py:913 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:910 +#: counter/models.py:914 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:912 counter/models.py:925 +#: counter/models.py:916 counter/models.py:929 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3807,65 +3822,69 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:963 +#: counter/models.py:967 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:966 +#: counter/models.py:970 msgid "permanency" msgstr "permanence" -#: counter/models.py:999 +#: counter/models.py:1003 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:1002 +#: counter/models.py:1006 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:1068 +#: counter/models.py:1072 msgid "cash summary" msgstr "relevé" -#: counter/models.py:1071 +#: counter/models.py:1075 msgid "value" msgstr "valeur" -#: counter/models.py:1074 +#: counter/models.py:1078 msgid "check" msgstr "chèque" -#: counter/models.py:1076 +#: counter/models.py:1080 msgid "True if this is a bank check, else False" msgstr "Vrai si c'est un chèque, sinon Faux." -#: counter/models.py:1080 +#: counter/models.py:1084 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1096 +#: counter/models.py:1100 msgid "banner" msgstr "bannière" -#: counter/models.py:1098 +#: counter/models.py:1102 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1100 +#: counter/models.py:1104 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1102 +#: counter/models.py:1106 msgid "secret" msgstr "secret" -#: counter/models.py:1141 +#: counter/models.py:1145 msgid "uid" msgstr "uid" -#: counter/models.py:1146 +#: counter/models.py:1150 counter/models.py:1155 +msgid "student card" +msgstr "carte étudiante" + +#: counter/models.py:1156 msgid "student cards" -msgstr "cartes étudiante" +msgstr "cartes étudiantes" #: counter/templates/counter/activity.jinja:5 #: counter/templates/counter/activity.jinja:13 @@ -3910,7 +3929,8 @@ 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:956 +#: counter/templates/counter/cash_summary_list.jinja:36 +#: counter/views/cash.py:88 msgid "Emptied" msgstr "Coffre vidé" @@ -3923,16 +3943,13 @@ msgid "There is no cash register summary in this website." msgstr "Il n'y a pas de relevé de caisse dans ce site web." #: counter/templates/counter/counter_click.jinja:35 -msgid "Add a student card" -msgstr "Ajouter une carte étudiante" +#: launderette/templates/launderette/launderette_admin.jinja:8 +msgid "Selling" +msgstr "Vente" -#: counter/templates/counter/counter_click.jinja:38 -msgid "This is not a valid student card UID" -msgstr "Ce n'est pas un UID de carte étudiante valide" - -#: counter/templates/counter/counter_click.jinja:40 -#: counter/templates/counter/counter_click.jinja:67 -#: counter/templates/counter/counter_click.jinja:132 +#: counter/templates/counter/counter_click.jinja:46 +#: counter/templates/counter/fragments/create_refill.jinja:8 +#: counter/templates/counter/fragments/create_student_card.jinja:10 #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 @@ -3941,33 +3958,30 @@ msgstr "Ce n'est pas un UID de carte étudiante valide" msgid "Go" msgstr "Valider" -#: counter/templates/counter/counter_click.jinja:42 -msgid "Registered cards" -msgstr "Cartes enregistrées" - -#: counter/templates/counter/counter_click.jinja:51 -msgid "No card registered" -msgstr "Aucune carte enregistrée" - -#: counter/templates/counter/counter_click.jinja:56 -#: launderette/templates/launderette/launderette_admin.jinja:8 -msgid "Selling" -msgstr "Vente" - -#: counter/templates/counter/counter_click.jinja:74 +#: counter/templates/counter/counter_click.jinja:53 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:19 msgid "Basket: " msgstr "Panier : " -#: counter/templates/counter/counter_click.jinja:115 +#: counter/templates/counter/counter_click.jinja:94 msgid "Finish" msgstr "Terminer" -#: counter/templates/counter/counter_click.jinja:125 +#: counter/templates/counter/counter_click.jinja:104 #: counter/templates/counter/refilling_list.jinja:9 msgid "Refilling" msgstr "Rechargement" +#: counter/templates/counter/counter_click.jinja:114 +msgid "" +"As a barman, you are not able to refill any account on your own. An admin " +"should be connected on this counter for that. The customer can refill by " +"using the eboutic." +msgstr "" +"En tant que barman, vous n'êtes pas en mesure de recharger un compte par " +"vous même. Un admin doit être connecté sur ce comptoir pour cela. Le client " +"peut recharger son compte en utilisant l'eboutic" + #: counter/templates/counter/counter_list.jinja:4 #: counter/templates/counter/counter_list.jinja:10 msgid "Counter admin list" @@ -4047,6 +4061,19 @@ msgstr "Nouveau eticket" msgid "There is no eticket in this website." msgstr "Il n'y a pas de eticket sur ce site web." +#: counter/templates/counter/fragments/create_student_card.jinja:12 +msgid "No student card registered." +msgstr "Aucune carte étudiante enregistrée." + +#: counter/templates/counter/fragments/create_student_card.jinja:15 +#, python-format +msgid "uid: %(uid)s " +msgstr "uid: %(uid)s" + +#: counter/templates/counter/fragments/create_student_card.jinja:16 +msgid "Card registered" +msgstr "Carte enregistrée" + #: counter/templates/counter/invoices_call.jinja:8 #, python-format msgid "Invoices call for %(date)s" @@ -4219,104 +4246,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:147 -msgid "Cash summary" -msgstr "Relevé de caisse" - -#: counter/views.py:156 -msgid "Last operations" -msgstr "Dernières opérations" - -#: counter/views.py:203 -msgid "Bad credentials" -msgstr "Mauvais identifiants" - -#: counter/views.py:205 -msgid "User is not barman" -msgstr "L'utilisateur n'est pas barman." - -#: counter/views.py:210 -msgid "Bad location, someone is already logged in somewhere else" -msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" - -#: counter/views.py:252 -msgid "Too young for that product" -msgstr "Trop jeune pour ce produit" - -#: counter/views.py:255 -msgid "Not allowed for that product" -msgstr "Non autorisé pour ce produit" - -#: counter/views.py:258 -msgid "No date of birth provided" -msgstr "Pas de date de naissance renseignée" - -#: counter/views.py:546 -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:673 -msgid "Counter administration" -msgstr "Administration des comptoirs" - -#: counter/views.py:693 -msgid "Product types" -msgstr "Types de produit" - -#: counter/views.py:913 +#: counter/views/cash.py:45 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:914 +#: counter/views/cash.py:46 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:915 +#: counter/views/cash.py:47 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:916 +#: counter/views/cash.py:48 msgid "1 euro" msgstr "1 €" -#: counter/views.py:917 +#: counter/views/cash.py:49 msgid "2 euros" msgstr "2 €" -#: counter/views.py:918 +#: counter/views/cash.py:50 msgid "5 euros" msgstr "5 €" -#: counter/views.py:919 +#: counter/views/cash.py:51 msgid "10 euros" msgstr "10 €" -#: counter/views.py:920 +#: counter/views/cash.py:52 msgid "20 euros" msgstr "20 €" -#: counter/views.py:921 +#: counter/views/cash.py:53 msgid "50 euros" msgstr "50 €" -#: counter/views.py:923 +#: counter/views/cash.py:55 msgid "100 euros" msgstr "100 €" -#: counter/views.py:926 counter/views.py:932 counter/views.py:938 -#: counter/views.py:944 counter/views.py:950 +#: counter/views/cash.py:58 counter/views/cash.py:64 counter/views/cash.py:70 +#: counter/views/cash.py:76 counter/views/cash.py:82 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:929 counter/views.py:935 counter/views.py:941 -#: counter/views.py:947 counter/views.py:953 +#: counter/views/cash.py:61 counter/views/cash.py:67 counter/views/cash.py:73 +#: counter/views/cash.py:79 counter/views/cash.py:85 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1473 +#: counter/views/click.py:59 +msgid "Too young for that product" +msgstr "Trop jeune pour ce produit" + +#: counter/views/click.py:62 +msgid "Not allowed for that product" +msgstr "Non autorisé pour ce produit" + +#: counter/views/click.py:65 +msgid "No date of birth provided" +msgstr "Pas de date de naissance renseignée" + +#: counter/views/click.py:325 +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/eticket.py:120 msgid "people(s)" msgstr "personne(s)" +#: counter/views/home.py:74 +msgid "Bad credentials" +msgstr "Mauvais identifiants" + +#: counter/views/home.py:76 +msgid "User is not barman" +msgstr "L'utilisateur n'est pas barman." + +#: counter/views/home.py:81 +msgid "Bad location, someone is already logged in somewhere else" +msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" + +#: counter/views/mixins.py:68 +msgid "Cash summary" +msgstr "Relevé de caisse" + +#: counter/views/mixins.py:77 +msgid "Last operations" +msgstr "Dernières opérations" + +#: counter/views/mixins.py:84 +msgid "Counter administration" +msgstr "Administration des comptoirs" + +#: counter/views/mixins.py:104 +msgid "Product types" +msgstr "Types de produit" + +#: counter/views/student_card.py:54 +#, python-format +msgid "%(name)s has no registered student card" +msgstr "%(name)s n'a pas de carte étudiante enregistrée" + #: eboutic/forms.py:88 msgid "The request was badly formatted." msgstr "La requête a été mal formatée." @@ -4861,7 +4893,7 @@ msgstr "date d'emprunt" msgid "Token" msgstr "Jeton" -#: launderette/models.py:149 launderette/views.py:262 +#: launderette/models.py:149 launderette/views.py:260 msgid "Token name can not be blank" msgstr "Le nom du jeton ne peut pas être vide" @@ -4894,12 +4926,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:656 +#: sith/settings.py:650 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:656 +#: sith/settings.py:650 msgid "Drying" msgstr "Séchage" @@ -4924,25 +4956,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:224 +#: launderette/views.py:222 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:246 +#: launderette/views.py:244 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:258 +#: launderette/views.py:256 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:309 +#: launderette/views.py:307 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:417 +#: launderette/views.py:415 msgid "Token not found" msgstr "Jeton non trouvé" @@ -5414,380 +5446,372 @@ msgstr "Personne(s)" msgid "Identify users on pictures" msgstr "Identifiez les utilisateurs sur les photos" -#: sith/settings.py:254 sith/settings.py:475 +#: sith/settings.py:253 sith/settings.py:469 msgid "English" msgstr "Anglais" -#: sith/settings.py:254 sith/settings.py:474 +#: sith/settings.py:253 sith/settings.py:468 msgid "French" msgstr "Français" -#: sith/settings.py:394 +#: sith/settings.py:396 msgid "TC" msgstr "TC" -#: sith/settings.py:395 +#: sith/settings.py:397 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:396 +#: sith/settings.py:398 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:397 +#: sith/settings.py:399 msgid "INFO" msgstr "INFO" -#: sith/settings.py:398 +#: sith/settings.py:400 msgid "GI" msgstr "GI" -#: sith/settings.py:399 sith/settings.py:485 +#: sith/settings.py:401 sith/settings.py:479 msgid "E" msgstr "E" -#: sith/settings.py:400 +#: sith/settings.py:402 msgid "EE" msgstr "EE" -#: sith/settings.py:401 +#: sith/settings.py:403 msgid "GESC" msgstr "GESC" -#: sith/settings.py:402 +#: sith/settings.py:404 msgid "GMC" msgstr "GMC" -#: sith/settings.py:403 +#: sith/settings.py:405 msgid "MC" msgstr "MC" -#: sith/settings.py:404 +#: sith/settings.py:406 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:405 +#: sith/settings.py:407 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:406 +#: sith/settings.py:408 msgid "N/A" msgstr "N/A" -#: sith/settings.py:410 sith/settings.py:417 sith/settings.py:436 -msgid "Check" -msgstr "Chèque" - -#: sith/settings.py:411 sith/settings.py:419 sith/settings.py:437 -msgid "Cash" -msgstr "Espèces" - -#: sith/settings.py:412 +#: sith/settings.py:414 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:425 +#: sith/settings.py:427 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:426 +#: sith/settings.py:428 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:427 +#: sith/settings.py:429 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:455 +#: sith/settings.py:449 msgid "Free" msgstr "Libre" -#: sith/settings.py:456 +#: sith/settings.py:450 msgid "CS" msgstr "CS" -#: sith/settings.py:457 +#: sith/settings.py:451 msgid "TM" msgstr "TM" -#: sith/settings.py:458 +#: sith/settings.py:452 msgid "OM" msgstr "OM" -#: sith/settings.py:459 +#: sith/settings.py:453 msgid "QC" msgstr "QC" -#: sith/settings.py:460 +#: sith/settings.py:454 msgid "EC" msgstr "EC" -#: sith/settings.py:461 +#: sith/settings.py:455 msgid "RN" msgstr "RN" -#: sith/settings.py:462 +#: sith/settings.py:456 msgid "ST" msgstr "ST" -#: sith/settings.py:463 +#: sith/settings.py:457 msgid "EXT" msgstr "EXT" -#: sith/settings.py:468 +#: sith/settings.py:462 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:469 +#: sith/settings.py:463 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:470 +#: sith/settings.py:464 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:476 +#: sith/settings.py:470 msgid "German" msgstr "Allemand" -#: sith/settings.py:477 +#: sith/settings.py:471 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:481 +#: sith/settings.py:475 msgid "A" msgstr "A" -#: sith/settings.py:482 +#: sith/settings.py:476 msgid "B" msgstr "B" -#: sith/settings.py:483 +#: sith/settings.py:477 msgid "C" msgstr "C" -#: sith/settings.py:484 +#: sith/settings.py:478 msgid "D" msgstr "D" -#: sith/settings.py:486 +#: sith/settings.py:480 msgid "FX" msgstr "FX" -#: sith/settings.py:487 +#: sith/settings.py:481 msgid "F" msgstr "F" -#: sith/settings.py:488 +#: sith/settings.py:482 msgid "Abs" msgstr "Abs" -#: sith/settings.py:492 +#: sith/settings.py:486 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:493 +#: sith/settings.py:487 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:537 +#: sith/settings.py:531 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:538 +#: sith/settings.py:532 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:540 +#: sith/settings.py:534 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:544 +#: sith/settings.py:538 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:545 +#: sith/settings.py:539 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:546 +#: sith/settings.py:540 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:547 +#: sith/settings.py:541 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:548 +#: sith/settings.py:542 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:549 +#: sith/settings.py:543 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:550 +#: sith/settings.py:544 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:551 +#: sith/settings.py:545 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:553 +#: sith/settings.py:547 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:557 +#: sith/settings.py:551 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:558 +#: sith/settings.py:552 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:559 +#: sith/settings.py:553 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:561 +#: sith/settings.py:555 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:565 +#: sith/settings.py:559 msgid "One day" msgstr "Un jour" -#: sith/settings.py:566 +#: sith/settings.py:560 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:569 +#: sith/settings.py:563 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:574 +#: sith/settings.py:568 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:579 +#: sith/settings.py:573 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:584 +#: sith/settings.py:578 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:589 +#: sith/settings.py:583 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:595 +#: sith/settings.py:589 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:615 +#: sith/settings.py:609 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:616 +#: sith/settings.py:610 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:617 +#: sith/settings.py:611 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:618 +#: sith/settings.py:612 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:619 +#: sith/settings.py:613 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:620 +#: sith/settings.py:614 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:621 +#: sith/settings.py:615 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:622 +#: sith/settings.py:616 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:623 +#: sith/settings.py:617 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:660 +#: sith/settings.py:654 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:661 +#: sith/settings.py:655 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:664 +#: sith/settings.py:658 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:666 +#: sith/settings.py:660 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:667 +#: sith/settings.py:661 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:668 +#: sith/settings.py:662 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:669 +#: sith/settings.py:663 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:670 +#: sith/settings.py:664 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:671 +#: sith/settings.py:665 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:672 +#: sith/settings.py:666 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:684 +#: sith/settings.py:678 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:685 +#: sith/settings.py:679 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:686 +#: sith/settings.py:680 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:687 +#: sith/settings.py:681 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:688 +#: sith/settings.py:682 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:696 +#: sith/settings.py:690 msgid "AE tee-shirt" msgstr "Tee-shirt AE" @@ -5828,7 +5852,7 @@ msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" msgid "Subscription created for %(user)s" msgstr "Cotisation créée pour %(user)s" -#: subscription/templates/subscription/fragments/creation_success.jinja:8 +#: subscription/templates/subscription/fragments/creation_success.jinja:7 #, python-format msgid "" "%(user)s received its new %(type)s subscription. It will be active until " @@ -5837,18 +5861,14 @@ msgstr "" "%(user)s a reçu sa nouvelle cotisaton %(type)s. Elle sert active jusqu'au " "%(end)s inclu." -#: subscription/templates/subscription/fragments/creation_success.jinja:16 +#: subscription/templates/subscription/fragments/creation_success.jinja:15 msgid "Go to user profile" msgstr "Voir le profil de l'utilisateur" -#: subscription/templates/subscription/fragments/creation_success.jinja:24 +#: subscription/templates/subscription/fragments/creation_success.jinja:23 msgid "Create another subscription" msgstr "Créer une nouvelle cotisation" -#: subscription/templates/subscription/subscription.jinja -msgid "Existing member" -msgstr "Membre existant" - #: subscription/templates/subscription/stats.jinja:27 msgid "Total subscriptions" msgstr "Cotisations totales" @@ -5857,6 +5877,10 @@ msgstr "Cotisations totales" msgid "Subscriptions by type" msgstr "Cotisations par type" +#: subscription/templates/subscription/subscription.jinja:38 +msgid "Existing member" +msgstr "Membre existant" + #: trombi/models.py:55 msgid "subscription deadline" msgstr "fin des inscriptions" diff --git a/poetry.lock b/poetry.lock index b622f423..311df18a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -38,21 +38,18 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "asttokens" -version = "2.4.1" +version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] -[package.dependencies] -six = ">=1.12.0" - [package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "babel" @@ -81,13 +78,13 @@ files = [ [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, + {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, ] [[package]] @@ -343,73 +340,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.3" +version = "7.6.9" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6da42bbcec130b188169107ecb6ee7bd7b4c849d24c9370a0c884cf728d8e976"}, - {file = "coverage-7.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c222958f59b0ae091f4535851cbb24eb57fc0baea07ba675af718fb5302dddb2"}, - {file = "coverage-7.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab84a8b698ad5a6c365b08061920138e7a7dd9a04b6feb09ba1bfae68346ce6d"}, - {file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70a6756ce66cd6fe8486c775b30889f0dc4cb20c157aa8c35b45fd7868255c5c"}, - {file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c2e6fa98032fec8282f6b27e3f3986c6e05702828380618776ad794e938f53a"}, - {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:921fbe13492caf6a69528f09d5d7c7d518c8d0e7b9f6701b7719715f29a71e6e"}, - {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6d99198203f0b9cb0b5d1c0393859555bc26b548223a769baf7e321a627ed4fc"}, - {file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87cd2e29067ea397a47e352efb13f976eb1b03e18c999270bb50589323294c6e"}, - {file = "coverage-7.6.3-cp310-cp310-win32.whl", hash = "sha256:a3328c3e64ea4ab12b85999eb0779e6139295bbf5485f69d42cf794309e3d007"}, - {file = "coverage-7.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bca4c8abc50d38f9773c1ec80d43f3768df2e8576807d1656016b9d3eeaa96fd"}, - {file = "coverage-7.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c51ef82302386d686feea1c44dbeef744585da16fcf97deea2a8d6c1556f519b"}, - {file = "coverage-7.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ca37993206402c6c35dc717f90d4c8f53568a8b80f0bf1a1b2b334f4d488fba"}, - {file = "coverage-7.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c77326300b839c44c3e5a8fe26c15b7e87b2f32dfd2fc9fee1d13604347c9b38"}, - {file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e484e479860e00da1f005cd19d1c5d4a813324e5951319ac3f3eefb497cc549"}, - {file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6c0f4d53ef603397fc894a895b960ecd7d44c727df42a8d500031716d4e8d2"}, - {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37be7b5ea3ff5b7c4a9db16074dc94523b5f10dd1f3b362a827af66a55198175"}, - {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:43b32a06c47539fe275106b376658638b418c7cfdfff0e0259fbf877e845f14b"}, - {file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee77c7bef0724165e795b6b7bf9c4c22a9b8468a6bdb9c6b4281293c6b22a90f"}, - {file = "coverage-7.6.3-cp311-cp311-win32.whl", hash = "sha256:43517e1f6b19f610a93d8227e47790722c8bf7422e46b365e0469fc3d3563d97"}, - {file = "coverage-7.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:04f2189716e85ec9192df307f7c255f90e78b6e9863a03223c3b998d24a3c6c6"}, - {file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"}, - {file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"}, - {file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"}, - {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"}, - {file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"}, - {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"}, - {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"}, - {file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"}, - {file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"}, - {file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"}, - {file = "coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6"}, - {file = "coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929"}, - {file = "coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990"}, - {file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4"}, - {file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39"}, - {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21"}, - {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b"}, - {file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4"}, - {file = "coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f"}, - {file = "coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce"}, - {file = "coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3"}, - {file = "coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3"}, - {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d"}, - {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38"}, - {file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd"}, - {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92"}, - {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5"}, - {file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91"}, - {file = "coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43"}, - {file = "coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0"}, - {file = "coverage-7.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da29ceabe3025a1e5a5aeeb331c5b1af686daab4ff0fb4f83df18b1180ea83e2"}, - {file = "coverage-7.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df8c05a0f574d480947cba11b947dc41b1265d721c3777881da2fb8d3a1ddfba"}, - {file = "coverage-7.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1e3b40b82236d100d259854840555469fad4db64f669ab817279eb95cd535c"}, - {file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4adeb878a374126f1e5cf03b87f66279f479e01af0e9a654cf6d1509af46c40"}, - {file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43d6a66e33b1455b98fc7312b124296dad97a2e191c80320587234a77b1b736e"}, - {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1990b1f4e2c402beb317840030bb9f1b6a363f86e14e21b4212e618acdfce7f6"}, - {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:12f9515d875859faedb4144fd38694a761cd2a61ef9603bf887b13956d0bbfbb"}, - {file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99ded130555c021d99729fabd4ddb91a6f4cc0707df4b1daf912c7850c373b13"}, - {file = "coverage-7.6.3-cp39-cp39-win32.whl", hash = "sha256:c3a79f56dee9136084cf84a6c7c4341427ef36e05ae6415bf7d787c96ff5eaa3"}, - {file = "coverage-7.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:aac7501ae73d4a02f4b7ac8fcb9dc55342ca98ffb9ed9f2dfb8a25d53eda0e4d"}, - {file = "coverage-7.6.3-pp39.pp310-none-any.whl", hash = "sha256:b9853509b4bf57ba7b1f99b9d866c422c9c5248799ab20e652bbb8a184a38181"}, - {file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, + {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, + {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, + {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, + {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, + {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, + {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, + {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, + {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, + {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, + {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, + {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, + {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, + {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, + {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, ] [package.extras] @@ -417,51 +414,51 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "43.0.1" +version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" +python-versions = "!=3.9.0,!=3.9.1,>=3.7" files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -502,13 +499,13 @@ files = [ [[package]] name = "django" -version = "4.2.16" +version = "4.2.17" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"}, - {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"}, + {file = "Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0"}, + {file = "Django-4.2.17.tar.gz", hash = "sha256:6b56d834cc94c8b21a8f4e775064896be3b4a4ca387f2612d4406a5927cd2fdc"}, ] [package.dependencies] @@ -625,13 +622,13 @@ test = ["django-stubs", "mypy (==1.7.1)", "psycopg2-binary", "pytest", "pytest-a [[package]] name = "django-ninja-extra" -version = "0.21.4" +version = "0.21.8" description = "Django Ninja Extra - Class Based Utility and more for Django Ninja(Fast Django REST framework)" optional = false python-versions = ">=3.7" files = [ - {file = "django_ninja_extra-0.21.4-py3-none-any.whl", hash = "sha256:732317bea626ac8323b863f3f52332de011c1c1597e91104dc98f6cb68a78a5b"}, - {file = "django_ninja_extra-0.21.4.tar.gz", hash = "sha256:519db624542f300699a043bfa3d436d6d41dae55bcec5e37f871fe3797279833"}, + {file = "django_ninja_extra-0.21.8-py3-none-any.whl", hash = "sha256:3cc765d03dc10f9daba57cfd17e3da60d1471eea36078825aa9bc61f8fd1a0e2"}, + {file = "django_ninja_extra-0.21.8.tar.gz", hash = "sha256:f2df496540bcad3a1364258d62a506050893bd6528ea1fea99e10def84077de6"}, ] [package.dependencies] @@ -704,12 +701,12 @@ test = ["testfixtures"] [[package]] name = "djhtml" -version = "3.0.6" +version = "3.0.7" description = "Django/Jinja template indenter" optional = false python-versions = "*" files = [ - {file = "djhtml-3.0.6.tar.gz", hash = "sha256:abfc4d7b4730432ca6a98322fbdf8ae9d6ba254ea57ba3759a10ecb293bc57de"}, + {file = "djhtml-3.0.7.tar.gz", hash = "sha256:558c905b092a0c8afcbed27dea2f50aa6eb853a658b309e4e0f2bb378bdf6178"}, ] [package.extras] @@ -742,13 +739,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "faker" -version = "30.3.0" +version = "33.1.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-30.3.0-py3-none-any.whl", hash = "sha256:e8a15fd1b0f72992b008f5ea94c70d3baa0cb51b0d5a0e899c17b1d1b23d2771"}, - {file = "faker-30.3.0.tar.gz", hash = "sha256:8760fbb34564fbb2f394345eef24aec5b8f6506b6cfcefe8195ed66dd1032bdb"}, + {file = "Faker-33.1.0-py3-none-any.whl", hash = "sha256:d30c5f0e2796b8970de68978365247657486eb0311c5abe88d0b895b68dff05d"}, + {file = "faker-33.1.0.tar.gz", hash = "sha256:1c925fc0e86a51fc46648b504078c88d0cd48da1da2595c4e712841cab43a1e4"}, ] [package.dependencies] @@ -804,13 +801,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "griffe" -version = "1.4.1" +version = "1.5.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" files = [ - {file = "griffe-1.4.1-py3-none-any.whl", hash = "sha256:84295ee0b27743bd880aea75632830ef02ded65d16124025e4c263bb826ab645"}, - {file = "griffe-1.4.1.tar.gz", hash = "sha256:911a201b01dc92e08c0e84c38a301e9da5ec067f00e7d9f2e39bc24dbfa3c176"}, + {file = "griffe-1.5.1-py3-none-any.whl", hash = "sha256:ad6a7980f8c424c9102160aafa3bcdf799df0e75f7829d75af9ee5aef656f860"}, + {file = "griffe-1.5.1.tar.gz", hash = "sha256:72964f93e08c553257706d6cd2c42d1c172213feb48b2be386f243380b405d4b"}, ] [package.dependencies] @@ -818,116 +815,131 @@ colorama = ">=0.4" [[package]] name = "hiredis" -version = "3.0.0" +version = "3.1.0" description = "Python wrapper for hiredis" optional = false python-versions = ">=3.8" files = [ - {file = "hiredis-3.0.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:4b182791c41c5eb1d9ed736f0ff81694b06937ca14b0d4dadde5dadba7ff6dae"}, - {file = "hiredis-3.0.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:13c275b483a052dd645eb2cb60d6380f1f5215e4c22d6207e17b86be6dd87ffa"}, - {file = "hiredis-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1018cc7f12824506f165027eabb302735b49e63af73eb4d5450c66c88f47026"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83a29cc7b21b746cb6a480189e49f49b2072812c445e66a9e38d2004d496b81c"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e241fab6332e8fb5f14af00a4a9c6aefa22f19a336c069b7ddbf28ef8341e8d6"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fb8de899f0145d6c4d5d4bd0ee88a78eb980a7ffabd51e9889251b8f58f1785"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b23291951959141173eec10f8573538e9349fa27f47a0c34323d1970bf891ee5"}, - {file = "hiredis-3.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e421ac9e4b5efc11705a0d5149e641d4defdc07077f748667f359e60dc904420"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77c8006c12154c37691b24ff293c077300c22944018c3ff70094a33e10c1d795"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:41afc0d3c18b59eb50970479a9c0e5544fb4b95e3a79cf2fbaece6ddefb926fe"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:04ccae6dcd9647eae6025425ab64edb4d79fde8b9e6e115ebfabc6830170e3b2"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fe91d62b0594db5ea7d23fc2192182b1a7b6973f628a9b8b2e0a42a2be721ac6"}, - {file = "hiredis-3.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:99516d99316062824a24d145d694f5b0d030c80da693ea6f8c4ecf71a251d8bb"}, - {file = "hiredis-3.0.0-cp310-cp310-win32.whl", hash = "sha256:562eaf820de045eb487afaa37e6293fe7eceb5b25e158b5a1974b7e40bf04543"}, - {file = "hiredis-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1c81c89ed765198da27412aa21478f30d54ef69bf5e4480089d9c3f77b8f882"}, - {file = "hiredis-3.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:4664dedcd5933364756d7251a7ea86d60246ccf73a2e00912872dacbfcef8978"}, - {file = "hiredis-3.0.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:47de0bbccf4c8a9f99d82d225f7672b9dd690d8fd872007b933ef51a302c9fa6"}, - {file = "hiredis-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e43679eca508ba8240d016d8cca9d27342d70184773c15bea78a23c87a1922f1"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13c345e7278c210317e77e1934b27b61394fee0dec2e8bd47e71570900f75823"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00018f22f38530768b73ea86c11f47e8d4df65facd4e562bd78773bd1baef35e"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ea3a86405baa8eb0d3639ced6926ad03e07113de54cb00fd7510cb0db76a89d"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c073848d2b1d5561f3903879ccf4e1a70c9b1e7566c7bdcc98d082fa3e7f0a1d"}, - {file = "hiredis-3.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a8dffb5f5b3415a4669d25de48b617fd9d44b0bccfc4c2ab24b06406ecc9ecb"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:22c17c96143c2a62dfd61b13803bc5de2ac526b8768d2141c018b965d0333b66"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c3ece960008dab66c6b8bb3a1350764677ee7c74ccd6270aaf1b1caf9ccebb46"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f75999ae00a920f7dce6ecae76fa5e8674a3110e5a75f12c7a2c75ae1af53396"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e069967cbd5e1900aafc4b5943888f6d34937fc59bf8918a1a546cb729b4b1e4"}, - {file = "hiredis-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0aacc0a78e1d94d843a6d191f224a35893e6bdfeb77a4a89264155015c65f126"}, - {file = "hiredis-3.0.0-cp311-cp311-win32.whl", hash = "sha256:719c32147ba29528cb451f037bf837dcdda4ff3ddb6cdb12c4216b0973174718"}, - {file = "hiredis-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:bdc144d56333c52c853c31b4e2e52cfbdb22d3da4374c00f5f3d67c42158970f"}, - {file = "hiredis-3.0.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:484025d2eb8f6348f7876fc5a2ee742f568915039fcb31b478fd5c242bb0fe3a"}, - {file = "hiredis-3.0.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:fcdb552ffd97151dab8e7bc3ab556dfa1512556b48a367db94b5c20253a35ee1"}, - {file = "hiredis-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bb6f9fd92f147ba11d338ef5c68af4fd2908739c09e51f186e1d90958c68cc1"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa86bf9a0ed339ec9e8a9a9d0ae4dccd8671625c83f9f9f2640729b15e07fbfd"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e194a0d5df9456995d8f510eab9f529213e7326af6b94770abf8f8b7952ddcaa"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a1df39d74ec507d79c7a82c8063eee60bf80537cdeee652f576059b9cdd15c"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f91456507427ba36fd81b2ca11053a8e112c775325acc74e993201ea912d63e9"}, - {file = "hiredis-3.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9862db92ef67a8a02e0d5370f07d380e14577ecb281b79720e0d7a89aedb9ee5"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d10fcd9e0eeab835f492832b2a6edb5940e2f1230155f33006a8dfd3bd2c94e4"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:48727d7d405d03977d01885f317328dc21d639096308de126c2c4e9950cbd3c9"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e0bb6102ebe2efecf8a3292c6660a0e6fac98176af6de67f020bea1c2343717"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:df274e3abb4df40f4c7274dd3e587dfbb25691826c948bc98d5fead019dfb001"}, - {file = "hiredis-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:034925b5fb514f7b11aac38cd55b3fd7e9d3af23bd6497f3f20aa5b8ba58e232"}, - {file = "hiredis-3.0.0-cp312-cp312-win32.whl", hash = "sha256:120f2dda469b28d12ccff7c2230225162e174657b49cf4cd119db525414ae281"}, - {file = "hiredis-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:e584fe5f4e6681d8762982be055f1534e0170f6308a7a90f58d737bab12ff6a8"}, - {file = "hiredis-3.0.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:122171ff47d96ed8dd4bba6c0e41d8afaba3e8194949f7720431a62aa29d8895"}, - {file = "hiredis-3.0.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:ba9fc605ac558f0de67463fb588722878641e6fa1dabcda979e8e69ff581d0bd"}, - {file = "hiredis-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a631e2990b8be23178f655cae8ac6c7422af478c420dd54e25f2e26c29e766f1"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63482db3fadebadc1d01ad33afa6045ebe2ea528eb77ccaabd33ee7d9c2bad48"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f669212c390eebfbe03c4e20181f5970b82c5d0a0ad1df1785f7ffbe7d61150"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a49ef161739f8018c69b371528bdb47d7342edfdee9ddc75a4d8caddf45a6e"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98a152052b8878e5e43a2e3a14075218adafc759547c98668a21e9485882696c"}, - {file = "hiredis-3.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50a196af0ce657fcde9bf8a0bbe1032e22c64d8fcec2bc926a35e7ff68b3a166"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f2f312eef8aafc2255e3585dcf94d5da116c43ef837db91db9ecdc1bc930072d"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:6ca41fa40fa019cde42c21add74aadd775e71458051a15a352eabeb12eb4d084"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:6eecb343c70629f5af55a8b3e53264e44fa04e155ef7989de13668a0cb102a90"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:c3fdad75e7837a475900a1d3a5cc09aa024293c3b0605155da2d42f41bc0e482"}, - {file = "hiredis-3.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8854969e7480e8d61ed7549eb232d95082a743e94138d98d7222ba4e9f7ecacd"}, - {file = "hiredis-3.0.0-cp38-cp38-win32.whl", hash = "sha256:f114a6c86edbf17554672b050cce72abf489fe58d583c7921904d5f1c9691605"}, - {file = "hiredis-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:7d99b91e42217d7b4b63354b15b41ce960e27d216783e04c4a350224d55842a4"}, - {file = "hiredis-3.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:4c6efcbb5687cf8d2aedcc2c3ed4ac6feae90b8547427d417111194873b66b06"}, - {file = "hiredis-3.0.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5b5cff42a522a0d81c2ae7eae5e56d0ee7365e0c4ad50c4de467d8957aff4414"}, - {file = "hiredis-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:82f794d564f4bc76b80c50b03267fe5d6589e93f08e66b7a2f674faa2fa76ebc"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7a4c1791d7aa7e192f60fe028ae409f18ccdd540f8b1e6aeb0df7816c77e4a4"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2537b2cd98192323fce4244c8edbf11f3cac548a9d633dbbb12b48702f379f4"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fed69bbaa307040c62195a269f82fc3edf46b510a17abb6b30a15d7dab548df"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:869f6d5537d243080f44253491bb30aa1ec3c21754003b3bddeadedeb65842b0"}, - {file = "hiredis-3.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d435ae89073d7cd51e6b6bf78369c412216261c9c01662e7008ff00978153729"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:204b79b30a0e6be0dc2301a4d385bb61472809f09c49f400497f1cdd5a165c66"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ea635101b739c12effd189cc19b2671c268abb03013fd1f6321ca29df3ca625"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f359175197fd833c8dd7a8c288f1516be45415bb5c939862ab60c2918e1e1943"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ac6d929cb33dd12ad3424b75725975f0a54b5b12dbff95f2a2d660c510aa106d"}, - {file = "hiredis-3.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:100431e04d25a522ef2c3b94f294c4219c4de3bfc7d557b6253296145a144c11"}, - {file = "hiredis-3.0.0-cp39-cp39-win32.whl", hash = "sha256:e1a9c14ae9573d172dc050a6f63a644457df5d01ec4d35a6a0f097f812930f83"}, - {file = "hiredis-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:54a6dd7b478e6eb01ce15b3bb5bf771e108c6c148315bf194eb2ab776a3cac4d"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50da7a9edf371441dfcc56288d790985ee9840d982750580710a9789b8f4a290"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b285ef6bf1581310b0d5e8f6ce64f790a1c40e89c660e1320b35f7515433672"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dcfa684966f25b335072115de2f920228a3c2caf79d4bfa2b30f6e4f674a948"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a41be8af1fd78ca97bc948d789a09b730d1e7587d07ca53af05758f31f4b985d"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:038756db735e417ab36ee6fd7725ce412385ed2bd0767e8179a4755ea11b804f"}, - {file = "hiredis-3.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fcecbd39bd42cef905c0b51c9689c39d0cc8b88b1671e7f40d4fb213423aef3a"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a131377493a59fb0f5eaeb2afd49c6540cafcfba5b0b3752bed707be9e7c4eaf"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d22c53f0ec5c18ecb3d92aa9420563b1c5d657d53f01356114978107b00b860"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a91e9520fbc65a799943e5c970ffbcd67905744d8becf2e75f9f0a5e8414f0"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dc8043959b50141df58ab4f398e8ae84c6f9e673a2c9407be65fc789138f4a6"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51b99cfac514173d7b8abdfe10338193e8a0eccdfe1870b646009d2fb7cbe4b5"}, - {file = "hiredis-3.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fa1fcad89d8a41d8dc10b1e54951ec1e161deabd84ed5a2c95c3c7213bdb3514"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:898636a06d9bf575d2c594129085ad6b713414038276a4bfc5db7646b8a5be78"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:466f836dbcf86de3f9692097a7a01533dc9926986022c6617dc364a402b265c5"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23142a8af92a13fc1e3f2ca1d940df3dcf2af1d176be41fe8d89e30a837a0b60"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:793c80a3d6b0b0e8196a2d5de37a08330125668c8012922685e17aa9108c33ac"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:467d28112c7faa29b7db743f40803d927c8591e9da02b6ce3d5fadc170a542a2"}, - {file = "hiredis-3.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dc384874a719c767b50a30750f937af18842ee5e288afba95a5a3ed703b1515a"}, - {file = "hiredis-3.0.0.tar.gz", hash = "sha256:fed8581ae26345dea1f1e0d1a96e05041a727a45e7d8d459164583e23c6ac441"}, + {file = "hiredis-3.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:2892db9db21f0cf7cc298d09f85d3e1f6dc4c4c24463ab67f79bc7a006d51867"}, + {file = "hiredis-3.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:93cfa6cc25ee2ceb0be81dc61eca9995160b9e16bdb7cca4a00607d57e998918"}, + {file = "hiredis-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2af62070aa9433802cae7be7364d5e82f76462c6a2ae34e53008b637aaa9a156"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:072c162260ebb1d892683107da22d0d5da7a1414739eae4e185cac22fe89627f"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b232c43e89755ba332c2745ddab059c0bc1a0f01448a3a14d506f8448b1ce6"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb5316c9a65c4dde80796aa245b76011bab64eb84461a77b0a61c1bf2970bcc9"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e812a4e656bbd1c1c15c844b28259c49e26bb384837e44e8d2aa55412c91d2f7"}, + {file = "hiredis-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93a6c9230e5a5565847130c0e1005c8d3aa5ca681feb0ed542c4651323d32feb"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a5f65e89ce50a94d9490d5442a649c6116f53f216c8c14eb37cf9637956482b2"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b2d6e33601c67c074c367fdccdd6033e642284e7a56adc130f18f724c378ca8"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:bad3b1e0c83849910f28c95953417106f539277035a4b515d1425f93947bc28f"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9646de31f5994e6218311dcf216e971703dbf804c510fd3f84ddb9813c495824"}, + {file = "hiredis-3.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59a9230f3aa38a33d09d8171400de202f575d7a38869e5ce2947829bca6fe359"}, + {file = "hiredis-3.1.0-cp310-cp310-win32.whl", hash = "sha256:0322d70f3328b97da14b6e98b18f0090a12ed8a8bf7ae20932e2eb9d1bb0aa2c"}, + {file = "hiredis-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:802474c18e878b3f9905e160a8b7df87d57885758083eda76c5978265acb41aa"}, + {file = "hiredis-3.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c339ff4b4739b2a40da463763dd566129762f72926bca611ad9a457a9fe64abd"}, + {file = "hiredis-3.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:0ffa2552f704a45954627697a378fc2f559004e53055b82f00daf30bd4305330"}, + {file = "hiredis-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9acf7f0e7106f631cd618eb60ec9bbd6e43045addd5310f66ba1177209567e59"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea4f5ecf9dbea93c827486f59c606684c3496ea71c7ba9a8131932780696e61a"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39efab176fca3d5111075f6ba56cd864f18db46d858289d39360c5672e0e5c3e"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1110eae007f30e70a058d743e369c24430327cd01fd97d99519d6794a58dd587"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b390f63191bcccbb6044d4c118acdf4fa55f38e5658ac4cfd5a33a6f0c07659"}, + {file = "hiredis-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72a98ccc7b8ec9ce0100ecf59f45f05d2023606e8e3676b07a316d1c1c364072"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c76e751fd1e2f221dec09cdc24040ee486886e943d5d7ffc256e8cf15c75e51"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7d3880f213b6f14e9c69ce52beffd1748eecc8669698c4782761887273b6e1bd"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:87c2b3fe7e7c96eba376506a76e11514e07e848f737b254e0973e4b5c3a491e9"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d3cfb4089e96f8f8ee9554da93148a9261aa6612ad2cc202c1a494c7b712e31f"}, + {file = "hiredis-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f12018e5c5f866a1c3f7017cb2d88e5c6f9440df2281e48865a2b6c40f247f4"}, + {file = "hiredis-3.1.0-cp311-cp311-win32.whl", hash = "sha256:107b66ce977bb2dff8f2239e68344360a75d05fed3d9fa0570ac4d3020ce2396"}, + {file = "hiredis-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f1240bde53d3d1676f0aba61b3661560dc9a681cae24d9de33e650864029aa4"}, + {file = "hiredis-3.1.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:f7c7f89e0bc4246115754e2eda078a111282f6d6ecc6fb458557b724fe6f2aac"}, + {file = "hiredis-3.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:3dbf9163296fa45fbddcfc4c5900f10e9ddadda37117dbfb641e327e536b53e0"}, + {file = "hiredis-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af46a4be0e82df470f68f35316fa16cd1e134d1c5092fc1082e1aad64cce716d"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc63d698c43aea500a84d8b083f830c03808b6cf3933ae4d35a27f0a3d881652"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:676b3d88674134bfaaf70dac181d1790b0f33b3187bfb9da9221e17e0e624f83"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aed10d9df1e2fb0011db2713ac64497462e9c2c0208b648c97569da772b959ca"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b5bd8adfe8742e331a94cccd782bffea251fa70d9a709e71f4510f50794d700"}, + {file = "hiredis-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fc4e35b4afb0af6da55495dd0742ad32ab88150428a6ecdbb3085cbd60714e8"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:89b83e76eb00ab0464e7b0752a3ffcb02626e742e9509bc141424a9c3202e8dc"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98ebf08c907836b70a8f40e030df8ab6f174dc7f6fa765251d813e89f14069d8"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6c840b9cec086328f2ee2cfee0038b5d6bbb514bac7b5e579da6e346eaac056c"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c5c44e9fa6f4462d0330cb5f5d46fa652512fc86b41d4d1974d0356f263e9105"}, + {file = "hiredis-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e665b14ab50aa175cfa306fcb00fffd4e3ff02ceb36ca6a4df00b1246d6a73c4"}, + {file = "hiredis-3.1.0-cp312-cp312-win32.whl", hash = "sha256:bd33db977ac7af97e8d035ffadb163b00546be22e5f1297b2123f5f9bf0f8a21"}, + {file = "hiredis-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:37aed4aa9348600145e2d019c7be27855e503ecc4906c6976ff2f3b52e3d5d97"}, + {file = "hiredis-3.1.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:b87cddd8107487863fed6994de51e5594a0be267b0b19e213694e99cdd614623"}, + {file = "hiredis-3.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d302deff8cb63a7feffc1844e4dafc8076e566bbf10c5aaaf0f4fe791b8a6bd0"}, + {file = "hiredis-3.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a018340c073cf88cb635b2bedff96619df2f666018c655e7911f46fa2c1c178"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1e8ba6414ac1ae536129e18c069f3eb497df5a74e136e3566471620a4fa5f95"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a86b9fef256c2beb162244791fdc025aa55f936d6358e86e2020e512fe2e4972"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7acdc68e29a446ad17aadaff19c981a36b3bd8c894c3520412c8a7ab1c3e0de7"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e06baea05de57e1e7548064f505a6964e992674fe61b8f274afe2ac93b6371"}, + {file = "hiredis-3.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35b5fc061c8a0dbfdb440053280504d6aaa8d9726bd4d1d0e1cfcbbdf0d60b73"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c89d2dcb271d24c44f02264233b75d5db8c58831190fa92456a90b87fa17b748"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:aa36688c10a08f626fddcf68c2b1b91b0e90b070c26e550a4151a877f5c2d431"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3982a9c16c1c4bc05a00b65d01ffb8d80ea1a7b6b533be2f1a769d3e989d2c0"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d1a6f889514ee2452300c9a06862fceedef22a2891f1c421a27b1ba52ef130b2"}, + {file = "hiredis-3.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8a45ff7915392a55d9386bb235ea1d1eb9960615f301979f02143fc20036b699"}, + {file = "hiredis-3.1.0-cp313-cp313-win32.whl", hash = "sha256:539e5bb725b62b76a5319a4e68fc7085f01349abc2316ef3df608ea0883c51d2"}, + {file = "hiredis-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9020fd7e58f489fda6a928c31355add0e665fd6b87b21954e675cf9943eafa32"}, + {file = "hiredis-3.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:b621a89fc29b3f4b01be6640ec81a6a94b5382bc78fecb876408d57a071e45aa"}, + {file = "hiredis-3.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:363e21fba55e1a26349dc9ca7da6b14332123879b6359bcee4a9acecb40ca33b"}, + {file = "hiredis-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c156156798729eadc9ab76ffee96c88b93cc1c3b493f4dd0a4341f53939194ee"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e38d8a325f9a6afac1b1c72d996d1add9e1b99696ce9410538ba5e9aa8fdba02"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3004ef7436feb7bfa61c0b36d422b8fb8c29aaa1a514c9405f0fdee5e9694dd3"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13f5b16f97d0bbd1c04ce367c49097d1214d60e11f9fee7ef2a9b54e0a6645c8"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:230dd0e77cb0f525f58a1306a7b4aaf078037fc5229110922332ca46f90821bb"}, + {file = "hiredis-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d968116caddd19d63120d1298e62b1bbc694db3360ed0d5df8c3a97edbc12552"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:511e36a6fa41d3efab3cd5cd70ac388ed825993b9e66fa3b0e47cf27a2f5ffee"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c5cd20804e3cb0d31e7d899d8dd091f569c33fe40d4bade670a067ab7d31c2ac"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:09e89e7d34cfe5ca8f7a869fca827d1af0afe8aaddb26b38c01058730edb79ad"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:570cbf31413c77fe5e7c157f2943ca4400493ddd9cf2184731cfcafc753becd7"}, + {file = "hiredis-3.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b9b4da8162cf289781732d6a5ba01d820c42c05943fcdb7de307d03639961db3"}, + {file = "hiredis-3.1.0-cp38-cp38-win32.whl", hash = "sha256:bc117a04bcb461d3bb1b2c5b417aee3442e1e8aa33ebc800481431f4c09fe0c5"}, + {file = "hiredis-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:34f3f5f0354db2d6797a6fb08d2c036a50af62a1d919d122c1c784304ef49347"}, + {file = "hiredis-3.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a26fa888025badb5563f283cc19594c215a413e905729e59a5f7cf3f46d66c32"}, + {file = "hiredis-3.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f50763cd819d4a52a47b5966d4bb47dee34b637c5fa6402509800eee6ecb61e6"}, + {file = "hiredis-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6d1c9e1fce5e0a94072667ae2bf0142b89ebbb1917d3531184e060a43f3ee11"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e38d7a56b1a79ed0bbb9e6fe376d82e3f4dcc646ae47472f2c858e19a597c112"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ef5ad8b91530e4d10a68562b0a380ea22705a60e88cecee086d7c63a38564ce"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf3d2299b054e57a9f97ca08704c2843e44f29b57dc69b76a2592ecd212efe1a"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93811d60b0f73d0f049c86f4373a3833b4a38fce374ab151074d929553eb4304"}, + {file = "hiredis-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e703ff860c1d83abbcf57012b309ead02b56b60e85150c6c3bfb37cbb16ebf"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f9ea0678806c53d96758e74c6a898f9d506a2e3367a344757f768bef9e069366"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cf6844035abf47d52a1c3f4257255af3bf3b0f14d559b08eaa45885418c6c55d"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7acf35cfa7ec9e1e7559c04e7095628f7d06049b5f24dcb58c1a55ef6dc689f8"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b885695dce7a39b1fd9a609ed9c4cf312e53df2ec028d5a78af7a891b5fbea4d"}, + {file = "hiredis-3.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c22fa74ddd063396b19fe8445a1ae8b4190eff755d5750dda48e860a45b2ee7"}, + {file = "hiredis-3.1.0-cp39-cp39-win32.whl", hash = "sha256:0614e16339f1784df3bbd2800322e20b4127d3f3a3509f00a5562efddb2521aa"}, + {file = "hiredis-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:c2bc713ee73ab9de4a0d68b0ab0f29612342b63173714742437b977584adb2d8"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:07ab990d0835f36bf358dbb84db4541ac0a8f533128ec09af8f80a576eef2e88"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c54a88eb9d8ebc4e5eefaadbe2102a4f7499f9e413654172f40aefd25350959"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8095ef159896e5999a795b0f80e4d64281301a109e442a8d29cd750ca6bd8303"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f8ca13e2476ffd6d5be4763f5868133506ddcfa5ce54b4dac231ebdc19be6c6"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d25aa25c10f966d5415795ed271da84605044dbf436c054966cea5442451b3"}, + {file = "hiredis-3.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4180dc5f646b426e5fa1212e1348c167ee2a864b3a70d56579163d64a847dd1e"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d92144e0cd6e6e841a6ad343e9d58631626eeb4ac96b0322649379b5d4527447"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fcb91ba42903de637b94a1b64477f381f94ad82c0742c264f9245be76a7a3cbc"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ce71a797b5bc02c51da082428c00251ed6a7a67a03acbda5fbf9e8d028725f6"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e04c7feb9467e3170cd4d5bee381775783d81bbc45d6147c1c0ce3b50dc04f9"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a31806306a60f3565c04c964d6bee0e9d4a5120e1da589e41976b53972edf635"}, + {file = "hiredis-3.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bc51f594c2c0863ded6501642dc96701ca8bbea9ced4fa3af0a1aeda8aa634cb"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4663a319ab7d22c597b9421e5ea384fd583e044f2f1ca9a1b98d4fef8a0fea2f"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:8060fa256862b0c3de64a73ab45bc1ccf381caca464f2647af9075b200828948"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e9445b7f117a9c8c8ccad97cb44daa55ddccff3cbc9079984eac56d982ba01f"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:732cf1c5cf1324f7bf3b6086976fe62a2ca98f0bf6316f31063c2c67be8797bc"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2102a94063d878c40df92f55199637a74f535e3a0b79ceba4a00538853a21be3"}, + {file = "hiredis-3.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d968dde69e3fe903bf9ef00667669dcf04a3e096e33aaf138775106ead138bc8"}, + {file = "hiredis-3.1.0.tar.gz", hash = "sha256:51d40ac3611091020d7dea6b05ed62cb152bff595fa4f931e7b6479d777acf7c"}, ] [[package]] name = "identify" -version = "2.6.1" +version = "2.6.3" description = "File identification library for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, - {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, + {file = "identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd"}, + {file = "identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02"}, ] [package.extras] @@ -985,13 +997,13 @@ dev = ["black (==24.3.0)", "build (==1.0.3)", "check-manifest (==0.49)", "click [[package]] name = "ipython" -version = "8.28.0" +version = "8.30.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, - {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, + {file = "ipython-8.30.0-py3-none-any.whl", hash = "sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321"}, + {file = "ipython-8.30.0.tar.gz", hash = "sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e"}, ] [package.dependencies] @@ -1000,15 +1012,15 @@ decorator = "*" jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt-toolkit = ">=3.0.41,<3.1.0" +prompt_toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" -stack-data = "*" +stack_data = "*" traitlets = ">=5.13.0" [package.extras] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] kernel = ["ipykernel"] matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] @@ -1021,22 +1033,22 @@ test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "num [[package]] name = "jedi" -version = "0.19.1" +version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, - {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, ] [package.dependencies] -parso = ">=0.8.3,<0.9.0" +parso = ">=0.8.4,<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 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] [[package]] name = "jinja2" @@ -1087,72 +1099,72 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "3.0.1" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" files = [ - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, - {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, - {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, - {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, - {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, - {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, - {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, - {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] @@ -1255,13 +1267,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-include-markdown-plugin" -version = "6.2.2" +version = "7.1.2" description = "Mkdocs Markdown includer plugin." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "mkdocs_include_markdown_plugin-6.2.2-py3-none-any.whl", hash = "sha256:d293950f6499d2944291ca7b9bc4a60e652bbfd3e3a42b564f6cceee268694e7"}, - {file = "mkdocs_include_markdown_plugin-6.2.2.tar.gz", hash = "sha256:f2bd5026650492a581d2fd44be6c22f90391910d76582b96a34c264f2d17875d"}, + {file = "mkdocs_include_markdown_plugin-7.1.2-py3-none-any.whl", hash = "sha256:ff1175d1b4f83dea6a38e200d6f0c3db10308975bf60c197d31172671753dbc4"}, + {file = "mkdocs_include_markdown_plugin-7.1.2.tar.gz", hash = "sha256:1b393157b1aa231b0e6c59ba80f52b723f4b7827bb7a1264b505334f8542aaf1"}, ] [package.dependencies] @@ -1273,13 +1285,13 @@ cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "9.5.40" +version = "9.5.48" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.40-py3-none-any.whl", hash = "sha256:8e7a16ada34e79a7b6459ff2602584222f522c738b6a023d1bea853d5049da6f"}, - {file = "mkdocs_material-9.5.40.tar.gz", hash = "sha256:b69d70e667ec51fc41f65e006a3184dd00d95b2439d982cb1586e4c018943156"}, + {file = "mkdocs_material-9.5.48-py3-none-any.whl", hash = "sha256:b695c998f4b939ce748adbc0d3bff73fa886a670ece948cf27818fa115dc16f8"}, + {file = "mkdocs_material-9.5.48.tar.gz", hash = "sha256:a582531e8b34f4c7ed38c29d5c44763053832cf2a32f7409567e0c74749a47db"}, ] [package.dependencies] @@ -1313,13 +1325,13 @@ files = [ [[package]] name = "mkdocstrings" -version = "0.26.2" +version = "0.27.0" description = "Automatic documentation from sources, for MkDocs." optional = false python-versions = ">=3.9" files = [ - {file = "mkdocstrings-0.26.2-py3-none-any.whl", hash = "sha256:1248f3228464f3b8d1a15bd91249ce1701fe3104ac517a5f167a0e01ca850ba5"}, - {file = "mkdocstrings-0.26.2.tar.gz", hash = "sha256:34a8b50f1e6cfd29546c6c09fbe02154adfb0b361bb758834bf56aa284ba876e"}, + {file = "mkdocstrings-0.27.0-py3-none-any.whl", hash = "sha256:6ceaa7ea830770959b55a16203ac63da24badd71325b96af950e59fd37366332"}, + {file = "mkdocstrings-0.27.0.tar.gz", hash = "sha256:16adca6d6b0a1f9e0c07ff0b02ced8e16f228a9d65a37c063ec4c14d7b76a657"}, ] [package.dependencies] @@ -1339,13 +1351,13 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.12.1" +version = "1.12.2" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.9" files = [ - {file = "mkdocstrings_python-1.12.1-py3-none-any.whl", hash = "sha256:205244488199c9aa2a39787ad6a0c862d39b74078ea9aa2be817bc972399563f"}, - {file = "mkdocstrings_python-1.12.1.tar.gz", hash = "sha256:60d6a5ca912c9af4ad431db6d0111ce9f79c6c48d33377dde6a05a8f5f48d792"}, + {file = "mkdocstrings_python-1.12.2-py3-none-any.whl", hash = "sha256:7f7d40d6db3cb1f5d19dbcd80e3efe4d0ba32b073272c0c0de9de2e604eda62a"}, + {file = "mkdocstrings_python-1.12.2.tar.gz", hash = "sha256:7a1760941c0b52a2cd87b960a9e21112ffe52e7df9d0b9583d04d47ed2e186f3"}, ] [package.dependencies] @@ -1384,13 +1396,13 @@ files = [ [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] @@ -1450,106 +1462,101 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.47" +version = "8.13.52" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." optional = false python-versions = "*" files = [ - {file = "phonenumbers-8.13.47-py2.py3-none-any.whl", hash = "sha256:5d3c0142ef7055ca5551884352e3b6b93bfe002a0bc95b8eaba39b0e2184541b"}, - {file = "phonenumbers-8.13.47.tar.gz", hash = "sha256:53c5e7c6d431cafe4efdd44956078404ae9bc8b0eacc47be3105d3ccc88aaffa"}, + {file = "phonenumbers-8.13.52-py2.py3-none-any.whl", hash = "sha256:e803210038ece9d208b129e3023dc20e656a820d6bf6f1cb0471d4164f54bada"}, + {file = "phonenumbers-8.13.52.tar.gz", hash = "sha256:fdc371ea6a4da052beb1225de63963d5a2fddbbff2bb53e3a957f360e0185f80"}, ] [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -1691,22 +1698,19 @@ files = [ [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] +pydantic-core = "2.27.1" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -1714,100 +1718,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, + {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, + {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, ] [package.dependencies] @@ -1815,31 +1830,27 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-extra-types" -version = "2.9.0" +version = "2.10.1" description = "Extra Pydantic types." optional = false python-versions = ">=3.8" -files = [] -develop = false +files = [ + {file = "pydantic_extra_types-2.10.1-py3-none-any.whl", hash = "sha256:db2c86c04a837bbac0d2d79bbd6f5d46c4c9253db11ca3fdd36a2b282575f1e2"}, + {file = "pydantic_extra_types-2.10.1.tar.gz", hash = "sha256:e4f937af34a754b8f1fa228a2fac867091a51f56ed0e8a61d5b3a6719b13c923"}, +] [package.dependencies] pydantic = ">=2.5.2" typing-extensions = "*" [package.extras] -all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<3)", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"] +all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<4)", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"] pendulum = ["pendulum (>=3.0.0,<4.0.0)"] phonenumbers = ["phonenumbers (>=8,<9)"] pycountry = ["pycountry (>=23)"] -python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<3)"] +python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<4)"] semver = ["semver (>=3.0.2)"] -[package.source] -type = "git" -url = "https://github.com/pydantic/pydantic-extra-types.git" -reference = "58db4b0" -resolved_reference = "58db4b096d7c90566d3d48d51b4665c01a591df6" - [[package]] name = "pygments" version = "2.18.0" @@ -1856,13 +1867,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.11.2" +version = "10.12" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf"}, - {file = "pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049"}, + {file = "pymdown_extensions-10.12-py3-none-any.whl", hash = "sha256:49f81412242d3527b8b4967b990df395c89563043bc51a3d2d7d500e52123b77"}, + {file = "pymdown_extensions-10.12.tar.gz", hash = "sha256:b0ee1e0b2bef1071a47891ab17003bfe5bf824a398e13f49f8ed653b699369a7"}, ] [package.dependencies] @@ -1874,13 +1885,13 @@ extra = ["pygments (>=2.12)"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -1894,17 +1905,17 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-cov" -version = "5.0.0" +version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] @@ -2020,13 +2031,13 @@ pyyaml = "*" [[package]] name = "redis" -version = "5.1.1" +version = "5.2.1" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.8" files = [ - {file = "redis-5.1.1-py3-none-any.whl", hash = "sha256:f8ea06b7482a668c6475ae202ed8d9bcaa409f6e87fb77ed1043d912afd62e24"}, - {file = "redis-5.1.1.tar.gz", hash = "sha256:f6c997521fedbae53387307c5d0bf784d9acc28d9f1d058abeac566ec4dbed72"}, + {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"}, + {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"}, ] [package.dependencies] @@ -2038,105 +2049,105 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)" [[package]] name = "regex" -version = "2024.9.11" +version = "2024.11.6" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, - {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, - {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, - {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, - {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, - {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, - {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, - {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, - {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, - {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, - {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, - {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, - {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, - {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, - {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, - {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, - {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, - {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, - {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, - {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, - {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, - {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, - {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, - {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, - {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, - {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, - {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, - {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, - {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, - {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, - {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, - {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, ] [[package]] @@ -2250,40 +2261,40 @@ files = [ [[package]] name = "ruff" -version = "0.6.9" +version = "0.8.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, + {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, + {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, + {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, + {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, + {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, + {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, + {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, + {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, + {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, ] [[package]] name = "sentry-sdk" -version = "2.16.0" +version = "2.19.2" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.16.0-py2.py3-none-any.whl", hash = "sha256:49139c31ebcd398f4f6396b18910610a0c1602f6e67083240c33019d1f6aa30c"}, - {file = "sentry_sdk-2.16.0.tar.gz", hash = "sha256:90f733b32e15dfc1999e6b7aca67a38688a567329de4d6e184154a73f96c6892"}, + {file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"}, + {file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"}, ] [package.dependencies] @@ -2309,14 +2320,16 @@ grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] http2 = ["httpcore[http2] (==1.*)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] -huggingface-hub = ["huggingface-hub (>=0.22)"] +huggingface-hub = ["huggingface_hub (>=0.22)"] langchain = ["langchain (>=0.0.210)"] +launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"] litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +openfeature = ["openfeature-sdk (>=0.7.1)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro"] -pure-eval = ["asttokens", "executing", "pure-eval"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -2329,13 +2342,13 @@ tornado = ["tornado (>=6)"] [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -2479,13 +2492,13 @@ test = ["pytest"] [[package]] name = "sqlparse" -version = "0.5.1" +version = "0.5.3" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" files = [ - {file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"}, - {file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"}, + {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, + {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, ] [package.extras] @@ -2513,13 +2526,43 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "tomli" -version = "2.0.2" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, - {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] @@ -2578,13 +2621,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.26.6" +version = "20.28.0" description = "Virtual Python Environment builder" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, - {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] [package.dependencies] @@ -2598,41 +2641,41 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "5.0.3" +version = "6.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" files = [ - {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea"}, - {file = "watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb"}, - {file = "watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490"}, - {file = "watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926"}, - {file = "watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906"}, - {file = "watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2"}, - {file = "watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627"}, - {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7"}, - {file = "watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8"}, - {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e"}, - {file = "watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97"}, - {file = "watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7"}, - {file = "watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49"}, - {file = "watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9"}, - {file = "watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45"}, - {file = "watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] [package.extras] @@ -2681,4 +2724,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "e64ed169395d2c32672a2f2ad6a40d0910e4a51941b564fbdc505db6332084d2" +content-hash = "5836c1a8ad42645d7d045194c8c371754b19957ebdcd2aaa902a2fb3dc97cc53" diff --git a/pyproject.toml b/pyproject.toml index 40086159..be892cdf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,41 +21,38 @@ license = "GPL-3.0-only" [tool.poetry.dependencies] python = "^3.12" -Django = "^4.2.14" +Django = "^4.2.17" django-ninja = "^1.3.0" -django-ninja-extra = "^0.21.4" -Pillow = "^10.4.0" +django-ninja-extra = "^0.21.8" +Pillow = "^11.0.0" mistune = "^3.0.2" -django-jinja = "^2.11" -cryptography = "^43.0.0" +django-jinja = "^2.11.0" +cryptography = "^44.0.0" django-phonenumber-field = "^8.0.0" -phonenumbers = "^8.13" -reportlab = "^4.2" -django-haystack = "^3.2.1" -xapian-haystack = "^3.0.1" -libsass = "^0.23" -django-ordered-model = "^3.7" +phonenumbers = "^8.13.52" +reportlab = "^4.2.5" +django-haystack = "^3.3.0" +xapian-haystack = "^3.1.0" +libsass = "^0.23.0" +django-ordered-model = "^3.7.4" django-simple-captcha = "^0.6.0" -python-dateutil = "^2.8.2" -sentry-sdk = "^2.16.0" -Jinja2 = "^3.1" +python-dateutil = "^2.9.0.post0" +sentry-sdk = "^2.19.2" +Jinja2 = "^3.1.4" django-countries = "^7.6.1" -dict2xml = "^1.7.3" +dict2xml = "^1.7.6" Sphinx = "^5" # Needed for building xapian -tomli = "^2.0.1" +tomli = "^2.2.1" django-honeypot = "^1.2.1" -# When I introduced pydantic-extra-types, I needed *right now* -# the PhoneNumberValidator class which was on the master branch but not released yet. -# Once it's released, switch this to a regular version. -pydantic-extra-types = { git = "https://github.com/pydantic/pydantic-extra-types.git", rev = "58db4b0" } +pydantic-extra-types = "^2.10.1" [tool.poetry.group.prod.dependencies] # deps used in prod, but unnecessary for development -# The C extra triggers compilation against sytem libs during install. +# The C extra triggers compilation against system libs during install. # Removing it would switch psycopg to a slower full-python implementation -psycopg = {extras = ["c"], version = "^3.2.1"} -redis = {extras = ["hiredis"], version = "^5.0.8"} +psycopg = {extras = ["c"], version = "^3.2.3"} +redis = {extras = ["hiredis"], version = "^5.2.0"} [tool.poetry.group.prod] optional = true @@ -63,28 +60,28 @@ optional = true [tool.poetry.group.dev.dependencies] # deps used for development purposes, but unneeded in prod django-debug-toolbar = "^4.4.6" -ipython = "^8.26.0" +ipython = "^8.30.0" pre-commit = "^4.0.1" -ruff = "^0.6.9" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml -djhtml = "^3.0.6" -faker = "^30.3.0" -rjsmin = "^1.2.2" +ruff = "^0.8.3" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml +djhtml = "^3.0.7" +faker = "^33.1.0" +rjsmin = "^1.2.3" [tool.poetry.group.tests.dependencies] # deps used for testing purposes freezegun = "^1.5.1" # used to test time-dependent code -pytest = "^8.3.2" -pytest-cov = "^5.0.0" +pytest = "^8.3.4" +pytest-cov = "^6.0.0" pytest-django = "^4.9.0" model-bakery = "^1.20.0" [tool.poetry.group.docs.dependencies] # deps used to work on the documentation mkdocs = "^1.6.1" -mkdocs-material = "^9.5.40" -mkdocstrings = "^0.26.2" -mkdocstrings-python = "^1.12.0" -mkdocs-include-markdown-plugin = "^6.2.2" +mkdocs-material = "^9.5.47" +mkdocstrings = "^0.27.0" +mkdocstrings-python = "^1.12.2" +mkdocs-include-markdown-plugin = "^7.1.2" [tool.poetry.group.docs] optional = true diff --git a/sas/tests/test_api.py b/sas/tests/test_api.py index ebc33638..733838d2 100644 --- a/sas/tests/test_api.py +++ b/sas/tests/test_api.py @@ -1,10 +1,10 @@ from django.conf import settings +from django.core.cache import cache from django.db import transaction from django.test import TestCase from django.urls import reverse from model_bakery import baker from model_bakery.recipe import Recipe -from pytest_django.asserts import assertNumQueries from core.baker_recipes import old_subscriber_user, subscriber_user from core.models import RealGroup, SithFile, User @@ -128,9 +128,11 @@ class TestPictureSearch(TestSas): def test_num_queries(self): """Test that the number of queries is stable.""" self.client.force_login(subscriber_user.make()) - with assertNumQueries(5): + cache.clear() + with self.assertNumQueries(7): + # 2 requests to create the session # 1 request to fetch the user from the db - # 2 requests to check the user permissions + # 2 requests to check the user permissions, depends on the db engine # 1 request to fetch the pictures # 1 request to count the total number of items in the pagination self.client.get(self.url) diff --git a/sith/settings.py b/sith/settings.py index a06043ef..054787e7 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -351,6 +351,9 @@ SITH_SEMESTER_START_SPRING = (2, 15) # 15 February # Used to determine the valid promos SITH_SCHOOL_START_YEAR = 1999 +# id of the Root account +SITH_ROOT_USER_ID = 0 + SITH_GROUP_ROOT_ID = 1 SITH_GROUP_PUBLIC_ID = 2 SITH_GROUP_SUBSCRIBERS_ID = 3 @@ -429,14 +432,6 @@ SITH_SUBSCRIPTION_LOCATIONS = [ SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] -SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"} - -SITH_COUNTER_PAYMENT_METHOD = [ - ("CHECK", _("Check")), - ("CASH", _("Cash")), - ("CARD", _("Credit card")), -] - SITH_COUNTER_BANK = [ ("OTHER", "Autre"), ("SOCIETE-GENERALE", "Société générale"), diff --git a/sith/tests.py b/sith/tests.py index c205fe93..e99eeff2 100644 --- a/sith/tests.py +++ b/sith/tests.py @@ -26,7 +26,8 @@ def test_sentry_debug_endpoint( expected_error: RaisesContext[ZeroDivisionError] | does_not_raise[None], expected_return_code: int | None, ): - with expected_error, override_settings( - SENTRY_DSN=sentry_dsn, SENTRY_ENV=sentry_env + with ( + expected_error, + override_settings(SENTRY_DSN=sentry_dsn, SENTRY_ENV=sentry_env), ): assert client.get(reverse("sentry-debug")).status_code == expected_return_code diff --git a/subscription/templates/subscription/fragments/creation_success.jinja b/subscription/templates/subscription/fragments/creation_success.jinja index 6a50c2e3..7e6e1659 100644 --- a/subscription/templates/subscription/fragments/creation_success.jinja +++ b/subscription/templates/subscription/fragments/creation_success.jinja @@ -4,13 +4,9 @@ {% trans user=subscription.member %}Subscription created for {{ user }}{% endtrans %}

- {% trans trimmed - user=subscription.member.get_short_name(), - type=subscription.subscription_type, - end=subscription.subscription_end - %} - {{ user }} received its new {{ type }} subscription. - It will be active until {{ end }} included. + {% trans trimmed user=subscription.member.get_short_name(), type=subscription.subscription_type, end=subscription.subscription_end %} + {{ user }} received its new {{ type }} subscription. + It will be active until {{ end }} included. {% endtrans %}

diff --git a/subscription/views.py b/subscription/views.py index 2948391b..b285a137 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -21,6 +21,7 @@ from django.utils.timezone import localdate from django.views.generic import CreateView, DetailView, TemplateView from django.views.generic.edit import FormView +from counter.apps import PAYMENT_METHOD from subscription.forms import ( SelectionDateForm, SubscriptionExistingUserForm, @@ -108,6 +109,6 @@ class SubscriptionsStatsView(FormView): subscription_end__gte=self.end_date, subscription_start__lte=self.start_date ) kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS - kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD + kwargs["payment_types"] = PAYMENT_METHOD kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS return kwargs