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

Panier

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

{{ error }}

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

{{ error }}

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

{% trans %}Eboutic{% endtrans %}

+

{% trans %}Eboutic{% endtrans %}

-
-

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

- - +
+

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

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

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

- {% if settings.SITH_EBOUTIC_CB_ENABLED %} -

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

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

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

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

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

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

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

+ {% else %} +
+ {% csrf_token %} + + +
+ {% endif %} +
{% endblock %} +{% block script %} + + {{ super() }} +{% endblock %} diff --git a/eboutic/tests.py b/eboutic/tests.py index 98b73014..b5d4d6ce 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -24,7 +24,6 @@ # import base64 import json -import re import urllib from OpenSSL import crypto @@ -40,18 +39,19 @@ from eboutic.models import Basket class EbouticTest(TestCase): - def setUp(self): + @classmethod + def setUpTestData(cls): call_command("populate") - self.skia = User.objects.filter(username="skia").first() - self.subscriber = User.objects.filter(username="subscriber").first() - self.old_subscriber = User.objects.filter(username="old_subscriber").first() - self.public = User.objects.filter(username="public").first() - self.barbar = Product.objects.filter(code="BARB").first() - self.refill = Product.objects.filter(code="15REFILL").first() - self.cotis = Product.objects.filter(code="1SCOTIZ").first() - self.eboutic = Counter.objects.filter(name="Eboutic").first() + cls.barbar = Product.objects.filter(code="BARB").first() + cls.refill = Product.objects.filter(code="15REFILL").first() + cls.cotis = Product.objects.filter(code="1SCOTIZ").first() + cls.eboutic = Counter.objects.filter(name="Eboutic").first() + cls.skia = User.objects.filter(username="skia").first() + cls.subscriber = User.objects.filter(username="subscriber").first() + cls.old_subscriber = User.objects.filter(username="old_subscriber").first() + cls.public = User.objects.filter(username="public").first() - def get_busy_basket(self, user): + def get_busy_basket(self, user) -> Basket: """ Create and return a basket with 3 barbar and 1 cotis in it. Edit the client session to store the basket id in it @@ -64,11 +64,11 @@ class EbouticTest(TestCase): basket.add_product(self.cotis) return basket - def generate_bank_valid_answer_from_page_content(self, content): - content = str(content) - basket_id = re.search(r"PBX_CMD\" value=\"(\d*)\"", content).group(1) - amount = re.search(r"PBX_TOTAL\" value=\"(\d*)\"", content).group(1) - query = "Amount=%s&BasketID=%s&Auto=42&Error=00000" % (amount, basket_id) + def generate_bank_valid_answer(self) -> str: + basket = Basket.from_session(self.client.session) + basket_id = basket.id + amount = int(basket.get_total() * 100) + query = f"Amount={amount}&BasketID={basket_id}&Auto=42&Error=00000" with open("./eboutic/tests/private_key.pem") as f: PRIVKEY = f.read() with open("./eboutic/tests/public_key.pem") as f: @@ -81,8 +81,7 @@ class EbouticTest(TestCase): query, urllib.parse.quote_plus(b64sig), ) - response = self.client.get(url) - return response + return url def test_buy_with_sith_account(self): self.client.login(username="subscriber", password="plop") @@ -102,7 +101,7 @@ class EbouticTest(TestCase): def test_buy_with_sith_account_no_money(self): self.client.login(username="subscriber", password="plop") basket = self.get_busy_basket(self.subscriber) - initial = basket.get_total() - 1 + initial = basket.get_total() - 1 # just not enough to complete the sale self.subscriber.customer.amount = initial self.subscriber.customer.save() response = self.client.post(reverse("eboutic:pay_with_sith")) @@ -122,7 +121,7 @@ class EbouticTest(TestCase): {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28}, {"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertEqual(response.status_code, 200) self.assertInHTML( "Cotis 2 semestres128.00 €", @@ -146,7 +145,7 @@ class EbouticTest(TestCase): def test_submit_empty_basket(self): self.client.login(username="subscriber", password="plop") self.client.cookies["basket_items"] = "[]" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertRedirects(response, "/eboutic/") def test_submit_invalid_basket(self): @@ -157,7 +156,7 @@ class EbouticTest(TestCase): ] = f"""[ {{"id": {max_id + 1}, "name": "", "quantity": 1, "unit_price": 28}} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertIn( 'basket_items=""', self.client.cookies["basket_items"].OutputString(), @@ -175,7 +174,7 @@ class EbouticTest(TestCase): ] = """[ {"id": 4, "name": "Barbar", "quantity": -1, "unit_price": 1.7} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertRedirects(response, "/eboutic/") def test_buy_subscribe_product_with_credit_card(self): @@ -189,14 +188,14 @@ class EbouticTest(TestCase): ] = """[ {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28} ]""" - response = self.client.post(reverse("eboutic:command")) + response = self.client.get(reverse("eboutic:command")) self.assertInHTML( "Cotis 2 semestres128.00 €", response.content.decode(), ) basket = Basket.objects.get(id=self.client.session["basket_id"]) self.assertEqual(basket.items.count(), 1) - response = self.generate_bank_valid_answer_from_page_content(response.content) + response = self.client.get(self.generate_bank_valid_answer()) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode("utf-8") == "Payment successful") @@ -215,9 +214,10 @@ class EbouticTest(TestCase): [{"id": 3, "name": "Rechargement 15 €", "quantity": 1, "unit_price": 15}] ) initial_balance = self.subscriber.customer.amount - response = self.client.post(reverse("eboutic:command")) + self.client.get(reverse("eboutic:command")) - response = self.generate_bank_valid_answer_from_page_content(response.content) + url = self.generate_bank_valid_answer() + response = self.client.get(url) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode() == "Payment successful") new_balance = Customer.objects.get(user=self.subscriber).amount @@ -228,14 +228,15 @@ class EbouticTest(TestCase): self.client.cookies["basket_items"] = json.dumps( [{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}] ) - response = self.client.post(reverse("eboutic:command")) + self.client.get(reverse("eboutic:command")) + et_answer_url = self.generate_bank_valid_answer() self.client.cookies["basket_items"] = json.dumps( [ # alter basket {"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7} ] ) - self.client.post(reverse("eboutic:command")) - response = self.generate_bank_valid_answer_from_page_content(response.content) + self.client.get(reverse("eboutic:command")) + response = self.client.get(et_answer_url) self.assertEqual(response.status_code, 500) self.assertIn( "Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'", @@ -247,8 +248,9 @@ class EbouticTest(TestCase): self.client.cookies["basket_items"] = json.dumps( [{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}] ) - response = self.client.post(reverse("eboutic:command")) - response = self.generate_bank_valid_answer_from_page_content(response.content) + self.client.get(reverse("eboutic:command")) + et_answer_url = self.generate_bank_valid_answer() + response = self.client.get(et_answer_url) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode("utf-8") == "Payment successful") diff --git a/eboutic/urls.py b/eboutic/urls.py index 417a8b85..a1bd6ecd 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -34,8 +34,9 @@ urlpatterns = [ # Subscription views path("", eboutic_main, name="main"), path("command/", EbouticCommand.as_view(), name="command"), - path("pay/", pay_with_sith, name="pay_with_sith"), + path("pay/sith/", pay_with_sith, name="pay_with_sith"), path("pay//", payment_result, name="payment_result"), + path("et_data/", e_transaction_data, name="et_data"), path( "et_autoanswer", EtransactionAutoAnswer.as_view(), diff --git a/eboutic/views.py b/eboutic/views.py index b478397a..811872ec 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -23,13 +23,10 @@ # import base64 -import hmac import json -from collections import OrderedDict from datetime import datetime + import sentry_sdk - - from OpenSSL import crypto from django.conf import settings from django.contrib.auth.decorators import login_required @@ -41,7 +38,8 @@ from django.utils.decorators import method_decorator from django.views.decorators.http import require_GET, require_POST from django.views.generic import TemplateView, View -from counter.models import Customer, Counter, Selling +from counter.forms import BillingInfoForm +from counter.models import Customer, Counter, Product from eboutic.forms import BasketForm from eboutic.models import Basket, Invoice, InvoiceItem, get_eboutic_products @@ -85,11 +83,11 @@ class EbouticCommand(TemplateView): template_name = "eboutic/eboutic_makecommand.jinja" @method_decorator(login_required) - def get(self, request, *args, **kwargs): + def post(self, request, *args, **kwargs): return redirect("eboutic:main") @method_decorator(login_required) - def post(self, request: HttpRequest, *args, **kwargs): + def get(self, request: HttpRequest, *args, **kwargs): form = BasketForm(request) if not form.is_valid(): request.session["errors"] = form.get_error_messages() @@ -98,65 +96,56 @@ class EbouticCommand(TemplateView): res.set_cookie("basket_items", form.get_cleaned_cookie(), path="/eboutic") return res - if "basket_id" in request.session: - basket, _ = Basket.objects.get_or_create( - id=request.session["basket_id"], user=request.user - ) + basket = Basket.from_session(request.session) + if basket is not None: basket.clear() else: basket = Basket.objects.create(user=request.user) + request.session["basket_id"] = basket.id + request.session.modified = True - basket.save() - eboutique = Counter.objects.get(type="EBOUTIC") - for item in json.loads(request.COOKIES["basket_items"]): - basket.add_product( - eboutique.products.get(id=(item["id"])), item["quantity"] - ) - request.session["basket_id"] = basket.id - request.session.modified = True + items = json.loads(request.COOKIES["basket_items"]) + items.sort(key=lambda item: item["id"]) + ids = [item["id"] for item in items] + quantities = [item["quantity"] for item in items] + products = Product.objects.filter(id__in=ids) + for product, qty in zip(products, quantities): + basket.add_product(product, qty) kwargs["basket"] = basket return self.render_to_response(self.get_context_data(**kwargs)) def get_context_data(self, **kwargs): - kwargs = super(EbouticCommand, self).get_context_data(**kwargs) + # basket is already in kwargs when the method is called + default_billing_info = None if hasattr(self.request.user, "customer"): - kwargs["customer_amount"] = self.request.user.customer.amount + customer = self.request.user.customer + kwargs["customer_amount"] = customer.amount + if hasattr(customer, "billing_infos"): + default_billing_info = customer.billing_infos else: kwargs["customer_amount"] = None - kwargs["et_request"] = OrderedDict() - kwargs["et_request"]["PBX_SITE"] = settings.SITH_EBOUTIC_PBX_SITE - kwargs["et_request"]["PBX_RANG"] = settings.SITH_EBOUTIC_PBX_RANG - kwargs["et_request"]["PBX_IDENTIFIANT"] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT - kwargs["et_request"]["PBX_TOTAL"] = int(kwargs["basket"].get_total() * 100) - kwargs["et_request"][ - "PBX_DEVISE" - ] = 978 # This is Euro. ET support only this value anyway - kwargs["et_request"]["PBX_CMD"] = kwargs["basket"].id - kwargs["et_request"]["PBX_PORTEUR"] = kwargs["basket"].user.email - kwargs["et_request"]["PBX_RETOUR"] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" - kwargs["et_request"]["PBX_HASH"] = "SHA512" - kwargs["et_request"]["PBX_TYPEPAIEMENT"] = "CARTE" - kwargs["et_request"]["PBX_TYPECARTE"] = "CB" - kwargs["et_request"]["PBX_TIME"] = str( - datetime.now().replace(microsecond=0).isoformat("T") - ) - kwargs["et_request"]["PBX_HMAC"] = ( - hmac.new( - settings.SITH_EBOUTIC_HMAC_KEY, - bytes( - "&".join( - ["%s=%s" % (k, v) for k, v in kwargs["et_request"].items()] - ), - "utf-8", - ), - "sha512", - ) - .hexdigest() - .upper() - ) + kwargs["must_fill_billing_infos"] = default_billing_info is None + if not kwargs["must_fill_billing_infos"]: + # the user has already filled its billing_infos, thus we can + # get it without expecting an error + data = kwargs["basket"].get_e_transaction_data() + data = {"data": [{"key": key, "value": val} for key, val in data]} + kwargs["billing_infos"] = json.dumps(data) + kwargs["billing_form"] = BillingInfoForm(instance=default_billing_info) return kwargs +@login_required +@require_GET +def e_transaction_data(request): + basket = Basket.from_session(request.session) + if basket is None: + return HttpResponse(status=404, content=json.dumps({"data": []})) + data = basket.get_e_transaction_data() + data = {"data": [{"key": key, "value": val} for key, val in data]} + return HttpResponse(status=200, content=json.dumps(data)) + + @login_required @require_POST def pay_with_sith(request): @@ -171,24 +160,14 @@ def pay_with_sith(request): res = redirect("eboutic:payment_result", "failure") else: eboutic = Counter.objects.filter(type="EBOUTIC").first() + sales = basket.generate_sales(eboutic, c.user, "SITH_ACCOUNT") try: with transaction.atomic(): - for it in basket.items.all(): - product = eboutic.products.get(id=it.product_id) - Selling( - label=it.product_name, - counter=eboutic, - club=product.club, - product=product, - seller=c.user, - customer=c, - unit_price=it.product_unit_price, - quantity=it.quantity, - payment_method="SITH_ACCOUNT", - ).save() + for sale in sales: + sale.save() basket.delete() - request.session.pop("basket_id", None) - res = redirect("eboutic:payment_result", "success") + request.session.pop("basket_id", None) + res = redirect("eboutic:payment_result", "success") except DatabaseError as e: with sentry_sdk.push_scope() as scope: scope.user = {"username": request.user.username} @@ -205,12 +184,8 @@ class EtransactionAutoAnswer(View): # Response documentation http://www1.paybox.com/espace-integrateur-documentation # /la-solution-paybox-system/gestion-de-la-reponse/ def get(self, request, *args, **kwargs): - if ( - not "Amount" in request.GET.keys() - or not "BasketID" in request.GET.keys() - or not "Error" in request.GET.keys() - or not "Sig" in request.GET.keys() - ): + required = {"Amount", "BasketID", "Error", "Sig"} + if not required.issubset(set(request.GET.keys())): return HttpResponse("Bad arguments", status=400) key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) cert = crypto.X509() diff --git a/launderette/views.py b/launderette/views.py index 0387c3d3..7c15dc6d 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -41,7 +41,8 @@ from club.models import Club from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin from launderette.models import Launderette, Token, Machine, Slot from counter.models import Counter, Customer, Selling -from counter.views import GetUserForm +from counter.forms import GetUserForm + # For users diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 799a4fb6..e2b442a5 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-15 20:59+0100\n" +"POT-Creation-Date: 2022-11-28 16:54+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -18,8 +18,8 @@ msgstr "" #: accounting/models.py:61 accounting/models.py:110 accounting/models.py:143 #: accounting/models.py:216 club/models.py:48 com/models.py:279 -#: com/models.py:296 counter/models.py:131 counter/models.py:159 -#: counter/models.py:243 forum/models.py:58 launderette/models.py:38 +#: com/models.py:296 counter/models.py:199 counter/models.py:232 +#: counter/models.py:316 forum/models.py:58 launderette/models.py:38 #: launderette/models.py:93 launderette/models.py:131 stock/models.py:40 #: stock/models.py:63 stock/models.py:105 stock/models.py:133 msgid "name" @@ -66,8 +66,8 @@ msgid "account number" msgstr "numero de compte" #: accounting/models.py:116 accounting/models.py:147 club/models.py:275 -#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:177 -#: counter/models.py:245 trombi/models.py:217 +#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:250 +#: counter/models.py:318 trombi/models.py:217 msgid "club" msgstr "club" @@ -88,12 +88,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:214 club/models.py:281 counter/models.py:679 +#: accounting/models.py:214 club/models.py:281 counter/models.py:752 #: election/models.py:18 launderette/models.py:194 msgid "start date" msgstr "date de début" -#: accounting/models.py:215 club/models.py:282 counter/models.py:680 +#: accounting/models.py:215 club/models.py:282 counter/models.py:753 #: election/models.py:19 msgid "end date" msgstr "date de fin" @@ -106,8 +106,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:225 accounting/models.py:289 counter/models.py:56 -#: counter/models.py:401 +#: accounting/models.py:225 accounting/models.py:289 counter/models.py:60 +#: counter/models.py:474 msgid "amount" msgstr "montant" @@ -129,18 +129,18 @@ msgstr "classeur" #: accounting/models.py:290 core/models.py:862 core/models.py:1400 #: core/models.py:1448 core/models.py:1477 core/models.py:1501 -#: counter/models.py:411 counter/models.py:504 counter/models.py:709 -#: eboutic/models.py:61 eboutic/models.py:148 forum/models.py:311 +#: counter/models.py:484 counter/models.py:577 counter/models.py:782 +#: eboutic/models.py:66 eboutic/models.py:240 forum/models.py:311 #: forum/models.py:408 stock/models.py:104 msgid "date" msgstr "date" -#: accounting/models.py:291 counter/models.py:133 counter/models.py:710 +#: accounting/models.py:291 counter/models.py:201 counter/models.py:783 #: pedagogy/models.py:219 stock/models.py:107 msgid "comment" msgstr "commentaire" -#: accounting/models.py:293 counter/models.py:413 counter/models.py:506 +#: accounting/models.py:293 counter/models.py:486 counter/models.py:579 #: subscription/models.py:66 msgid "payment method" msgstr "méthode de paiement" @@ -149,7 +149,7 @@ msgstr "méthode de paiement" msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:303 eboutic/models.py:233 +#: accounting/models.py:303 eboutic/models.py:332 msgid "invoice" msgstr "facture" @@ -167,7 +167,7 @@ msgstr "type comptable" #: accounting/models.py:328 accounting/models.py:475 accounting/models.py:510 #: accounting/models.py:545 core/models.py:1476 core/models.py:1502 -#: counter/models.py:470 +#: counter/models.py:543 msgid "label" msgstr "étiquette" @@ -266,7 +266,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:467 counter/models.py:169 pedagogy/models.py:46 +#: accounting/models.py:467 counter/models.py:242 pedagogy/models.py:46 msgid "code" msgstr "code" @@ -378,7 +378,7 @@ msgstr "Compte en banque : " #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:226 pedagogy/templates/pedagogy/guide.jinja:67 +#: launderette/views.py:227 pedagogy/templates/pedagogy/guide.jinja:67 #: pedagogy/templates/pedagogy/guide.jinja:90 #: pedagogy/templates/pedagogy/guide.jinja:126 #: pedagogy/templates/pedagogy/uv_detail.jinja:185 @@ -670,7 +670,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1201 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1064 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:138 #: trombi/templates/trombi/comment.jinja:4 @@ -930,7 +930,7 @@ msgstr "S'abonner" msgid "Remove" msgstr "Retirer" -#: club/forms.py:76 launderette/views.py:228 +#: club/forms.py:76 launderette/views.py:229 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" @@ -955,11 +955,11 @@ msgstr "Une action est requise" msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:162 counter/views.py:1589 +#: club/forms.py:162 counter/forms.py:157 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/views.py:1590 +#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/forms.py:158 #: election/views.py:172 subscription/views.py:49 msgid "End date" msgstr "Date de fin" @@ -967,15 +967,15 @@ msgstr "Date de fin" #: club/forms.py:166 club/templates/club/club_sellings.jinja:21 #: core/templates/core/user_account_detail.jinja:18 #: core/templates/core/user_account_detail.jinja:51 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:216 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:148 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:174 counter/views.py:830 +#: club/forms.py:174 counter/views.py:762 msgid "Products" msgstr "Produits" -#: club/forms.py:179 counter/views.py:835 +#: club/forms.py:179 counter/views.py:767 msgid "Archived products" msgstr "Produits archivés" @@ -1045,8 +1045,8 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs" msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:267 counter/models.py:670 counter/models.py:700 -#: eboutic/models.py:57 eboutic/models.py:144 election/models.py:192 +#: club/models.py:267 counter/models.py:743 counter/models.py:773 +#: eboutic/models.py:62 eboutic/models.py:236 election/models.py:192 #: launderette/models.py:145 launderette/models.py:213 sas/models.py:244 #: trombi/models.py:213 msgid "user" @@ -1057,8 +1057,8 @@ msgstr "nom d'utilisateur" msgid "role" msgstr "rôle" -#: club/models.py:289 core/models.py:81 counter/models.py:132 -#: counter/models.py:160 election/models.py:15 election/models.py:120 +#: club/models.py:289 core/models.py:81 counter/models.py:200 +#: counter/models.py:233 election/models.py:15 election/models.py:120 #: election/models.py:197 forum/models.py:59 forum/models.py:240 msgid "description" msgstr "description" @@ -1155,7 +1155,7 @@ msgstr "Il n'y a pas de membres dans ce club." #: club/templates/club/club_members.jinja:78 #: core/templates/core/file_detail.jinja:19 core/views/forms.py:345 -#: launderette/views.py:226 trombi/templates/trombi/detail.jinja:19 +#: launderette/views.py:227 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1560,7 +1560,7 @@ msgstr "Informations affichées" #: com/templates/com/news_admin_list.jinja:248 #: com/templates/com/news_admin_list.jinja:285 #: launderette/templates/launderette/launderette_admin.jinja:42 -#: launderette/views.py:233 +#: launderette/views.py:234 msgid "Type" msgstr "Type" @@ -1845,7 +1845,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:11 -#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:226 +#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:227 #: pedagogy/templates/pedagogy/uv_detail.jinja:12 #: pedagogy/templates/pedagogy/uv_detail.jinja:21 #: stock/templates/stock/shopping_list_items.jinja:9 @@ -2484,85 +2484,85 @@ msgstr "Forum" msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:188 counter/models.py:253 +#: core/templates/core/base.jinja:187 counter/models.py:326 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:23 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:8 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 #: sith/settings.py:366 sith/settings.py:374 msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:191 +#: core/templates/core/base.jinja:189 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:195 +#: core/templates/core/base.jinja:193 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:196 launderette/models.py:47 +#: core/templates/core/base.jinja:194 launderette/models.py:47 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:197 core/templates/core/file.jinja:20 +#: core/templates/core/base.jinja:195 core/templates/core/file.jinja:20 #: core/views/files.py:86 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:198 core/templates/core/user_tools.jinja:109 +#: core/templates/core/base.jinja:196 core/templates/core/user_tools.jinja:109 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:202 +#: core/templates/core/base.jinja:200 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:206 +#: core/templates/core/base.jinja:204 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:207 +#: core/templates/core/base.jinja:205 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:211 +#: core/templates/core/base.jinja:209 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:215 +#: core/templates/core/base.jinja:213 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:216 core/templates/core/base.jinja:258 +#: core/templates/core/base.jinja:214 core/templates/core/base.jinja:256 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:217 +#: core/templates/core/base.jinja:215 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:259 +#: core/templates/core/base.jinja:257 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:260 +#: core/templates/core/base.jinja:258 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:261 +#: core/templates/core/base.jinja:259 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:262 +#: core/templates/core/base.jinja:260 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:264 +#: core/templates/core/base.jinja:262 msgid "Site made by good people" msgstr "Site réalisé par des gens bons" @@ -3070,7 +3070,7 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:57 -#: core/templates/core/user_tools.jinja:37 counter/views.py:855 +#: core/templates/core/user_tools.jinja:37 counter/views.py:787 msgid "Etickets" msgstr "Etickets" @@ -3381,7 +3381,7 @@ msgstr "Achats" msgid "Product top 10" msgstr "Top 10 produits" -#: core/templates/core/user_stats.jinja:27 counter/views.py:1735 +#: core/templates/core/user_stats.jinja:27 counter/forms.py:168 msgid "Product" msgstr "Produit" @@ -3426,8 +3426,8 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:29 counter/views.py:825 -#: counter/views.py:1033 +#: core/templates/core/user_tools.jinja:29 counter/forms.py:131 +#: counter/views.py:757 msgid "Counters" msgstr "Comptoirs" @@ -3444,12 +3444,12 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:35 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:845 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:777 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:36 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:850 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:782 msgid "Invoices call" msgstr "Appels à facture" @@ -3678,7 +3678,7 @@ msgstr "Parrain / Marraine" msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:348 counter/views.py:156 trombi/views.py:158 +#: core/views/forms.py:348 counter/forms.py:47 trombi/views.py:158 msgid "Select user" msgstr "Choisir un utilisateur" @@ -3713,146 +3713,190 @@ msgstr "Photos" msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/app.py:31 counter/models.py:267 counter/models.py:676 -#: counter/models.py:706 launderette/models.py:41 stock/models.py:43 +#: counter/app.py:31 counter/models.py:340 counter/models.py:749 +#: counter/models.py:779 launderette/models.py:41 stock/models.py:43 msgid "counter" msgstr "comptoir" +#: counter/forms.py:30 +msgid "This UID is invalid" +msgstr "Cet UID est invalide" + +#: counter/forms.py:69 +msgid "User not found" +msgstr "Utilisateur non trouvé" + +#: counter/forms.py:117 +msgid "Parent product" +msgstr "Produit parent" + +#: counter/forms.py:123 +msgid "Buying groups" +msgstr "Groupes d'achat" + #: counter/migrations/0013_customer_recorded_products.py:26 msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:55 +#: counter/models.py:59 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:57 +#: counter/models.py:61 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:60 +#: counter/models.py:64 msgid "customer" msgstr "client" -#: counter/models.py:61 +#: counter/models.py:65 msgid "customers" msgstr "clients" -#: counter/models.py:107 counter/views.py:377 +#: counter/models.py:126 counter/views.py:309 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:137 counter/models.py:164 +#: counter/models.py:157 +msgid "First name" +msgstr "Prénom" + +#: counter/models.py:158 +msgid "Last name" +msgstr "Nom de famille" + +#: counter/models.py:159 +msgid "Address 1" +msgstr "Adresse 1" + +#: counter/models.py:161 +msgid "Address 2" +msgstr "Adresse 2" + +#: counter/models.py:163 +msgid "Zip code" +msgstr "Code postal" + +#: counter/models.py:164 +msgid "City" +msgstr "Ville" + +#: counter/models.py:165 +msgid "Country" +msgstr "Pays" + +#: counter/models.py:209 counter/models.py:237 msgid "product type" msgstr "type du produit" -#: counter/models.py:170 +#: counter/models.py:243 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:171 +#: counter/models.py:244 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:172 +#: counter/models.py:245 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:174 +#: counter/models.py:247 msgid "icon" msgstr "icône" -#: counter/models.py:179 +#: counter/models.py:252 msgid "limit age" msgstr "âge limite" -#: counter/models.py:180 +#: counter/models.py:253 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:184 +#: counter/models.py:257 msgid "parent product" msgstr "produit parent" -#: counter/models.py:190 +#: counter/models.py:263 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:192 election/models.py:52 +#: counter/models.py:265 election/models.py:52 msgid "archived" msgstr "archivé" -#: counter/models.py:195 counter/models.py:801 +#: counter/models.py:268 counter/models.py:874 msgid "product" msgstr "produit" -#: counter/models.py:248 +#: counter/models.py:321 msgid "products" msgstr "produits" -#: counter/models.py:251 +#: counter/models.py:324 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:253 +#: counter/models.py:326 msgid "Bar" msgstr "Bar" -#: counter/models.py:253 +#: counter/models.py:326 msgid "Office" msgstr "Bureau" -#: counter/models.py:256 +#: counter/models.py:329 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:264 launderette/models.py:207 +#: counter/models.py:337 launderette/models.py:207 msgid "token" msgstr "jeton" -#: counter/models.py:419 +#: counter/models.py:492 msgid "bank" msgstr "banque" -#: counter/models.py:421 counter/models.py:511 +#: counter/models.py:494 counter/models.py:584 msgid "is validated" msgstr "est validé" -#: counter/models.py:424 +#: counter/models.py:497 msgid "refilling" msgstr "rechargement" -#: counter/models.py:488 eboutic/models.py:209 +#: counter/models.py:561 eboutic/models.py:292 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:489 counter/models.py:786 eboutic/models.py:210 +#: counter/models.py:562 counter/models.py:859 eboutic/models.py:293 msgid "quantity" msgstr "quantité" -#: counter/models.py:508 +#: counter/models.py:581 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:508 sith/settings.py:359 sith/settings.py:364 +#: counter/models.py:581 sith/settings.py:359 sith/settings.py:364 #: sith/settings.py:384 msgid "Credit card" msgstr "Carte bancaire" -#: counter/models.py:514 +#: counter/models.py:587 msgid "selling" msgstr "vente" -#: counter/models.py:541 +#: counter/models.py:614 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:542 +#: counter/models.py:615 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:544 counter/models.py:567 +#: counter/models.py:617 counter/models.py:640 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3864,59 +3908,59 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:681 +#: counter/models.py:754 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:684 +#: counter/models.py:757 msgid "permanency" msgstr "permanence" -#: counter/models.py:711 +#: counter/models.py:784 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:714 +#: counter/models.py:787 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:782 +#: counter/models.py:855 msgid "cash summary" msgstr "relevé" -#: counter/models.py:785 +#: counter/models.py:858 msgid "value" msgstr "valeur" -#: counter/models.py:787 +#: counter/models.py:860 msgid "check" msgstr "chèque" -#: counter/models.py:790 +#: counter/models.py:863 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:805 +#: counter/models.py:878 msgid "banner" msgstr "bannière" -#: counter/models.py:807 +#: counter/models.py:880 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:809 +#: counter/models.py:882 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:811 +#: counter/models.py:884 msgid "secret" msgstr "secret" -#: counter/models.py:867 +#: counter/models.py:940 msgid "uid" msgstr "uid" -#: counter/models.py:872 +#: counter/models.py:945 msgid "student cards" msgstr "cartes étudiante" @@ -3968,7 +4012,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1202 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1065 msgid "Emptied" msgstr "Coffre vidé" @@ -4013,7 +4057,7 @@ msgid "Selling" msgstr "Vente" #: counter/templates/counter/counter_click.jinja:65 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:11 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:20 msgid "Basket: " msgstr "Panier : " @@ -4026,11 +4070,11 @@ msgstr "Terminer" msgid "Refilling" msgstr "Rechargement" -#: counter/templates/counter/counter_click.jinja:193 counter/views.py:646 +#: counter/templates/counter/counter_click.jinja:193 counter/views.py:578 msgid "END" msgstr "FIN" -#: counter/templates/counter/counter_click.jinja:193 counter/views.py:648 +#: counter/templates/counter/counter_click.jinja:193 counter/views.py:580 msgid "CAN" msgstr "ANN" @@ -4196,125 +4240,109 @@ msgstr "Temps" msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:120 -msgid "This UID is invalid" -msgstr "Cet UID est invalide" - -#: counter/views.py:178 -msgid "User not found" -msgstr "Utilisateur non trouvé" - -#: counter/views.py:235 +#: counter/views.py:167 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:249 +#: counter/views.py:181 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:264 +#: counter/views.py:196 msgid "Take items from stock" msgstr "Prendre des éléments du stock" -#: counter/views.py:317 +#: counter/views.py:249 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:319 +#: counter/views.py:251 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:324 +#: counter/views.py:256 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:368 +#: counter/views.py:300 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:371 +#: counter/views.py:303 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:374 +#: counter/views.py:306 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:671 +#: counter/views.py:603 msgid "You have not enough money to buy all the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" -#: counter/views.py:819 +#: counter/views.py:751 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:821 +#: counter/views.py:753 msgid "Stocks" msgstr "Stocks" -#: counter/views.py:840 +#: counter/views.py:772 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:1019 -msgid "Parent product" -msgstr "Produit parent" - -#: counter/views.py:1025 -msgid "Buying groups" -msgstr "Groupes d'achat" - -#: counter/views.py:1159 +#: counter/views.py:1022 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:1160 +#: counter/views.py:1023 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:1161 +#: counter/views.py:1024 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:1162 +#: counter/views.py:1025 msgid "1 euro" msgstr "1 €" -#: counter/views.py:1163 +#: counter/views.py:1026 msgid "2 euros" msgstr "2 €" -#: counter/views.py:1164 +#: counter/views.py:1027 msgid "5 euros" msgstr "5 €" -#: counter/views.py:1165 +#: counter/views.py:1028 msgid "10 euros" msgstr "10 €" -#: counter/views.py:1166 +#: counter/views.py:1029 msgid "20 euros" msgstr "20 €" -#: counter/views.py:1167 +#: counter/views.py:1030 msgid "50 euros" msgstr "50 €" -#: counter/views.py:1169 +#: counter/views.py:1032 msgid "100 euros" msgstr "100 €" -#: counter/views.py:1172 counter/views.py:1178 counter/views.py:1184 -#: counter/views.py:1190 counter/views.py:1196 +#: counter/views.py:1035 counter/views.py:1041 counter/views.py:1047 +#: counter/views.py:1053 counter/views.py:1059 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:1175 counter/views.py:1181 counter/views.py:1187 -#: counter/views.py:1193 counter/views.py:1199 +#: counter/views.py:1038 counter/views.py:1044 counter/views.py:1050 +#: counter/views.py:1056 counter/views.py:1062 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1829 +#: counter/views.py:1676 msgid "people(s)" msgstr "personne(s)" @@ -4340,39 +4368,36 @@ msgid "%(name)s : this product does not exist." msgstr "%(name)s : ce produit n'existe pas." #: eboutic/forms.py:134 -#, fuzzy, python-format -#| msgid "%(name)s : this product does not exist." +#, python-format msgid "%(name)s : this product does not exist or may no longer be available." -msgstr "" -"%(name)s : ce produit n'existe pas ou n'est peut-être plus disponnible." +msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible." #: eboutic/forms.py:141 -#, fuzzy, python-format -#| msgid "You cannot buy %(nbr)d %(name)%s." -msgid "You cannot buy %(nbr)d %(name)%s." +#, python-format +msgid "You cannot buy %(nbr)d %(name)s." msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s." -#: eboutic/models.py:149 +#: eboutic/models.py:241 msgid "validated" msgstr "validé" -#: eboutic/models.py:159 +#: eboutic/models.py:251 msgid "Invoice already validated" msgstr "Facture déjà validée" -#: eboutic/models.py:206 +#: eboutic/models.py:289 msgid "product id" msgstr "ID du produit" -#: eboutic/models.py:207 +#: eboutic/models.py:290 msgid "product name" msgstr "nom du produit" -#: eboutic/models.py:208 +#: eboutic/models.py:291 msgid "product type id" msgstr "id du type du produit" -#: eboutic/models.py:225 +#: eboutic/models.py:308 msgid "basket" msgstr "panier" @@ -4380,38 +4405,41 @@ msgstr "panier" msgid "Your basket has been cleaned accordingly to those errors." msgstr "Votre panier a été nettoyé en fonction de ces erreurs." -#: eboutic/templates/eboutic/eboutic_main.jinja:40 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:36 +#: eboutic/templates/eboutic/eboutic_main.jinja:41 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:45 msgid "Current account amount: " msgstr "Solde actuel : " -#: eboutic/templates/eboutic/eboutic_main.jinja:59 -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:32 +#: eboutic/templates/eboutic/eboutic_main.jinja:60 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:41 msgid "Basket amount: " msgstr "Valeur du panier : " -#: eboutic/templates/eboutic/eboutic_main.jinja:66 +#: eboutic/templates/eboutic/eboutic_main.jinja:67 msgid "Clear" msgstr "Vider" -#: eboutic/templates/eboutic/eboutic_main.jinja:72 +#: eboutic/templates/eboutic/eboutic_main.jinja:73 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:93 msgid "Validate" msgstr "Valider" -#: eboutic/templates/eboutic/eboutic_main.jinja:81 +#: eboutic/templates/eboutic/eboutic_main.jinja:82 msgid "" -"You have not filled in your date of birth. As a result, you may not have access to all the products in the online shop. To fill in your date of birth, you can go to" +"You have not filled in your date of birth. As a result, you may not have " +"access to all the products in the online shop. To fill in your date of " +"birth, you can go to" msgstr "" -"Vous n'avez pas renseigné votre date de naissance. " -"Par conséquent, il est possible que vous n'ayez pas accès à l'intégralité " -"des produits de la boutique en ligne. Pour remplir votre date de naissance, " -"vous pouvez aller sur" +"Vous n'avez pas renseigné votre date de naissance. Par conséquent, il est " +"possible que vous n'ayez pas accès à l'intégralité des produits de la " +"boutique en ligne. Pour remplir votre date de naissance, vous pouvez aller " +"sur" -#: eboutic/templates/eboutic/eboutic_main.jinja:87 +#: eboutic/templates/eboutic/eboutic_main.jinja:84 msgid "this page" msgstr "cette page" -#: eboutic/templates/eboutic/eboutic_main.jinja:125 +#: eboutic/templates/eboutic/eboutic_main.jinja:124 msgid "There are no items available for sale" msgstr "Aucun article n'est disponible à la vente" @@ -4419,22 +4447,34 @@ msgstr "Aucun article n'est disponible à la vente" msgid "Basket state" msgstr "État du panier" -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:40 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:50 msgid "Remaining account amount: " msgstr "Solde restant : " -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:51 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:60 +msgid "Edit billing information" +msgstr "Éditer les informations de facturation" + +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:100 +msgid "" +"You must fill your billing infos if you want to pay with your credit\n" +" card" +msgstr "" +"Vous devez renseigner vos coordonnées de facturation si vous voulez payer " +"par carte bancaire" + +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:112 msgid "Pay with credit card" msgstr "Payer avec une carte bancaire" -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:56 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:116 msgid "" "AE account payment disabled because your basket contains refilling items." msgstr "" "Paiement par compte AE désactivé parce que votre panier contient des bons de " "rechargement." -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:61 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:121 msgid "Pay with Sith account" msgstr "Payer avec un compte AE" @@ -4854,25 +4894,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:240 +#: launderette/views.py:241 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:260 launderette/views.py:282 +#: launderette/views.py:261 launderette/views.py:283 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:271 +#: launderette/views.py:272 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:338 +#: launderette/views.py:339 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:450 +#: launderette/views.py:451 msgid "Token not found" msgstr "Jeton non trouvé" diff --git a/poetry.lock b/poetry.lock index ef60aa13..8ddf1390 100644 --- a/poetry.lock +++ b/poetry.lock @@ -23,11 +23,11 @@ optional = false python-versions = ">=3.7" [package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] +tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] -name = "babel" -version = "2.10.3" +name = "Babel" +version = "2.11.0" description = "Internationalization utilities" category = "main" optional = true @@ -46,11 +46,11 @@ python-versions = "*" [[package]] name = "black" -version = "22.6.0" +version = "22.10.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" [package.dependencies] click = ">=8.0.0" @@ -68,7 +68,7 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.6.15" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -87,7 +87,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.1.0" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = true @@ -109,11 +109,11 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" @@ -138,12 +138,12 @@ python-versions = ">=3.6" cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] [[package]] name = "decorator" @@ -154,8 +154,19 @@ optional = false python-versions = ">=3.5" [[package]] -name = "django" -version = "3.2.15" +name = "dict2xml" +version = "1.7.2" +description = "Small utility to convert a python dictionary into an XML string" +category = "main" +optional = false +python-versions = ">= 3.5" + +[package.extras] +tests = ["noseOfYeti (==2.3.1)", "pytest (==7.1.3)"] + +[[package]] +name = "Django" +version = "3.2.16" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -178,16 +189,33 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "django-countries" +version = "7.4.2" +description = "Provides a country field for Django models." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +dev = ["black", "django", "djangorestframework", "graphene-django", "pytest", "pytest-django", "tox"] +maintainer = ["django", "transifex-client", "zest.releaser[recommended]"] +pyuca = ["pyuca"] +test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytest-django"] + [[package]] name = "django-debug-toolbar" -version = "3.5.0" +version = "3.7.0" description = "A configurable set of panels that display various debug information about the current request/response." category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -Django = ">=3.2" +Django = ">=3.2.4" sqlparse = ">=0.2.0" [[package]] @@ -226,14 +254,14 @@ python-versions = "*" [[package]] name = "django-phonenumber-field" -version = "6.3.0" +version = "6.4.0" description = "An international phone number field for django models." category = "main" optional = false python-versions = ">=3.7" [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" [package.extras] phonenumbers = ["phonenumbers (>=7.0.2)"] @@ -268,14 +296,14 @@ test = ["testfixtures"] [[package]] name = "djangorestframework" -version = "3.13.1" +version = "3.14.0" description = "Web APIs for Django, made easy." category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -django = ">=2.2" +django = ">=3.0" pytz = "*" [[package]] @@ -288,7 +316,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "idna" -version = "3.3" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = true @@ -304,7 +332,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.12.0" +version = "5.1.0" description = "Read metadata from Python packages" category = "main" optional = true @@ -314,9 +342,9 @@ python-versions = ">=3.7" zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "ipython" @@ -337,6 +365,7 @@ pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" +setuptools = ">=18.5" traitlets = ">=4.2" [package.extras] @@ -345,14 +374,14 @@ doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] -notebook = ["notebook", "ipywidgets"] +notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] [[package]] name = "jedi" -version = "0.18.1" +version = "0.18.2" description = "An autocompletion tool for Python that can be used for text editors." category = "dev" optional = false @@ -362,11 +391,12 @@ python-versions = ">=3.6" parso = ">=0.8.0,<0.9.0" [package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] -name = "jinja2" +name = "Jinja2" version = "3.1.2" description = "A very fast and expressive template engine." category = "main" @@ -391,7 +421,7 @@ python-versions = "*" six = "*" [[package]] -name = "markupsafe" +name = "MarkupSafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" @@ -400,7 +430,7 @@ python-versions = ">=3.7" [[package]] name = "matplotlib-inline" -version = "0.1.3" +version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" category = "dev" optional = false @@ -458,11 +488,11 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.9.0" +version = "0.10.2" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" [[package]] name = "pexpect" @@ -477,7 +507,7 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.12.53" +version = "8.13.1" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." category = "main" optional = false @@ -492,8 +522,8 @@ optional = false python-versions = "*" [[package]] -name = "pillow" -version = "9.2.0" +name = "Pillow" +version = "9.3.0" description = "Python Imaging Library (Fork)" category = "main" optional = false @@ -505,19 +535,19 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "2.5.2" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "2.5.4" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] -test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] [[package]] name = "prompt-toolkit" -version = "3.0.30" +version = "3.0.33" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false @@ -551,23 +581,26 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "pygments" -version = "2.12.0" +name = "Pygments" +version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.6" +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pygraphviz" -version = "1.9" +version = "1.10" description = "Python interface to Graphviz" category = "main" optional = false python-versions = ">=3.8" [[package]] -name = "pyopenssl" +name = "pyOpenSSL" version = "21.0.0" description = "Python wrapper module around the OpenSSL library" category = "main" @@ -591,7 +624,7 @@ optional = true python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "python-dateutil" @@ -614,17 +647,18 @@ python-versions = "*" [[package]] name = "reportlab" -version = "3.6.11" +version = "3.6.12" description = "The Reportlab Toolkit" category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7,<4" [package.dependencies] pillow = ">=9.0.0" [package.extras] -rlpycairo = ["rlPyCairo (>=0.0.5)"] +fttextpath = ["freetype-py (>=2.3.0,<2.4)"] +rlpycairo = ["rlPyCairo (>=0.1.0)"] [[package]] name = "requests" @@ -646,7 +680,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sentry-sdk" -version = "1.9.0" +version = "1.11.1" description = "Python client for Sentry (https://sentry.io)" category = "main" optional = false @@ -654,7 +688,7 @@ python-versions = "*" [package.dependencies] certifi = "*" -urllib3 = ">=1.10.0" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] @@ -665,17 +699,31 @@ chalice = ["chalice (>=1.16.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] -flask = ["flask (>=0.11)", "blinker (>=1.1)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)"] httpx = ["httpx (>=0.16.0)"] -pure_eval = ["pure-eval", "executing", "asttokens"] +pure_eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] -quart = ["quart (>=0.16.1)", "blinker (>=1.1)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] rq = ["rq (>=0.6)"] sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] tornado = ["tornado (>=5)"] +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -693,7 +741,7 @@ optional = true python-versions = "*" [[package]] -name = "sphinx" +name = "Sphinx" version = "4.5.0" description = "Python documentation generator" category = "main" @@ -721,8 +769,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] [[package]] name = "sphinx-copybutton" @@ -737,22 +785,22 @@ sphinx = ">=1.8" [package.extras] code_style = ["pre-commit (==2.12.1)"] -rtd = ["sphinx", "ipython", "sphinx-book-theme"] +rtd = ["ipython", "sphinx", "sphinx-book-theme"] [[package]] name = "sphinx-rtd-theme" -version = "1.0.0" +version = "1.1.1" description = "Read the Docs theme for Sphinx" category = "main" optional = true -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" [package.dependencies] docutils = "<0.18" -sphinx = ">=1.6" +sphinx = ">=1.6,<6" [package.extras] -dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" @@ -763,7 +811,7 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -775,7 +823,7 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -787,8 +835,8 @@ optional = true python-versions = ">=3.6" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] -test = ["pytest", "html5lib"] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" @@ -799,7 +847,7 @@ optional = true python-versions = ">=3.5" [package.extras] -test = ["pytest", "flake8", "mypy"] +test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" @@ -810,7 +858,7 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] @@ -822,12 +870,12 @@ optional = true python-versions = ">=3.5" [package.extras] -lint = ["flake8", "mypy", "docutils-stubs"] +lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] [[package]] name = "sqlparse" -version = "0.4.2" +version = "0.4.3" description = "A non-validating SQL parser." category = "main" optional = false @@ -843,34 +891,35 @@ python-versions = ">=3.7" [[package]] name = "traitlets" -version = "5.3.0" +version = "5.5.0" description = "" category = "dev" optional = false python-versions = ">=3.7" [package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["pre-commit", "pytest"] [[package]] name = "typing-extensions" -version = "4.3.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.11" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -903,15 +952,15 @@ django-haystack = ">=2.8.0" [[package]] name = "zipp" -version = "3.8.1" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = true python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] @@ -921,26 +970,134 @@ testing = ["coverage"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "08911593e7e5f4267de94d632be1332f4ef01a22b37a827f557eb15f0dba340b" +content-hash = "c335c796e1f2841eedbc714576a3906c267a66758ca21d46dc52dcffb2f36a16" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appnope = [] -asgiref = [] -babel = [] +appnope = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] +asgiref = [ + {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, + {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, +] +Babel = [ + {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, + {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, +] backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -black = [] -certifi = [] -cffi = [] -charset-normalizer = [] -click = [] -colorama = [] +black = [ + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, @@ -995,18 +1152,67 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] -cryptography = [] +cryptography = [ + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, +] decorator = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] -django = [] -django-ajax-selects = [] -django-debug-toolbar = [] -django-haystack = [] -django-jinja = [] -django-ordered-model = [] -django-phonenumber-field = [] +dict2xml = [ + {file = "dict2xml-1.7.2.tar.gz", hash = "sha256:4027330957466d14a4f692e6b499717e743bf3d8477f474d0a3cf2d31c8178af"}, +] +Django = [ + {file = "Django-3.2.16-py3-none-any.whl", hash = "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121"}, + {file = "Django-3.2.16.tar.gz", hash = "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d"}, +] +django-ajax-selects = [ + {file = "django-ajax-selects-2.2.0.tar.gz", hash = "sha256:539298874b2d26ce9e778a5173d312f55340c887a126c7e2d3460b9a5b4593a2"}, +] +django-countries = [ + {file = "django-countries-7.4.2.tar.gz", hash = "sha256:9d553531de8eaf384fec7fdd5867f3cb9b922d384984c3e613b6b29031e5c3c2"}, + {file = "django_countries-7.4.2-py3-none-any.whl", hash = "sha256:2eadbe91578fe7b5a0c7782c07487b2ceb4bde9bc5c76eb43458929969f25c62"}, +] +django-debug-toolbar = [ + {file = "django-debug-toolbar-3.7.0.tar.gz", hash = "sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465"}, + {file = "django_debug_toolbar-3.7.0-py3-none-any.whl", hash = "sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8"}, +] +django-haystack = [ + {file = "django-haystack-3.2.1.tar.gz", hash = "sha256:97e3197aefc225fe405b6f17600a2534bf827cb4d6743130c20bc1a06f7293a4"}, +] +django-jinja = [ + {file = "django-jinja-2.10.2.tar.gz", hash = "sha256:bfdfbb55c1f5a679d69ad575d550c4707d386634009152efe014089f3c4d1412"}, + {file = "django_jinja-2.10.2-py3-none-any.whl", hash = "sha256:dd003ec1c95c0989eb28a538831bced62b1b61da551cb44a5dfd708fcf75589f"}, +] +django-ordered-model = [ + {file = "django-ordered-model-3.6.tar.gz", hash = "sha256:62161a6bc51d8b402644854b257605d7b5183d01fd349826682a87e9227c05b5"}, + {file = "django_ordered_model-3.6-py3-none-any.whl", hash = "sha256:0006b111f472a2348f75554a4e77bee2b1f379a0f96726af6b1a3ebf3a950789"}, +] +django-phonenumber-field = [ + {file = "django-phonenumber-field-6.4.0.tar.gz", hash = "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed"}, + {file = "django_phonenumber_field-6.4.0-py3-none-any.whl", hash = "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58"}, +] django-ranged-response = [ {file = "django-ranged-response-0.2.0.tar.gz", hash = "sha256:f71fff352a37316b9bead717fc76e4ddd6c9b99c4680cdf4783b9755af1cf985"}, ] @@ -1015,25 +1221,37 @@ django-simple-captcha = [ {file = "django_simple_captcha-0.5.17-py2.py3-none-any.whl", hash = "sha256:f9a07e5e9de264ba4039c9eaad66bc48188a21ceda5fcdc2fa13c5512141c2c9"}, ] djangorestframework = [ - {file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"}, - {file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"}, + {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, + {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] +importlib-metadata = [ + {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, + {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, +] +ipython = [ + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, ] -imagesize = [] -importlib-metadata = [] -ipython = [] jedi = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, +] +Jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -jinja2 = [] libsass = [ {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"}, @@ -1043,9 +1261,10 @@ libsass = [ {file = "libsass-0.21.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e2b1a7d093f2e76dc694c17c0c285e846d0b0deb0e8b21dc852ba1a3a4e2f1d6"}, {file = "libsass-0.21.0-cp36-abi3-win32.whl", hash = "sha256:abc29357ee540849faf1383e1746d40d69ed5cb6d4c346df276b258f5aa8977a"}, {file = "libsass-0.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:659ae41af8708681fa3ec73f47b9735a6725e71c3b66ff570bfce78952f2314e"}, + {file = "libsass-0.21.0-cp38-abi3-macosx_12_0_arm64.whl", hash = "sha256:c9ec490609752c1d81ff6290da33485aa7cb6d7365ac665b74464c1b7d97f7da"}, {file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"}, ] -markupsafe = [ +MarkupSafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, @@ -1088,8 +1307,8 @@ markupsafe = [ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] matplotlib-inline = [ - {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, - {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, ] mistune = [ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, @@ -1099,7 +1318,15 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -mysqlclient = [] +mysqlclient = [ + {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"}, + {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"}, + {file = "mysqlclient-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c"}, + {file = "mysqlclient-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994"}, + {file = "mysqlclient-2.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855"}, + {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"}, + {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -1109,24 +1336,96 @@ parso = [ {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, ] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] -phonenumbers = [] +phonenumbers = [ + {file = "phonenumbers-8.13.1-py2.py3-none-any.whl", hash = "sha256:07a95c2f178687fd1c3f722cf792b3d33e3a225ae71577e500c99b28544cd6d0"}, + {file = "phonenumbers-8.13.1.tar.gz", hash = "sha256:7cadfe900e833857500b7bafa3e5a7eddc3263eb66b66a767870b33e44665f92"}, +] pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] -pillow = [] -platformdirs = [] -prompt-toolkit = [] +Pillow = [ + {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, + {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, + {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, + {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, + {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, + {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, + {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, + {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, + {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, + {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, + {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, + {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, + {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, + {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, + {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, + {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, + {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, + {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, +] +platformdirs = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.33-py3-none-any.whl", hash = "sha256:ced598b222f6f4029c0800cefaa6a17373fb580cd093223003475ce32805c35b"}, + {file = "prompt_toolkit-3.0.33.tar.gz", hash = "sha256:535c29c31216c77302877d5120aef6c94ff573748a5b5ca5b1b1f76f5e700c73"}, +] psycopg2-binary = [ {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f2534ab7dc7e776a263b463a16e189eb30e85ec9bbe1bff9e78dae802608932"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, @@ -1160,6 +1459,7 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e6aa71ae45f952a2205377773e76f4e3f27951df38e69a4c95440c779e013560"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, @@ -1171,6 +1471,7 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b3a24a1982ae56461cc24f6680604fffa2c1b818e9dc55680da038792e004d18"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, @@ -1190,13 +1491,21 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pygments = [] -pygraphviz = [] -pyopenssl = [ +Pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pygraphviz = [ + {file = "pygraphviz-1.10.zip", hash = "sha256:457e093a888128903251a266a8cc16b4ba93f3f6334b3ebfed92c7471a74d867"}, +] +pyOpenSSL = [ {file = "pyOpenSSL-21.0.0-py2.py3-none-any.whl", hash = "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"}, {file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"}, ] -pyparsing = [] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -1205,9 +1514,65 @@ pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] -reportlab = [] -requests = [] -sentry-sdk = [] +reportlab = [ + {file = "reportlab-3.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dfcf7bd6db5d80711cbbd0996b6e7a79cc414ca81457960367df11d2860f92a"}, + {file = "reportlab-3.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0bc7a1d64fe754b62e175ba0cf47a630b529c0488ec9ac4e4c7655e295ea4d"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adf78ccb2defad5b6ecb2e2e9f2a672719b0a8e2278592a7d77f6c220a042388"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c84afd5bef6e407c80ba9f99b6abbe3ea78e8243b0f19897a871a7bcad1f749d"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fa3cdf490f3828b055381e8c7dc7819b3e5f7a442d7af7a8f90e9806a7fff51"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07fdd968df7941c2bfb67b9bb4532f424992dfafc71b72a4e4b291ff707e6b0e"}, + {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce85a204f46c871c8af6fa64b9bbed165456935c1d0bfb2f570a3194f6723ddb"}, + {file = "reportlab-3.6.12-cp310-cp310-win32.whl", hash = "sha256:090ea99ff829d918f7b6140594373b1340a34e1e6876eddae5aa06662ec10d64"}, + {file = "reportlab-3.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:4c599645af9b5b2241a23e977a82c965a59c24cd94b2600b8d34373c66cad763"}, + {file = "reportlab-3.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:236a6483210049205f6180d7a7595d0ca2e4ce343d83cc94ca719a4145809c6f"}, + {file = "reportlab-3.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:69f41295d696c822224334f0994f1f107df7efed72211d45a1118696f1427c84"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f51dcb39e910a853749250c0f82aced80bca3f7315e9c4ee14349eb7cab6a3f8"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8dddc52e0e486291be0ad39184da0607fae9cc665fdba1881211de9cfc0b332"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4863c49602722237e35cbce5aa91af4539cc63a671f59504d2b3f3767d898cf"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b1215facead57cc5325aef4229ef886e85d270b2ba02080fb5809ce9d2b81b4"}, + {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12049314497d872f6788f811e2b331654db207937f8a2fb34ff3e3cd9897faa"}, + {file = "reportlab-3.6.12-cp311-cp311-win32.whl", hash = "sha256:759495c2b8c15cb0d6b539c246896029e4cde42a896c3956f77e311c5f6b0807"}, + {file = "reportlab-3.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:666bdba4958b348460a765c48b8c0640e7085540846ed9494f47d8651604b33c"}, + {file = "reportlab-3.6.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a7c3369fa618eca79f9554ce06c618a5e738e592d61d96aa09b2457ca3ea410"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9b0861d8f40d7a24b094b8834f6a489b9e8c70bceaa7fa98237eed229671ce"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26c25ea4afa8b92a2c14f4edc41c8fc30505745ce84cae86538e80cacadd7ae2"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55a070206580e161b6bbe1a96abf816c18d4c2c225d49916654714c93d842835"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40e108072379ff83dd7442159ebc249d12eb8eec15b70614953fecd2c403792"}, + {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39e92fa4ab2a8f0f2cc051d9c1e3acb881340c07ef59c0c8b627861343d653c0"}, + {file = "reportlab-3.6.12-cp37-cp37m-win32.whl", hash = "sha256:3fd1ffdd5204301eb4c290a5752ac62f44d2d0b262e02e35a1e5234c13e14662"}, + {file = "reportlab-3.6.12-cp37-cp37m-win_amd64.whl", hash = "sha256:d4cecfb48a6cfbfe2caf0fc280cecea999699e63bc98cb02254bd87b39eff677"}, + {file = "reportlab-3.6.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b6a1b685da0b9a8000bb980e02d9d5be202d0cc539af113b661c76c051fca6f1"}, + {file = "reportlab-3.6.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f5808e1dac6b66c109d6205ce2aebf84bb89e1a1493b7e6df38932df5ebfb9cf"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb83df8f7840321d34cb5b24c972c617a8c1716c8a36e5050fff56adf5891b8c"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72ec333f089b4fce5a6d740ed0a1963a3994146be195722da0d8e14d4a7e1600"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71cf73f9907c444ef663ea653dbac24af07c307079572c3ff8f20ad1463af3b7"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cee3b6ebef5e4a8654ec5f0effeb1a2bb157ad87b0ac856871d25a805c0f2f90"}, + {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db62bed0774778fdf82c609cb9efd0062f2fdcd285be527d01f6be9fd9755888"}, + {file = "reportlab-3.6.12-cp38-cp38-win32.whl", hash = "sha256:b777ddc57b2d3366cbc540616034cdc1089ca0a31fefc907028e1dd62a6bf16c"}, + {file = "reportlab-3.6.12-cp38-cp38-win_amd64.whl", hash = "sha256:c07ec796a2a5d44bf787f2b623b6e668a389b0cafb78af34cf74554ff3bc532b"}, + {file = "reportlab-3.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cdd206883e999278d2af656f988dfcc89eb0c175ce6d75e87b713cf1e792c0c4"}, + {file = "reportlab-3.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a62e51a4a47616896bd0f1e9cc3fbfb174b713794a5031a34b84f69dbe01775"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dd0307b2b13b0482ac8314fd793fbbce263a428b189371addf0466784e1d597"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56d701f7dc662e1d3d7fe364e66fa1339eafce54a488c2d16ec0ea49dc213c2"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:109009b02fc225882ea766a5ed8be0ef473fa1356e252a3f651a6aa89b4a195f"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3648f3c340b6b6aabf9352341478c708cee6f00c5cd5c902311fcf4ce870f3c"}, + {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:907f7cd4832bb295d0c1573de15cc5aab5988282caf2ee7a2b1276fb6cdf502b"}, + {file = "reportlab-3.6.12-cp39-cp39-win32.whl", hash = "sha256:93e229519d046491b798f2c12dbbf2f3e237e89589aa5cbb5e1d8c1a978816db"}, + {file = "reportlab-3.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:498b4ec7e73426de64c6bf6ec03c5b3f10dedf5db8a9e13fdf195f95a3d065aa"}, + {file = "reportlab-3.6.12.tar.gz", hash = "sha256:b13cebf4e397bba14542bcd023338b6ff2c151a3a12aabca89eecbf972cb361a"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +sentry-sdk = [ + {file = "sentry-sdk-1.11.1.tar.gz", hash = "sha256:675f6279b6bb1fea09fd61751061f9a90dca3b5929ef631dd50dc8b3aeb245e9"}, + {file = "sentry_sdk-1.11.1-py2.py3-none-any.whl", hash = "sha256:8b4ff696c0bdcceb3f70bbb87a57ba84fd3168b1332d493fcd16c137f709578c"}, +] +setuptools = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1216,14 +1581,17 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -sphinx = [] +Sphinx = [ + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, +] sphinx-copybutton = [ {file = "sphinx-copybutton-0.4.0.tar.gz", hash = "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386"}, {file = "sphinx_copybutton-0.4.0-py3-none-any.whl", hash = "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0"}, ] sphinx-rtd-theme = [ - {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, - {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, + {file = "sphinx_rtd_theme-1.1.1-py2.py3-none-any.whl", hash = "sha256:31faa07d3e97c8955637fc3f1423a5ab2c44b74b8cc558a51498c202ce5cbda7"}, + {file = "sphinx_rtd_theme-1.1.1.tar.gz", hash = "sha256:6146c845f1e1947b3c3dd4432c28998a1693ccc742b4f9ad7c63129f0757c103"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -1250,16 +1618,25 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] sqlparse = [ - {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"}, - {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"}, + {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, + {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, ] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -traitlets = [] -typing-extensions = [] -urllib3 = [] +traitlets = [ + {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, + {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +urllib3 = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, @@ -1267,5 +1644,10 @@ wcwidth = [ xapian-bindings = [ {file = "xapian-bindings-0.1.0.tar.gz", hash = "sha256:f2b0396082ebf4f6681ab43d6d8fd1f63b6964b18c32c91236ed067c6f62ad14"}, ] -xapian-haystack = [] -zipp = [] +xapian-haystack = [ + {file = "xapian-haystack-3.0.1.tar.gz", hash = "sha256:a5c0e1262b95008df4dfeb58d093c654acee3f2b27ea3f7d366900895cdc70f9"}, +] +zipp = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] diff --git a/pyproject.toml b/pyproject.toml index 3a88a467..99b3958f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ psycopg2-binary = "2.9.3" sentry-sdk = "^1.4.3" pygraphviz = "^1.9" Jinja2 = "^3.1" +django-countries = "^7.4.2" +dict2xml = "^1.7.2" # Extra optional dependencies mysqlclient = { version = "^2.0.3", optional = true } diff --git a/subscription/models.py b/subscription/models.py index 40eeb207..29d4224a 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -97,19 +97,12 @@ class Subscription(models.Model): # TODO see SubscriptionForm's clean method raise ValidationError(_("Subscription error")) - def save(self): + def save(self, *args, **kwargs): super(Subscription, self).save() from counter.models import Customer if not Customer.objects.filter(user=self.member).exists(): - last_id = ( - Customer.objects.count() + 1504 - ) # Number to keep a continuity with the old site - Customer( - user=self.member, - account_id=Customer.generate_account_id(last_id + 1), - amount=0, - ).save() + Customer.new_for_user(self.member).save() form = PasswordResetForm({"email": self.member.email}) if form.is_valid(): form.save( From 4d7d22c33731673a187a8b03a526c5c3fdf6805b Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Thu, 1 Dec 2022 10:01:56 +0100 Subject: [PATCH 02/95] edit yml to avoid git conflict when deploying on test --- .github/workflows/taiste.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/taiste.yml b/.github/workflows/taiste.yml index 4a2ae8f6..676476b7 100644 --- a/.github/workflows/taiste.yml +++ b/.github/workflows/taiste.yml @@ -34,7 +34,8 @@ jobs: export PATH="$HOME/.poetry/bin:$PATH" pushd ${{secrets.SITH_PATH}} - git pull + git fetch + git reset --hard origin/taiste poetry update poetry run ./manage.py migrate echo "yes" | poetry run ./manage.py collectstatic From b8a72c57e1058702d34345722af2780b709251fe Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Sat, 10 Dec 2022 20:41:35 +0100 Subject: [PATCH 03/95] escape html characters on xml (#505) --- counter/models.py | 27 +++++++++++++-------------- eboutic/models.py | 33 +++++++++++++-------------------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/counter/models.py b/counter/models.py index 20fa77a0..bea151b3 100644 --- a/counter/models.py +++ b/counter/models.py @@ -21,10 +21,9 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # -from django.db.models.functions import Length -from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from django.db import models +from django.db.models.functions import Length from django.utils.translation import gettext_lazy as _ from django.utils import timezone from django.conf import settings @@ -41,6 +40,7 @@ import base64 import datetime from dict2xml import dict2xml +from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from club.models import Club, Membership from accounting.models import CurrencyField from core.models import Group, User, Notification @@ -166,23 +166,22 @@ class BillingInfo(models.Model): """ Convert the data from this model into a xml usable by the online paying service of the Crédit Agricole bank. - see : `https://www.ca-moncommerce.com/espace-client-mon-commerce/up2pay-e-transactions/ma-documentation/manuel-dintegration-focus-3ds-v2/principes-generaux/#boutique-cms-utilisation-des-modules-up2pay-e-transactions-mise-a-jour-module` + see : `https://www.ca-moncommerce.com/espace-client-mon-commerce/up2pay-e-transactions/ma-documentation/manuel-dintegration-focus-3ds-v2/principes-generaux/#integration-3dsv2-developpeur-webmaster` """ data = { - "Billing": { - "Address": { - "FirstName": self.first_name, - "LastName": self.last_name, - "Address1": self.address_1, - "ZipCode": self.zip_code, - "City": self.city, - "CountryCode": self.country, - } + "Address": { + "FirstName": self.first_name, + "LastName": self.last_name, + "Address1": self.address_1, + "ZipCode": self.zip_code, + "City": self.city, + "CountryCode": self.country, } } if self.address_2: - data["Billing"]["Address"]["Address2"] = self.address_2 - return dict2xml(data) + data["Address"]["Address2"] = self.address_2 + xml = dict2xml(data, wrap="Billing", newlines=False) + return '' + xml def __str__(self): return f"{self.first_name} {self.last_name}" diff --git a/eboutic/models.py b/eboutic/models.py index 0b2014e7..ced2821c 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -22,6 +22,7 @@ # # import hmac +import html import typing from datetime import datetime from typing import List @@ -197,30 +198,22 @@ class Basket(models.Model): ("PBX_TYPEPAIEMENT", "CARTE"), ("PBX_TYPECARTE", "CB"), ("PBX_TIME", datetime.now().replace(microsecond=0).isoformat("T")), - ("PBX_BILLING", customer.billing_infos.to_3dsv2_xml()), - ( - "PBX_SHOPPINGCART", - dict2xml({"shoppingcart": {"total": {min(self.items.count(), 99)}}}), - ), ] - data.append( - ( - "PBX_HMAC", - ( - hmac.new( - settings.SITH_EBOUTIC_HMAC_KEY, - bytes("&".join("=".join(d) for d in data), "utf-8"), - "sha512", - ) - .hexdigest() - .upper() - ), - ) + cart = {"shoppingcart": {"total": min(self.items.count(), 99)}} + cart = dict2xml(cart, newlines=False) + cart = '' + cart + data += [ + ("PBX_SHOPPINGCART", html.escape(cart)), + ("PBX_BILLING", html.escape(customer.billing_infos.to_3dsv2_xml())), + ] + pbx_hmac = hmac.new( + settings.SITH_EBOUTIC_HMAC_KEY, + bytes("&".join("=".join(d) for d in data), "utf-8"), + "sha512", ) + data.append(("PBX_HMAC", pbx_hmac.hexdigest().upper())) return data - # def validate(self, exclude=None): - def __str__(self): return "%s's basket (%d items)" % (self.user, self.items.all().count()) From 1d82e2a7d90b955e045347c669847c602eb21b64 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Mon, 12 Dec 2022 22:54:31 +0100 Subject: [PATCH 04/95] Change country id to ISO 3166 1 numeric for 3DSV2 (#510) --- counter/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/counter/models.py b/counter/models.py index bea151b3..4f6f25ea 100644 --- a/counter/models.py +++ b/counter/models.py @@ -175,13 +175,13 @@ class BillingInfo(models.Model): "Address1": self.address_1, "ZipCode": self.zip_code, "City": self.city, - "CountryCode": self.country, + "CountryCode": self.country.numeric, # ISO-3166-1 numeric code } } if self.address_2: data["Address"]["Address2"] = self.address_2 xml = dict2xml(data, wrap="Billing", newlines=False) - return '' + xml + return '\n' + xml def __str__(self): return f"{self.first_name} {self.last_name}" From 22b83b0814095d78b7e8023ae3cacdd3bdc825f7 Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Mon, 12 Dec 2022 22:56:06 +0100 Subject: [PATCH 05/95] remove useless tests --- subscription/models.py | 66 ------------------------------------------ 1 file changed, 66 deletions(-) diff --git a/subscription/models.py b/subscription/models.py index 29d4224a..b724b734 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -170,69 +170,3 @@ class Subscription(models.Model): self.subscription_start <= date.today() and date.today() <= self.subscription_end ) - - -def guy_test(date, duration=4): - print( - str(date) - + " - " - + str(duration) - + " -> " - + str(Subscription.compute_start(date, duration)) - ) - - -def bibou_test(duration, date=date.today()): - print( - str(date) - + " - " - + str(duration) - + " -> " - + str( - Subscription.compute_end( - duration, Subscription.compute_start(date, duration) - ) - ) - ) - - -def guy(): - guy_test(date(2015, 7, 11)) - guy_test(date(2015, 8, 11)) - guy_test(date(2015, 2, 17)) - guy_test(date(2015, 3, 17)) - guy_test(date(2015, 1, 11)) - guy_test(date(2015, 2, 11)) - guy_test(date(2015, 8, 17)) - guy_test(date(2015, 9, 17)) - print("=" * 80) - guy_test(date(2015, 7, 11), 1) - guy_test(date(2015, 8, 11), 2) - guy_test(date(2015, 2, 17), 3) - guy_test(date(2015, 3, 17), 4) - guy_test(date(2015, 1, 11), 1) - guy_test(date(2015, 2, 11), 2) - guy_test(date(2015, 8, 17), 3) - guy_test(date(2015, 9, 17), 4) - print("=" * 80) - bibou_test(1, date(2015, 2, 18)) - bibou_test(2, date(2015, 2, 18)) - bibou_test(3, date(2015, 2, 18)) - bibou_test(4, date(2015, 2, 18)) - bibou_test(1, date(2015, 9, 18)) - bibou_test(2, date(2015, 9, 18)) - bibou_test(3, date(2015, 9, 18)) - bibou_test(4, date(2015, 9, 18)) - print("=" * 80) - bibou_test(1, date(2000, 2, 29)) - bibou_test(2, date(2000, 2, 29)) - bibou_test(1, date(2000, 5, 31)) - bibou_test(1, date(2000, 7, 31)) - bibou_test(1) - bibou_test(2) - bibou_test(3) - bibou_test(4) - - -if __name__ == "__main__": - guy() From faccc1367f7868b8813f0d44ed4f7591d8eaa192 Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Wed, 14 Dec 2022 08:38:41 +0100 Subject: [PATCH 06/95] Fix le panier de l'Eboutic pour Safari (#518) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théo DURR Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com> --- .gitignore | 2 +- club/tests.py | 6 ++++-- core/static/core/js/shorten.min.js | 2 +- core/static/core/style.scss | 1 + core/static/election/election.scss | 16 +++++++++++++--- eboutic/forms.py | 17 +++++++++++++++-- eboutic/static/eboutic/js/eboutic.js | 2 +- eboutic/views.py | 7 ++++--- .../templates/election/election_detail.jinja | 6 +++--- sith/settings.py | 6 +++--- 10 files changed, 46 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index d7337889..c6c093e6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ db.sqlite3 pyrightconfig.json dist/ .vscode/ -.idea +.idea/ env/ doc/html data/ diff --git a/club/tests.py b/club/tests.py index 63b48f47..b7f3226f 100644 --- a/club/tests.py +++ b/club/tests.py @@ -33,6 +33,8 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from core.models import User from club.models import Club, Membership, Mailing from club.forms import MailingForm +from sith.settings import SITH_BAR_MANAGER + # Create your tests here. @@ -43,7 +45,7 @@ class ClubTest(TestCase): self.skia = User.objects.filter(username="skia").first() self.rbatsbak = User.objects.filter(username="rbatsbak").first() self.guy = User.objects.filter(username="guy").first() - self.bdf = Club.objects.filter(unix_name="bdf").first() + self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first() def test_create_add_user_to_club_from_root_ok(self): self.client.login(username="root", password="plop") @@ -390,7 +392,7 @@ class MailingFormTest(TestCase): self.rbatsbak = User.objects.filter(username="rbatsbak").first() self.krophil = User.objects.filter(username="krophil").first() self.comunity = User.objects.filter(username="comunity").first() - self.bdf = Club.objects.filter(unix_name="bdf").first() + self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first() Membership( user=self.rbatsbak, club=self.bdf, diff --git a/core/static/core/js/shorten.min.js b/core/static/core/js/shorten.min.js index 5e79d040..9aeaadf1 100644 --- a/core/static/core/js/shorten.min.js +++ b/core/static/core/js/shorten.min.js @@ -19,4 +19,4 @@ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),e(this).data("jquery.shorten")&&!t.force?!1:(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({height:"0%"},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({height:"100%"},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html(),r=s.text().length;if(r>t.showChars+t.minHideChars){var o=n.substr(0,t.showChars);if(o.indexOf("<")>=0){for(var a=!1,i="",h=0,l=[],c=null,f=0,u=0;u<=t.showChars;f++)if("<"!=n[f]||a||(a=!0,c=n.substring(f+1,n.indexOf(">",f)),"/"==c[0]?c!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=c.toLowerCase()&&l.unshift(c)),a&&">"==n[f]&&(a=!1),a)i+=n.charAt(f);else if(u++,h<=t.showChars)i+=n.charAt(f),h++;else if(l.length>0){for(j=0;j";break}o=e("
").html(i+''+t.ellipsesText+"").html()}else o+=t.ellipsesText;var m='
'+o+'
'+n+'
'+t.moreText+"";s.html(m),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery); +!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),(!e(this).data("jquery.shorten")||!!t.force)&&(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html();if(s.text().length>t.showChars+t.minHideChars){var r=n.substr(0,t.showChars);if(r.indexOf("<")>=0){for(var a=!1,o="",i=0,l=[],h=null,c=0,f=0;f<=t.showChars;c++)if("<"!=n[c]||a||(a=!0,"/"==(h=n.substring(c+1,n.indexOf(">",c)))[0]?h!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=h.toLowerCase()&&l.unshift(h)),a&&">"==n[c]&&(a=!1),a)o+=n.charAt(c);else if(f++,i<=t.showChars)o+=n.charAt(c),i++;else if(l.length>0){for(j=0;j";break}r=e("
").html(o+''+t.ellipsesText+"").html()}else r+=t.ellipsesText;var p='
'+r+'
'+n+'
'+t.moreText+"";s.html(p),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery); \ No newline at end of file diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 04fac15e..a9e19f1a 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -381,6 +381,7 @@ header { color: $white-color; border-radius: 6px 6px 0 0; box-shadow: $shadow-color 0 0 15px; + align-items: center; a { flex: auto; diff --git a/core/static/election/election.scss b/core/static/election/election.scss index 0af2286d..c6f7cc26 100644 --- a/core/static/election/election.scss +++ b/core/static/election/election.scss @@ -166,14 +166,15 @@ $min_col_width: 100px; flex-direction: row; flex-wrap: wrap; justify-content: center; + width: 100%; gap: $gap; >.candidate { display: flex; flex-direction: column; align-items: center; - list-style-type: none; + width: 100%; gap: $gap; >input[type="radio"]:checked + label, @@ -186,6 +187,10 @@ $min_col_width: 100px; } } + >label { + width: 100%; + } + >label>figure, >figure { position: relative; @@ -194,17 +199,22 @@ $min_col_width: 100px; align-items: center; gap: $gap; padding: 10px; + max-width: 100%; >img { - max-width: 100%; + max-width: 100% !important; } >figcaption { + width: 100%; + max-width: inherit !important; + overflow: hidden; + h5 { margin: 0; text-align: center; } - q { + .candidate_program { margin: 5px 0; } } diff --git a/eboutic/forms.py b/eboutic/forms.py index 91356b3d..20e99797 100644 --- a/eboutic/forms.py +++ b/eboutic/forms.py @@ -26,8 +26,10 @@ import json import re import typing +from urllib.parse import unquote from django.http import HttpRequest from django.utils.translation import gettext as _ +from sentry_sdk import capture_message from eboutic.models import get_eboutic_products @@ -97,23 +99,34 @@ class BasketForm: - all the ids refer to products the user is allowed to buy - all the quantities are positive integers """ - basket = self.cookies.get("basket_items", None) + basket = unquote(self.cookies.get("basket_items", None)) if basket is None or basket in ("[]", ""): self.error_messages.add(_("You have no basket.")) return + # check that the json is not nested before parsing it to make sure - # malicious user can't ddos the server with deeply nested json + # malicious user can't DDoS the server with deeply nested json if not BasketForm.json_cookie_re.match(basket): + # As the validation of the cookie goes through a rather boring regex, + # we can regularly have to deal with subtle errors that we hadn't forecasted, + # so we explicitly lay a Sentry message capture here. + capture_message( + "Eboutic basket regex checking failed to validate basket json", + level="error", + ) self.error_messages.add(_("The request was badly formatted.")) return + try: basket = json.loads(basket) except json.JSONDecodeError: self.error_messages.add(_("The basket cookie was badly formatted.")) return + if type(basket) is not list or len(basket) == 0: self.error_messages.add(_("Your basket is empty.")) return + for item in basket: expected_keys = {"id", "quantity", "name", "unit_price"} if type(item) is not dict or set(item.keys()) != expected_keys: diff --git a/eboutic/static/eboutic/js/eboutic.js b/eboutic/static/eboutic/js/eboutic.js index 4df6b02e..8662d79f 100644 --- a/eboutic/static/eboutic/js/eboutic.js +++ b/eboutic/static/eboutic/js/eboutic.js @@ -63,7 +63,7 @@ document.addEventListener('alpine:init', () => { edit_cookies() { // a cookie survives an hour - document.cookie = "basket_items=" + JSON.stringify(this.items) + ";Max-Age=3600"; + document.cookie = "basket_items=" + encodeURIComponent(JSON.stringify(this.items)) + ";Max-Age=3600"; }, /** diff --git a/eboutic/views.py b/eboutic/views.py index 811872ec..8c816db6 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -24,9 +24,10 @@ import base64 import json -from datetime import datetime - import sentry_sdk + +from datetime import datetime +from urllib.parse import unquote from OpenSSL import crypto from django.conf import settings from django.contrib.auth.decorators import login_required @@ -104,7 +105,7 @@ class EbouticCommand(TemplateView): request.session["basket_id"] = basket.id request.session.modified = True - items = json.loads(request.COOKIES["basket_items"]) + items = json.loads(unquote(request.COOKIES["basket_items"])) items.sort(key=lambda item: item["id"]) ids = [item["id"] for item in items] quantities = [item["quantity"] for item in items] diff --git a/election/templates/election/election_detail.jinja b/election/templates/election/election_detail.jinja index 853fab78..51eeb704 100644 --- a/election/templates/election/election_detail.jinja +++ b/election/templates/election/election_detail.jinja @@ -100,7 +100,7 @@ - + {%- if role.max_choice == 1 and election.can_vote(user) %}
@@ -118,7 +118,7 @@ {%- endif %} {%- for election_list in election_lists %} - +
- + diff --git a/sith/settings.py b/sith/settings.py index 1a2ed664..1b0998b7 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -682,13 +682,13 @@ if SENTRY_DSN: SITH_FRONT_DEP_VERSIONS = { "https://github.com/chartjs/Chart.js/": "2.6.0", "https://github.com/xdan/datetimepicker/": "2.5.21", - "https://github.com/Ionaru/easy-markdown-editor/": "2.7.0", + "https://github.com/Ionaru/easy-markdown-editor/": "2.18.0", "https://github.com/FortAwesome/Font-Awesome/": "4.7.0", - "https://github.com/jquery/jquery/": "3.1.0", + "https://github.com/jquery/jquery/": "3.6.2", "https://github.com/sethmcl/jquery-ui/": "1.11.1", "https://github.com/viralpatel/jquery.shorten/": "", "https://github.com/getsentry/sentry-javascript/": "4.0.6", "https://github.com/jhuckaby/webcamjs/": "1.0.0", "https://github.com/vuejs/vue-next": "3.2.18", - "https://github.com/alpinejs/alpine": "3.10.3", + "https://github.com/alpinejs/alpine": "3.10.5", } From 26c94c9ec6e6504d32d2099d02cfd764d37f7b6e Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Fri, 16 Dec 2022 00:37:07 +0100 Subject: [PATCH 08/95] [Eboutic] Fix double quote issue & improved user experience on small screen (#522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix #511 Regex issue with escaped double quotes * Fix basket being when reloading the page (when cookie != "") + Added JSDoc + Cleaned some code * Fix #509 Improved user experience on small screens * Fix css class not being added back when reloading page * CSS Fixes (see description) + Fixed overlaping item title with the cart emoji on small screen + Fixed minimal size of the basket on small screen (full width) * Added darkened background circle to items with no image * Fix issue were the basket could be None * Edited CSS to have bette img ratio & the 🛒 icon Adapt, Improve, Overcome * Moved basket down on small screen size --- eboutic/forms.py | 7 +- eboutic/static/eboutic/css/eboutic.css | 150 ++++++++++++++---- eboutic/static/eboutic/js/eboutic.js | 153 +++++++++++++------ eboutic/templates/eboutic/eboutic_main.jinja | 16 +- 4 files changed, 234 insertions(+), 92 deletions(-) diff --git a/eboutic/forms.py b/eboutic/forms.py index 20e99797..c5842700 100644 --- a/eboutic/forms.py +++ b/eboutic/forms.py @@ -99,8 +99,11 @@ class BasketForm: - all the ids refer to products the user is allowed to buy - all the quantities are positive integers """ - basket = unquote(self.cookies.get("basket_items", None)) - if basket is None or basket in ("[]", ""): + # replace escaped double quotes by single quotes, as the RegEx used to check the json + # does not support escaped double quotes + basket = unquote(self.cookies.get("basket_items", "")).replace('\\"', "'") + + if basket in ("[]", ""): self.error_messages.add(_("You have no basket.")) return diff --git a/eboutic/static/eboutic/css/eboutic.css b/eboutic/static/eboutic/css/eboutic.css index aecb67a3..bc9757e2 100644 --- a/eboutic/static/eboutic/css/eboutic.css +++ b/eboutic/static/eboutic/css/eboutic.css @@ -28,7 +28,7 @@ @media screen and (max-width: 765px) { #eboutic { - flex-direction: column; + flex-direction: column-reverse; align-items: center; margin: 10px; row-gap: 20px; @@ -38,6 +38,7 @@ margin-top: 4px; } #basket { + width: -webkit-fill-available; } } @@ -52,23 +53,39 @@ margin-bottom: 10px } +#eboutic .item-row { + gap: 10px; +} + #eboutic .item-name { - flex: 1; + word-break: break-word; + width: 100%; + line-height: 100%; } -#eboutic .item-price, #eboutic .item-quantity { - width: 65px; -} - -#eboutic .item-quantity { +#eboutic .fa-plus, +#eboutic .fa-minus { + cursor: pointer; + background-color: #354a5f; + color: white; + border-radius: 50%; + padding: 5px; + font-size: 10px; + line-height: 10px; + width: 10px; text-align: center; } -#eboutic .fa-plus, #eboutic .fa-minus { - cursor: pointer; +#eboutic .item-quantity { + min-width: 65px; + justify-content: space-between; + align-items: center; + display: flex; + gap: 5px; } #eboutic .item-price { + min-width: 65px; text-align: right; } @@ -91,44 +108,65 @@ column-gap: 15px; row-gap: 15px; } - -@media screen and (max-width: 765px) { - #eboutic #catalog { - row-gap: 15px; - } - - #eboutic section { - text-align: center; - } - - #eboutic .product-group { - justify-content: space-around; - } -} - #eboutic .product-button { position: relative; box-sizing: border-box; - min-width: 120px; - max-width: 150px; - padding: 10px; + min-height: 180px; + height: fit-content; + width: 150px; + padding: 15px; overflow: hidden; box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px; display: flex; flex-direction: column; align-items: center; row-gap: 5px; + justify-content: flex-start; +} + +#eboutic .product-button.selected { + animation: bg-in-out 1s ease; + background-color: rgb(216, 236, 255); +} + +#eboutic .product-button.selected::after { + content: "🛒"; + position: absolute; + top: 5px; + right: 5px; + padding: 5px; + border-radius: 50%; + box-shadow: 0px 0px 12px 2px rgb(0 0 0 / 14%); + background-color: white; + width: 20px; + height: 20px; + font-size: 16px; + line-height: 20px; } #eboutic .product-button:active { box-shadow: none; } -#eboutic .product-button img, #eboutic .product-button .fa { - margin: 0; +#eboutic .product-image { + width: 100%; + height: 100%; + min-height: 70px; + max-height: 70px; + object-fit: contain; border-radius: 4px; - height: 40px; - line-height: 40px; + line-height: 70px; + margin-bottom: 15px; +} + +#eboutic i.product-image { + background-color: rgba(173, 173, 173, 0.2); +} + +#eboutic .product-description h4 { + font-size: .75em; + word-break: break-word; + margin: 0 0 5px 0; } #eboutic .product-button p { @@ -152,8 +190,9 @@ font-weight: normal; color: white; min-width: 60px; - padding: 5px 10px; + padding: 10px 15px; } + #eboutic .catalog-buttons .validate { background-color: #354a5f; } @@ -174,4 +213,51 @@ #eboutic .catalog-buttons form { margin: 0; +} + +@media screen and (max-width: 765px) { + #eboutic #catalog { + row-gap: 15px; + width: 100%; + } + + #eboutic section { + text-align: center; + } + + #eboutic .product-group { + justify-content: space-around; + flex-direction: column; + } + + #eboutic .product-group .product-button { + min-height: 100px; + width: 100%; + max-width: 100%; + display: flex; + flex-direction: row; + gap: 10px; + } + + #eboutic .product-group .product-description { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + } + + #eboutic .product-description h4 { + text-align: left; + max-width: 90%; + } + + #eboutic .product-image { + margin-bottom: 0px; + max-width: 70px; + } +} + +@keyframes bg-in-out { + 0% { background-color: white; } + 100% { background-color: rgb(216, 236, 255); } } \ No newline at end of file diff --git a/eboutic/static/eboutic/js/eboutic.js b/eboutic/static/eboutic/js/eboutic.js index 8662d79f..a1a96db2 100644 --- a/eboutic/static/eboutic/js/eboutic.js +++ b/eboutic/static/eboutic/js/eboutic.js @@ -1,76 +1,123 @@ +/** + * @typedef {Object} BasketItem An item in the basket + * @property {number} id The id of the product + * @property {string} name The name of the product + * @property {number} quantity The quantity of the product + * @property {number} unit_price The unit price of the product + */ + +const BASKET_ITEMS_COOKIE_NAME = "basket_items"; + +/** + * Search for a cookie by name + * @param {string} name Name of the cookie to get + * @returns {string|null|undefined} the value of the cookie or null if it does not exist, undefined if not found + */ function getCookie(name) { - let cookieValue = null; - if (document.cookie && document.cookie !== '') { - const cookies = document.cookie.split(';'); - for (let i = 0; i < cookies.length; i++) { - const cookie = cookies[i].trim(); - // Does this cookie string begin with the name we want? - if (cookie.substring(0, name.length + 1) === (name + '=')) { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; + if (!document.cookie || document.cookie.length === 0) return null; + + let found = document.cookie + .split(';') + .map(c => c.trim()) + .find(c => c.startsWith(name + '=')); + + return found === undefined ? undefined : decodeURIComponent(found.split('=')[1]); } +/** + * Fetch the basket items from the associated cookie + * @returns {BasketItem[]|[]} the items in the basket + */ function get_starting_items() { - const cookie = getCookie("basket_items") + const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME); + let output = []; + try { - // django cookie backend does an utter mess on non-trivial data types + // Django cookie backend does an utter mess on non-trivial data types // so we must perform a conversion of our own - const biscuit = JSON.parse(JSON.parse(cookie.replace(/\\054/g, ','))); - if (Array.isArray(biscuit)) { - return biscuit; - } - return []; - } catch (e) { - return []; - } + const biscuit = JSON.parse(cookie.replace(/\\054/g, ',')); + output = Array.isArray(biscuit) ? biscuit : []; + + } catch (e) {} + + output.forEach(item => { + let el = document.getElementById(item.id); + el.classList.add("selected"); + }); + + return output; } document.addEventListener('alpine:init', () => { Alpine.data('basket', () => ({ items: get_starting_items(), + /** + * Get the total price of the basket + * @returns {number} The total price of the basket + */ get_total() { - let total = 0; - for (const item of this.items) { - total += item["quantity"] * item["unit_price"]; - } - return total; + return this.items + .reduce((acc, item) => acc + item["quantity"] * item["unit_price"], 0); }, + /** + * Add 1 to the quantity of an item in the basket + * @param {BasketItem} item + */ add(item) { item.quantity++; - this.edit_cookies() + this.set_cookies(); }, + /** + * Remove 1 to the quantity of an item in the basket + * @param {BasketItem} item_id + */ remove(item_id) { const index = this.items.findIndex(e => e.id === item_id); + if (index < 0) return; this.items[index].quantity -= 1; + if (this.items[index].quantity === 0) { + let el = document.getElementById(this.items[index].id); + el.classList.remove("selected"); + this.items = this.items.filter((e) => e.id !== this.items[index].id); } - this.edit_cookies(); + this.set_cookies(); }, + /** + * Remove all the items from the basket & cleans the catalog CSS classes + */ clear_basket() { - this.items = [] - this.edit_cookies(); + // We remove the class "selected" from all the items in the catalog + this.items.forEach(item => { + let el = document.getElementById(item.id); + el.classList.remove("selected"); + }) + + this.items = []; + this.set_cookies(); }, - edit_cookies() { - // a cookie survives an hour - document.cookie = "basket_items=" + encodeURIComponent(JSON.stringify(this.items)) + ";Max-Age=3600"; + /** + * Set the cookie in the browser with the basket items + * ! the cookie survives an hour + */ + set_cookies() { + if (this.items.length === 0) document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0`; + else document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.items))};Max-Age=3600`; }, /** * Create an item in the basket if it was not already in - * @param id : int the id of the product to add - * @param name : String the name of the product - * @param price : number the unit price of the product + * @param {number} id The id of the product to add + * @param {string} name The name of the product + * @param {number} price The unit price of the product + * @returns {BasketItem} The created item */ create_item(id, name, price) { let new_item = { @@ -79,25 +126,31 @@ document.addEventListener('alpine:init', () => { quantity: 0, unit_price: price }; + this.items.push(new_item); this.add(new_item); + + return new_item; }, /** - * add an item to the basket. - * This is called when the user click - * on a button in the catalog (left side of the page) - * @param id : int the id of the product to add - * @param name : String the name of the product - * @param price : number the unit price of the product + * Add an item to the basket. + * This is called when the user click on a button in the catalog + * @param {number} id The id of the product to add + * @param {string} name The name of the product + * @param {number} price The unit price of the product */ add_from_catalog(id, name, price) { - const item = this.items.find(e => e.id === id) - if (item === undefined) { - this.create_item(id, name, price); - } else { - // the user clicked on an item which is already in the basket - this.add(item); + let item = this.items.find(e => e.id === id) + + // if the item is not in the basket, we create it + // else we add + 1 to it + if (item === undefined) item = this.create_item(id, name, price); + else this.add(item); + + if (item.quantity > 0) { + let el = document.getElementById(item.id); + el.classList.add("selected"); } }, })) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index e707505b..b2ff6681 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -46,12 +46,12 @@ @@ -104,16 +104,16 @@
{% for p in items %} - {% endfor %}
From ceb2888f822c9f5ce4c224e282a81d44b29926d1 Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Mon, 19 Dec 2022 20:55:33 +0100 Subject: [PATCH 09/95] enhance admin pages --- club/admin.py | 19 +++++-- com/admin.py | 26 +++++++--- core/admin.py | 20 +++++--- core/lookups.py | 18 ++++++- counter/admin.py | 115 +++++++++++++++++++++++++++++++++++++----- counter/models.py | 10 ++++ eboutic/admin.py | 29 +++++++++-- election/admin.py | 36 ++++++++++++- launderette/admin.py | 28 +++++++--- pedagogy/admin.py | 36 ++++++++++++- pedagogy/models.py | 4 ++ subscription/admin.py | 21 +++++--- 12 files changed, 313 insertions(+), 49 deletions(-) diff --git a/club/admin.py b/club/admin.py index 10de0e67..d72a66b2 100644 --- a/club/admin.py +++ b/club/admin.py @@ -21,11 +21,24 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin from club.models import Club, Membership -admin.site.register(Club) -admin.site.register(Membership) +@admin.register(Club) +class ClubAdmin(admin.ModelAdmin): + list_display = ("name", "unix_name", "parent", "is_active") + + +@admin.register(Membership) +class MembershipAdmin(admin.ModelAdmin): + list_display = ("user", "club", "role", "start_date", "end_date") + search_fields = ( + "user__username", + "user__first_name", + "user__last_name", + "club__name", + ) + form = make_ajax_form(Membership, {"user": "users"}) diff --git a/com/admin.py b/com/admin.py index 43ca4e1e..f7ffbeb3 100644 --- a/com/admin.py +++ b/com/admin.py @@ -21,23 +21,37 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin from haystack.admin import SearchModelAdmin from com.models import * +@admin.register(News) class NewsAdmin(SearchModelAdmin): - search_fields = ["title", "summary", "content"] + list_display = ("title", "type", "club", "author") + search_fields = ("title", "summary", "content") + form = make_ajax_form( + News, + { + "author": "users", + "moderator": "users", + }, + ) +@admin.register(Poster) +class PosterAdmin(SearchModelAdmin): + list_display = ("name", "club", "date_begin", "date_end", "moderator") + form = make_ajax_form(Poster, {"moderator": "users"}) + + +@admin.register(Weekmail) class WeekmailAdmin(SearchModelAdmin): - search_fields = ["title"] + list_display = ("title", "sent") + search_fields = ("title",) admin.site.register(Sith) -admin.site.register(News, NewsAdmin) -admin.site.register(Weekmail, WeekmailAdmin) admin.site.register(Screen) -admin.site.register(Poster) diff --git a/core/admin.py b/core/admin.py index 5b7c9c97..4698a489 100644 --- a/core/admin.py +++ b/core/admin.py @@ -24,17 +24,19 @@ from django.contrib import admin from ajax_select import make_ajax_form -from core.models import User, Page, RealGroup, SithFile +from core.models import User, Page, RealGroup, MetaGroup, SithFile from django.contrib.auth.models import Group as AuthGroup from haystack.admin import SearchModelAdmin admin.site.unregister(AuthGroup) +admin.site.register(MetaGroup) admin.site.register(RealGroup) +@admin.register(User) class UserAdmin(SearchModelAdmin): - list_display = ["first_name", "last_name", "username", "email", "nick_name"] + list_display = ("first_name", "last_name", "username", "email", "nick_name") form = make_ajax_form( User, { @@ -48,11 +50,9 @@ class UserAdmin(SearchModelAdmin): search_fields = ["first_name", "last_name", "username"] -admin.site.register(User, UserAdmin) - - @admin.register(Page) class PageAdmin(admin.ModelAdmin): + list_display = ("name", "_full_name", "owner_group") form = make_ajax_form( Page, { @@ -66,4 +66,12 @@ class PageAdmin(admin.ModelAdmin): @admin.register(SithFile) class SithFileAdmin(admin.ModelAdmin): - form = make_ajax_form(SithFile, {"parent": "files"}) # ManyToManyField + list_display = ("name", "owner", "size", "date", "is_in_sas") + form = make_ajax_form( + SithFile, + { + "parent": "files", + "owner": "users", + "moderator": "users", + }, + ) # ManyToManyField diff --git a/core/lookups.py b/core/lookups.py index 477ef95e..18e61f70 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -28,8 +28,9 @@ from ajax_select import register, LookupChannel from core.views.site import search_user from core.models import User, Group, SithFile from club.models import Club -from counter.models import Product, Counter +from counter.models import Product, Counter, Customer from accounting.models import ClubAccount, Company +from eboutic.models import BasketItem def check_token(request): @@ -60,6 +61,21 @@ class UsersLookup(RightManagedLookupChannel): return item.get_display_name() +@register("customers") +class CustomerLookup(RightManagedLookupChannel): + model = Customer + + def get_query(self, q, request): + users = search_user(q) + return [user.customer for user in users] + + def format_match(self, obj): + return obj.user.get_mini_item() + + def format_item_display(self, obj): + return f"{obj.user.get_display_name()} ({obj.account_id})" + + @register("groups") class GroupsLookup(RightManagedLookupChannel): model = Group diff --git a/counter/admin.py b/counter/admin.py index 9faea823..573c117e 100644 --- a/counter/admin.py +++ b/counter/admin.py @@ -21,19 +21,36 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin from haystack.admin import SearchModelAdmin from counter.models import * +@admin.register(Product) class ProductAdmin(SearchModelAdmin): - search_fields = ["name", "code"] + list_display = ( + "name", + "code", + "product_type", + "selling_price", + "profit", + "archived", + ) + search_fields = ("name", "code") +@admin.register(Customer) class CustomerAdmin(SearchModelAdmin): - search_fields = ["account_id"] + list_display = ("user", "account_id", "amount") + search_fields = ( + "account_id", + "user__username", + "user__first_name", + "user__last_name", + ) + form = make_ajax_form(Customer, {"user": "users"}) @admin.register(BillingInfo) @@ -41,12 +58,86 @@ class BillingInfoAdmin(admin.ModelAdmin): list_display = ("first_name", "last_name", "address_1", "city", "country") -admin.site.register(Customer, CustomerAdmin) -admin.site.register(Product, ProductAdmin) -admin.site.register(ProductType) -admin.site.register(Counter) -admin.site.register(Refilling) -admin.site.register(Selling) -admin.site.register(Permanency) -admin.site.register(CashRegisterSummary) -admin.site.register(Eticket) +@admin.register(Counter) +class CounterAdmin(admin.ModelAdmin): + list_display = ("name", "club", "type") + form = make_ajax_form( + Counter, + { + "products": "products", + "sellers": "users", + }, + ) + + +@admin.register(Refilling) +class RefillingAdmin(SearchModelAdmin): + list_display = ("customer", "amount", "counter", "payment_method", "date") + search_fields = ( + "customer__user__username", + "customer__user__first_name", + "customer__user__last_name", + "customer__account_id", + "counter__name", + ) + form = make_ajax_form( + Refilling, + { + "customer": "customers", + "operator": "users", + }, + ) + + +@admin.register(Selling) +class SellingAdmin(SearchModelAdmin): + list_display = ("customer", "label", "unit_price", "quantity", "counter", "date") + search_fields = ( + "customer__user__username", + "customer__user__first_name", + "customer__user__last_name", + "customer__account_id", + "counter__name", + ) + form = make_ajax_form( + Selling, + { + "customer": "customers", + "seller": "users", + }, + ) + + +@admin.register(Permanency) +class PermanencyAdmin(SearchModelAdmin): + list_display = ("user", "counter", "start", "duration") + search_fields = ( + "user__username", + "user__first_name", + "user__last_name", + "counter__name", + ) + form = make_ajax_form(Permanency, {"user": "users"}) + + +@admin.register(ProductType) +class ProductTypeAdmin(admin.ModelAdmin): + list_display = ("name", "priority") + + +@admin.register(CashRegisterSummary) +class CashRegisterSummaryAdmin(SearchModelAdmin): + list_display = ("user", "counter", "date") + search_fields = ( + "user__username", + "user__first_name", + "user__last_name", + "counter__name", + ) + form = make_ajax_form(CashRegisterSummary, {"user": "users"}) + + +@admin.register(Eticket) +class EticketAdmin(SearchModelAdmin): + list_display = ("product", "event_date", "event_title") + search_fields = ("product__name", "event_title") diff --git a/counter/models.py b/counter/models.py index 4f6f25ea..cfbc437e 100644 --- a/counter/models.py +++ b/counter/models.py @@ -305,6 +305,10 @@ class Product(models.Model): return True return False + @property + def profit(self): + return self.selling_price - self.purchase_price + def __str__(self): return "%s (%s)" % (self.name, self.code) @@ -762,6 +766,12 @@ class Permanency(models.Model): self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", ) + @property + def duration(self): + if self.end is None: + return self.activity - self.start + return self.end - self.start + class CashRegisterSummary(models.Model): user = models.ForeignKey( diff --git a/eboutic/admin.py b/eboutic/admin.py index d420e280..30900f5b 100644 --- a/eboutic/admin.py +++ b/eboutic/admin.py @@ -21,11 +21,32 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin from eboutic.models import * -admin.site.register(Basket) -admin.site.register(Invoice) -admin.site.register(BasketItem) + +@admin.register(Basket) +class BasketAdmin(admin.ModelAdmin): + list_display = ("user", "date", "get_total") + form = make_ajax_form(Basket, {"user": "users"}) + + +@admin.register(BasketItem) +class BasketItemAdmin(admin.ModelAdmin): + list_display = ("basket", "product_name", "product_unit_price", "quantity") + search_fields = ("product_name",) + + +@admin.register(Invoice) +class InvoiceAdmin(admin.ModelAdmin): + list_display = ("user", "date", "validated") + search_fields = ("user__username", "user__first_name", "user__last_name") + form = make_ajax_form(Invoice, {"user": "users"}) + + +@admin.register(InvoiceItem) +class InvoiceItemAdmin(admin.ModelAdmin): + list_display = ("invoice", "product_name", "product_unit_price", "quantity") + search_fields = ("product_name",) diff --git a/election/admin.py b/election/admin.py index 8c38f3f3..3cd3ce37 100644 --- a/election/admin.py +++ b/election/admin.py @@ -1,3 +1,37 @@ +from ajax_select import make_ajax_form from django.contrib import admin -# Register your models here. +from election.models import Election, Role, ElectionList, Candidature + + +@admin.register(Election) +class ElectionAdmin(admin.ModelAdmin): + list_display = ( + "title", + "is_candidature_active", + "is_vote_active", + "is_vote_finished", + "archived", + ) + form = make_ajax_form(Election, {"voters": "users"}) + + +@admin.register(Role) +class RoleAdmin(admin.ModelAdmin): + list_display = ("election", "title", "max_choice") + search_fields = ("election__title", "title") + + +@admin.register(ElectionList) +class ElectionListAdmin(admin.ModelAdmin): + list_display = ("election", "title") + search_fields = ("election__title", "title") + + +@admin.register(Candidature) +class CandidatureAdmin(admin.ModelAdmin): + list_display = ("user", "role", "election_list") + form = make_ajax_form(Candidature, {"user": "users"}) + + +# Votes must stay fully anonymous, so no ModelAdmin for Vote model diff --git a/launderette/admin.py b/launderette/admin.py index 954a74a8..0346ae22 100644 --- a/launderette/admin.py +++ b/launderette/admin.py @@ -21,13 +21,29 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin from launderette.models import * -# Register your models here. -admin.site.register(Launderette) -admin.site.register(Machine) -admin.site.register(Token) -admin.site.register(Slot) + +@admin.register(Launderette) +class LaunderetteAdmin(admin.ModelAdmin): + list_display = ("name", "counter") + + +@admin.register(Machine) +class MachineAdmin(admin.ModelAdmin): + list_display = ("name", "launderette", "type", "is_working") + + +@admin.register(Token) +class TokenAdmin(admin.ModelAdmin): + list_display = ("name", "launderette", "type", "user") + form = make_ajax_form(Token, {"user": "users"}) + + +@admin.register(Slot) +class SlotAdmin(admin.ModelAdmin): + list_display = ("machine", "user", "start_date") + form = make_ajax_form(Slot, {"user": "users"}) diff --git a/pedagogy/admin.py b/pedagogy/admin.py index 24eb52b8..0f68234d 100644 --- a/pedagogy/admin.py +++ b/pedagogy/admin.py @@ -21,7 +21,39 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin +from haystack.admin import SearchModelAdmin -# Register your models here. +from pedagogy.models import UV, UVComment, UVCommentReport + + +@admin.register(UV) +class UVAdmin(admin.ModelAdmin): + list_display = ("code", "title", "credit_type", "credits", "department") + search_fields = ("code", "title", "department") + form = make_ajax_form(UV, {"author": "users"}) + + +@admin.register(UVComment) +class UVCommentAdmin(admin.ModelAdmin): + list_display = ("author", "uv", "grade_global", "publish_date") + search_fields = ( + "author__username", + "author__first_name", + "author__last_name", + "uv__code", + ) + form = make_ajax_form(UVComment, {"author": "users"}) + + +@admin.register(UVCommentReport) +class UVCommentReportAdmin(SearchModelAdmin): + list_display = ("reporter", "uv") + search_fields = ( + "reporter__username", + "reporter__first_name", + "reporter__last_name", + "comment__uv__code", + ) + form = make_ajax_form(UVCommentReport, {"reporter": "users"}) diff --git a/pedagogy/models.py b/pedagogy/models.py index 5fa21096..cb32a9fe 100644 --- a/pedagogy/models.py +++ b/pedagogy/models.py @@ -325,6 +325,10 @@ class UVCommentReport(models.Model): ) reason = models.TextField(_("reason")) + @cached_property + def uv(self): + return self.comment.uv + def is_owned_by(self, user): """ Can be created by a pedagogy admin, a superuser or a subscriber diff --git a/subscription/admin.py b/subscription/admin.py index ba96e1d4..06a83e6f 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -21,20 +21,25 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - +from ajax_select import make_ajax_form from django.contrib import admin from subscription.models import Subscription -from haystack.admin import SearchModelAdmin -class SubscriptionAdmin(SearchModelAdmin): - search_fields = [ +@admin.register(Subscription) +class SubscriptionAdmin(admin.ModelAdmin): + list_display = ( + "member", + "subscription_type", + "subscription_start", + "subscription_end", + "location", + ) + search_fields = ( "member__username", "subscription_start", "subscription_end", "subscription_type", - ] - - -admin.site.register(Subscription, SubscriptionAdmin) + ) + form = make_ajax_form(Subscription, {"member": "users"}) From da2c15525445d6b7960726e3a41c1575a5edfc50 Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Sun, 18 Dec 2022 15:27:33 +0100 Subject: [PATCH 10/95] update documentation --- doc/about/introduction.rst | 2 +- doc/about/tech.rst | 83 +++++++++++++--- doc/about/versioning.rst | 4 +- doc/frequent/subscriptions.rst | 2 +- doc/start/devtools.rst | 62 +++++++----- doc/start/hello_world.rst | 52 +++++----- doc/start/install.rst | 135 +++++++++++++++----------- doc/start/structure.rst | 170 +++++++++++++++++---------------- pyproject.toml | 2 - 9 files changed, 307 insertions(+), 205 deletions(-) diff --git a/doc/about/introduction.rst b/doc/about/introduction.rst index 42760743..3d682ead 100644 --- a/doc/about/introduction.rst +++ b/doc/about/introduction.rst @@ -10,7 +10,7 @@ Pourquoi réécrire le site L'ancienne version du site, sobrement baptisée `ae2 `_ présentait un nombre impressionnant de fonctionnalités. Il avait été écrit en PHP et se basait sur son propre framework maison. -Malheureusement, son entretiens était plus ou moins hasardeux et son framework reposait sur des principes assez différents de ce qui se fait aujourd'hui, rendant la maintenance difficile. De plus, la version de PHP qu'il utilisait était plus que déprécié et à l'heure de l'arrivée de PHP 7 et de sa non rétrocompatibilité il était vital de faire quelque chose. Il a donc été décidé de le réécrire. +Malheureusement, son entretien était plus ou moins hasardeux et son framework reposait sur des principes assez différents de ce qui se fait aujourd'hui, rendant la maintenance difficile. De plus, la version de PHP qu'il utilisait était plus que dépréciée et à l'heure de l'arrivée de PHP 7 et de sa non rétrocompatibilité il était vital de faire quelque chose. Il a donc été décidé de le réécrire. La philosophie du projet ------------------------ diff --git a/doc/about/tech.rst b/doc/about/tech.rst index 43ac1de3..9585655b 100644 --- a/doc/about/tech.rst +++ b/doc/about/tech.rst @@ -15,6 +15,13 @@ L'écosystème Javascript étant à peine naissant et les frameworks allant et v Ne restait plus que le Python et le Ruby avec les frameworks Django et Ruby On Rails. Ruby ayant une réputation d'être très "cutting edge", c'est Python, un langage bien implanté et ayant fait ses preuves, qui a été retenu. +Il est à noter que réécrire le site avec un framework PHP comme Laravel ou Symphony +eut aussi été possible, ces deux technologies étant assez matures et robustes +au moment où le développement a commencé. +Cependant, il aurait été potentiellemet fastidieux de maintenir en parallèle deux +versions de PHP sur le serveur durant toute la durée du développement. +Il faut aussi prendre en compte que nous étions à ce moment dégoûtés du PHP. + Backend ------- @@ -27,7 +34,7 @@ Le python est un langage de programmation interprété multi paradigme sorti en .. note:: - Puisque toutes les dépendances du backend sont des packages Python, elles sont toutes ajoutées directement dans le fichier **requirements.txt** à la racine du projet. + Puisque toutes les dépendances du backend sont des packages Python, elles sont toutes ajoutées directement dans le fichier **pyproject.toml** à la racine du projet. Django ~~~~~~ @@ -37,7 +44,10 @@ Django Django est un framework web pour Python apparu en 2005. Il fourni un grand nombre de fonctionnalités pour développer un site rapidement et simplement. Cela inclu entre autre un serveur Web de développement, un parseur d'URLs pour le routage des différentes URI du site, un ORM (Object-Relational Mapper) pour la gestion de la base de donnée ainsi qu'un moteur de templates pour le rendu HTML. Django propose une version LTS (Long Term Support) qui reste stable et est maintenu sur des cycles plus longs, ce sont ces versions qui sont utilisées. -PostgreSQL / SQLite +Il est possible que la version de Django utilisée ne soit pas la plus récente. +En effet, la version de Django utilisée est systématiquement celle munie d'un support au long-terme. + +PostgreSQL / SQLite3 ~~~~~~~~~~~~~~~~~~~ | `Site officiel PostgreSQL `__ @@ -48,7 +58,7 @@ Comme la majorité des sites internet, le Sith de l'AE enregistre ses données d Le principal à retenir ici est : * Sur la version de production nous utilisons PostgreSQL, c'est cette version qui doit fonctionner en priorité -* Sur les versions de développement, pour faciliter l'installation du projet, nous utilisons la technologie SQLite qui ne requiert aucune installation spécifique. Certaines instructions ne sont pas supportées par cette technologie et il est parfois nécessaire d'installer PostgreSQL pour le développement de certaines parties du site. +* Sur les versions de développement, pour faciliter l'installation du projet, nous utilisons la technologie SQLite3 qui ne requiert aucune installation spécifique (La librairie `sqlite` est incluse dans les librairies par défaut de Python). Certaines instructions ne sont pas supportées par cette technologie et il est parfois nécessaire d'installer PostgreSQL pour le développement de certaines parties du site. Frontend -------- @@ -82,7 +92,39 @@ jQuery jQuery est une bibliothèque JavaScript libre et multiplateforme créée pour faciliter l'écriture de scripts côté client dans le code HTML des pages web. La première version est lancée en janvier 2006 par John Resig. -C'est une vieille technologie et certains feront remarquer à juste titre que le Javascript moderne permet d'utiliser assez simplement la majorité de ce que fourni jQuery sans rien avoir à installer. Cependant, de nombreuses dépendances du projet utilisent encore jQuery qui est toujours très implanté aujourd'hui. Le sucre syntaxique qu'offre cette libraire reste très agréable à utiliser et économise parfois beaucoup de temps. Ça fonctionne et ça fonctionne très bien. C'est maintenu, léger et pratique, il n'y a pas de raison particulière de s'en séparer. +C'est une vieille technologie et certains feront remarquer à juste titre que le Javascript moderne permet d'utiliser assez simplement la majorité de ce que fournit jQuery sans rien avoir à installer. Cependant, de nombreuses dépendances du projet utilisent encore jQuery qui est toujours très implanté aujourd'hui. Le sucre syntaxique qu'offre cette librairie reste très agréable à utiliser et économise parfois beaucoup de temps. Ça fonctionne et ça fonctionne très bien. C'est maintenu et pratique. + +VueJS +~~~~~ + +`Site officiel `__ + +jQuery permet de facilement manipuler le DOM et faire des requêtes en AJAX, +mais est moins pratique à utiliser pour créer des applications réactives. +C'est pour cette raison que Vue a été intégré au projet. + +Vue est une librairie Javascript qui se concentre sur le rendu déclaratif et la composition des composants. +C'est une technologie très utilisée pour la création d'applications web monopages (ce que le site n'est pas) +mais son architecture progressivement adoptable permet aisément d'adapter son +comportement à une application multipage comme le site AE. + +A ce jour, il est utilisé pour l'interface des barmen, dans l'application des comptoirs. + +AlpineJS +~~~~~~~~ + +`Site officiel `__ + +Dans une démarche similaire à celle de l'introduction de Vue, Alpine a aussi fait son +apparition au sein des dépendances Javascript du site. +La librairie est décrite par ses créateurs comme : +"un outil robuste et minimal pour composer un comportement directement dans vos balises". + +Alpine permet d'accomplir la plupart du temps le même résultat qu'un usage des fonctionnalités +de base de Vue, mais est beaucoup plus léger, un peu plus facile à prendre en main +et ne s'embarasse pas d'un DOM virtuel. +De par son architecture, il extrêmement bien adapté pour un usage dans un site multipage. +C'est une technologie simple et puissante qui se veut comme le jQuery du web moderne. Sass ~~~~ @@ -102,11 +144,11 @@ Fontawesome regroupe tout un ensemble d'icônes libres de droits utilisables fac .. note:: - C'est une dépendance capricieuse qu'il évolue très vite et qu'il faut très souvent mettre à jour. + C'est une dépendance capricieuse qui évolue très vite et qu'il faut très souvent mettre à jour. .. warning:: - Il a été décidé de **ne pas utiliser** de CDN puisque le site ralentissait régulièrement. Il est préférable de fournir cette dépendance avec le site. + Il a été décidé de **ne pas utiliser** de CDN puisque le site ralentissait régulièrement. Il est préférable de fournir cette dépendance avec le site. Documentation ------------- @@ -140,17 +182,28 @@ Git `Site officiel `__ -Git est un logiciel de gestion de versions écrit par Linus Torsvald pour les besoins du noyau linux en 2005. C'est ce logiciel qui remplace svn anciennement utilisé pour gérer les sources du projet (rappelez vous, l'ancien site date d'avant 2005). Git est plus complexe à utiliser mais est bien plus puissant, permet de gérer plusieurs version en parallèle et génère des codebases vraiment plus légères puisque seules les modifications sont enregistrées (contrairement à svn qui garde une copie de la codebase par version). +Git est un logiciel de gestion de versions écrit par Linus Torvalds pour les besoins du noyau linux en 2005. C'est ce logiciel qui remplace svn anciennement utilisé pour gérer les sources du projet (rappelez vous, l'ancien site date d'avant 2005). Git est plus complexe à utiliser mais est bien plus puissant, permet de gérer plusieurs version en parallèle et génère des codebases vraiment plus légères puisque seules les modifications sont enregistrées (contrairement à svn qui garde une copie de la codebase par version). -GitLab +Git s'étant imposé comme le principal outil de gestion de versions, +sa communauté est très grande et sa documentation très fournie. +Il est également aisé de trouver des outils avec une interface graphique, +qui simplifient grandement son usage. + +GitHub ~~~~~~ -| `Site officiel `__ -| `Instance de l'AE `__ +| `Site officiel `__ +| `Page github d'AE-Dev `__ -GitLab est une alternative libre à GitHub. C'est une plate-forme avec interface web permettant de déposer du code géré avec Git offrant également de l'intégration continue et du déploiement automatique. +Github est un service web d'hébergement et de gestion de développement de logiciel. +C'est une plate-forme avec interface web permettant de déposer du code géré avec Git +offrant également de l'intégration continue et du déploiement automatique. +C'est au travers de cette plate-forme que le Sith de l'AE est géré. -C'est au travers de cette plate-forme que le Sith de l'AE est géré, sur une instance hébergée directement sur nos serveurs. +Depuis le 1er Octobre 2022, GitHub remplace GitLab dans un soucis de facilité d'entretien, +les machines sur lesquelles tournent le site étant de plus en plus vielles, il devenait très +difficile d'effectuer les mise à jours de sécurité du GitLab sans avoir de soucis matériel. +pour l'hébergement et la gestion des projets informatiques de l'AE. Sentry ~~~~~~ @@ -166,7 +219,9 @@ Poetry `Utiliser Poetry `__ Poetry est un utilitaire qui permet de créer et gérer des environements Python de manière simple et intuitive. Il permet également de gérer et mettre à jour le fichier de dépendances. -L'avantage d'utiliser poetry (et les environnements virtuels en général) est de pouvoir gérer plusieurs projets différents en parallèles puisqu'il permet d'avoir sur sa machine plusieurs environnements différents et donc plusieurs versions d'une même dépendance dans plusieurs projets différent sans impacter le système sur lequel le tout est installé. +L'avantage d'utiliser poetry (et les environnements virtuels en général) est de pouvoir gérer plusieurs projets différents en parallèle puisqu'il permet d'avoir sur sa machine plusieurs environnements différents et donc plusieurs versions d'une même dépendance dans plusieurs projets différent sans impacter le système sur lequel le tout est installé. + +Les dépendances utilisées par poetry sont déclarées dans le fichier `pyproject.toml`, situé à la racine du projet. Black ~~~~~ @@ -175,4 +230,4 @@ Black Pour faciliter la lecture du code, il est toujours appréciable d'avoir une norme d'écriture cohérente. C'est généralement à l'étape de relecture des modifications par les autres contributeurs que sont repérées ces fautes de normes qui se doivent d'être corrigées pour le bien commun. -Imposer une norme est très fastidieux, que ce soit pour ceux qui relisent ou pour ceux qui écrivent. C'est pour cela que nous utilisons black qui est un formateur automatique de code. Une fois l'outil lancé, il parcours la codebase pour y repérer les fautes de norme et les corrige automatiquement sans que l'utilisateur ai à s'en soucier. Bien installé, il peut effectuer ce travail à chaque sauvegarde d'un fichier dans son éditeur, ce qui est très agréable pour travailler. \ No newline at end of file +Imposer une norme est très fastidieux, que ce soit pour ceux qui relisent ou pour ceux qui écrivent. C'est pour cela que nous utilisons black qui est un formateur automatique de code. Une fois l'outil lancé, il parcours la codebase pour y repérer les fautes de norme et les corrige automatiquement sans que l'utilisateur n'ait à s'en soucier. Bien installé, il peut effectuer ce travail à chaque sauvegarde d'un fichier dans son éditeur, ce qui est très agréable pour travailler. \ No newline at end of file diff --git a/doc/about/versioning.rst b/doc/about/versioning.rst index ab713746..8d32e39a 100644 --- a/doc/about/versioning.rst +++ b/doc/about/versioning.rst @@ -3,11 +3,11 @@ Le versioning Dans le monde du développement, nous faisons face à un problème relativement étrange pour un domaine aussi avancé : on est brouillon. -On teste, on envoie, ça marche pas, on reteste, c'est ok. Par contre, on a oublie plein d'exceptions. Et on refactor. Ça marche mieux mais c'est moins rapide, etc, etc. +On teste, on envoie, ça marche pas, on reteste, c'est ok. Par contre, on a oublié plein d'exceptions. Et on refactor. Ça marche mieux mais c'est moins rapide, etc. Et derrière tout ça, on fait des trucs qui marchent puis on se retrouve dans la mouise parce qu'on a effacé un morceau de code qui nous aurait servi plus tard. -Pour palier à ce problème, le programmeur a créé un principe révolutionnaire (ouais... à mon avis, on s'est inspiré d'autres trucs, mais on va dire que c'est nous les créateurs) : le Versioning (*Apparition inexpliquée*). +Pour pallier ce problème, le programmeur a créé un principe révolutionnaire (ouais... à mon avis, on s'est inspiré d'autres trucs, mais on va dire que c'est nous les créateurs) : le Versioning (*Apparition inexpliquée*). D'après projet-isika (c'est pas wikipedia ouais, ils étaient pas clairs eux), le versioning (ou versionnage en français mais c'est quand même vachement dégueu comme mot) consiste à travailler directement sur le code source d'un projet, en gardant toutes les versions précédentes. Les outils du versioning aident les développeurs à travailler parallèlement sur différentes parties du projet et à revenir facilement aux étapes précédentes de leur travail en cas de besoin. L’utilisation d’un logiciel de versioning est devenue quasi-indispensable pour tout développeur, même s’il travaille seul. diff --git a/doc/frequent/subscriptions.rst b/doc/frequent/subscriptions.rst index c151b35a..fc446201 100644 --- a/doc/frequent/subscriptions.rst +++ b/doc/frequent/subscriptions.rst @@ -8,7 +8,7 @@ Il arrive régulièrement que le type de cotisation proposé varie en prix et en Comprendre la configuration --------------------------- -Pour modifier les cotisations disponnibles, tout se gère dans la configuration avec la variable *SITH_SUBSCRIPTIONS*. Dans cet exemple, nous allons ajouter une nouvelle cotisation d'un mois. +Pour modifier les cotisations disponibles, tout se gère dans la configuration avec la variable *SITH_SUBSCRIPTIONS*. Dans cet exemple, nous allons ajouter une nouvelle cotisation d'un mois. .. code-block:: python diff --git a/doc/start/devtools.rst b/doc/start/devtools.rst index 3688d099..9256bfb4 100644 --- a/doc/start/devtools.rst +++ b/doc/start/devtools.rst @@ -3,35 +3,53 @@ Configurer son environnement de développement Le projet n'est en aucun cas lié à un quelconque environnement de développement. Il est possible pour chacun de travailler avec les outils dont il a envie et d'utiliser l'éditeur de code avec lequel il est le plus à l'aise. -Pour donner une idée, Skia a écrit une énorme partie de projet avec l'éditeur *vim* sur du GNU/Linux alors que Sli a utilisé *Sublime Text* sur MacOS. +Pour donner une idée, Skia a écrit une énorme partie de projet avec l'éditeur *Vim* sur du GNU/Linux +alors que Sli a utilisé *Sublime Text* sur MacOS et que Maréchal travaille avec PyCharm +sur Windows muni de WSL. Configurer Black pour son éditeur --------------------------------- -Tous les détails concernant l'installation de black sont ici : https://black.readthedocs.io/en/stable/editor_integration.html +.. note:: -Néanmoins, nous tenterons de vous faire ici un résumé pour deux éditeurs de textes populaires que sont VsCode et Sublime Text. + Black est inclus dans les dépendances du projet. + Si vous avez réussi à terminer l'installation, vous n'avez donc pas de configuration + supplémentaire à effectuer. -.. sourcecode:: bash +Pour utiliser Black, placez-vous à la racine du projet et lancez la commande suivante : - # Installation de black - pip install black +.. code-block:: + + black . + +Black va alors faire son travail sur l'ensemble du projet puis vous dire quels documents +ont été reformatés. + +Appeler Black en ligne de commandes avant de pousser votre code sur Github +est une technique qui marche très bien. +Cependant, vous risquez de souvent l'oublier. +Or, lorsque le code est mal formaté, la pipeline bloque les PR sur les branches protégées. + +Pour éviter de vous faire régulièrement blacked, vous pouvez configurer +votre éditeur pour que Black fasse son travail automatiquement à chaque édition d'un fichier. +Nous tenterons de vous faire ici un résumé pour deux éditeurs de textes populaires +que sont VsCode et Sublime Text. VsCode ~~~~~~ .. warning:: - Il faut installer black dans son environement virtuel pour cet éditeur + Il faut installer black dans son environement virtuel pour cet éditeur Black est directement pris en charge par l'extension pour le Python de VsCode, il suffit de rentrer la configuration suivante : .. sourcecode:: json - { - "python.formatting.provider": "black", - "editor.formatOnSave": true - } + { + "python.formatting.provider": "black", + "editor.formatOnSave": true + } Sublime Text ~~~~~~~~~~~~ @@ -42,19 +60,19 @@ Il suffit ensuite d'ajouter dans les settings du projet (ou directement dans les .. sourcecode:: json - { - "sublack.black_on_save": true - } + { + "sublack.black_on_save": true + } Si vous utilisez le plugin `anaconda `__, pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de black comme ceci : .. sourcecode:: json - { - "pep8_ignore": [ - "E203", - "E266", - "E501", - "W503" - ] - } + { + "pep8_ignore": [ + "E203", + "E266", + "E501", + "W503" + ] + } diff --git a/doc/start/hello_world.rst b/doc/start/hello_world.rst index 9d3c2b2a..88e585bf 100644 --- a/doc/start/hello_world.rst +++ b/doc/start/hello_world.rst @@ -32,13 +32,13 @@ Enfin, on vas inclure les URLs de cette application dans le projet sous le préf # sith/urls.py urlpatterns = [ ... - url(r"^hello/", include("hello.urls", namespace="hello", app_name="hello")), + path("hello/", include("hello.urls", namespace="hello", app_name="hello")), ] Un Hello World simple --------------------- -Dans un premier temps, nous allons créer une vue qui vas charger un template en utilisant le système de vues basées sur les classes de Django. +Dans un premier temps, nous allons créer une vue qui va charger un template en utilisant le système de vues basé sur les classes de Django. .. code-block:: python @@ -63,26 +63,26 @@ On vas ensuite créer le template. {# On remplis la partie titre du template étendu #} {# Il s'agit du titre qui sera affiché dans l'onglet du navigateur #} {% block title %} - Hello World - {% endblock title %} + Hello World + {% endblock %} {# On remplis le contenu de la page #} {% block content %} -

Hello World !

- {% endblock content %} +

Hello World !

+ {% endblock %} Enfin, on crée l'URL. On veut pouvoir appeler la page depuis https://localhost:8000/hello, le préfixe indiqué précédemment suffit donc. .. code-block:: python # hello/urls.py - from django.conf.urls import url + from django.urls import path from hello.views import HelloView urlpatterns = [ # Le préfixe étant retiré lors du passage du routeur d'URL # dans le fichier d'URL racine du projet, l'URL à matcher ici est donc vide - url(r"^$", HelloView.as_view(), name="hello"), + path("", HelloView.as_view(), name="hello"), ] Et voilà, c'est fini, il ne reste plus qu'à lancer le serveur et à se rendre sur la page. @@ -95,13 +95,12 @@ Dans cette partie, on cherche à détecter les numéros passés dans l'URL pour .. code-block:: python # hello/urls.py - from django.conf.urls import url + from django.urls import path from hello.views import HelloView urlpatterns = [ - url(r"^$", HelloView.as_view(), name="hello"), - # On utilise un regex pour matcher un numéro - url(r"^(?P[0-9]+)$", HelloView.as_view(), name="hello"), + path("", HelloView.as_view(), name="hello"), + path("", HelloView.as_view(), name="hello"), ] Cette deuxième URL vas donc appeler la classe crée tout à l'heure en lui passant une variable *hello_id* dans ses *kwargs*, nous allons la récupérer et la passer dans le contexte du template en allant modifier la vue. @@ -143,16 +142,16 @@ Enfin, on modifie le template en rajoutant une petite condition sur la présence {% extends "core/base.jinja" %} {% block title %} - Hello World + Hello World {% endblock title %} {% block content %} -

- Hello World ! - {% if hello_id -%} - {{ hello_id }} - {%- endif -%} -

+

+ Hello World ! + {% if hello_id -%} + {{ hello_id }} + {%- endif -%} +

{% endblock content %} .. note:: @@ -162,7 +161,7 @@ Enfin, on modifie le template en rajoutant une petite condition sur la présence À l'assaut des modèles ---------------------- -Pour cette dernière partie, nous allons ajouter une entrée dans la base de donnée et l'afficher dans un template. Nous allons ainsi créer un modèle nommé *Article* qui contiendra une entrée de texte pour le titre et une autre pour le contenu. +Pour cette dernière partie, nous allons ajouter une entrée dans la base de données et l'afficher dans un template. Nous allons ainsi créer un modèle nommé *Article* qui contiendra une entrée de texte pour le titre et une autre pour le contenu. Commençons par le modèle en lui même. @@ -203,7 +202,7 @@ On n'oublie pas l'URL. urlpatterns = [ ... - url(r"^articles$", ArticlesListView.as_view(), name="articles_list") + path("articles/", ArticlesListView.as_view(), name="articles_list") ] Et enfin le template. @@ -227,7 +226,7 @@ Et enfin le template. Maintenant que toute la logique de récupération et d'affichage est terminée, la page est accessible à l'adresse https://localhost:8000/hello/articles. -Mais, j'ai une erreur ! Il se passe quoi ?! Et bien c'est simple, nous avons crée le modèle mais il n'existe pas dans la base de données. Il est dans un premier temps important de créer un fichier de migrations qui contiens des instructions pour la génération de celle-ci. Ce sont les fichiers qui sont enregistrés dans le dossier migration. Pour les générer à partir des classes de modèles qu'on viens de manipuler il suffit d'une seule commande. +Mais, j'ai une erreur ! Il se passe quoi ?! Et bien c'est simple, nous avons créé le modèle mais il n'existe pas dans la base de données. Il est dans un premier temps important de créer un fichier de migrations qui contient des instructions pour la génération de celles-ci. Ce sont les fichiers qui sont enregistrés dans le dossier migration. Pour les générer à partir des classes de modèles qu'on vient de manipuler il suffit d'une seule commande. .. code-block:: bash @@ -245,7 +244,7 @@ J'ai toujours une erreur ! Mais oui, c'est pas fini, faut pas aller trop vite. M ./manage.py migrate -Et voilà, là il n'y a plus d'erreur. Tout fonctionne et on a une superbe page vide puisque aucun contenu pour cette table n'est dans la base. Nous allons en rajouter. Pour cela nous allons utiliser le fichier *core/management/commands/populate.py* qui contiens la commande qui initialise les données de la base de données de test. C'est un fichier très important qu'on viendra à modifier assez souvent. Nous allons y ajouter quelques articles. +Et voilà, là il n'y a plus d'erreur. Tout fonctionne et on a une superbe page vide puisque aucun contenu pour cette table n'est dans la base. Nous allons en rajouter. Pour cela nous allons utiliser le fichier *core/management/commands/populate.py* qui contient la commande qui initialise les données de la base de données de test. C'est un fichier très important qu'on viendra à modifier assez souvent. Nous allons y ajouter quelques articles. .. code-block:: python @@ -262,14 +261,15 @@ Et voilà, là il n'y a plus d'erreur. Tout fonctionne et on a une superbe page ... + # les deux syntaxes ci-dessous sont correctes et donnent le même résultat Article(title="First hello", content="Bonjour tout le monde").save() - Article(title="Tutorial", content="C'était un super tutoriel").save() + Article.objects.create(title="Tutorial", content="C'était un super tutoriel") -On regénère enfin les données de test en lançant la commande que l'on viens de modifier. +On regénère enfin les données de test en lançant la commande que l'on vient de modifier. .. code-block:: bash ./manage.py setup -On reviens sur https://localhost:8000/hello/articles et cette fois-ci nos deux articles apparaissent correctement. \ No newline at end of file +On revient sur https://localhost:8000/hello/articles et cette fois-ci nos deux articles apparaissent correctement. \ No newline at end of file diff --git a/doc/start/install.rst b/doc/start/install.rst index 2aa51991..f94e9122 100644 --- a/doc/start/install.rst +++ b/doc/start/install.rst @@ -7,7 +7,6 @@ Dépendances du système Certaines dépendances sont nécessaires niveau système : * poetry -* libmysqlclient * libssl * libjpeg * libxapian-dev @@ -15,18 +14,80 @@ Certaines dépendances sont nécessaires niveau système : * python * gettext * graphviz -* mysql-client (pour migrer de l'ancien site) + +Sur Windows +~~~~~~~~~~~ + +Chers utilisateurs de Windows, quel que soit votre amour de Windows, +de Bill Gates et des bloatwares, je suis désolé +de vous annoncer que, certaines dépendances étant uniquement disponibles sur des sytèmes UNIX, +il n'est pas possible développer le site sur Windows. + +Heureusement, il existe une alternative qui ne requiert pas de désinstaller votre +OS ni de mettre un dual boot sur votre ordinateur : :code:`WSL`. + +- **Prérequis:** vous devez être sur Windows 10 version 2004 ou ultérieure (build 19041 & versions ultérieures) ou Windows 11. +- **Plus d'info:** `docs.microsoft.com `_ + +.. sourcecode:: bash + + # dans un shell Windows + wsl --install + + # afficher la liste des distribution disponible avec WSL + wsl -l -o + + # installer WSL avec une distro (ubuntu conseillé) + wsl --install -d + +.. note:: + + Si vous rencontrez le code d'erreur ``0x80370102``, regardez les réponses de ce `post `_. + +Une fois :code:`WSL` installé, mettez à jour votre distro & installez les dépendances **(voir la partie installation sous Ubuntu)**. + +.. note:: + + Comme `git` ne fonctionne pas de la même manière entre Windows & Unix, il est nécessaire de cloner le repository depuis Windows. + (cf: `stackoverflow.com `_) + +Pour accéder au contenu d'un répertoire externe à :code:`WSL`, il suffit simplement d'utiliser la commande suivante: + +.. sourcecode:: bash + + # oui c'est beau, simple et efficace + cd /mnt//vos/fichiers/comme/dhab + +Une fois l'installation des dépendances terminée (juste en dessous), il vous suffira, pour commencer à dev, d'ouvrir votre plus bel IDE et d'avoir 2 consoles: +1 console :code:`WSL` pour lancer le projet & 1 console pour utiliser :code:`git` + +.. note:: + + A ce stade, si vous avez réussi votre installation de :code:`WSL` ou bien qu'il + était déjà installé, vous pouvez effectuer la mise en place du projet en suivant + les instructions pour Ubuntu. + Sur Ubuntu ~~~~~~~~~~ .. sourcecode:: bash - sudo apt install libssl-dev libjpeg-dev zlib1g-dev python-dev libffi-dev python-dev libgraphviz-dev pkg-config libxapian-dev gettext git + # Sait-on jamais + sudo apt update + + sudo apt install python-is-python3 # Permet d'utiliser python au lieu de python3, c'est optionel + + sudo apt install build-essentials libssl-dev libjpeg-dev zlib1g-dev python-dev \ + libffi-dev python-dev-is-python3 libgraphviz-dev pkg-config libxapian-dev \ + gettext git + curl -sSL https://install.python-poetry.org | python - - # To include mysql for importing old bdd - sudo apt install libmysqlclient-dev +.. note:: + + Si vous avez réussi à exécuter les instructions ci-dessus sans trop de problèmes, + vous pouvez passer à la partie :ref:`Finalise installation` Sur MacOS ~~~~~~~~~ @@ -51,61 +112,21 @@ Pour installer les dépendances, il est fortement recommandé d'installer le ges Si vous rencontrez des erreurs lors de votre configuration, n'hésitez pas à vérifier l'état de votre installation homebrew avec :code:`brew doctor` -Sur Windows avec :code:`WSL` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - .. note:: - Comme certaines dépendances sont uniquement disponible dans un environnement Unix, il est obligatoire de passer par :code:`WSL` pour installer le projet. + Si vous avez réussi à exécuter les instructions ci-dessus sans trop de problèmes, + vous pouvez passer à la partie :ref:`Finalise installation` -- **Prérequis:** vous devez exécuter Windows 10 versions 2004 et ultérieures (build 19041 & versions ultérieures) ou Windows 11. -- **Plus d'info:** `docs.microsoft.com `_ - -.. sourcecode:: bash +.. _Finalise installation: - # dans un shell Windows - wsl --install - - # afficher la liste des distribution disponible avec WSL - wsl -l -o - - # installer WSL avec une distro - wsl --install -d - -.. note:: - - Si vous rencontrez le code d'erreur ``0x80370102``, regardez les réponses de ce `post `_. - -Une fois :code:`WSL` installé, mettez à jour votre distro & installez les dépendances **(voir la partie installation sous Ubuntu)**. - -.. note:: - - Comme `git` ne fonctionne pas de la même manière entre Windows & Unix, il est nécessaire de cloner le repository depuis Windows. - (cf: `stackoverflow.com `_) - -Pour accéder au contenu d'un répertoire externe à :code:`WSL`, il suffit simplement d'utiliser la commande suivante: +Finaliser l'installation +------------------------ .. sourcecode:: bash - - # oui c'est beau, simple et efficace - cd /mnt//vos/fichiers/comme/dhab - -.. note:: - - Une fois l'installation des dépendances terminée (juste en dessous), il vous suffira, pour commencer à dev, d'ouvrir votre plus bel IDE et d'avoir 2 consoles: - 1 console :code:`WSL` pour lancer le projet & 1 console pour utiliser :code:`git` - -Installer le projet ------------------------------------ - -.. sourcecode:: bash - - # Sait-on jamais - sudo apt update # Les commandes git doivent se faire depuis le terminal de Windows si on utilise WSL ! git clone https://github.com/ae-utbm/sith3.git - cd Sith + cd sith3 # Création de l'environnement et installation des dépendances poetry install @@ -113,7 +134,7 @@ Installer le projet # Activation de l'environnement virtuel poetry shell - # Prépare la base de donnée + # Prépare la base de données python manage.py setup # Installe les traductions @@ -144,15 +165,21 @@ Il faut toujours avoir préalablement activé l'environnement virtuel comme fait .. note:: - Le serveur est alors accessible à l'adresse http://localhost:8000. + Le serveur est alors accessible à l'adresse http://localhost:8000 ou bien http://127.0.0.1:8000/. Générer la documentation ------------------------ -La documentation est automatiquement mise en ligne sur readthedocs à chaque envoi de code sur GitLab. +La documentation est automatiquement mise en ligne sur readthedocs à chaque envoi de code sur Github. Pour l'utiliser en local ou globalement pour la modifier, il existe une commande du site qui génère la documentation et lance un serveur la rendant accessible à l'adresse http://localhost:8080. Cette commande génère la documentation à chacune de ses modifications, inutile de relancer le serveur à chaque fois. +.. note:: + + Les dépendances pour la documentation sont optionnelles. + Avant de commencer à travailler sur la doc, il faut donc les installer + avec la commande :code:`poetry install -E docs` + .. sourcecode:: bash python manage.py documentation diff --git a/doc/start/structure.rst b/doc/start/structure.rst index 5bbfde32..1c5e3cb5 100644 --- a/doc/start/structure.rst +++ b/doc/start/structure.rst @@ -28,75 +28,76 @@ Le découpage en applications ---------------------------- | /projet -| **sith/** -| Application principale du projet. -| **accounting/** -| Ajoute un système de comptabilité. -| **api/** -| Application où mettre les endpoints publiques d'API. -| **club/** -| Contiens les modèles liés aux clubs associatifs et ajoute leur gestion. -| **com/** -| Fournis des outils de communications aux clubs (weekmail, affiches…). -| **core/** -| Application la plus importante. Contiens les principales surcouches -| liées au projet comme la gestion des droits et les templates de base. -| **counter/** -| Ajoute des comptoirs de vente pour les clubs et gère les ventes sur les lieux de vie. -| **data/** -| Contiens les fichiers statiques ajoutées par les utilisateurs. -| N'est pas suivit par Git. -| **doc/** -| Contiens la documentation du projet. -| **eboutic/** -| Ajoute le comptoir de vente en ligne. Permet d'acheter en carte bancaire. -| **election/** -| Ajoute un système d'élection permettant d'élire les représentants étudiants. -| **forum/** -| Ajoute un forum de discutions. -| **launderette/** -| Permet la gestion des laveries. -| **locale/** -| Contiens les fichiers de traduction. -| **matmat/** -| Système de recherche de membres. -| **pedagogy/** -| Contiens le guide des UVs. -| **rootplace/** -| Ajoute des outils destinés aux administrateurs. -| **static/** -| Contiens l'ensemble des fichiers statiques ajoutés par les développeurs. -| Ce dossier est généré par le framework, il est surtout utile en production. -| Ce dossier n'es pas suivit par Git. -| **stock/** -| Système de gestion des stocks. -| **subscription/** -| Ajoute la gestion des cotisations des membres. -| **trombi/** -| Permet la génération du trombinoscope des élèves en fin de cursus. -| **.coveragec** -| Configure l'outil permettant de calculer la couverture des tests sur le projet. -| **.gitignore** -| Permet de définir quels fichiers sont suivis ou non par Git. -| **.gitlab-ci.yml** -| Permet de configurer la pipeline automatique de GitLab. -| **.readthedocs.yml** -| Permet de configurer la génération de documentation sur Readthedocs. -| **.db.sqlite3** -| Base de données de développement par défaut. Est automatiquement généré -| lors de la configuration du projet en local. N'est pas suivis par Git. -| **LICENSE** -| Licence du projet. -| **LICENSE.old** -| Ancienne licence du projet. -| **manage.py** -| Permet de lancer les commandes liées au framework Django. -| **migrate.py** -| Contiens des scripts de migration à exécuter pour importer les données de l'ancien site. -| **README.rst** -| Fichier de README. À lire pour avoir des informations sur le projet. -| **requirements.txt** -| Contiens les dépendances Python du projet. +| **sith/** +| Application principale du projet. +| **accounting/** +| Ajoute un système de comptabilité. +| **api/** +| Application où mettre les endpoints publiques d'API. +| **club/** +| Contient les modèles liés aux clubs et assos et ajoute leur gestion. +| **com/** +| Fournis des outils de communications aux clubs (weekmail, affiches…). +| **core/** +| Application la plus importante. Contient les principales surcouches +| liées au projet comme la gestion des droits et les templates de base. +| **counter/** +| Ajoute des comptoirs de vente pour les clubs et gère les ventes sur les lieux de vie. +| **data/** +| Contient les fichiers statiques ajoutés par les utilisateurs. +| N'est pas suivi par Git. +| **doc/** +| Contient la documentation du projet. +| **eboutic/** +| Ajoute le comptoir de vente en ligne. Permet d'acheter en carte bancaire. +| **election/** +| Ajoute un système d'élection permettant d'élire les représentants étudiants. +| **forum/** +| Ajoute un forum de discussion. +| **launderette/** +| Permet la gestion des laveries. +| **locale/** +| Contient les fichiers de traduction. +| **matmat/** +| Système de recherche de membres. +| **pedagogy/** +| Contient le guide des UVs. +| **rootplace/** +| Ajoute des outils destinés aux administrateurs. +| **static/** +| Contient l'ensemble des fichiers statiques ajoutés par les développeurs. +| Ce dossier est généré par le framework, il est surtout utile en production ; +| évitez d'y toucher pendant le développement. +| Ce dossier n'est pas suivi par Git. +| **stock/** +| Système de gestion des stocks. +| **subscription/** +| Ajoute la gestion des cotisations des membres. +| **trombi/** +| Permet la génération du trombinoscope des élèves en fin de cursus. +| **.coveragec** +| Configure l'outil permettant de calculer la couverture des tests sur le projet. +| **.gitignore** +| Permet de définir quels fichiers sont suivis ou non par Git. +| **.github/** +| Contient les fichiers de configuration des actions github. +| **.readthedocs.yml** +| Permet de configurer la génération de documentation sur Readthedocs. +| **.db.sqlite3** +| Base de données de développement par défaut. Est automatiquement généré +| lors de la configuration du projet en local. N'est pas suivie par Git. +| **LICENSE** +| Licence du projet. +| **LICENSE.old** +| Ancienne licence du projet. +| **manage.py** +| Permet de lancer les commandes liées au framework Django. +| **migrate.py** +| Contiens des scripts de migration à exécuter pour importer les données de l'ancien site. +| **README.md** +| Fichier de README. À lire pour avoir des informations sur le projet. +| **pyproject.toml** +| Contient les dépendances Python du projet. L'application principale @@ -107,16 +108,20 @@ L'application principale | Permet de définir le dossier comme un package Python. | Ce fichier est vide. | **settings.py** -| Contiens les paramètres par défaut du projet. +| Contient les paramètres par défaut du projet. | Ce fichier est versionné et fait partie intégrant de celui-ci. -| **settings_curtom.py** -| Contiens les paramètres spécifiques à l'installation courante. -| Ce fichier n'est pas versionné et surcharges les paramètres par défaut. +| Notez que les informations sensibles qui se trouvent dans ce fichier +| ne sont pas celles utilisées en production. +| Ce sont des paramètres factices préremplies pour faciliter la mise en place +| du projet qui sont surchargés en production par les vrais paramètres. +| **settings_custom.py** +| Contient les paramètres spécifiques à l'installation courante. +| Ce fichier n'est pas versionné et surcharge les paramètres par défaut. | **urls.py** -| Contiens les routes d'URLs racines du projet. -| On y inclus les autres fichiers d'URLs et leur namespace. +| Contient les routes d'URLs racines du projet. +| On y inclut les autres fichiers d'URLs et leur namespace. | **toolbar_debug.py** -| Contiens la configuration de la barre de debug à gauche à destination +| Contient la configuration de la barre de debug à gauche à destination | du site de développement. | **et_keys/** | Contiens la clef publique du système de paiement E-Transactions. @@ -131,23 +136,22 @@ Le contenu d'une application | /app1 | **__init__.py** | Permet de définir le dossier comme un package Python. -| Ce fichier est généralement vide. | **models.py** | C'est là que les modèles sont définis. Ces classes définissent -| les tables dans la base de donnée. +| les tables dans la base de données. | **views.py** | C'est là où les vues sont définies. | **admin.py** | C'est là que l'on déclare quels modèles doivent apparaître | dans l'interface du module d'administration de Django. | **tests.py** -| Ce fichier contiens les tests fonctionnels, unitaires -| mais aussi d'intégrations qui sont lancés par la pipeline. +| Ce fichier contient les tests fonctionnels, unitaires +| et d'intégrations qui sont lancés par la pipeline. | **urls.py** -| On y défini les URLs de l'application et on les lies aux vues. +| On y définit les URLs de l'application et on les lie aux vues. | **migrations/** | Ce dossier sert à stocker les fichiers de migration de la base -| de données générées par la commande *makemigrations*. +| de données générés par la commande *makemigrations*. | **templates/** -| Ce dossier ci contiens généralement des sous dossiers et sert +| Ce dossier-ci contient généralement des sous-dossiers et sert | à accueillir les templates. Les sous dossiers servent de namespace. diff --git a/pyproject.toml b/pyproject.toml index 99b3958f..4206ccfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,6 @@ django-countries = "^7.4.2" dict2xml = "^1.7.2" # Extra optional dependencies -mysqlclient = { version = "^2.0.3", optional = true } coverage = {version = "^5.5", optional = true} # Docs extra dependencies @@ -57,7 +56,6 @@ sphinx-copybutton = {version = "^0.4.0", optional = true} [tool.poetry.extras] testing = ["coverage"] -migration = ["mysqlclient"] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] [tool.poetry.dev-dependencies] From 754be1c9c9fd1bb48fbe170f318b51238537f970 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Tue, 20 Dec 2022 21:17:52 +0100 Subject: [PATCH 11/95] Update doc/about/tech.rst Co-authored-by: Julien Constant <49886317+Juknum@users.noreply.github.com> --- doc/about/tech.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/about/tech.rst b/doc/about/tech.rst index 9585655b..475d7b38 100644 --- a/doc/about/tech.rst +++ b/doc/about/tech.rst @@ -202,7 +202,7 @@ C'est au travers de cette plate-forme que le Sith de l'AE est géré. Depuis le 1er Octobre 2022, GitHub remplace GitLab dans un soucis de facilité d'entretien, les machines sur lesquelles tournent le site étant de plus en plus vielles, il devenait très -difficile d'effectuer les mise à jours de sécurité du GitLab sans avoir de soucis matériel. +difficile d'effectuer les mise à jours de sécurité du GitLab sans avoir de soucis matériel pour l'hébergement et la gestion des projets informatiques de l'AE. Sentry From f681c981c6fb5337f7448711d40c609b78099286 Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Mon, 26 Dec 2022 18:51:04 +0100 Subject: [PATCH 12/95] remove csrf_token --- eboutic/templates/eboutic/eboutic_makecommand.jinja | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eboutic/templates/eboutic/eboutic_makecommand.jinja b/eboutic/templates/eboutic/eboutic_makecommand.jinja index eb2f6def..1256c72e 100644 --- a/eboutic/templates/eboutic/eboutic_makecommand.jinja +++ b/eboutic/templates/eboutic/eboutic_makecommand.jinja @@ -103,7 +103,6 @@

{% endif %}
- {% csrf_token %} @@ -129,8 +128,7 @@ const create_billing_info_url = '{{ url("counter:create_billing_info", user_id=request.user.id) }}' const edit_billing_info_url = '{{ url("counter:edit_billing_info", user_id=request.user.id) }}'; const et_data_url = '{{ url("eboutic:et_data") }}' - let billing_info_exist = - {{ "true" if billing_infos else "false" }} + let billing_info_exist = {{ "true" if billing_infos else "false" }} {% if billing_infos %} const et_data = {{ billing_infos|tojson }} From fe8b8f46aafe58dd7a06f6c06403c99b6b873e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DURR?= Date: Fri, 6 Jan 2023 20:02:45 +0100 Subject: [PATCH 13/95] Fix 3DSv2 implementation (#542) * Fixed wrong HMAC signature generation * Fix xml du panier Co-authored-by: Julien Constant --- counter/models.py | 2 +- eboutic/models.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/counter/models.py b/counter/models.py index cfbc437e..ea8c2338 100644 --- a/counter/models.py +++ b/counter/models.py @@ -181,7 +181,7 @@ class BillingInfo(models.Model): if self.address_2: data["Address"]["Address2"] = self.address_2 xml = dict2xml(data, wrap="Billing", newlines=False) - return '\n' + xml + return '' + xml def __str__(self): return f"{self.first_name} {self.last_name}" diff --git a/eboutic/models.py b/eboutic/models.py index ced2821c..0c5c7dc7 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -199,9 +199,12 @@ class Basket(models.Model): ("PBX_TYPECARTE", "CB"), ("PBX_TIME", datetime.now().replace(microsecond=0).isoformat("T")), ] - cart = {"shoppingcart": {"total": min(self.items.count(), 99)}} - cart = dict2xml(cart, newlines=False) - cart = '' + cart + cart = { + "shoppingcart": {"total": {"totalQuantity": min(self.items.count(), 99)}} + } + cart = '' + dict2xml( + cart, newlines=False + ) data += [ ("PBX_SHOPPINGCART", html.escape(cart)), ("PBX_BILLING", html.escape(customer.billing_infos.to_3dsv2_xml())), From 4fe46fbcef15ea52b06b28a6379d7f1817305fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DURR?= Date: Mon, 9 Jan 2023 17:46:34 +0100 Subject: [PATCH 14/95] [FIX] 3DSv2 - Echappement du XML et modif tables (#543) * Fixed wrong HMAC signature generation * Updated migration files Co-authored-by: Julien Constant --- counter/migrations/0019_billinginfo.py | 27 ++++++++++++++++---------- counter/models.py | 4 ++-- eboutic/models.py | 4 ++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/counter/migrations/0019_billinginfo.py b/counter/migrations/0019_billinginfo.py index d45f287e..4a8af24b 100644 --- a/counter/migrations/0019_billinginfo.py +++ b/counter/migrations/0019_billinginfo.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.15 on 2022-11-14 13:26 +# Generated by Django 3.2.16 on 2023-01-08 12:49 from django.db import migrations, models import django.db.models.deletion @@ -12,6 +12,10 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AlterModelOptions( + name="producttype", + options={"ordering": ["-priority", "name"], "verbose_name": "product type"}, + ), migrations.CreateModel( name="BillingInfo", fields=[ @@ -24,23 +28,26 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("first_name", models.CharField(max_length=30)), - ("last_name", models.CharField(max_length=30)), + ( + "first_name", + models.CharField(max_length=22, verbose_name="First name"), + ), + ( + "last_name", + models.CharField(max_length=22, verbose_name="Last name"), + ), ( "address_1", - models.CharField(max_length=50, verbose_name="address line 1"), + models.CharField(max_length=50, verbose_name="Address 1"), ), ( "address_2", models.CharField( - blank=True, - max_length=50, - null=True, - verbose_name="address line 2", + blank=True, max_length=50, null=True, verbose_name="Address 2" ), ), - ("zip_code", models.CharField(max_length=16, verbose_name="zip code")), - ("city", models.CharField(max_length=50, verbose_name="city")), + ("zip_code", models.CharField(max_length=16, verbose_name="Zip code")), + ("city", models.CharField(max_length=50, verbose_name="City")), ("country", django_countries.fields.CountryField(max_length=2)), ( "customer", diff --git a/counter/models.py b/counter/models.py index ea8c2338..564d6a3b 100644 --- a/counter/models.py +++ b/counter/models.py @@ -154,8 +154,8 @@ class BillingInfo(models.Model): # declaring surname and name even though they are already defined # in User add some redundancy, but ensures that the billing infos # shall stay correct, whatever shenanigans the user commits on its profile - first_name = models.CharField(_("First name"), max_length=30) - last_name = models.CharField(_("Last name"), max_length=30) + first_name = models.CharField(_("First name"), max_length=22) + last_name = models.CharField(_("Last name"), max_length=22) address_1 = models.CharField(_("Address 1"), max_length=50) address_2 = models.CharField(_("Address 2"), max_length=50, blank=True, null=True) zip_code = models.CharField(_("Zip code"), max_length=16) # code postal diff --git a/eboutic/models.py b/eboutic/models.py index 0c5c7dc7..99fe6410 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -206,8 +206,8 @@ class Basket(models.Model): cart, newlines=False ) data += [ - ("PBX_SHOPPINGCART", html.escape(cart)), - ("PBX_BILLING", html.escape(customer.billing_infos.to_3dsv2_xml())), + ("PBX_SHOPPINGCART", cart), + ("PBX_BILLING", customer.billing_infos.to_3dsv2_xml()), ] pbx_hmac = hmac.new( settings.SITH_EBOUTIC_HMAC_KEY, From cce686f3a824b972dadc86b4712d058830499e36 Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Mon, 9 Jan 2023 19:04:32 +0100 Subject: [PATCH 15/95] Update doc/about/tech.rst --- doc/about/tech.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/about/tech.rst b/doc/about/tech.rst index 475d7b38..933e232d 100644 --- a/doc/about/tech.rst +++ b/doc/about/tech.rst @@ -193,7 +193,7 @@ GitHub ~~~~~~ | `Site officiel `__ -| `Page github d'AE-Dev `__ +| `Page github du Pôle Informatique de l'AE `__ Github est un service web d'hébergement et de gestion de développement de logiciel. C'est une plate-forme avec interface web permettant de déposer du code géré avec Git From 7cadc0bc289f2f6d6fea2b866d937e0ce14d9c5b Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Mon, 9 Jan 2023 19:04:43 +0100 Subject: [PATCH 16/95] Update doc/start/install.rst --- doc/start/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/start/install.rst b/doc/start/install.rst index f94e9122..0e97ab15 100644 --- a/doc/start/install.rst +++ b/doc/start/install.rst @@ -170,7 +170,7 @@ Il faut toujours avoir préalablement activé l'environnement virtuel comme fait Générer la documentation ------------------------ -La documentation est automatiquement mise en ligne sur readthedocs à chaque envoi de code sur Github. +La documentation est automatiquement mise en ligne sur readthedocs à chaque envoi de code sur GitHub. Pour l'utiliser en local ou globalement pour la modifier, il existe une commande du site qui génère la documentation et lance un serveur la rendant accessible à l'adresse http://localhost:8080. Cette commande génère la documentation à chacune de ses modifications, inutile de relancer le serveur à chaque fois. From 37216cd16b3fb665ab11c2aedc5dc25879906206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20DURR?= Date: Mon, 9 Jan 2023 19:29:04 +0100 Subject: [PATCH 17/95] Updated lock file according to pyproject --- poetry.lock | 2195 +++++++++++++++++++++++++-------------------------- 1 file changed, 1078 insertions(+), 1117 deletions(-) diff --git a/poetry.lock b/poetry.lock index f94ebc5e..898b312d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + [[package]] name = "alabaster" version = "0.7.12" @@ -5,6 +7,10 @@ description = "A configurable sidebar-enabled Sphinx theme" category = "main" optional = true python-versions = "*" +files = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] [[package]] name = "appnope" @@ -13,25 +19,37 @@ description = "Disable App Nap on macOS >= 10.9" category = "dev" optional = false python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] [[package]] name = "asgiref" -version = "3.5.2" +version = "3.6.0" description = "ASGI specs, helper code, and adapters" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, + {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, +] [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] -name = "Babel" +name = "babel" version = "2.11.0" description = "Internationalization utilities" category = "main" optional = true python-versions = ">=3.6" +files = [ + {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, + {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, +] [package.dependencies] pytz = ">=2015.7" @@ -43,14 +61,32 @@ description = "Specifications for callback functions passed in to an API" category = "dev" optional = false python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, +] [package.dependencies] click = ">=8.0.0" @@ -68,11 +104,15 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "cffi" @@ -81,953 +121,7 @@ description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = true -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" - -[[package]] -name = "coverage" -version = "5.5" -description = "Code coverage measurement for Python" -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" - -[package.extras] -toml = ["toml"] - -[[package]] -name = "cryptography" -version = "37.0.4" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "dict2xml" -version = "1.7.2" -description = "Small utility to convert a python dictionary into an XML string" -category = "main" -optional = false -python-versions = ">= 3.5" - -[package.extras] -tests = ["noseOfYeti (==2.3.1)", "pytest (==7.1.3)"] - -[[package]] -name = "Django" -version = "3.2.16" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -asgiref = ">=3.3.2,<4" -pytz = "*" -sqlparse = ">=0.2.2" - -[package.extras] -argon2 = ["argon2-cffi (>=19.1.0)"] -bcrypt = ["bcrypt"] - -[[package]] -name = "django-ajax-selects" -version = "2.2.0" -description = "Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "django-countries" -version = "7.4.2" -description = "Provides a country field for Django models." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -typing-extensions = "*" - -[package.extras] -dev = ["black", "django", "djangorestframework", "graphene-django", "pytest", "pytest-django", "tox"] -maintainer = ["django", "transifex-client", "zest.releaser[recommended]"] -pyuca = ["pyuca"] -test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytest-django"] - -[[package]] -name = "django-debug-toolbar" -version = "3.7.0" -description = "A configurable set of panels that display various debug information about the current request/response." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Django = ">=3.2.4" -sqlparse = ">=0.2.0" - -[[package]] -name = "django-haystack" -version = "3.2.1" -description = "Pluggable search for Django." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -Django = ">=2.2" - -[package.extras] -elasticsearch = ["elasticsearch (>=5,<8)"] - -[[package]] -name = "django-jinja" -version = "2.10.2" -description = "Jinja2 templating language integrated in Django." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -django = ">=2.2" -jinja2 = ">=3" - -[[package]] -name = "django-ordered-model" -version = "3.6" -description = "Allows Django models to be ordered and provides a simple admin interface for reordering them." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "django-phonenumber-field" -version = "6.4.0" -description = "An international phone number field for django models." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Django = ">=3.2" - -[package.extras] -phonenumbers = ["phonenumbers (>=7.0.2)"] -phonenumberslite = ["phonenumberslite (>=7.0.2)"] - -[[package]] -name = "django-ranged-response" -version = "0.2.0" -description = "Modified Django FileResponse that adds Content-Range headers." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -django = "*" - -[[package]] -name = "django-simple-captcha" -version = "0.5.17" -description = "A very simple, yet powerful, Django captcha application" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -Django = ">=2.2" -django-ranged-response = "0.2.0" -Pillow = ">=6.2.0" - -[package.extras] -test = ["testfixtures"] - -[[package]] -name = "djangorestframework" -version = "3.14.0" -description = "Web APIs for Django, made easy." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -django = ">=3.0" -pytz = "*" - -[[package]] -name = "docutils" -version = "0.17.1" -description = "Docutils -- Python Documentation Utilities" -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = true -python-versions = ">=3.5" - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "main" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "importlib-metadata" -version = "5.1.0" -description = "Read metadata from Python packages" -category = "main" -optional = true -python-versions = ">=3.7" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - - -[[package]] -name = "ipython" -version = "7.34.0" -description = "IPython: Productive Interactive Computing" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = "*" -setuptools = ">=18.5" -traitlets = ">=4.2" - -[package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] -doc = ["Sphinx (>=1.3)"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] - -[[package]] -name = "jedi" -version = "0.18.2" -description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -parso = ">=0.8.0,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -name = "Jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "libsass" -version = "0.21.0" -description = "Sass for Python: A straightforward binding of libsass for Python." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[[package]] -name = "MarkupSafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "matplotlib-inline" -version = "0.1.6" -description = "Inline Matplotlib backend for Jupyter" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "mistune" -version = "0.8.4" -description = "The fastest markdown parser in pure Python" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "mysqlclient" -version = "2.1.1" -description = "Python interface to MySQL" -category = "main" -optional = true -python-versions = ">=3.5" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "main" -optional = true -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - -[[package]] -name = "pathspec" -version = "0.10.2" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "pexpect" -version = "4.8.0" -description = "Pexpect allows easy control of interactive console applications." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "phonenumbers" -version = "8.13.1" -description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "Pillow" -version = "9.3.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "platformdirs" -version = "2.5.4" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - - -[[package]] -name = "prompt-toolkit" -version = "3.0.33" -description = "Library for building powerful interactive command lines in Python" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "psycopg2-binary" -version = "2.9.3" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "Pygments" -version = "2.13.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pygraphviz" -version = "1.10" -description = "Python interface to Graphviz" -category = "main" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "pyOpenSSL" -version = "21.0.0" -description = "Python wrapper module around the OpenSSL library" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" - -[package.dependencies] -cryptography = ">=3.3" -six = ">=1.5.2" - -[package.extras] -docs = ["sphinx", "sphinx-rtd-theme"] -test = ["flaky", "pretend", "pytest (>=3.0.1)"] - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = true -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2021.3" -description = "World timezone definitions, modern and historical" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "reportlab" -version = "3.6.12" -description = "The Reportlab Toolkit" -category = "main" -optional = false -python-versions = ">=3.7,<4" - -[package.dependencies] -pillow = ">=9.0.0" - -[package.extras] -fttextpath = ["freetype-py (>=2.3.0,<2.4)"] -rlpycairo = ["rlPyCairo (>=0.1.0)"] - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "main" -optional = true -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "sentry-sdk" -version = "1.11.1" -description = "Python client for Sentry (https://sentry.io)" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} - -[package.extras] -aiohttp = ["aiohttp (>=3.5)"] -beam = ["apache-beam (>=2.12)"] -bottle = ["bottle (>=0.12.13)"] -celery = ["celery (>=3)"] -chalice = ["chalice (>=1.16.0)"] -django = ["django (>=1.8)"] -falcon = ["falcon (>=1.4)"] -fastapi = ["fastapi (>=0.79.0)"] -flask = ["blinker (>=1.1)", "flask (>=0.11)"] -httpx = ["httpx (>=0.16.0)"] -pure_eval = ["asttokens", "executing", "pure-eval"] -pymongo = ["pymongo (>=3.1)"] - -pyspark = ["pyspark (>=2.4.4)"] -quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] -rq = ["rq (>=0.6)"] -sanic = ["sanic (>=0.8)"] -sqlalchemy = ["sqlalchemy (>=1.2)"] -starlette = ["starlette (>=0.19.1)"] -tornado = ["tornado (>=5)"] - -[[package]] -name = "setuptools" -version = "65.6.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "main" -optional = true -python-versions = "*" - -[[package]] -name = "Sphinx" -version = "4.5.0" -description = "Python documentation generator" -category = "main" -optional = true -python-versions = ">=3.6" - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.18" -imagesize = "*" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" -requests = ">=2.5.0" -snowballstemmer = ">=1.1" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] - -[[package]] -name = "sphinx-copybutton" -version = "0.4.0" -description = "Add a copy button to each of your code cells." -category = "main" -optional = true -python-versions = ">=3.6" - -[package.dependencies] -sphinx = ">=1.8" - -[package.extras] -code_style = ["pre-commit (==2.12.1)"] - -rtd = ["ipython", "sphinx", "sphinx-book-theme"] - -[[package]] -name = "sphinx-rtd-theme" -version = "1.1.1" -description = "Read the Docs theme for Sphinx" -category = "main" -optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[package.dependencies] -docutils = "<0.18" -sphinx = ">=1.6,<6" - -[package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] - - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.2" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "main" -optional = true -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "main" -optional = true -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "main" -optional = true -python-versions = ">=3.6" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "main" -optional = true -python-versions = ">=3.5" - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "main" -optional = true -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "main" -optional = true -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sqlparse" -version = "0.4.3" -description = "A non-validating SQL parser." -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "traitlets" -version = "5.5.0" -description = "" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["pre-commit", "pytest"] - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.13" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] - -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "wcwidth" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "xapian-bindings" -version = "0.1.0" -description = "Meta-package to build and install xapian-bindings extension." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "xapian-haystack" -version = "3.0.1" -description = "A Xapian backend for Haystack" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -django = ">=2.2" -django-haystack = ">=2.8.0" - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = true -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - - -[extras] -docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] -migration = ["mysqlclient"] -testing = ["coverage"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.8" -content-hash = "c335c796e1f2841eedbc714576a3906c267a66758ca21d46dc52dcffb2f36a16" - -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -appnope = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] -asgiref = [ - {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, - {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, -] -Babel = [ - {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, - {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, -] -backcall = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] -black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] -cffi = [ +files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, @@ -1093,19 +187,60 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] -charset-normalizer = [ + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = true +python-versions = ">=3.6.0" +files = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] -click = [ + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] -colorama = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -coverage = [ + +[[package]] +name = "coverage" +version = "5.5" +description = "Code coverage measurement for Python" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +files = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, @@ -1159,7 +294,18 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] -cryptography = [ + +[package.extras] +toml = ["toml"] + +[[package]] +name = "cryptography" +version = "37.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, @@ -1183,83 +329,366 @@ cryptography = [ {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, ] -decorator = [ + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools_rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] -dict2xml = [ + +[[package]] +name = "dict2xml" +version = "1.7.2" +description = "Small utility to convert a python dictionary into an XML string" +category = "main" +optional = false +python-versions = ">= 3.5" +files = [ {file = "dict2xml-1.7.2.tar.gz", hash = "sha256:4027330957466d14a4f692e6b499717e743bf3d8477f474d0a3cf2d31c8178af"}, ] -Django = [ + +[package.extras] +tests = ["noseOfYeti (==2.3.1)", "pytest (==7.1.3)"] + +[[package]] +name = "django" +version = "3.2.16" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Django-3.2.16-py3-none-any.whl", hash = "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121"}, {file = "Django-3.2.16.tar.gz", hash = "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d"}, ] -django-ajax-selects = [ + +[package.dependencies] +asgiref = ">=3.3.2,<4" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-ajax-selects" +version = "2.2.0" +description = "Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "django-ajax-selects-2.2.0.tar.gz", hash = "sha256:539298874b2d26ce9e778a5173d312f55340c887a126c7e2d3460b9a5b4593a2"}, ] -django-countries = [ - {file = "django-countries-7.4.2.tar.gz", hash = "sha256:9d553531de8eaf384fec7fdd5867f3cb9b922d384984c3e613b6b29031e5c3c2"}, - {file = "django_countries-7.4.2-py3-none-any.whl", hash = "sha256:2eadbe91578fe7b5a0c7782c07487b2ceb4bde9bc5c76eb43458929969f25c62"}, + +[[package]] +name = "django-countries" +version = "7.5" +description = "Provides a country field for Django models." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "django-countries-7.5.tar.gz", hash = "sha256:979676b1147ebbc10e8cdd67857ffffbcba8d7a92abf7ca70696ecd57d8f3d4f"}, + {file = "django_countries-7.5-py3-none-any.whl", hash = "sha256:5097d9c16eb5f8a8c195f55e647a1cf1ce8a88fdeb27b104de089424013845a6"}, ] -django-debug-toolbar = [ - {file = "django-debug-toolbar-3.7.0.tar.gz", hash = "sha256:1e3acad24e3d351ba45c6fa2072e4164820307332a776b16c9f06d1f89503465"}, - {file = "django_debug_toolbar-3.7.0-py3-none-any.whl", hash = "sha256:80de23066b624d3970fd296cf02d61988e5d56c31aa0dc4a428970b46e2883a8"}, + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +dev = ["black", "django", "djangorestframework", "graphene-django", "pytest", "pytest-django", "tox"] +maintainer = ["django", "transifex-client", "zest.releaser[recommended]"] +pyuca = ["pyuca"] +test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytest-django"] + +[[package]] +name = "django-debug-toolbar" +version = "3.8.1" +description = "A configurable set of panels that display various debug information about the current request/response." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "django_debug_toolbar-3.8.1-py3-none-any.whl", hash = "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478"}, + {file = "django_debug_toolbar-3.8.1.tar.gz", hash = "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27"}, ] -django-haystack = [ + +[package.dependencies] +django = ">=3.2.4" +sqlparse = ">=0.2" + +[[package]] +name = "django-haystack" +version = "3.2.1" +description = "Pluggable search for Django." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "django-haystack-3.2.1.tar.gz", hash = "sha256:97e3197aefc225fe405b6f17600a2534bf827cb4d6743130c20bc1a06f7293a4"}, ] -django-jinja = [ + +[package.dependencies] +Django = ">=2.2" + +[package.extras] +elasticsearch = ["elasticsearch (>=5,<8)"] + +[[package]] +name = "django-jinja" +version = "2.10.2" +description = "Jinja2 templating language integrated in Django." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "django-jinja-2.10.2.tar.gz", hash = "sha256:bfdfbb55c1f5a679d69ad575d550c4707d386634009152efe014089f3c4d1412"}, {file = "django_jinja-2.10.2-py3-none-any.whl", hash = "sha256:dd003ec1c95c0989eb28a538831bced62b1b61da551cb44a5dfd708fcf75589f"}, ] -django-ordered-model = [ + +[package.dependencies] +django = ">=2.2" +jinja2 = ">=3" + +[[package]] +name = "django-ordered-model" +version = "3.6" +description = "Allows Django models to be ordered and provides a simple admin interface for reordering them." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "django-ordered-model-3.6.tar.gz", hash = "sha256:62161a6bc51d8b402644854b257605d7b5183d01fd349826682a87e9227c05b5"}, {file = "django_ordered_model-3.6-py3-none-any.whl", hash = "sha256:0006b111f472a2348f75554a4e77bee2b1f379a0f96726af6b1a3ebf3a950789"}, ] -django-phonenumber-field = [ + +[[package]] +name = "django-phonenumber-field" +version = "6.4.0" +description = "An international phone number field for django models." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "django-phonenumber-field-6.4.0.tar.gz", hash = "sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed"}, {file = "django_phonenumber_field-6.4.0-py3-none-any.whl", hash = "sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58"}, ] -django-ranged-response = [ + +[package.dependencies] +Django = ">=3.2" + +[package.extras] +phonenumbers = ["phonenumbers (>=7.0.2)"] +phonenumberslite = ["phonenumberslite (>=7.0.2)"] + +[[package]] +name = "django-ranged-response" +version = "0.2.0" +description = "Modified Django FileResponse that adds Content-Range headers." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "django-ranged-response-0.2.0.tar.gz", hash = "sha256:f71fff352a37316b9bead717fc76e4ddd6c9b99c4680cdf4783b9755af1cf985"}, ] -django-simple-captcha = [ + +[package.dependencies] +django = "*" + +[[package]] +name = "django-simple-captcha" +version = "0.5.17" +description = "A very simple, yet powerful, Django captcha application" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "django-simple-captcha-0.5.17.tar.gz", hash = "sha256:9649e66dab4e71efacbfef02f48b83b91684898352a1ab56f1686ce71033b328"}, {file = "django_simple_captcha-0.5.17-py2.py3-none-any.whl", hash = "sha256:f9a07e5e9de264ba4039c9eaad66bc48188a21ceda5fcdc2fa13c5512141c2c9"}, ] -djangorestframework = [ + +[package.dependencies] +Django = ">=2.2" +django-ranged-response = "0.2.0" +Pillow = ">=6.2.0" + +[package.extras] +test = ["testfixtures"] + +[[package]] +name = "djangorestframework" +version = "3.14.0" +description = "Web APIs for Django, made easy." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, ] -docutils = [ + +[package.dependencies] +django = ">=3.0" +pytz = "*" + +[[package]] +name = "docutils" +version = "0.17.1" +description = "Docutils -- Python Documentation Utilities" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -idna = [ + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = true +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -imagesize = [ + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -importlib-metadata = [ - {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, - {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, + +[[package]] +name = "importlib-metadata" +version = "6.0.0" +description = "Read metadata from Python packages" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, + {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, ] -ipython = [ + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "ipython" +version = "7.34.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, ] -jedi = [ + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] + +[[package]] +name = "jedi" +version = "0.18.2" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, ] -Jinja2 = [ + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -libsass = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "libsass" +version = "0.21.0" +description = "Sass for Python: A straightforward binding of libsass for Python." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"}, {file = "libsass-0.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6b984510ed94993708c0d697b4fef2d118929bbfffc3b90037be0f5ccadf55e7"}, @@ -1271,7 +700,18 @@ libsass = [ {file = "libsass-0.21.0-cp38-abi3-macosx_12_0_arm64.whl", hash = "sha256:c9ec490609752c1d81ff6290da33485aa7cb6d7365ac665b74464c1b7d97f7da"}, {file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"}, ] -MarkupSafe = [ + +[package.dependencies] +six = "*" + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, @@ -1313,120 +753,248 @@ MarkupSafe = [ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] -matplotlib-inline = [ + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, ] -mistune = [ + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mistune" +version = "0.8.4" +description = "The fastest markdown parser in pure Python" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] -mypy-extensions = [ + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] -mysqlclient = [ - {file = "mysqlclient-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c1ed71bd6244993b526113cca3df66428609f90e4652f37eb51c33496d478b37"}, - {file = "mysqlclient-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:c812b67e90082a840efb82a8978369e6e69fc62ce1bda4ca8f3084a9d862308b"}, - {file = "mysqlclient-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0d1cd3a5a4d28c222fa199002810e8146cffd821410b67851af4cc80aeccd97c"}, - {file = "mysqlclient-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b355c8b5a7d58f2e909acdbb050858390ee1b0e13672ae759e5e784110022994"}, - {file = "mysqlclient-2.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:996924f3483fd36a34a5812210c69e71dea5a3d5978d01199b78b7f6d485c855"}, - {file = "mysqlclient-2.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:dea88c8d3f5a5d9293dfe7f087c16dd350ceb175f2f6631c9cf4caf3e19b7a96"}, - {file = "mysqlclient-2.1.1.tar.gz", hash = "sha256:828757e419fb11dd6c5ed2576ec92c3efaa93a0f7c39e263586d1ee779c3d782"}, + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -parso = [ + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] -pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.10.3" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, + {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, ] -pexpect = [ + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, ] -phonenumbers = [ - {file = "phonenumbers-8.13.1-py2.py3-none-any.whl", hash = "sha256:07a95c2f178687fd1c3f722cf792b3d33e3a225ae71577e500c99b28544cd6d0"}, - {file = "phonenumbers-8.13.1.tar.gz", hash = "sha256:7cadfe900e833857500b7bafa3e5a7eddc3263eb66b66a767870b33e44665f92"}, + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "phonenumbers" +version = "8.13.4" +description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "phonenumbers-8.13.4-py2.py3-none-any.whl", hash = "sha256:a577a46c069ad889c7b7cf4dd978751d059edeab28b97acead4775d2ea1fc70a"}, + {file = "phonenumbers-8.13.4.tar.gz", hash = "sha256:6d63455012fc9431105ffc7739befca61c3efc551b287dca58d2be2e745475a9"}, ] -pickleshare = [ + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] -pillow = [ - {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, - {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, - {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, - {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, - {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, - {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc"}, - {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, - {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, - {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, - {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, - {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, - {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, - {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, - {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, - {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, - {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, - {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, - {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, - {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, - {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, - {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, - {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, - {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, - {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, - {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, - {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, - {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, - {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, + +[[package]] +name = "pillow" +version = "9.4.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, + {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, + {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, + {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, + {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, + {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, + {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, + {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, + {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, + {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, + {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, + {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, + {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, + {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, + {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, + {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, + {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, + {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, + {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, ] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "2.6.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, + {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, ] -prompt-toolkit = [ - {file = "prompt_toolkit-3.0.30-py3-none-any.whl", hash = "sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289"}, - {file = "prompt_toolkit-3.0.30.tar.gz", hash = "sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0"}, + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.36" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, + {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, ] -psycopg2-binary = [ + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psycopg2-binary" +version = "2.9.3" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f2534ab7dc7e776a263b463a16e189eb30e85ec9bbe1bff9e78dae802608932"}, @@ -1487,38 +1055,112 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"}, {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"}, ] -ptyprocess = [ + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] -pycparser = [ + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -Pygments = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, ] -pygraphviz = [ + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pygraphviz" +version = "1.10" +description = "Python interface to Graphviz" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ {file = "pygraphviz-1.10.zip", hash = "sha256:457e093a888128903251a266a8cc16b4ba93f3f6334b3ebfed92c7471a74d867"}, ] -pyOpenSSL = [ + +[[package]] +name = "pyopenssl" +version = "21.0.0" +description = "Python wrapper module around the OpenSSL library" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +files = [ {file = "pyOpenSSL-21.0.0-py2.py3-none-any.whl", hash = "sha256:8935bd4920ab9abfebb07c41a4f58296407ed77f04bd1a92914044b848ba1ed6"}, {file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"}, ] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -python-dateutil = [ + +[package.dependencies] +cryptography = ">=3.3" +six = ">=1.5.2" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -pytz = [ + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2021.3" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] -reportlab = [ + +[[package]] +name = "reportlab" +version = "3.6.12" +description = "The Reportlab Toolkit" +category = "main" +optional = false +python-versions = ">=3.7,<4" +files = [ {file = "reportlab-3.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dfcf7bd6db5d80711cbbd0996b6e7a79cc414ca81457960367df11d2860f92a"}, {file = "reportlab-3.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0bc7a1d64fe754b62e175ba0cf47a630b529c0488ec9ac4e4c7655e295ea4d"}, {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adf78ccb2defad5b6ecb2e2e9f2a672719b0a8e2278592a7d77f6c220a042388"}, @@ -1565,93 +1207,412 @@ reportlab = [ {file = "reportlab-3.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:498b4ec7e73426de64c6bf6ec03c5b3f10dedf5db8a9e13fdf195f95a3d065aa"}, {file = "reportlab-3.6.12.tar.gz", hash = "sha256:b13cebf4e397bba14542bcd023338b6ff2c151a3a12aabca89eecbf972cb361a"}, ] -requests = [ + +[package.dependencies] +pillow = ">=9.0.0" + +[package.extras] +fttextpath = ["freetype-py (>=2.3.0,<2.4)"] +rlpycairo = ["rlPyCairo (>=0.1.0)"] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = true +python-versions = ">=3.7, <4" +files = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] -sentry-sdk = [ - {file = "sentry-sdk-1.11.1.tar.gz", hash = "sha256:675f6279b6bb1fea09fd61751061f9a90dca3b5929ef631dd50dc8b3aeb245e9"}, - {file = "sentry_sdk-1.11.1-py2.py3-none-any.whl", hash = "sha256:8b4ff696c0bdcceb3f70bbb87a57ba84fd3168b1332d493fcd16c137f709578c"}, + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "sentry-sdk" +version = "1.12.1" +description = "Python client for Sentry (https://sentry.io)" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "sentry-sdk-1.12.1.tar.gz", hash = "sha256:5bbe4b72de22f9ac1e67f2a4e6efe8fbd595bb59b7b223443f50fe5802a5551c"}, + {file = "sentry_sdk-1.12.1-py2.py3-none-any.whl", hash = "sha256:9f0b960694e2d8bb04db4ba6ac2a645040caef4e762c65937998ff06064f10d6"}, ] -setuptools = [ + +[package.dependencies] +certifi = "*" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)"] +httpx = ["httpx (>=0.16.0)"] +opentelemetry = ["opentelemetry-distro (>=0.350b0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pymongo = ["pymongo (>=3.1)"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +tornado = ["tornado (>=5)"] + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, ] -six = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -snowballstemmer = [ + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "main" +optional = true +python-versions = "*" +files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -Sphinx = [ + +[[package]] +name = "sphinx" +version = "4.5.0" +description = "Python documentation generator" +category = "main" +optional = true +python-versions = ">=3.6" +files = [ {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] -sphinx-copybutton = [ + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.18" +imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" +requests = ">=2.5.0" +snowballstemmer = ">=1.1" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] + +[[package]] +name = "sphinx-copybutton" +version = "0.4.0" +description = "Add a copy button to each of your code cells." +category = "main" +optional = true +python-versions = ">=3.6" +files = [ {file = "sphinx-copybutton-0.4.0.tar.gz", hash = "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386"}, {file = "sphinx_copybutton-0.4.0-py3-none-any.whl", hash = "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0"}, ] -sphinx-rtd-theme = [ + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "sphinx", "sphinx-book-theme"] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.1.1" +description = "Read the Docs theme for Sphinx" +category = "main" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +files = [ {file = "sphinx_rtd_theme-1.1.1-py2.py3-none-any.whl", hash = "sha256:31faa07d3e97c8955637fc3f1423a5ab2c44b74b8cc558a51498c202ce5cbda7"}, {file = "sphinx_rtd_theme-1.1.1.tar.gz", hash = "sha256:6146c845f1e1947b3c3dd4432c28998a1693ccc742b4f9ad7c63129f0757c103"}, ] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, + +[package.dependencies] +docutils = "<0.18" +sphinx = ">=1.6,<6" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.3" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "main" +optional = true +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib.applehelp-1.0.3-py3-none-any.whl", hash = "sha256:ba0f2a22e6eeada8da6428d0d520215ee8864253f32facf958cca81e426f661d"}, + {file = "sphinxcontrib.applehelp-1.0.3.tar.gz", hash = "sha256:83749f09f6ac843b8cb685277dbc818a8bf2d76cc19602699094fe9a74db529e"}, ] -sphinxcontrib-devhelp = [ + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "main" +optional = true +python-versions = ">=3.5" +files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] -sphinxcontrib-htmlhelp = [ + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "main" +optional = true +python-versions = ">=3.6" +files = [ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, ] -sphinxcontrib-jsmath = [ + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "main" +optional = true +python-versions = ">=3.5" +files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] -sphinxcontrib-qthelp = [ + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "main" +optional = true +python-versions = ">=3.5" +files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] -sphinxcontrib-serializinghtml = [ + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "main" +optional = true +python-versions = ">=3.5" +files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] -sqlparse = [ + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sqlparse" +version = "0.4.3" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, ] -tomli = [ + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -traitlets = [ - {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, - {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, + +[[package]] +name = "traitlets" +version = "5.8.1" +description = "Traitlets Python configuration system" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.8.1-py3-none-any.whl", hash = "sha256:a1ca5df6414f8b5760f7c5f256e326ee21b581742114545b462b35ffe3f04861"}, + {file = "traitlets-5.8.1.tar.gz", hash = "sha256:32500888f5ff7bbf3b9267ea31748fa657aaf34d56d85e60f91dda7dc7f5785b"}, ] -typing-extensions = [ + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "1.26.13" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, ] -wcwidth = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] -xapian-bindings = [ + +[[package]] +name = "xapian-bindings" +version = "0.1.0" +description = "Meta-package to build and install xapian-bindings extension." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "xapian-bindings-0.1.0.tar.gz", hash = "sha256:f2b0396082ebf4f6681ab43d6d8fd1f63b6964b18c32c91236ed067c6f62ad14"}, ] -xapian-haystack = [ + +[[package]] +name = "xapian-haystack" +version = "3.0.1" +description = "A Xapian backend for Haystack" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "xapian-haystack-3.0.1.tar.gz", hash = "sha256:a5c0e1262b95008df4dfeb58d093c654acee3f2b27ea3f7d366900895cdc70f9"}, ] -zipp = [ + +[package.dependencies] +django = ">=2.2" +django-haystack = ">=2.8.0" + +[[package]] +name = "zipp" +version = "3.11.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] +testing = ["coverage"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "bb91bfb25d83bd9b0356d5e018467fa62e344eae8bc9ffe3ac84c7df64eefa3d" From 751c8a8bc66e7f8dd569b324634a5dcc9ec8c52e Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Wed, 23 Nov 2022 12:23:17 +0100 Subject: [PATCH 18/95] unify account_id creation --- counter/migrations/0020_auto_20221215_1709.py | 21 +++++++++ counter/models.py | 47 ++++++++++++++----- counter/tests.py | 25 +++++++--- counter/views.py | 6 +-- eboutic/models.py | 5 +- subscription/models.py | 5 +- 6 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 counter/migrations/0020_auto_20221215_1709.py diff --git a/counter/migrations/0020_auto_20221215_1709.py b/counter/migrations/0020_auto_20221215_1709.py new file mode 100644 index 00000000..ca5d1176 --- /dev/null +++ b/counter/migrations/0020_auto_20221215_1709.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.16 on 2022-12-15 16:09 + +import accounting.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("counter", "0019_billinginfo"), + ] + + operations = [ + migrations.AlterField( + model_name="customer", + name="amount", + field=accounting.models.CurrencyField( + decimal_places=2, default=0, max_digits=12, verbose_name="amount" + ), + ), + ] diff --git a/counter/models.py b/counter/models.py index 564d6a3b..00e81b4f 100644 --- a/counter/models.py +++ b/counter/models.py @@ -21,6 +21,9 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +from __future__ import annotations + +from typing import Tuple from django.db import models from django.db.models.functions import Length @@ -57,7 +60,7 @@ class Customer(models.Model): user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE) account_id = models.CharField(_("account id"), max_length=10, unique=True) - amount = CurrencyField(_("amount")) + amount = CurrencyField(_("amount"), default=0) recorded_products = models.IntegerField(_("recorded product"), default=0) class Meta: @@ -94,12 +97,27 @@ class Customer(models.Model): ) < timedelta(days=90) @classmethod - def new_for_user(cls, user: User): + def get_or_create(cls, user: User) -> Tuple[Customer, bool]: """ - Create a new Customer instance for the user given in parameter without saving it - The account if is automatically generated and the amount set at 0 + Work in pretty much the same way as the usual get_or_create method, + but with the default field replaced by some under the hood. + + If the user has an account, return it as is. + Else create a new account with no money on it and a new unique account id + + Example : :: + + user = User.objects.get(pk=1) + account, created = Customer.get_or_create(user) + if created: + print(f"created a new account with id {account.id}") + else: + print(f"user has already an account, with {account.id} € on it" """ - # account_id are number with a letter appended + if hasattr(user, "customer"): + return user.customer, False + + # account_id are always a number with a letter appended account_id = ( Customer.objects.order_by(Length("account_id"), "account_id") .values("account_id") @@ -107,14 +125,19 @@ class Customer(models.Model): ) if account_id is None: # legacy from the old site - return cls(user=user, account_id="1504a", amount=0) - account_id = account_id["account_id"] - num = int(account_id[:-1]) - while Customer.objects.filter(account_id=account_id).exists(): - num += 1 - account_id = str(num) + random.choice(string.ascii_lowercase) + account = cls.objects.create(user=user, account_id="1504a") + return account, True - return cls(user=user, account_id=account_id, amount=0) + account_id = account_id["account_id"] + account_num = int(account_id[:-1]) + while Customer.objects.filter(account_id=account_id).exists(): + # when entering the first iteration, we are using an already existing account id + # so the loop should always execute at least one time + account_num += 1 + account_id = f"{account_num}{random.choice(string.ascii_lowercase)}" + + account = cls.objects.create(user=user, account_id=account_id) + return account, True def save(self, allow_negative=False, is_selling=False, *args, **kwargs): """ diff --git a/counter/tests.py b/counter/tests.py index 0a25a9ff..dbdf3898 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -23,6 +23,7 @@ # import json import re +import string from django.test import TestCase from django.urls import reverse @@ -745,18 +746,30 @@ class StudentCardTest(TestCase): self.assertEqual(response.status_code, 403) -class AccountIdTest(TestCase): +class CustomerAccountIdTest(TestCase): def setUp(self): - user_a = User.objects.create(username="a", password="plop", email="a.a@a.fr") + self.user_a = User.objects.create( + username="a", password="plop", email="a.a@a.fr" + ) user_b = User.objects.create(username="b", password="plop", email="b.b@b.fr") user_c = User.objects.create(username="c", password="plop", email="c.c@c.fr") - Customer.objects.create(user=user_a, amount=0, account_id="1111a") + Customer.objects.create(user=self.user_a, amount=10, account_id="1111a") Customer.objects.create(user=user_b, amount=0, account_id="9999z") Customer.objects.create(user=user_c, amount=0, account_id="12345f") def test_create_customer(self): user_d = User.objects.create(username="d", password="plop") - customer_d = Customer.new_for_user(user_d) - customer_d.save() - number = customer_d.account_id[:-1] + customer, created = Customer.get_or_create(user_d) + account_id = customer.account_id + number = account_id[:-1] + self.assertTrue(created) self.assertEqual(number, "12346") + self.assertEqual(6, len(account_id)) + self.assertIn(account_id[-1], string.ascii_lowercase) + self.assertEqual(0, customer.amount) + + def test_get_existing_account(self): + account, created = Customer.get_or_create(self.user_a) + self.assertFalse(created) + self.assertEqual(account.account_id, "1111a") + self.assertEqual(10, account.amount) diff --git a/counter/views.py b/counter/views.py index ca012688..69f88b22 100644 --- a/counter/views.py +++ b/counter/views.py @@ -1780,11 +1780,7 @@ def create_billing_info(request, user_id): if user.id != user_id and not user.has_perm("counter:add_billinginfo"): raise PermissionDenied() user = get_object_or_404(User, pk=user_id) - if not hasattr(user, "customer"): - customer = Customer.new_for_user(user) - customer.save() - else: - customer = get_object_or_404(Customer, user_id=user_id) + customer, _ = Customer.get_or_create(user) BillingInfo.objects.create(customer=customer) return __manage_billing_info_req(request, user_id, True) diff --git a/eboutic/models.py b/eboutic/models.py index 99fe6410..ebfb878b 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -245,12 +245,13 @@ class Invoice(models.Model): def validate(self): if self.validated: raise DataError(_("Invoice already validated")) + customer, created = Customer.get_or_create(user=self.user) eboutic = Counter.objects.filter(type="EBOUTIC").first() for i in self.items.all(): if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING: new = Refilling( counter=eboutic, - customer=self.user.customer, + customer=customer, operator=self.user, amount=i.product_unit_price * i.quantity, payment_method="CARD", @@ -266,7 +267,7 @@ class Invoice(models.Model): club=product.club, product=product, seller=self.user, - customer=self.user.customer, + customer=customer, unit_price=i.product_unit_price, quantity=i.quantity, payment_method="CARD", diff --git a/subscription/models.py b/subscription/models.py index b724b734..31f6a2de 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -24,7 +24,6 @@ from datetime import date, timedelta from django.db import models -from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.core.exceptions import ValidationError @@ -101,8 +100,8 @@ class Subscription(models.Model): super(Subscription, self).save() from counter.models import Customer - if not Customer.objects.filter(user=self.member).exists(): - Customer.new_for_user(self.member).save() + _, created = Customer.get_or_create(self.member) + if created: form = PasswordResetForm({"email": self.member.email}) if form.is_valid(): form.save( From 99827e005b20bc43e94da447e2824ed690462887 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Mon, 9 Jan 2023 22:07:03 +0100 Subject: [PATCH 19/95] upgrade re_path to path (#533) --- accounting/urls.py | 112 +++++++++++++------------- club/urls.py | 86 +++++++++----------- com/urls.py | 98 +++++++++++------------ core/converters.py | 35 +++++++++ core/tests.py | 63 ++++++++------- core/urls.py | 181 +++++++++++++++++++++---------------------- core/views/user.py | 18 ++--- counter/urls.py | 106 ++++++++++++------------- election/urls.py | 54 ++++++------- forum/urls.py | 63 +++++++-------- launderette/urls.py | 44 +++++------ matmat/urls.py | 10 +-- pedagogy/tests.py | 6 +- pedagogy/urls.py | 26 +++---- rootplace/urls.py | 10 +-- sas/urls.py | 36 ++++----- stock/urls.py | 52 ++++++------- subscription/urls.py | 6 +- trombi/urls.py | 58 +++++++------- 19 files changed, 528 insertions(+), 536 deletions(-) create mode 100644 core/converters.py diff --git a/accounting/urls.py b/accounting/urls.py index 70eeb158..4ae59a1e 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -22,133 +22,127 @@ # # -from django.urls import re_path +from django.urls import path from accounting.views import * urlpatterns = [ # Accounting types - re_path( - r"^simple_type$", + path( + "simple_type/", SimplifiedAccountingTypeListView.as_view(), name="simple_type_list", ), - re_path( - r"^simple_type/create$", + path( + "simple_type/create/", SimplifiedAccountingTypeCreateView.as_view(), name="simple_type_new", ), - re_path( - r"^simple_type/(?P[0-9]+)/edit$", + path( + "simple_type//edit/", SimplifiedAccountingTypeEditView.as_view(), name="simple_type_edit", ), # Accounting types - re_path(r"^type$", AccountingTypeListView.as_view(), name="type_list"), - re_path(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"), - re_path( - r"^type/(?P[0-9]+)/edit$", + path("type/", AccountingTypeListView.as_view(), name="type_list"), + path("type/create/", AccountingTypeCreateView.as_view(), name="type_new"), + path( + "type//edit/", AccountingTypeEditView.as_view(), name="type_edit", ), # Bank accounts - re_path(r"^$", BankAccountListView.as_view(), name="bank_list"), - re_path(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"), - re_path( - r"^bank/(?P[0-9]+)$", + path("", BankAccountListView.as_view(), name="bank_list"), + path("bank/create", BankAccountCreateView.as_view(), name="bank_new"), + path( + "bank//", BankAccountDetailView.as_view(), name="bank_details", ), - re_path( - r"^bank/(?P[0-9]+)/edit$", + path( + "bank//edit/", BankAccountEditView.as_view(), name="bank_edit", ), - re_path( - r"^bank/(?P[0-9]+)/delete$", + path( + "bank//delete/", BankAccountDeleteView.as_view(), name="bank_delete", ), # Club accounts - re_path(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"), - re_path( - r"^club/(?P[0-9]+)$", + path("club/create/", ClubAccountCreateView.as_view(), name="club_new"), + path( + "club//", ClubAccountDetailView.as_view(), name="club_details", ), - re_path( - r"^club/(?P[0-9]+)/edit$", + path( + "club//edit/", ClubAccountEditView.as_view(), name="club_edit", ), - re_path( - r"^club/(?P[0-9]+)/delete$", + path( + "club//delete/", ClubAccountDeleteView.as_view(), name="club_delete", ), # Journals - re_path(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"), - re_path( - r"^journal/(?P[0-9]+)$", + path("journal/create/", JournalCreateView.as_view(), name="journal_new"), + path( + "journal//", JournalDetailView.as_view(), name="journal_details", ), - re_path( - r"^journal/(?P[0-9]+)/edit$", + path( + "journal//edit/", JournalEditView.as_view(), name="journal_edit", ), - re_path( - r"^journal/(?P[0-9]+)/delete$", + path( + "journal//delete/", JournalDeleteView.as_view(), name="journal_delete", ), - re_path( - r"^journal/(?P[0-9]+)/statement/nature$", + path( + "journal//statement/nature/", JournalNatureStatementView.as_view(), name="journal_nature_statement", ), - re_path( - r"^journal/(?P[0-9]+)/statement/person$", + path( + "journal//statement/person/", JournalPersonStatementView.as_view(), name="journal_person_statement", ), - re_path( - r"^journal/(?P[0-9]+)/statement/accounting$", + path( + "journal//statement/accounting/", JournalAccountingStatementView.as_view(), name="journal_accounting_statement", ), # Operations - re_path( - r"^operation/create/(?P[0-9]+)$", + path( + "operation/create//", OperationCreateView.as_view(), name="op_new", ), - re_path( - r"^operation/(?P[0-9]+)$", OperationEditView.as_view(), name="op_edit" - ), - re_path( - r"^operation/(?P[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf" - ), + path("operation//", OperationEditView.as_view(), name="op_edit"), + path("operation//pdf/", OperationPDFView.as_view(), name="op_pdf"), # Companies - re_path(r"^company/list$", CompanyListView.as_view(), name="co_list"), - re_path(r"^company/create$", CompanyCreateView.as_view(), name="co_new"), - re_path(r"^company/(?P[0-9]+)$", CompanyEditView.as_view(), name="co_edit"), + path("company/list/", CompanyListView.as_view(), name="co_list"), + path("company/create/", CompanyCreateView.as_view(), name="co_new"), + path("company//", CompanyEditView.as_view(), name="co_edit"), # Labels - re_path(r"^label/new$", LabelCreateView.as_view(), name="label_new"), - re_path( - r"^label/(?P[0-9]+)$", + path("label/new/", LabelCreateView.as_view(), name="label_new"), + path( + "label//", LabelListView.as_view(), name="label_list", ), - re_path( - r"^label/(?P[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit" - ), - re_path( - r"^label/(?P[0-9]+)/delete$", + path("label//edit/", LabelEditView.as_view(), name="label_edit"), + path( + "label//delete/", LabelDeleteView.as_view(), name="label_delete", ), # User account - re_path(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"), + path("refound/account/", RefoundAccountView.as_view(), name="refound_account"), ] diff --git a/club/urls.py b/club/urls.py index ed08472f..d33a5167 100644 --- a/club/urls.py +++ b/club/urls.py @@ -23,94 +23,84 @@ # # -from django.urls import re_path +from django.urls import path from club.views import * urlpatterns = [ - re_path(r"^$", ClubListView.as_view(), name="club_list"), - re_path(r"^new$", ClubCreateView.as_view(), name="club_new"), - re_path(r"^stats$", ClubStatView.as_view(), name="club_stats"), - re_path(r"^(?P[0-9]+)/$", ClubView.as_view(), name="club_view"), - re_path( - r"^(?P[0-9]+)/rev/(?P[0-9]+)/$", + path("", ClubListView.as_view(), name="club_list"), + path("new/", ClubCreateView.as_view(), name="club_new"), + path("stats/", ClubStatView.as_view(), name="club_stats"), + path("/", ClubView.as_view(), name="club_view"), + path( + "/rev//", ClubRevView.as_view(), name="club_view_rev", ), - re_path( - r"^(?P[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist" - ), - re_path(r"^(?P[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"), - re_path( - r"^(?P[0-9]+)/edit/page$", + path("/hist/", ClubPageHistView.as_view(), name="club_hist"), + path("/edit/", ClubEditView.as_view(), name="club_edit"), + path( + "/edit/page/", ClubPageEditView.as_view(), name="club_edit_page", ), - re_path( - r"^(?P[0-9]+)/members$", ClubMembersView.as_view(), name="club_members" - ), - re_path( - r"^(?P[0-9]+)/elderlies$", + path("/members/", ClubMembersView.as_view(), name="club_members"), + path( + "/elderlies/", ClubOldMembersView.as_view(), name="club_old_members", ), - re_path( - r"^(?P[0-9]+)/sellings$", + path( + "/sellings/", ClubSellingView.as_view(), name="club_sellings", ), - re_path( - r"^(?P[0-9]+)/sellings/csv$", + path( + "/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv", ), - re_path( - r"^(?P[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop" - ), - re_path(r"^(?P[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"), - re_path( - r"^(?P[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing" - ), - re_path( - r"^(?P[0-9]+)/mailing/generate$", + path("/prop/", ClubEditPropView.as_view(), name="club_prop"), + path("/tools/", ClubToolsView.as_view(), name="tools"), + path("/mailing/", ClubMailingView.as_view(), name="mailing"), + path( + "/mailing/generate/", MailingAutoGenerationView.as_view(), name="mailing_generate", ), - re_path( - r"^(?P[0-9]+)/mailing/delete$", + path( + "/mailing/delete/", MailingDeleteView.as_view(), name="mailing_delete", ), - re_path( - r"^(?P[0-9]+)/mailing/delete/subscription$", + path( + "/mailing/delete/subscription/", MailingSubscriptionDeleteView.as_view(), name="mailing_subscription_delete", ), - re_path( - r"^membership/(?P[0-9]+)/set_old$", + path( + "membership//set_old/", MembershipSetOldView.as_view(), name="membership_set_old", ), - re_path( - r"^membership/(?P[0-9]+)/delete$", + path( + "membership//delete/", MembershipDeleteView.as_view(), name="membership_delete", ), - re_path( - r"^(?P[0-9]+)/poster$", PosterListView.as_view(), name="poster_list" - ), - re_path( - r"^(?P[0-9]+)/poster/create$", + path("/poster/", PosterListView.as_view(), name="poster_list"), + path( + "/poster/create/", PosterCreateView.as_view(), name="poster_create", ), - re_path( - r"^(?P[0-9]+)/poster/(?P[0-9]+)/edit$", + path( + "/poster//edit/", PosterEditView.as_view(), name="poster_edit", ), - re_path( - r"^(?P[0-9]+)/poster/(?P[0-9]+)/delete$", + path( + "/poster//delete/", PosterDeleteView.as_view(), name="poster_delete", ), diff --git a/com/urls.py b/com/urls.py index 6128d1d4..433ec3b3 100644 --- a/com/urls.py +++ b/com/urls.py @@ -22,104 +22,98 @@ # # -from django.urls import re_path +from django.urls import path -from com.views import * from club.views import MailingDeleteView +from com.views import * urlpatterns = [ - re_path(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"), - re_path(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"), - re_path( - r"^sith/edit/weekmail_destinations$", + path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"), + path("sith/edit/info/", InfoMsgEditView.as_view(), name="info_edit"), + path( + "sith/edit/weekmail_destinations/", WeekmailDestinationEditView.as_view(), name="weekmail_destinations", ), - re_path(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"), - re_path( - r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview" - ), - re_path( - r"^weekmail/new_article$", + path("weekmail/", WeekmailEditView.as_view(), name="weekmail"), + path("weekmail/preview/", WeekmailPreviewView.as_view(), name="weekmail_preview"), + path( + "weekmail/new_article/", WeekmailArticleCreateView.as_view(), name="weekmail_article", ), - re_path( - r"^weekmail/article/(?P[0-9]+)/delete$", + path( + "weekmail/article//delete/", WeekmailArticleDeleteView.as_view(), name="weekmail_article_delete", ), - re_path( - r"^weekmail/article/(?P[0-9]+)/edit$", + path( + "weekmail/article//edit/", WeekmailArticleEditView.as_view(), name="weekmail_article_edit", ), - re_path(r"^news$", NewsListView.as_view(), name="news_list"), - re_path(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"), - re_path(r"^news/create$", NewsCreateView.as_view(), name="news_new"), - re_path( - r"^news/(?P[0-9]+)/delete$", + path("news/", NewsListView.as_view(), name="news_list"), + path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"), + path("news/create/", NewsCreateView.as_view(), name="news_new"), + path( + "news//delete/", NewsDeleteView.as_view(), name="news_delete", ), - re_path( - r"^news/(?P[0-9]+)/moderate$", + path( + "news//moderate/", NewsModerateView.as_view(), name="news_moderate", ), - re_path( - r"^news/(?P[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit" - ), - re_path( - r"^news/(?P[0-9]+)$", NewsDetailView.as_view(), name="news_detail" - ), - re_path(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"), - re_path( - r"^mailings/(?P[0-9]+)/moderate$", + path("news//edit/", NewsEditView.as_view(), name="news_edit"), + path("news//", NewsDetailView.as_view(), name="news_detail"), + path("mailings/", MailingListAdminView.as_view(), name="mailing_admin"), + path( + "mailings//moderate/", MailingModerateView.as_view(), name="mailing_moderate", ), - re_path( - r"^mailings/(?P[0-9]+)/delete$", + path( + "mailings//delete/", MailingDeleteView.as_view(redirect_page="com:mailing_admin"), name="mailing_delete", ), - re_path(r"^poster$", PosterListView.as_view(), name="poster_list"), - re_path(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"), - re_path( - r"^poster/(?P[0-9]+)/edit$", + path("poster/", PosterListView.as_view(), name="poster_list"), + path("poster/create/", PosterCreateView.as_view(), name="poster_create"), + path( + "poster//edit/", PosterEditView.as_view(), name="poster_edit", ), - re_path( - r"^poster/(?P[0-9]+)/delete$", + path( + "poster//delete/", PosterDeleteView.as_view(), name="poster_delete", ), - re_path( - r"^poster/moderate$", + path( + "poster/moderate/", PosterModerateListView.as_view(), name="poster_moderate_list", ), - re_path( - r"^poster/(?P[0-9]+)/moderate$", + path( + "poster//moderate/", PosterModerateView.as_view(), name="poster_moderate", ), - re_path(r"^screen$", ScreenListView.as_view(), name="screen_list"), - re_path(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"), - re_path( - r"^screen/(?P[0-9]+)/slideshow$", + path("screen/", ScreenListView.as_view(), name="screen_list"), + path("screen/create/", ScreenCreateView.as_view(), name="screen_create"), + path( + "screen//slideshow/", ScreenSlideshowView.as_view(), name="screen_slideshow", ), - re_path( - r"^screen/(?P[0-9]+)/edit$", + path( + "screen//edit/", ScreenEditView.as_view(), name="screen_edit", ), - re_path( - r"^screen/(?P[0-9]+)/delete$", + path( + "screen//delete/", ScreenDeleteView.as_view(), name="screen_delete", ), diff --git a/core/converters.py b/core/converters.py new file mode 100644 index 00000000..cb7bc95b --- /dev/null +++ b/core/converters.py @@ -0,0 +1,35 @@ +from core.models import Page + + +class FourDigitYearConverter: + regex = "[0-9]{4}" + + def to_python(self, value): + return int(value) + + def to_url(self, value): + return str(value).zfill(4) + + +class TwoDigitMonthConverter: + regex = "[0-9]{2}" + + def to_python(self, value): + return int(value) + + def to_url(self, value): + return str(value).zfill(2) + + +class BooleanStringConverter: + """ + Converter whose regex match either True or False + """ + + regex = r"(True)|(False)" + + def to_python(self, value): + return str(value) == "True" + + def to_url(self, value): + return str(value) diff --git a/core/tests.py b/core/tests.py index d1ebd6af..ddb9f03d 100644 --- a/core/tests.py +++ b/core/tests.py @@ -290,33 +290,40 @@ class MarkdownTest(TestCase): class PageHandlingTest(TestCase): def setUp(self): - try: - Group.objects.create(name="root") - u = User( - username="root", - last_name="", - first_name="Bibou", - email="ae.info@utbm.fr", - date_of_birth="1942-06-12", - is_superuser=True, - is_staff=True, - ) - u.set_password("plop") - u.save() - self.client.login(username="root", password="plop") - except Exception as e: - print(e) + self.root_group = Group.objects.create(name="root") + u = User( + username="root", + last_name="", + first_name="Bibou", + email="ae.info@utbm.fr", + date_of_birth="1942-06-12", + is_superuser=True, + is_staff=True, + ) + u.set_password("plop") + u.save() + self.client.login(username="root", password="plop") def test_create_page_ok(self): """ Should create a page correctly """ - self.client.post( - reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": 1} + + response = self.client.post( + reverse("core:page_new"), + {"parent": "", "name": "guy", "owner_group": self.root_group.id}, ) + self.assertRedirects( + response, reverse("core:page", kwargs={"page_name": "guy"}) + ) + self.assertTrue(Page.objects.filter(name="guy").exists()) + response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) - self.assertTrue(response.status_code == 200) - self.assertTrue('' in str(response.content)) + self.assertEqual(response.status_code, 200) + html = response.content.decode() + self.assertIn('', html) + self.assertIn('', html) + self.assertIn('', html) def test_create_child_page_ok(self): """ @@ -339,29 +346,25 @@ class PageHandlingTest(TestCase): """ Should display a page correctly """ - parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first()) + parent = Page(name="guy", owner_group=self.root_group) parent.save(force_lock=True) - page = Page( - name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent - ) + page = Page(name="bibou", owner_group=self.root_group, parent=parent) page.save(force_lock=True) response = self.client.get( reverse("core:page", kwargs={"page_name": "guy/bibou"}) ) self.assertTrue(response.status_code == 200) - self.assertTrue( - '\\xc3\\x89diter' - in str(response.content) - ) + html = response.content.decode() + self.assertIn('', html) def test_access_page_not_found(self): """ Should not display a page correctly """ response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"})) - response = self.client.get("/page/swagg/") self.assertTrue(response.status_code == 200) - self.assertTrue('' in str(response.content)) + html = response.content.decode() + self.assertIn('', html) def test_create_page_markdown_safe(self): """ diff --git a/core/urls.py b/core/urls.py index 1c2577fc..ec42f880 100644 --- a/core/urls.py +++ b/core/urls.py @@ -23,40 +23,46 @@ # # -from django.urls import re_path, path +from django.urls import path, re_path, register_converter from core.views import * +from core.converters import ( + FourDigitYearConverter, + TwoDigitMonthConverter, + BooleanStringConverter, +) + +register_converter(FourDigitYearConverter, "yyyy") +register_converter(TwoDigitMonthConverter, "mm") +register_converter(BooleanStringConverter, "bool") + urlpatterns = [ - re_path(r"^$", index, name="index"), - re_path(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"), - re_path(r"^notifications$", NotificationList.as_view(), name="notification_list"), - re_path(r"^notification/(?P[0-9]+)$", notification, name="notification"), + path("", index, name="index"), + path("to_markdown/", ToMarkdownView.as_view(), name="to_markdown"), + path("notifications/", NotificationList.as_view(), name="notification_list"), + path("notification//", notification, name="notification"), # Search - re_path(r"^search/$", search_view, name="search"), - re_path(r"^search_json/$", search_json, name="search_json"), - re_path(r"^search_user/$", search_user_json, name="search_user"), + path("search/", search_view, name="search"), + path("search_json/", search_json, name="search_json"), + path("search_user/", search_user_json, name="search_user"), # Login and co - re_path(r"^login/$", SithLoginView.as_view(), name="login"), - re_path(r"^logout/$", logout, name="logout"), - re_path( - r"^password_change/$", SithPasswordChangeView.as_view(), name="password_change" - ), - re_path( - r"^password_change/(?P[0-9]+)$", + path("login/", SithLoginView.as_view(), name="login"), + path("logout/", logout, name="logout"), + path("password_change/", SithPasswordChangeView.as_view(), name="password_change"), + path( + "password_change//", password_root_change, name="password_root_change", ), - re_path( - r"^password_change/done$", + path( + "password_change/done/", SithPasswordChangeDoneView.as_view(), name="password_change_done", ), - re_path( - r"^password_reset/$", SithPasswordResetView.as_view(), name="password_reset" - ), - re_path( - r"^password_reset/done$", + path("password_reset/", SithPasswordResetView.as_view(), name="password_reset"), + path( + "password_reset/done/", SithPasswordResetDoneView.as_view(), name="password_reset_done", ), @@ -65,110 +71,103 @@ urlpatterns = [ SithPasswordResetConfirmView.as_view(), name="password_reset_confirm", ), - re_path( - r"^reset/done/$", + path( + "reset/done/", SithPasswordResetCompleteView.as_view(), name="password_reset_complete", ), - re_path(r"^register$", register, name="register"), + path("register/", register, name="register"), # Group handling - re_path(r"^group/$", GroupListView.as_view(), name="group_list"), - re_path(r"^group/new/$", GroupCreateView.as_view(), name="group_new"), - re_path( - r"^group/(?P[0-9]+)/$", GroupEditView.as_view(), name="group_edit" - ), - re_path( - r"^group/(?P[0-9]+)/delete$", + path("group/", GroupListView.as_view(), name="group_list"), + path("group/new/", GroupCreateView.as_view(), name="group_new"), + path("group//", GroupEditView.as_view(), name="group_edit"), + path( + "group//delete/", GroupDeleteView.as_view(), name="group_delete", ), - re_path( - r"^group/(?P[0-9]+)/detail$", + path( + "group//detail/", GroupTemplateView.as_view(), name="group_detail", ), # User views - re_path(r"^user/$", UserListView.as_view(), name="user_list"), - re_path( - r"^user/(?P[0-9]+)/mini$", + path("user/", UserListView.as_view(), name="user_list"), + path( + "user//mini/", UserMiniView.as_view(), name="user_profile_mini", ), - re_path(r"^user/(?P[0-9]+)/$", UserView.as_view(), name="user_profile"), - re_path( - r"^user/(?P[0-9]+)/pictures$", + path("user//", UserView.as_view(), name="user_profile"), + path( + "user//pictures/", UserPicturesView.as_view(), name="user_pictures", ), - re_path( - r"^user/(?P[0-9]+)/godfathers$", + path( + "user//godfathers/", UserGodfathersView.as_view(), name="user_godfathers", ), - re_path( - r"^user/(?P[0-9]+)/godfathers/tree$", + path( + "user//godfathers/tree/", UserGodfathersTreeView.as_view(), name="user_godfathers_tree", ), - re_path( - r"^user/(?P[0-9]+)/godfathers/tree/pict$", + path( + "user//godfathers/tree/pict/", UserGodfathersTreePictureView.as_view(), name="user_godfathers_tree_pict", ), - re_path( - r"^user/(?P[0-9]+)/godfathers/(?P[0-9]+)/(?P(True)|(False))/delete$", - DeleteUserGodfathers, + path( + "user//godfathers///delete/", + delete_user_godfather, name="user_godfathers_delete", ), - re_path( - r"^user/(?P[0-9]+)/edit$", + path( + "user//edit/", UserUpdateProfileView.as_view(), name="user_edit", ), - re_path( - r"^user/(?P[0-9]+)/profile_upload$", + path( + "user//profile_upload/", UserUploadProfilePictView.as_view(), name="user_profile_upload", ), - re_path( - r"^user/(?P[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs" - ), - re_path( - r"^user/(?P[0-9]+)/prefs$", + path("user//clubs/", UserClubView.as_view(), name="user_clubs"), + path( + "user//prefs/", UserPreferencesView.as_view(), name="user_prefs", ), - re_path( - r"^user/(?P[0-9]+)/groups$", + path( + "user//groups/", UserUpdateGroupView.as_view(), name="user_groups", ), - re_path(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"), - re_path( - r"^user/(?P[0-9]+)/account$", + path("user/tools/", UserToolsView.as_view(), name="user_tools"), + path( + "user//account/", UserAccountView.as_view(), name="user_account", ), - re_path( - r"^user/(?P[0-9]+)/account/(?P[0-9]+)/(?P[0-9]+)$", + path( + "user//account///", UserAccountDetailView.as_view(), name="user_account_detail", ), - re_path( - r"^user/(?P[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats" - ), - re_path( - r"^user/(?P[0-9]+)/gift/create$", + path("user//stats/", UserStatsView.as_view(), name="user_stats"), + path( + "user//gift/create/", GiftCreateView.as_view(), name="user_gift_create", ), - re_path( - r"^user/(?P[0-9]+)/gift/delete/(?P[0-9]+)/$", + path( + "user//gift/delete//", GiftDeleteView.as_view(), name="user_gift_delete", ), # File views - # re_path(r'^file/add/(?Ppopup)?$', FileCreateView.as_view(), name='file_new'), re_path(r"^file/(?Ppopup)?$", FileListView.as_view(), name="file_list"), re_path( r"^file/(?P[0-9]+)/(?Ppopup)?$", @@ -190,43 +189,43 @@ urlpatterns = [ FileDeleteView.as_view(), name="file_delete", ), - re_path(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"), - re_path( - r"^file/(?P[0-9]+)/moderate$", + path("file/moderation/", FileModerationView.as_view(), name="file_moderation"), + path( + "file//moderate/", FileModerateView.as_view(), name="file_moderate", ), - re_path(r"^file/(?P[0-9]+)/download$", send_file, name="download"), + path("file//download/", send_file, name="download"), # Page views - re_path(r"^page/$", PageListView.as_view(), name="page_list"), - re_path(r"^page/create$", PageCreateView.as_view(), name="page_new"), - re_path( - r"^page/(?P[0-9]*)/delete$", + path("page/", PageListView.as_view(), name="page_list"), + path("page/create/", PageCreateView.as_view(), name="page_new"), + path( + "page//delete/", PageDeleteView.as_view(), name="page_delete", ), - re_path( - r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$", + path( + "page//edit/", PageEditView.as_view(), name="page_edit", ), - re_path( - r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$", + path( + "page//prop/", PagePropView.as_view(), name="page_prop", ), - re_path( - r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$", + path( + "page//hist/", PageHistView.as_view(), name="page_hist", ), - re_path( - r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P[0-9]+)/", + path( + "page//rev//", PageRevView.as_view(), name="page_rev", ), - re_path( - r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$", + path( + "page//", PageView.as_view(), name="page", ), diff --git a/core/views/user.py b/core/views/user.py index 3dc6c28b..8a8a97cd 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -338,16 +338,16 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView): return kwargs -def DeleteUserGodfathers(request, user_id, godfather_id, is_father): - user = User.objects.get(id=user_id) - if (user == request.user) or request.user.is_root or request.user.is_board_member: - ud = get_object_or_404(User, id=godfather_id) - if is_father == "True": - user.godfathers.remove(ud) - else: - user.godchildren.remove(ud) +def delete_user_godfather(request, user_id, godfather_id, is_father): + user_is_admin = request.user.is_root or request.user.is_board_member + if user_id != request.user.id and not user_is_admin: + raise PermissionDenied() + user = get_object_or_404(User, id=user_id) + to_remove = get_object_or_404(User, id=godfather_id) + if is_father: + user.godfathers.remove(to_remove) else: - raise PermissionDenied + user.godchildren.remove(to_remove) return redirect("core:user_godfathers", user_id=user_id) diff --git a/counter/urls.py b/counter/urls.py index 7195ddc6..449c0fe0 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -22,47 +22,47 @@ # # -from django.urls import re_path, path +from django.urls import path from counter.views import * urlpatterns = [ - re_path(r"^(?P[0-9]+)$", CounterMain.as_view(), name="details"), - re_path( - r"^(?P[0-9]+)/click/(?P[0-9]+)$", + path("/", CounterMain.as_view(), name="details"), + path( + "/click//", CounterClick.as_view(), name="click", ), - re_path( - r"^(?P[0-9]+)/last_ops$", + path( + "/last_ops/", CounterLastOperationsView.as_view(), name="last_ops", ), - re_path( - r"^(?P[0-9]+)/cash_summary$", + path( + "/cash_summary/", CounterCashSummaryView.as_view(), name="cash_summary", ), - re_path( - r"^(?P[0-9]+)/activity$", + path( + "/activity/", CounterActivityView.as_view(), name="activity", ), - re_path(r"^(?P[0-9]+)/stats$", CounterStatView.as_view(), name="stats"), - re_path(r"^(?P[0-9]+)/login$", CounterLogin.as_view(), name="login"), - re_path(r"^(?P[0-9]+)/logout$", CounterLogout.as_view(), name="logout"), - re_path( - r"^eticket/(?P[0-9]+)/pdf$", + path("/stats/", CounterStatView.as_view(), name="stats"), + path("/login/", CounterLogin.as_view(), name="login"), + path("/logout/", CounterLogout.as_view(), name="logout"), + path( + "eticket//pdf/", EticketPDFView.as_view(), name="eticket_pdf", ), - re_path( - r"^customer/(?P[0-9]+)/card/add$", + path( + "customer//card/add/", StudentCardFormView.as_view(), name="add_student_card", ), - re_path( - r"^customer/(?P[0-9]+)/card/delete/(?P[0-9]+)/$", + path( + "customer//card/delete//", StudentCardDeleteView.as_view(), name="delete_student_card", ), @@ -76,76 +76,76 @@ urlpatterns = [ edit_billing_info, name="edit_billing_info", ), - re_path(r"^admin/(?P[0-9]+)$", CounterEditView.as_view(), name="admin"), - re_path( - r"^admin/(?P[0-9]+)/prop$", + path("admin//", CounterEditView.as_view(), name="admin"), + path( + "admin//prop/", CounterEditPropView.as_view(), name="prop_admin", ), - re_path(r"^admin$", CounterListView.as_view(), name="admin_list"), - re_path(r"^admin/new$", CounterCreateView.as_view(), name="new"), - re_path( - r"^admin/delete/(?P[0-9]+)$", + path("admin/", CounterListView.as_view(), name="admin_list"), + path("admin/new/", CounterCreateView.as_view(), name="new"), + path( + "admin/delete//", CounterDeleteView.as_view(), name="delete", ), - re_path(r"^admin/invoices_call$", InvoiceCallView.as_view(), name="invoices_call"), - re_path( - r"^admin/cash_summary/list$", + path("admin/invoices_call/", InvoiceCallView.as_view(), name="invoices_call"), + path( + "admin/cash_summary/list/", CashSummaryListView.as_view(), name="cash_summary_list", ), - re_path( - r"^admin/cash_summary/(?P[0-9]+)$", + path( + "admin/cash_summary//", CashSummaryEditView.as_view(), name="cash_summary_edit", ), - re_path(r"^admin/product/list$", ProductListView.as_view(), name="product_list"), - re_path( - r"^admin/product/list_archived$", + path("admin/product/list/", ProductListView.as_view(), name="product_list"), + path( + "admin/product/list_archived/", ProductArchivedListView.as_view(), name="product_list_archived", ), - re_path(r"^admin/product/create$", ProductCreateView.as_view(), name="new_product"), - re_path( - r"^admin/product/(?P[0-9]+)$", + path("admin/product/create/", ProductCreateView.as_view(), name="new_product"), + path( + "admin/product//", ProductEditView.as_view(), name="product_edit", ), - re_path( - r"^admin/producttype/list$", + path( + "admin/producttype/list/", ProductTypeListView.as_view(), name="producttype_list", ), - re_path( - r"^admin/producttype/create$", + path( + "admin/producttype/create/", ProductTypeCreateView.as_view(), name="new_producttype", ), - re_path( - r"^admin/producttype/(?P[0-9]+)$", + path( + "admin/producttype//", ProductTypeEditView.as_view(), name="producttype_edit", ), - re_path(r"^admin/eticket/list$", EticketListView.as_view(), name="eticket_list"), - re_path(r"^admin/eticket/new$", EticketCreateView.as_view(), name="new_eticket"), - re_path( - r"^admin/eticket/(?P[0-9]+)$", + path("admin/eticket/list/", EticketListView.as_view(), name="eticket_list"), + path("admin/eticket/new/", EticketCreateView.as_view(), name="new_eticket"), + path( + "admin/eticket//", EticketEditView.as_view(), name="edit_eticket", ), - re_path( - r"^admin/selling/(?P[0-9]+)/delete$", + path( + "admin/selling//delete/", SellingDeleteView.as_view(), name="selling_delete", ), - re_path( - r"^admin/refilling/(?P[0-9]+)/delete$", + path( + "admin/refilling//delete/", RefillingDeleteView.as_view(), name="refilling_delete", ), - re_path( - r"^admin/(?P[0-9]+)/refillings$", + path( + "admin//refillings/", CounterRefillingListView.as_view(), name="refilling_list", ), diff --git a/election/urls.py b/election/urls.py index 4d6b4e1e..697b2464 100644 --- a/election/urls.py +++ b/election/urls.py @@ -1,57 +1,49 @@ -from django.urls import re_path +from django.urls import path from election.views import * urlpatterns = [ - re_path(r"^$", ElectionsListView.as_view(), name="list"), - re_path(r"^archived$", ElectionListArchivedView.as_view(), name="list_archived"), - re_path(r"^add$", ElectionCreateView.as_view(), name="create"), - re_path( - r"^(?P[0-9]+)/edit$", ElectionUpdateView.as_view(), name="update" - ), - re_path( - r"^(?P[0-9]+)/delete$", ElectionDeleteView.as_view(), name="delete" - ), - re_path( - r"^(?P[0-9]+)/list/add$", + path("", ElectionsListView.as_view(), name="list"), + path("archived/", ElectionListArchivedView.as_view(), name="list_archived"), + path("add/", ElectionCreateView.as_view(), name="create"), + path("/edit/", ElectionUpdateView.as_view(), name="update"), + path("/delete/", ElectionDeleteView.as_view(), name="delete"), + path( + "/list/add/", ElectionListCreateView.as_view(), name="create_list", ), - re_path( - r"^(?P[0-9]+)/list/delete$", + path( + "/list/delete/", ElectionListDeleteView.as_view(), name="delete_list", ), - re_path( - r"^(?P[0-9]+)/role/create$", + path( + "/role/create/", RoleCreateView.as_view(), name="create_role", ), - re_path( - r"^(?P[0-9]+)/role/edit$", RoleUpdateView.as_view(), name="update_role" - ), - re_path( - r"^(?P[0-9]+)/role/delete$", + path("/role/edit/", RoleUpdateView.as_view(), name="update_role"), + path( + "/role/delete/", RoleDeleteView.as_view(), name="delete_role", ), - re_path( - r"^(?P[0-9]+)/candidate/add$", + path( + "/candidate/add/", CandidatureCreateView.as_view(), name="candidate", ), - re_path( - r"^(?P[0-9]+)/candidate/edit$", + path( + "/candidate/edit/", CandidatureUpdateView.as_view(), name="update_candidate", ), - re_path( - r"^(?P[0-9]+)/candidate/delete$", + path( + "/candidate/delete/", CandidatureDeleteView.as_view(), name="delete_candidate", ), - re_path(r"^(?P[0-9]+)/vote$", VoteFormView.as_view(), name="vote"), - re_path( - r"^(?P[0-9]+)/detail$", ElectionDetailView.as_view(), name="detail" - ), + path("/vote/", VoteFormView.as_view(), name="vote"), + path("/detail/", ElectionDetailView.as_view(), name="detail"), ] diff --git a/forum/urls.py b/forum/urls.py index de19d7cc..8926ea01 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -22,69 +22,62 @@ # # -from django.urls import re_path +from django.urls import path from forum.views import * - urlpatterns = [ - re_path(r"^$", ForumMainView.as_view(), name="main"), - re_path(r"^search/$", ForumSearchView.as_view(), name="search"), - re_path(r"^new_forum$", ForumCreateView.as_view(), name="new_forum"), - re_path( - r"^mark_all_as_read$", ForumMarkAllAsRead.as_view(), name="mark_all_as_read" - ), - re_path(r"^last_unread$", ForumLastUnread.as_view(), name="last_unread"), - re_path( - r"^favorite_topics$", ForumFavoriteTopics.as_view(), name="favorite_topics" - ), - re_path(r"^(?P[0-9]+)$", ForumDetailView.as_view(), name="view_forum"), - re_path(r"^(?P[0-9]+)/edit$", ForumEditView.as_view(), name="edit_forum"), - re_path( - r"^(?P[0-9]+)/delete$", ForumDeleteView.as_view(), name="delete_forum" - ), - re_path( - r"^(?P[0-9]+)/new_topic$", + path("", ForumMainView.as_view(), name="main"), + path("search/", ForumSearchView.as_view(), name="search"), + path("new_forum/", ForumCreateView.as_view(), name="new_forum"), + path("mark_all_as_read/", ForumMarkAllAsRead.as_view(), name="mark_all_as_read"), + path("last_unread/", ForumLastUnread.as_view(), name="last_unread"), + path("favorite_topics/", ForumFavoriteTopics.as_view(), name="favorite_topics"), + path("/", ForumDetailView.as_view(), name="view_forum"), + path("/edit/", ForumEditView.as_view(), name="edit_forum"), + path("/delete/", ForumDeleteView.as_view(), name="delete_forum"), + path( + "/new_topic/", ForumTopicCreateView.as_view(), name="new_topic", ), - re_path( - r"^topic/(?P[0-9]+)$", + path( + "topic//", ForumTopicDetailView.as_view(), name="view_topic", ), - re_path( - r"^topic/(?P[0-9]+)/edit$", + path( + "topic//edit/", ForumTopicEditView.as_view(), name="edit_topic", ), - re_path( - r"^topic/(?P[0-9]+)/new_message$", + path( + "topic//new_message/", ForumMessageCreateView.as_view(), name="new_message", ), - re_path( - r"^topic/(?P[0-9]+)/toggle_subscribe$", + path( + "topic//toggle_subscribe/", ForumTopicSubscribeView.as_view(), name="toggle_subscribe_topic", ), - re_path( - r"^message/(?P[0-9]+)$", + path( + "message//", ForumMessageView.as_view(), name="view_message", ), - re_path( - r"^message/(?P[0-9]+)/edit$", + path( + "message//edit/", ForumMessageEditView.as_view(), name="edit_message", ), - re_path( - r"^message/(?P[0-9]+)/delete$", + path( + "message//delete/", ForumMessageDeleteView.as_view(), name="delete_message", ), - re_path( - r"^message/(?P[0-9]+)/undelete$", + path( + "message//undelete/", ForumMessageUndeleteView.as_view(), name="undelete_message", ), diff --git a/launderette/urls.py b/launderette/urls.py index b259c2d9..7bc214c0 100644 --- a/launderette/urls.py +++ b/launderette/urls.py @@ -22,54 +22,54 @@ # # -from django.urls import re_path +from django.urls import path from launderette.views import * urlpatterns = [ # views - re_path(r"^$", LaunderetteMainView.as_view(), name="launderette_main"), - re_path( - r"^slot/(?P[0-9]+)/delete$", + path("", LaunderetteMainView.as_view(), name="launderette_main"), + path( + "slot//delete/", SlotDeleteView.as_view(), name="delete_slot", ), - re_path(r"^book$", LaunderetteBookMainView.as_view(), name="book_main"), - re_path( - r"^book/(?P[0-9]+)$", + path("book/", LaunderetteBookMainView.as_view(), name="book_main"), + path( + "book//", LaunderetteBookView.as_view(), name="book_slot", ), - re_path( - r"^(?P[0-9]+)/click$", + path( + "/click/", LaunderetteMainClickView.as_view(), name="main_click", ), - re_path( - r"^(?P[0-9]+)/click/(?P[0-9]+)$", + path( + "/click//", LaunderetteClickView.as_view(), name="click", ), - re_path(r"^admin$", LaunderetteListView.as_view(), name="launderette_list"), - re_path( - r"^admin/(?P[0-9]+)$", + path("admin/", LaunderetteListView.as_view(), name="launderette_list"), + path( + "admin//", LaunderetteAdminView.as_view(), name="launderette_admin", ), - re_path( - r"^admin/(?P[0-9]+)/edit$", + path( + "admin//edit/", LaunderetteEditView.as_view(), name="launderette_edit", ), - re_path(r"^admin/new$", LaunderetteCreateView.as_view(), name="launderette_new"), - re_path(r"^admin/machine/new$", MachineCreateView.as_view(), name="machine_new"), - re_path( - r"^admin/machine/(?P[0-9]+)/edit$", + path("admin/new/", LaunderetteCreateView.as_view(), name="launderette_new"), + path("admin/machine/new/", MachineCreateView.as_view(), name="machine_new"), + path( + "admin/machine//edit/", MachineEditView.as_view(), name="machine_edit", ), - re_path( - r"^admin/machine/(?P[0-9]+)/delete$", + path( + "admin/machine//delete/", MachineDeleteView.as_view(), name="machine_delete", ), diff --git a/matmat/urls.py b/matmat/urls.py index 80960c59..6e657262 100644 --- a/matmat/urls.py +++ b/matmat/urls.py @@ -22,13 +22,13 @@ # # -from django.urls import re_path +from django.urls import path from matmat.views import * urlpatterns = [ - re_path(r"^$", SearchNormalFormView.as_view(), name="search"), - re_path(r"^reverse$", SearchReverseFormView.as_view(), name="search_reverse"), - re_path(r"^quick$", SearchQuickFormView.as_view(), name="search_quick"), - re_path(r"^clear$", SearchClearFormView.as_view(), name="search_clear"), + path("", SearchNormalFormView.as_view(), name="search"), + path("reverse/", SearchReverseFormView.as_view(), name="search_reverse"), + path("quick/", SearchQuickFormView.as_view(), name="search_quick"), + path("clear/", SearchClearFormView.as_view(), name="search_clear"), ] diff --git a/pedagogy/tests.py b/pedagogy/tests.py index a429bf02..d889f881 100644 --- a/pedagogy/tests.py +++ b/pedagogy/tests.py @@ -732,9 +732,9 @@ class UVSearchTest(TestCase): [ { "id": 1, - "absolute_url": "/pedagogy/uv/1", - "update_url": "/pedagogy/uv/1/edit", - "delete_url": "/pedagogy/uv/1/delete", + "absolute_url": "/pedagogy/uv/1/", + "update_url": "/pedagogy/uv/1/edit/", + "delete_url": "/pedagogy/uv/1/delete/", "code": "PA00", "author": 0, "credit_type": "OM", diff --git a/pedagogy/urls.py b/pedagogy/urls.py index 478d7ad1..8cfca8d2 100644 --- a/pedagogy/urls.py +++ b/pedagogy/urls.py @@ -22,33 +22,33 @@ # # -from django.urls import re_path +from django.urls import path from pedagogy.views import * urlpatterns = [ # Urls displaying the actual application for visitors - re_path(r"^$", UVListView.as_view(), name="guide"), - re_path(r"^uv/(?P[0-9]+)$", UVDetailFormView.as_view(), name="uv_detail"), - re_path( - r"^comment/(?P[0-9]+)/edit$", + path("", UVListView.as_view(), name="guide"), + path("uv//", UVDetailFormView.as_view(), name="uv_detail"), + path( + "comment//edit/", UVCommentUpdateView.as_view(), name="comment_update", ), - re_path( - r"^comment/(?P[0-9]+)/delete$", + path( + "comment//delete/", UVCommentDeleteView.as_view(), name="comment_delete", ), - re_path( - r"^comment/(?P[0-9]+)/report$", + path( + "comment//report/", UVCommentReportCreateView.as_view(), name="comment_report", ), # Moderation - re_path(r"^moderation$", UVModerationFormView.as_view(), name="moderation"), + path("moderation/", UVModerationFormView.as_view(), name="moderation"), # Administration : Create Update Delete Edit - re_path(r"^uv/create$", UVCreateView.as_view(), name="uv_create"), - re_path(r"^uv/(?P[0-9]+)/delete$", UVDeleteView.as_view(), name="uv_delete"), - re_path(r"^uv/(?P[0-9]+)/edit$", UVUpdateView.as_view(), name="uv_update"), + path("uv/create/", UVCreateView.as_view(), name="uv_create"), + path("uv//delete/", UVDeleteView.as_view(), name="uv_delete"), + path("uv//edit/", UVUpdateView.as_view(), name="uv_update"), ] diff --git a/rootplace/urls.py b/rootplace/urls.py index 33b3bd6d..696fb81e 100644 --- a/rootplace/urls.py +++ b/rootplace/urls.py @@ -23,16 +23,16 @@ # # -from django.urls import re_path +from django.urls import path from rootplace.views import * urlpatterns = [ - re_path(r"^merge$", MergeUsersView.as_view(), name="merge"), - re_path( - r"^forum/messages/delete$", + path("merge/", MergeUsersView.as_view(), name="merge"), + path( + "forum/messages/delete/", DeleteAllForumUserMessagesView.as_view(), name="delete_forum_messages", ), - re_path(r"^logs$", OperationLogListView.as_view(), name="operation_logs"), + path("logs/", OperationLogListView.as_view(), name="operation_logs"), ] diff --git a/sas/urls.py b/sas/urls.py index fada615f..28a2a152 100644 --- a/sas/urls.py +++ b/sas/urls.py @@ -22,40 +22,36 @@ # # -from django.urls import re_path +from django.urls import path from sas.views import * urlpatterns = [ - re_path(r"^$", SASMainView.as_view(), name="main"), - re_path(r"^moderation$", ModerationView.as_view(), name="moderation"), - re_path(r"^album/(?P[0-9]+)$", AlbumView.as_view(), name="album"), - re_path( - r"^album/(?P[0-9]+)/upload$", + path("", SASMainView.as_view(), name="main"), + path("moderation/", ModerationView.as_view(), name="moderation"), + path("album//", AlbumView.as_view(), name="album"), + path( + "album//upload/", AlbumUploadView.as_view(), name="album_upload", ), - re_path( - r"^album/(?P[0-9]+)/edit$", AlbumEditView.as_view(), name="album_edit" - ), - re_path(r"^album/(?P[0-9]+)/preview$", send_album, name="album_preview"), - re_path(r"^picture/(?P[0-9]+)$", PictureView.as_view(), name="picture"), - re_path( - r"^picture/(?P[0-9]+)/edit$", + path("album//edit/", AlbumEditView.as_view(), name="album_edit"), + path("album//preview/", send_album, name="album_preview"), + path("picture//", PictureView.as_view(), name="picture"), + path( + "picture//edit/", PictureEditView.as_view(), name="picture_edit", ), - re_path(r"^picture/(?P[0-9]+)/download$", send_pict, name="download"), - re_path( - r"^picture/(?P[0-9]+)/download/compressed$", + path("picture//download/", send_pict, name="download"), + path( + "picture//download/compressed/", send_compressed, name="download_compressed", ), - re_path( - r"^picture/(?P[0-9]+)/download/thumb$", + path( + "picture//download/thumb/", send_thumb, name="download_thumb", ), - # re_path(r'^album/new$', AlbumCreateView.as_view(), name='album_new'), - # re_path(r'^(?P[0-9]+)/$', ClubView.as_view(), name='club_view'), ] diff --git a/stock/urls.py b/stock/urls.py index 59bc466b..cf7ff13f 100644 --- a/stock/urls.py +++ b/stock/urls.py @@ -23,67 +23,65 @@ # # -from django.urls import include, re_path +from django.urls import path from stock.views import * urlpatterns = [ # Stock re_paths - re_path( - r"^new/counter/(?P[0-9]+)$", StockCreateView.as_view(), name="new" - ), - re_path(r"^edit/(?P[0-9]+)$", StockEditView.as_view(), name="edit"), - re_path(r"^list$", StockListView.as_view(), name="list"), + path("new/counter//", StockCreateView.as_view(), name="new"), + path("edit//", StockEditView.as_view(), name="edit"), + path("list/", StockListView.as_view(), name="list"), # StockItem re_paths - re_path(r"^(?P[0-9]+)$", StockItemList.as_view(), name="items_list"), - re_path( - r"^(?P[0-9]+)/stock_item/new_item$", + path("/", StockItemList.as_view(), name="items_list"), + path( + "/stock_item/new_item/", StockItemCreateView.as_view(), name="new_item", ), - re_path( - r"^stock_item/(?P[0-9]+)/edit$", + path( + "stock_item//edit/", StockItemEditView.as_view(), name="edit_item", ), - re_path( - r"^(?P[0-9]+)/stock_item/take_items$", + path( + "/stock_item/take_items/", StockTakeItemsBaseFormView.as_view(), name="take_items", ), # ShoppingList re_paths - re_path( - r"^(?P[0-9]+)/shopping_list/list$", + path( + "/shopping_list/list/", StockShoppingListView.as_view(), name="shoppinglist_list", ), - re_path( - r"^(?P[0-9]+)/shopping_list/create$", + path( + "/shopping_list/create/", StockItemQuantityBaseFormView.as_view(), name="shoppinglist_create", ), - re_path( - r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/items$", + path( + "/shopping_list//items/", StockShoppingListItemListView.as_view(), name="shoppinglist_items", ), - re_path( - r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/delete$", + path( + "/shopping_list//delete/", StockShoppingListDeleteView.as_view(), name="shoppinglist_delete", ), - re_path( - r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_done$", + path( + "/shopping_list//set_done/", StockShopppingListSetDone.as_view(), name="shoppinglist_set_done", ), - re_path( - r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_todo$", + path( + "/shopping_list//set_todo/", StockShopppingListSetTodo.as_view(), name="shoppinglist_set_todo", ), - re_path( - r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/update_stock$", + path( + "/shopping_list//update_stock/", StockUpdateAfterShopppingBaseFormView.as_view(), name="update_after_shopping", ), diff --git a/subscription/urls.py b/subscription/urls.py index d398197a..daf1c7d1 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -22,12 +22,12 @@ # # -from django.urls import re_path +from django.urls import path from subscription.views import * urlpatterns = [ # Subscription views - re_path(r"^$", NewSubscription.as_view(), name="subscription"), - re_path(r"stats", SubscriptionsStatsView.as_view(), name="stats"), + path("", NewSubscription.as_view(), name="subscription"), + path("stats/", SubscriptionsStatsView.as_view(), name="stats"), ] diff --git a/trombi/urls.py b/trombi/urls.py index 9858ea65..49ac42db 100644 --- a/trombi/urls.py +++ b/trombi/urls.py @@ -23,67 +23,65 @@ # # -from django.urls import re_path +from django.urls import path from trombi.views import * urlpatterns = [ - re_path(r"^(?P[0-9]+)/new$", TrombiCreateView.as_view(), name="create"), - re_path( - r"^(?P[0-9]+)/export$", TrombiExportView.as_view(), name="export" - ), - re_path(r"^(?P[0-9]+)/edit$", TrombiEditView.as_view(), name="edit"), - re_path( - r"^(?P[0-9]+)/moderate_comments$", + path("/new/", TrombiCreateView.as_view(), name="create"), + path("/export/", TrombiExportView.as_view(), name="export"), + path("/edit/", TrombiEditView.as_view(), name="edit"), + path( + "/moderate_comments/", TrombiModerateCommentsView.as_view(), name="moderate_comments", ), - re_path( - r"^(?P[0-9]+)/moderate$", + path( + "/moderate/", TrombiModerateCommentView.as_view(), name="moderate_comment", ), - re_path( - r"^user/(?P[0-9]+)/delete$", + path( + "user//delete/", TrombiDeleteUserView.as_view(), name="delete_user", ), - re_path(r"^(?P[0-9]+)$", TrombiDetailView.as_view(), name="detail"), - re_path( - r"^(?P[0-9]+)/new_comment$", + path("/", TrombiDetailView.as_view(), name="detail"), + path( + "/new_comment/", TrombiCommentCreateView.as_view(), name="new_comment", ), - re_path( - r"^(?P[0-9]+)/profile$", + path( + "/profile/", UserTrombiProfileView.as_view(), name="user_profile", ), - re_path( - r"^comment/(?P[0-9]+)/edit$", + path( + "comment//edit/", TrombiCommentEditView.as_view(), name="edit_comment", ), - re_path(r"^tools$", UserTrombiToolsView.as_view(), name="user_tools"), - re_path(r"^profile$", UserTrombiEditProfileView.as_view(), name="profile"), - re_path(r"^pictures$", UserTrombiEditPicturesView.as_view(), name="pictures"), - re_path( - r"^reset_memberships$", + path("tools/", UserTrombiToolsView.as_view(), name="user_tools"), + path("profile/", UserTrombiEditProfileView.as_view(), name="profile"), + path("pictures/", UserTrombiEditPicturesView.as_view(), name="pictures"), + path( + "reset_memberships/", UserTrombiResetClubMembershipsView.as_view(), name="reset_memberships", ), - re_path( - r"^membership/(?P[0-9]+)/edit$", + path( + "membership//edit/", UserTrombiEditMembershipView.as_view(), name="edit_membership", ), - re_path( - r"^membership/(?P[0-9]+)/delete$", + path( + "membership//delete/", UserTrombiDeleteMembershipView.as_view(), name="delete_membership", ), - re_path( - r"^membership/(?P[0-9]+)/create$", + path( + "membership//create/", UserTrombiAddMembershipView.as_view(), name="create_membership", ), From 31e8ad8a3e8788a5eceb67db04ca7d4a2ab320ed Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Sat, 17 Dec 2022 16:31:12 +0100 Subject: [PATCH 20/95] redirect directly on counter if user is barman --- core/templates/core/base.jinja | 34 ++++++++++++++++++++-------------- counter/models.py | 30 ++++++++++++++++++++++++++++++ counter/tests.py | 14 ++++++++++++++ 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 4ef8d911..e343f2a1 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -65,20 +65,26 @@
{% endcache %} diff --git a/counter/models.py b/counter/models.py index 4f6f25ea..64da8a68 100644 --- a/counter/models.py +++ b/counter/models.py @@ -22,7 +22,9 @@ # # +from __future__ import annotations from django.db import models +from django.db.models import OuterRef, Exists, QuerySet from django.db.models.functions import Length from django.utils.translation import gettext_lazy as _ from django.utils import timezone @@ -309,6 +311,32 @@ class Product(models.Model): return "%s (%s)" % (self.name, self.code) +class CounterQuerySet(models.QuerySet): + def annotate_has_barman(self, user: User) -> CounterQuerySet: + """ + Annotate the queryset with the `user_is_barman` field. + For each counter, this field has value True if the user + is a barman of this counter, else False. + + :param user: the user we want to check if he is a barman + + Example:: + + sli = User.objects.get(username="sli") + counters = ( + Counter.objects + .annotate_has_barman(sli) # add the user_has_barman boolean field + .filter(has_annotated_barman=True) # keep only counters where this user is barman + ) + print("Sli est barman dans les comptoirs suivants :") + for counter in counters: + print(f"- {counter.name}") + """ + subquery = user.counters.filter(pk=OuterRef("pk")) + # noinspection PyTypeChecker + return self.annotate(has_annotated_barman=Exists(subquery)) + + class Counter(models.Model): name = models.CharField(_("name"), max_length=30) club = models.ForeignKey( @@ -333,6 +361,8 @@ class Counter(models.Model): ) token = models.CharField(_("token"), max_length=30, null=True, blank=True) + objects = CounterQuerySet.as_manager() + class Meta: verbose_name = _("counter") diff --git a/counter/tests.py b/counter/tests.py index 0a25a9ff..9049e55e 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -138,6 +138,20 @@ class CounterTest(TestCase): ) self.assertTrue(response.status_code == 200) + def test_annotate_has_barman_queryset(self): + """ + Test if the custom queryset method ``annotate_has_barman`` + works as intended + """ + self.sli.counters.clear() + self.sli.counters.add(self.foyer, self.mde) + counters = Counter.objects.annotate_has_barman(self.sli) + for counter in counters: + if counter.name in ("Foyer", "MDE"): + self.assertTrue(counter.has_annotated_barman) + else: + self.assertFalse(counter.has_annotated_barman) + class CounterStatsTest(TestCase): def setUp(self): From 705b9b1e6a9dd981db9e4ddfd9567dd84d0e236c Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Tue, 10 Jan 2023 22:26:46 +0100 Subject: [PATCH 21/95] =?UTF-8?q?Passage=20de=20vue=20=C3=A0=20Alpine=20po?= =?UTF-8?q?ur=20les=20comptoirs=20(#561)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vue, c'est cool, mais avec Django c'est un peu chiant à utiliser. Alpine a l'avantage d'être plus léger et d'avoir une syntaxe qui ne ressemble pas à celle de Jinja (ce qui évite d'avoir à mettre des {% raw %} partout). --- core/static/core/js/vue.global.prod.js | 1 - core/static/core/style.scss | 2 +- counter/models.py | 8 +- counter/static/counter/js/counter_click.js | 78 ++++ counter/templates/counter/counter_click.jinja | 409 ++++++++---------- counter/tests.py | 43 +- counter/views.py | 100 +++-- eboutic/templates/eboutic/eboutic_main.jinja | 2 +- sith/settings.py | 1 - 9 files changed, 335 insertions(+), 309 deletions(-) delete mode 100644 core/static/core/js/vue.global.prod.js create mode 100644 counter/static/counter/js/counter_click.js diff --git a/core/static/core/js/vue.global.prod.js b/core/static/core/js/vue.global.prod.js deleted file mode 100644 index cc65afcd..00000000 --- a/core/static/core/js/vue.global.prod.js +++ /dev/null @@ -1 +0,0 @@ -var Vue=function(e){"use strict";function t(e,t){const n=Object.create(null),o=e.split(",");for(let r=0;r!!n[e.toLowerCase()]:e=>!!n[e]}const n=t("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),o=t("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function r(e){return!!e||""===e}function s(e){if(N(e)){const t={};for(let n=0;n{if(e){const n=e.split(l);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function a(e){let t="";if(A(e))t=e;else if(N(e))for(let n=0;nd(e,t)))}const m=(e,t)=>t&&t.__v_isRef?m(e,t.value):E(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:$(t)?{[`Set(${t.size})`]:[...t.values()]}:!M(t)||N(t)||B(t)?t:String(t),g={},v=[],y=()=>{},b=()=>!1,_=/^on[^a-z]/,S=e=>_.test(e),x=e=>e.startsWith("onUpdate:"),C=Object.assign,w=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},k=Object.prototype.hasOwnProperty,T=(e,t)=>k.call(e,t),N=Array.isArray,E=e=>"[object Map]"===I(e),$=e=>"[object Set]"===I(e),R=e=>e instanceof Date,O=e=>"function"==typeof e,A=e=>"string"==typeof e,F=e=>"symbol"==typeof e,M=e=>null!==e&&"object"==typeof e,P=e=>M(e)&&O(e.then)&&O(e.catch),V=Object.prototype.toString,I=e=>V.call(e),B=e=>"[object Object]"===I(e),L=e=>A(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,j=t(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),U=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},H=/-(\w)/g,D=U((e=>e.replace(H,((e,t)=>t?t.toUpperCase():"")))),W=/\B([A-Z])/g,z=U((e=>e.replace(W,"-$1").toLowerCase())),K=U((e=>e.charAt(0).toUpperCase()+e.slice(1))),G=U((e=>e?`on${K(e)}`:"")),q=(e,t)=>!Object.is(e,t),J=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Y=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Q;let X;const ee=[];class te{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&X&&(this.parent=X,this.index=(X.scopes||(X.scopes=[])).push(this)-1)}run(e){if(this.active)try{return this.on(),e()}finally{this.off()}}on(){this.active&&(ee.push(this),X=this)}off(){this.active&&(ee.pop(),X=ee[ee.length-1])}stop(e){if(this.active){if(this.effects.forEach((e=>e.stop())),this.cleanups.forEach((e=>e())),this.scopes&&this.scopes.forEach((e=>e.stop(!0))),this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function ne(e,t){(t=t||X)&&t.active&&t.effects.push(e)}const oe=e=>{const t=new Set(e);return t.w=0,t.n=0,t},re=e=>(e.w&ce)>0,se=e=>(e.n&ce)>0,ie=new WeakMap;let le=0,ce=1;const ae=[];let ue;const pe=Symbol(""),fe=Symbol("");class de{constructor(e,t=null,n){this.fn=e,this.scheduler=t,this.active=!0,this.deps=[],ne(this,n)}run(){if(!this.active)return this.fn();if(!ae.includes(this))try{return ae.push(ue=this),ge.push(me),me=!0,ce=1<<++le,le<=30?(({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let o=0;o0?ae[e-1]:void 0}}stop(){this.active&&(he(this),this.onStop&&this.onStop(),this.active=!1)}}function he(e){const{deps:t}=e;if(t.length){for(let n=0;n{("length"===t||t>=o)&&l.push(e)}));else switch(void 0!==n&&l.push(i.get(n)),t){case"add":N(e)?L(n)&&l.push(i.get("length")):(l.push(i.get(pe)),E(e)&&l.push(i.get(fe)));break;case"delete":N(e)||(l.push(i.get(pe)),E(e)&&l.push(i.get(fe)));break;case"set":E(e)&&l.push(i.get(pe))}if(1===l.length)l[0]&&Ce(l[0]);else{const e=[];for(const t of l)t&&e.push(...t);Ce(oe(e))}}function Ce(e,t){for(const n of N(e)?e:[...e])(n!==ue||n.allowRecurse)&&(n.scheduler?n.scheduler():n.run())}const we=t("__proto__,__v_isRef,__isVue"),ke=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(F)),Te=Ae(),Ne=Ae(!1,!0),Ee=Ae(!0),$e=Ae(!0,!0),Re=Oe();function Oe(){const e={};return["includes","indexOf","lastIndexOf"].forEach((t=>{e[t]=function(...e){const n=yt(this);for(let t=0,r=this.length;t{e[t]=function(...e){ve();const n=yt(this)[t].apply(this,e);return ye(),n}})),e}function Ae(e=!1,t=!1){return function(n,o,r){if("__v_isReactive"===o)return!e;if("__v_isReadonly"===o)return e;if("__v_raw"===o&&r===(e?t?at:ct:t?lt:it).get(n))return n;const s=N(n);if(!e&&s&&T(Re,o))return Reflect.get(Re,o,r);const i=Reflect.get(n,o,r);if(F(o)?ke.has(o):we(o))return i;if(e||be(n,0,o),t)return i;if(wt(i)){return!s||!L(o)?i.value:i}return M(i)?e?dt(i):pt(i):i}}function Fe(e=!1){return function(t,n,o,r){let s=t[n];if(!e&&(o=yt(o),s=yt(s),!N(t)&&wt(s)&&!wt(o)))return s.value=o,!0;const i=N(t)&&L(n)?Number(n)!0,deleteProperty:(e,t)=>!0},Ve=C({},Me,{get:Ne,set:Fe(!0)}),Ie=C({},Pe,{get:$e}),Be=e=>e,Le=e=>Reflect.getPrototypeOf(e);function je(e,t,n=!1,o=!1){const r=yt(e=e.__v_raw),s=yt(t);t!==s&&!n&&be(r,0,t),!n&&be(r,0,s);const{has:i}=Le(r),l=o?Be:n?St:_t;return i.call(r,t)?l(e.get(t)):i.call(r,s)?l(e.get(s)):void(e!==r&&e.get(t))}function Ue(e,t=!1){const n=this.__v_raw,o=yt(n),r=yt(e);return e!==r&&!t&&be(o,0,e),!t&&be(o,0,r),e===r?n.has(e):n.has(e)||n.has(r)}function He(e,t=!1){return e=e.__v_raw,!t&&be(yt(e),0,pe),Reflect.get(e,"size",e)}function De(e){e=yt(e);const t=yt(this);return Le(t).has.call(t,e)||(t.add(e),xe(t,"add",e,e)),this}function We(e,t){t=yt(t);const n=yt(this),{has:o,get:r}=Le(n);let s=o.call(n,e);s||(e=yt(e),s=o.call(n,e));const i=r.call(n,e);return n.set(e,t),s?q(t,i)&&xe(n,"set",e,t):xe(n,"add",e,t),this}function ze(e){const t=yt(this),{has:n,get:o}=Le(t);let r=n.call(t,e);r||(e=yt(e),r=n.call(t,e)),o&&o.call(t,e);const s=t.delete(e);return r&&xe(t,"delete",e,void 0),s}function Ke(){const e=yt(this),t=0!==e.size,n=e.clear();return t&&xe(e,"clear",void 0,void 0),n}function Ge(e,t){return function(n,o){const r=this,s=r.__v_raw,i=yt(s),l=t?Be:e?St:_t;return!e&&be(i,0,pe),s.forEach(((e,t)=>n.call(o,l(e),l(t),r)))}}function qe(e,t,n){return function(...o){const r=this.__v_raw,s=yt(r),i=E(s),l="entries"===e||e===Symbol.iterator&&i,c="keys"===e&&i,a=r[e](...o),u=n?Be:t?St:_t;return!t&&be(s,0,c?fe:pe),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:l?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function Je(e){return function(...t){return"delete"!==e&&this}}function Ze(){const e={get(e){return je(this,e)},get size(){return He(this)},has:Ue,add:De,set:We,delete:ze,clear:Ke,forEach:Ge(!1,!1)},t={get(e){return je(this,e,!1,!0)},get size(){return He(this)},has:Ue,add:De,set:We,delete:ze,clear:Ke,forEach:Ge(!1,!0)},n={get(e){return je(this,e,!0)},get size(){return He(this,!0)},has(e){return Ue.call(this,e,!0)},add:Je("add"),set:Je("set"),delete:Je("delete"),clear:Je("clear"),forEach:Ge(!0,!1)},o={get(e){return je(this,e,!0,!0)},get size(){return He(this,!0)},has(e){return Ue.call(this,e,!0)},add:Je("add"),set:Je("set"),delete:Je("delete"),clear:Je("clear"),forEach:Ge(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach((r=>{e[r]=qe(r,!1,!1),n[r]=qe(r,!0,!1),t[r]=qe(r,!1,!0),o[r]=qe(r,!0,!0)})),[e,n,t,o]}const[Ye,Qe,Xe,et]=Ze();function tt(e,t){const n=t?e?et:Xe:e?Qe:Ye;return(t,o,r)=>"__v_isReactive"===o?!e:"__v_isReadonly"===o?e:"__v_raw"===o?t:Reflect.get(T(n,o)&&o in t?n:t,o,r)}const nt={get:tt(!1,!1)},ot={get:tt(!1,!0)},rt={get:tt(!0,!1)},st={get:tt(!0,!0)},it=new WeakMap,lt=new WeakMap,ct=new WeakMap,at=new WeakMap;function ut(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>I(e).slice(8,-1))(e))}function pt(e){return e&&e.__v_isReadonly?e:ht(e,!1,Me,nt,it)}function ft(e){return ht(e,!1,Ve,ot,lt)}function dt(e){return ht(e,!0,Pe,rt,ct)}function ht(e,t,n,o,r){if(!M(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const s=r.get(e);if(s)return s;const i=ut(e);if(0===i)return e;const l=new Proxy(e,2===i?o:n);return r.set(e,l),l}function mt(e){return gt(e)?mt(e.__v_raw):!(!e||!e.__v_isReactive)}function gt(e){return!(!e||!e.__v_isReadonly)}function vt(e){return mt(e)||gt(e)}function yt(e){const t=e&&e.__v_raw;return t?yt(t):e}function bt(e){return Z(e,"__v_skip",!0),e}const _t=e=>M(e)?pt(e):e,St=e=>M(e)?dt(e):e;function xt(e){_e()&&((e=yt(e)).dep||(e.dep=oe()),Se(e.dep))}function Ct(e,t){(e=yt(e)).dep&&Ce(e.dep)}function wt(e){return Boolean(e&&!0===e.__v_isRef)}function kt(e){return Tt(e,!1)}function Tt(e,t){return wt(e)?e:new Nt(e,t)}class Nt{constructor(e,t){this._shallow=t,this.dep=void 0,this.__v_isRef=!0,this._rawValue=t?e:yt(e),this._value=t?e:_t(e)}get value(){return xt(this),this._value}set value(e){e=this._shallow?e:yt(e),q(e,this._rawValue)&&(this._rawValue=e,this._value=this._shallow?e:_t(e),Ct(this))}}function Et(e){return wt(e)?e.value:e}const $t={get:(e,t,n)=>Et(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return wt(r)&&!wt(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function Rt(e){return mt(e)?e:new Proxy(e,$t)}class Ot{constructor(e){this.dep=void 0,this.__v_isRef=!0;const{get:t,set:n}=e((()=>xt(this)),(()=>Ct(this)));this._get=t,this._set=n}get value(){return this._get()}set value(e){this._set(e)}}class At{constructor(e,t){this._object=e,this._key=t,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(e){this._object[this._key]=e}}function Ft(e,t){const n=e[t];return wt(n)?n:new At(e,t)}class Mt{constructor(e,t,n){this._setter=t,this.dep=void 0,this._dirty=!0,this.__v_isRef=!0,this.effect=new de(e,(()=>{this._dirty||(this._dirty=!0,Ct(this))})),this.__v_isReadonly=n}get value(){const e=yt(this);return xt(e),e._dirty&&(e._dirty=!1,e._value=e.effect.run()),e._value}set value(e){this._setter(e)}}function Pt(e,t){let n,o;const r=O(e);r?(n=e,o=y):(n=e.get,o=e.set);return new Mt(n,o,r||!o)}let Vt=[];function It(e,t,...n){const o=e.vnode.props||g;let r=n;const s=t.startsWith("update:"),i=s&&t.slice(7);if(i&&i in o){const e=`${"modelValue"===i?"model":i}Modifiers`,{number:t,trim:s}=o[e]||g;s?r=n.map((e=>e.trim())):t&&(r=n.map(Y))}let l,c=o[l=G(t)]||o[l=G(D(t))];!c&&s&&(c=o[l=G(z(t))]),c&&Rr(c,e,6,r);const a=o[l+"Once"];if(a){if(e.emitted){if(e.emitted[l])return}else e.emitted={};e.emitted[l]=!0,Rr(a,e,6,r)}}function Bt(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(void 0!==r)return r;const s=e.emits;let i={},l=!1;if(!O(e)){const o=e=>{const n=Bt(e,t,!0);n&&(l=!0,C(i,n))};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}return s||l?(N(s)?s.forEach((e=>i[e]=null)):C(i,s),o.set(e,i),i):(o.set(e,null),null)}function Lt(e,t){return!(!e||!S(t))&&(t=t.slice(2).replace(/Once$/,""),T(e,t[0].toLowerCase()+t.slice(1))||T(e,z(t))||T(e,t))}let jt=null,Ut=null;function Ht(e){const t=jt;return jt=e,Ut=e&&e.type.__scopeId||null,t}function Dt(e,t=jt,n){if(!t)return e;if(e._n)return e;const o=(...n)=>{o._d&&jo(-1);const r=Ht(t),s=e(...n);return Ht(r),o._d&&jo(1),s};return o._n=!0,o._c=!0,o._d=!0,o}function Wt(e){const{type:t,vnode:n,proxy:o,withProxy:r,props:s,propsOptions:[i],slots:l,attrs:c,emit:a,render:u,renderCache:p,data:f,setupState:d,ctx:h,inheritAttrs:m}=e;let g,v;const y=Ht(e);try{if(4&n.shapeFlag){const e=r||o;g=Xo(u.call(e,e,p,s,d,f,h)),v=c}else{const e=t;0,g=Xo(e(s,e.length>1?{attrs:c,slots:l,emit:a}:null)),v=t.props?c:zt(c)}}catch(_){Po.length=0,Or(_,e,1),g=Jo(Fo)}let b=g;if(v&&!1!==m){const e=Object.keys(v),{shapeFlag:t}=b;e.length&&7&t&&(i&&e.some(x)&&(v=Kt(v,i)),b=Yo(b,v))}return n.dirs&&(b.dirs=b.dirs?b.dirs.concat(n.dirs):n.dirs),n.transition&&(b.transition=n.transition),g=b,Ht(y),g}const zt=e=>{let t;for(const n in e)("class"===n||"style"===n||S(n))&&((t||(t={}))[n]=e[n]);return t},Kt=(e,t)=>{const n={};for(const o in e)x(o)&&o.slice(9)in t||(n[o]=e[o]);return n};function Gt(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).length)return!0;for(let r=0;r0?(Zt(e,"onPending"),Zt(e,"onFallback"),a(null,e.ssFallback,t,n,o,null,s,i),en(f,e.ssFallback)):f.resolve()}(t,n,o,r,s,i,l,c,a):function(e,t,n,o,r,s,i,l,{p:c,um:a,o:{createElement:u}}){const p=t.suspense=e.suspense;p.vnode=t,t.el=e.el;const f=t.ssContent,d=t.ssFallback,{activeBranch:h,pendingBranch:m,isInFallback:g,isHydrating:v}=p;if(m)p.pendingBranch=f,Wo(f,m)?(c(m,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0?p.resolve():g&&(c(h,d,n,o,r,null,s,i,l),en(p,d))):(p.pendingId++,v?(p.isHydrating=!1,p.activeBranch=m):a(m,r,p),p.deps=0,p.effects.length=0,p.hiddenContainer=u("div"),g?(c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0?p.resolve():(c(h,d,n,o,r,null,s,i,l),en(p,d))):h&&Wo(f,h)?(c(h,f,n,o,r,p,s,i,l),p.resolve(!0)):(c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0&&p.resolve()));else if(h&&Wo(f,h))c(h,f,n,o,r,p,s,i,l),en(p,f);else if(Zt(t,"onPending"),p.pendingBranch=f,p.pendingId++,c(null,f,p.hiddenContainer,null,r,p,s,i,l),p.deps<=0)p.resolve();else{const{timeout:e,pendingId:t}=p;e>0?setTimeout((()=>{p.pendingId===t&&p.fallback(d)}),e):0===e&&p.fallback(d)}}(e,t,n,o,r,i,l,c,a)},hydrate:function(e,t,n,o,r,s,i,l,c){const a=t.suspense=Yt(t,o,n,e.parentNode,document.createElement("div"),null,r,s,i,l,!0),u=c(e,a.pendingBranch=t.ssContent,n,a,s,i);0===a.deps&&a.resolve();return u},create:Yt,normalize:function(e){const{shapeFlag:t,children:n}=e,o=32&t;e.ssContent=Qt(o?n.default:n),e.ssFallback=o?Qt(n.fallback):Jo(Fo)}};function Zt(e,t){const n=e.props&&e.props[t];O(n)&&n()}function Yt(e,t,n,o,r,s,i,l,c,a,u=!1){const{p:p,m:f,um:d,n:h,o:{parentNode:m,remove:g}}=a,v=Y(e.props&&e.props.timeout),y={vnode:e,parent:t,parentComponent:n,isSVG:i,container:o,hiddenContainer:r,anchor:s,deps:0,pendingId:0,timeout:"number"==typeof v?v:-1,activeBranch:null,pendingBranch:null,isInFallback:!0,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1){const{vnode:t,activeBranch:n,pendingBranch:o,pendingId:r,effects:s,parentComponent:i,container:l}=y;if(y.isHydrating)y.isHydrating=!1;else if(!e){const e=n&&o.transition&&"out-in"===o.transition.mode;e&&(n.transition.afterLeave=()=>{r===y.pendingId&&f(o,l,t,0)});let{anchor:t}=y;n&&(t=h(n),d(n,i,y,!0)),e||f(o,l,t,0)}en(y,o),y.pendingBranch=null,y.isInFallback=!1;let c=y.parent,a=!1;for(;c;){if(c.pendingBranch){c.effects.push(...s),a=!0;break}c=c.parent}a||Jr(s),y.effects=[],Zt(t,"onResolve")},fallback(e){if(!y.pendingBranch)return;const{vnode:t,activeBranch:n,parentComponent:o,container:r,isSVG:s}=y;Zt(t,"onFallback");const i=h(n),a=()=>{y.isInFallback&&(p(null,e,r,i,o,null,s,l,c),en(y,e))},u=e.transition&&"out-in"===e.transition.mode;u&&(n.transition.afterLeave=a),y.isInFallback=!0,d(n,o,null,!0),u||a()},move(e,t,n){y.activeBranch&&f(y.activeBranch,e,t,n),y.container=e},next:()=>y.activeBranch&&h(y.activeBranch),registerDep(e,t){const n=!!y.pendingBranch;n&&y.deps++;const o=e.vnode.el;e.asyncDep.catch((t=>{Or(t,e,0)})).then((r=>{if(e.isUnmounted||y.isUnmounted||y.pendingId!==e.suspenseId)return;e.asyncResolved=!0;const{vnode:s}=e;yr(e,r,!1),o&&(s.el=o);const l=!o&&e.subTree.el;t(e,s,m(o||e.subTree.el),o?null:h(e.subTree),y,i,c),l&&g(l),qt(e,s.el),n&&0==--y.deps&&y.resolve()}))},unmount(e,t){y.isUnmounted=!0,y.activeBranch&&d(y.activeBranch,n,e,t),y.pendingBranch&&d(y.pendingBranch,n,e,t)}};return y}function Qt(e){let t;if(O(e)){const n=Lo&&e._c;n&&(e._d=!1,Io()),e=e(),n&&(e._d=!0,t=Vo,Bo())}if(N(e)){const t=function(e){let t;for(let n=0;nt!==e))),e}function Xt(e,t){t&&t.pendingBranch?N(e)?t.effects.push(...e):t.effects.push(e):Jr(e)}function en(e,t){e.activeBranch=t;const{vnode:n,parentComponent:o}=e,r=n.el=t.el;o&&o.subTree===n&&(o.vnode.el=r,qt(o,r))}function tn(e,t){if(ur){let n=ur.provides;const o=ur.parent&&ur.parent.provides;o===n&&(n=ur.provides=Object.create(o)),n[e]=t}else;}function nn(e,t,n=!1){const o=ur||jt;if(o){const r=null==o.parent?o.vnode.appContext&&o.vnode.appContext.provides:o.parent.provides;if(r&&e in r)return r[e];if(arguments.length>1)return n&&O(t)?t.call(o.proxy):t}}function on(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return En((()=>{e.isMounted=!0})),On((()=>{e.isUnmounting=!0})),e}const rn=[Function,Array],sn={name:"BaseTransition",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:rn,onEnter:rn,onAfterEnter:rn,onEnterCancelled:rn,onBeforeLeave:rn,onLeave:rn,onAfterLeave:rn,onLeaveCancelled:rn,onBeforeAppear:rn,onAppear:rn,onAfterAppear:rn,onAppearCancelled:rn},setup(e,{slots:t}){const n=pr(),o=on();let r;return()=>{const s=t.default&&fn(t.default(),!0);if(!s||!s.length)return;const i=yt(e),{mode:l}=i,c=s[0];if(o.isLeaving)return an(c);const a=un(c);if(!a)return an(c);const u=cn(a,i,o,n);pn(a,u);const p=n.subTree,f=p&&un(p);let d=!1;const{getTransitionKey:h}=a.type;if(h){const e=h();void 0===r?r=e:e!==r&&(r=e,d=!0)}if(f&&f.type!==Fo&&(!Wo(a,f)||d)){const e=cn(f,i,o,n);if(pn(f,e),"out-in"===l)return o.isLeaving=!0,e.afterLeave=()=>{o.isLeaving=!1,n.update()},an(c);"in-out"===l&&a.type!==Fo&&(e.delayLeave=(e,t,n)=>{ln(o,f)[String(f.key)]=f,e._leaveCb=()=>{t(),e._leaveCb=void 0,delete u.delayedLeave},u.delayedLeave=n})}return c}}};function ln(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||(o=Object.create(null),n.set(t.type,o)),o}function cn(e,t,n,o){const{appear:r,mode:s,persisted:i=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:a,onEnterCancelled:u,onBeforeLeave:p,onLeave:f,onAfterLeave:d,onLeaveCancelled:h,onBeforeAppear:m,onAppear:g,onAfterAppear:v,onAppearCancelled:y}=t,b=String(e.key),_=ln(n,e),S=(e,t)=>{e&&Rr(e,o,9,t)},x={mode:s,persisted:i,beforeEnter(t){let o=l;if(!n.isMounted){if(!r)return;o=m||l}t._leaveCb&&t._leaveCb(!0);const s=_[b];s&&Wo(e,s)&&s.el._leaveCb&&s.el._leaveCb(),S(o,[t])},enter(e){let t=c,o=a,s=u;if(!n.isMounted){if(!r)return;t=g||c,o=v||a,s=y||u}let i=!1;const l=e._enterCb=t=>{i||(i=!0,S(t?s:o,[e]),x.delayedLeave&&x.delayedLeave(),e._enterCb=void 0)};t?(t(e,l),t.length<=1&&l()):l()},leave(t,o){const r=String(e.key);if(t._enterCb&&t._enterCb(!0),n.isUnmounting)return o();S(p,[t]);let s=!1;const i=t._leaveCb=n=>{s||(s=!0,o(),S(n?h:d,[t]),t._leaveCb=void 0,_[r]===e&&delete _[r])};_[r]=e,f?(f(t,i),f.length<=1&&i()):i()},clone:e=>cn(e,t,n,o)};return x}function an(e){if(gn(e))return(e=Yo(e)).children=null,e}function un(e){return gn(e)?e.children?e.children[0]:void 0:e}function pn(e,t){6&e.shapeFlag&&e.component?pn(e.component.subTree,t):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function fn(e,t=!1){let n=[],o=0;for(let r=0;r1)for(let r=0;r!!e.type.__asyncLoader;function mn(e,{vnode:{ref:t,props:n,children:o}}){const r=Jo(e,n,o);return r.ref=t,r}const gn=e=>e.type.__isKeepAlive,vn={name:"KeepAlive",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){const n=pr(),o=n.ctx;if(!o.renderer)return t.default;const r=new Map,s=new Set;let i=null;const l=n.suspense,{renderer:{p:c,m:a,um:u,o:{createElement:p}}}=o,f=p("div");function d(e){Cn(e),u(e,n,l)}function h(e){r.forEach(((t,n)=>{const o=wr(t.type);!o||e&&e(o)||m(n)}))}function m(e){const t=r.get(e);i&&t.type===i.type?i&&Cn(i):d(t),r.delete(e),s.delete(e)}o.activate=(e,t,n,o,r)=>{const s=e.component;a(e,t,n,0,l),c(s.vnode,e,t,n,s,l,o,e.slotScopeIds,r),mo((()=>{s.isDeactivated=!1,s.a&&J(s.a);const t=e.props&&e.props.onVnodeMounted;t&&_o(t,s.parent,e)}),l)},o.deactivate=e=>{const t=e.component;a(e,f,null,1,l),mo((()=>{t.da&&J(t.da);const n=e.props&&e.props.onVnodeUnmounted;n&&_o(n,t.parent,e),t.isDeactivated=!0}),l)},ns((()=>[e.include,e.exclude]),(([e,t])=>{e&&h((t=>yn(e,t))),t&&h((e=>!yn(t,e)))}),{flush:"post",deep:!0});let g=null;const v=()=>{null!=g&&r.set(g,wn(n.subTree))};return En(v),Rn(v),On((()=>{r.forEach((e=>{const{subTree:t,suspense:o}=n,r=wn(t);if(e.type!==r.type)d(e);else{Cn(r);const e=r.component.da;e&&mo(e,o)}}))})),()=>{if(g=null,!t.default)return null;const n=t.default(),o=n[0];if(n.length>1)return i=null,n;if(!(Do(o)&&(4&o.shapeFlag||128&o.shapeFlag)))return i=null,o;let l=wn(o);const c=l.type,a=wr(hn(l)?l.type.__asyncResolved||{}:c),{include:u,exclude:p,max:f}=e;if(u&&(!a||!yn(u,a))||p&&a&&yn(p,a))return i=l,o;const d=null==l.key?c:l.key,h=r.get(d);return l.el&&(l=Yo(l),128&o.shapeFlag&&(o.ssContent=l)),g=d,h?(l.el=h.el,l.component=h.component,l.transition&&pn(l,l.transition),l.shapeFlag|=512,s.delete(d),s.add(d)):(s.add(d),f&&s.size>parseInt(f,10)&&m(s.values().next().value)),l.shapeFlag|=256,i=l,o}}};function yn(e,t){return N(e)?e.some((e=>yn(e,t))):A(e)?e.split(",").indexOf(t)>-1:!!e.test&&e.test(t)}function bn(e,t){Sn(e,"a",t)}function _n(e,t){Sn(e,"da",t)}function Sn(e,t,n=ur){const o=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(kn(t,o,n),n){let e=n.parent;for(;e&&e.parent;)gn(e.parent.vnode)&&xn(o,t,n,e),e=e.parent}}function xn(e,t,n,o){const r=kn(t,e,o,!0);An((()=>{w(o[t],r)}),n)}function Cn(e){let t=e.shapeFlag;256&t&&(t-=256),512&t&&(t-=512),e.shapeFlag=t}function wn(e){return 128&e.shapeFlag?e.ssContent:e}function kn(e,t,n=ur,o=!1){if(n){const r=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...o)=>{if(n.isUnmounted)return;ve(),fr(n);const r=Rr(t,n,e,o);return dr(),ye(),r});return o?r.unshift(s):r.push(s),s}}const Tn=e=>(t,n=ur)=>(!vr||"sp"===e)&&kn(e,t,n),Nn=Tn("bm"),En=Tn("m"),$n=Tn("bu"),Rn=Tn("u"),On=Tn("bum"),An=Tn("um"),Fn=Tn("sp"),Mn=Tn("rtg"),Pn=Tn("rtc");function Vn(e,t=ur){kn("ec",e,t)}let In=!0;function Bn(e){const t=Un(e),n=e.proxy,o=e.ctx;In=!1,t.beforeCreate&&Ln(t.beforeCreate,e,"bc");const{data:r,computed:s,methods:i,watch:l,provide:c,inject:a,created:u,beforeMount:p,mounted:f,beforeUpdate:d,updated:h,activated:m,deactivated:g,beforeUnmount:v,unmounted:b,render:_,renderTracked:S,renderTriggered:x,errorCaptured:C,serverPrefetch:w,expose:k,inheritAttrs:T,components:E,directives:$}=t;if(a&&function(e,t,n=y,o=!1){N(e)&&(e=zn(e));for(const r in e){const n=e[r];let s;s=M(n)?"default"in n?nn(n.from||r,n.default,!0):nn(n.from||r):nn(n),wt(s)&&o?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>s.value,set:e=>s.value=e}):t[r]=s}}(a,o,null,e.appContext.config.unwrapInjectedRef),i)for(const y in i){const e=i[y];O(e)&&(o[y]=e.bind(n))}if(r){const t=r.call(n,n);M(t)&&(e.data=pt(t))}if(In=!0,s)for(const N in s){const e=s[N],t=Pt({get:O(e)?e.bind(n,n):O(e.get)?e.get.bind(n,n):y,set:!O(e)&&O(e.set)?e.set.bind(n):y});Object.defineProperty(o,N,{enumerable:!0,configurable:!0,get:()=>t.value,set:e=>t.value=e})}if(l)for(const y in l)jn(l[y],o,n,y);if(c){const e=O(c)?c.call(n):c;Reflect.ownKeys(e).forEach((t=>{tn(t,e[t])}))}function R(e,t){N(t)?t.forEach((t=>e(t.bind(n)))):t&&e(t.bind(n))}if(u&&Ln(u,e,"c"),R(Nn,p),R(En,f),R($n,d),R(Rn,h),R(bn,m),R(_n,g),R(Vn,C),R(Pn,S),R(Mn,x),R(On,v),R(An,b),R(Fn,w),N(k))if(k.length){const t=e.exposed||(e.exposed={});k.forEach((e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})}))}else e.exposed||(e.exposed={});_&&e.render===y&&(e.render=_),null!=T&&(e.inheritAttrs=T),E&&(e.components=E),$&&(e.directives=$)}function Ln(e,t,n){Rr(N(e)?e.map((e=>e.bind(t.proxy))):e.bind(t.proxy),t,n)}function jn(e,t,n,o){const r=o.includes(".")?ss(n,o):()=>n[o];if(A(e)){const n=t[e];O(n)&&ns(r,n)}else if(O(e))ns(r,e.bind(n));else if(M(e))if(N(e))e.forEach((e=>jn(e,t,n,o)));else{const o=O(e.handler)?e.handler.bind(n):t[e.handler];O(o)&&ns(r,o,e)}}function Un(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,l=s.get(t);let c;return l?c=l:r.length||n||o?(c={},r.length&&r.forEach((e=>Hn(c,e,i,!0))),Hn(c,t,i)):c=t,s.set(t,c),c}function Hn(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&Hn(e,s,n,!0),r&&r.forEach((t=>Hn(e,t,n,!0)));for(const i in t)if(o&&"expose"===i);else{const o=Dn[i]||n&&n[i];e[i]=o?o(e[i],t[i]):t[i]}return e}const Dn={data:Wn,props:Gn,emits:Gn,methods:Gn,computed:Gn,beforeCreate:Kn,created:Kn,beforeMount:Kn,mounted:Kn,beforeUpdate:Kn,updated:Kn,beforeDestroy:Kn,beforeUnmount:Kn,destroyed:Kn,unmounted:Kn,activated:Kn,deactivated:Kn,errorCaptured:Kn,serverPrefetch:Kn,components:Gn,directives:Gn,watch:function(e,t){if(!e)return t;if(!t)return e;const n=C(Object.create(null),e);for(const o in t)n[o]=Kn(e[o],t[o]);return n},provide:Wn,inject:function(e,t){return Gn(zn(e),zn(t))}};function Wn(e,t){return t?e?function(){return C(O(e)?e.call(this,this):e,O(t)?t.call(this,this):t)}:t:e}function zn(e){if(N(e)){const t={};for(let n=0;n{c=!0;const[n,o]=Zn(e,t,!0);C(i,n),o&&l.push(...o)};!n&&t.mixins.length&&t.mixins.forEach(o),e.extends&&o(e.extends),e.mixins&&e.mixins.forEach(o)}if(!s&&!c)return o.set(e,v),v;if(N(s))for(let u=0;u-1,n[1]=o<0||t-1||T(n,"default"))&&l.push(e)}}}const a=[i,l];return o.set(e,a),a}function Yn(e){return"$"!==e[0]}function Qn(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:null===e?"null":""}function Xn(e,t){return Qn(e)===Qn(t)}function eo(e,t){return N(t)?t.findIndex((t=>Xn(t,e))):O(t)&&Xn(t,e)?0:-1}const to=e=>"_"===e[0]||"$stable"===e,no=e=>N(e)?e.map(Xo):[Xo(e)],oo=(e,t,n)=>{const o=Dt(((...e)=>no(t(...e))),n);return o._c=!1,o},ro=(e,t,n)=>{const o=e._ctx;for(const r in e){if(to(r))continue;const n=e[r];if(O(n))t[r]=oo(0,n,o);else if(null!=n){const e=no(n);t[r]=()=>e}}},so=(e,t)=>{const n=no(t);e.slots.default=()=>n};function io(e,t,n,o){const r=e.dirs,s=t&&t.dirs;for(let i=0;i(s.has(e)||(e&&O(e.install)?(s.add(e),e.install(l,...t)):O(e)&&(s.add(e),e(l,...t))),l),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),l),component:(e,t)=>t?(r.components[e]=t,l):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,l):r.directives[e],mount(s,c,a){if(!i){const u=Jo(n,o);return u.appContext=r,c&&t?t(u,s):e(u,s,a),i=!0,l._container=s,s.__vue_app__=l,xr(u.component)||u.component.proxy}},unmount(){i&&(e(null,l._container),delete l._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,l)};return l}}let uo=!1;const po=e=>/svg/.test(e.namespaceURI)&&"foreignObject"!==e.tagName,fo=e=>8===e.nodeType;function ho(e){const{mt:t,p:n,o:{patchProp:o,nextSibling:r,parentNode:s,remove:i,insert:l,createComment:c}}=e,a=(n,o,i,l,c,m=!1)=>{const g=fo(n)&&"["===n.data,v=()=>d(n,o,i,l,c,g),{type:y,ref:b,shapeFlag:_}=o,S=n.nodeType;o.el=n;let x=null;switch(y){case Ao:3!==S?x=v():(n.data!==o.children&&(uo=!0,n.data=o.children),x=r(n));break;case Fo:x=8!==S||g?v():r(n);break;case Mo:if(1===S){x=n;const e=!o.children.length;for(let t=0;t{l=l||!!t.dynamicChildren;const{type:c,props:a,patchFlag:u,shapeFlag:f,dirs:d}=t,h="input"===c&&d||"option"===c;if(h||-1!==u){if(d&&io(t,null,n,"created"),a)if(h||!l||48&u)for(const t in a)(h&&t.endsWith("value")||S(t)&&!j(t))&&o(e,t,null,a[t],!1,void 0,n);else a.onClick&&o(e,"onClick",null,a.onClick,!1,void 0,n);let c;if((c=a&&a.onVnodeBeforeMount)&&_o(c,n,t),d&&io(t,null,n,"beforeMount"),((c=a&&a.onVnodeMounted)||d)&&Xt((()=>{c&&_o(c,n,t),d&&io(t,null,n,"mounted")}),r),16&f&&(!a||!a.innerHTML&&!a.textContent)){let o=p(e.firstChild,t,e,n,r,s,l);for(;o;){uo=!0;const e=o;o=o.nextSibling,i(e)}}else 8&f&&e.textContent!==t.children&&(uo=!0,e.textContent=t.children)}return e.nextSibling},p=(e,t,o,r,s,i,l)=>{l=l||!!t.dynamicChildren;const c=t.children,u=c.length;for(let p=0;p{const{slotScopeIds:u}=t;u&&(i=i?i.concat(u):u);const f=s(e),d=p(r(e),t,f,n,o,i,a);return d&&fo(d)&&"]"===d.data?r(t.anchor=d):(uo=!0,l(t.anchor=c("]"),f,d),d)},d=(e,t,o,l,c,a)=>{if(uo=!0,t.el=null,a){const t=h(e);for(;;){const n=r(e);if(!n||n===t)break;i(n)}}const u=r(e),p=s(e);return i(e),n(null,t,p,u,o,l,po(p),c),u},h=e=>{let t=0;for(;e;)if((e=r(e))&&fo(e)&&("["===e.data&&t++,"]"===e.data)){if(0===t)return r(e);t--}return e};return[(e,t)=>{if(!t.hasChildNodes())return n(null,e,t),void Yr();uo=!1,a(t.firstChild,e,null,null,null),Yr(),uo&&console.error("Hydration completed but contains mismatches.")},a]}const mo=Xt;function go(e){return yo(e)}function vo(e){return yo(e,ho)}function yo(e,t){(Q||(Q="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{})).__VUE__=!0;const{insert:n,remove:o,patchProp:r,createElement:s,createText:i,createComment:l,setText:c,setElementText:a,parentNode:u,nextSibling:p,setScopeId:f=y,cloneNode:d,insertStaticContent:h}=e,m=(e,t,n,o=null,r=null,s=null,i=!1,l=null,c=!!t.dynamicChildren)=>{if(e===t)return;e&&!Wo(e,t)&&(o=X(e),W(e,r,s,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:p}=t;switch(a){case Ao:b(e,t,n,o);break;case Fo:_(e,t,n,o);break;case Mo:null==e&&S(t,n,o,i);break;case Oo:O(e,t,n,o,r,s,i,l,c);break;default:1&p?x(e,t,n,o,r,s,i,l,c):6&p?A(e,t,n,o,r,s,i,l,c):(64&p||128&p)&&a.process(e,t,n,o,r,s,i,l,c,ne)}null!=u&&r&&bo(u,e&&e.ref,s,t||e,!t)},b=(e,t,o,r)=>{if(null==e)n(t.el=i(t.children),o,r);else{const n=t.el=e.el;t.children!==e.children&&c(n,t.children)}},_=(e,t,o,r)=>{null==e?n(t.el=l(t.children||""),o,r):t.el=e.el},S=(e,t,n,o)=>{[e.el,e.anchor]=h(e.children,t,n,o)},x=(e,t,n,o,r,s,i,l,c)=>{i=i||"svg"===t.type,null==e?w(t,n,o,r,s,i,l,c):E(e,t,r,s,i,l,c)},w=(e,t,o,i,l,c,u,p)=>{let f,h;const{type:m,props:g,shapeFlag:v,transition:y,patchFlag:b,dirs:_}=e;if(e.el&&void 0!==d&&-1===b)f=e.el=d(e.el);else{if(f=e.el=s(e.type,c,g&&g.is,g),8&v?a(f,e.children):16&v&&N(e.children,f,null,i,l,c&&"foreignObject"!==m,u,p),_&&io(e,null,i,"created"),g){for(const t in g)"value"===t||j(t)||r(f,t,null,g[t],c,e.children,i,l,Y);"value"in g&&r(f,"value",null,g.value),(h=g.onVnodeBeforeMount)&&_o(h,i,e)}k(f,e,e.scopeId,u,i)}_&&io(e,null,i,"beforeMount");const S=(!l||l&&!l.pendingBranch)&&y&&!y.persisted;S&&y.beforeEnter(f),n(f,t,o),((h=g&&g.onVnodeMounted)||S||_)&&mo((()=>{h&&_o(h,i,e),S&&y.enter(f),_&&io(e,null,i,"mounted")}),l)},k=(e,t,n,o,r)=>{if(n&&f(e,n),o)for(let s=0;s{for(let a=c;a{const c=t.el=e.el;let{patchFlag:u,dynamicChildren:p,dirs:f}=t;u|=16&e.patchFlag;const d=e.props||g,h=t.props||g;let m;(m=h.onVnodeBeforeUpdate)&&_o(m,n,t,e),f&&io(t,e,n,"beforeUpdate");const v=s&&"foreignObject"!==t.type;if(p?$(e.dynamicChildren,p,c,n,o,v,i):l||B(e,t,c,null,n,o,v,i,!1),u>0){if(16&u)R(c,t,d,h,n,o,s);else if(2&u&&d.class!==h.class&&r(c,"class",null,h.class,s),4&u&&r(c,"style",d.style,h.style,s),8&u){const i=t.dynamicProps;for(let t=0;t{m&&_o(m,n,t,e),f&&io(t,e,n,"updated")}),o)},$=(e,t,n,o,r,s,i)=>{for(let l=0;l{if(n!==o){for(const c in o){if(j(c))continue;const a=o[c],u=n[c];a!==u&&"value"!==c&&r(e,c,u,a,l,t.children,s,i,Y)}if(n!==g)for(const c in n)j(c)||c in o||r(e,c,n[c],null,l,t.children,s,i,Y);"value"in o&&r(e,"value",n.value,o.value)}},O=(e,t,o,r,s,l,c,a,u)=>{const p=t.el=e?e.el:i(""),f=t.anchor=e?e.anchor:i("");let{patchFlag:d,dynamicChildren:h,slotScopeIds:m}=t;m&&(a=a?a.concat(m):m),null==e?(n(p,o,r),n(f,o,r),N(t.children,o,f,s,l,c,a,u)):d>0&&64&d&&h&&e.dynamicChildren?($(e.dynamicChildren,h,o,s,l,c,a),(null!=t.key||s&&t===s.subTree)&&So(e,t,!0)):B(e,t,o,f,s,l,c,a,u)},A=(e,t,n,o,r,s,i,l,c)=>{t.slotScopeIds=l,null==e?512&t.shapeFlag?r.ctx.activate(t,n,o,i,c):F(t,n,o,r,s,i,c):M(e,t,c)},F=(e,t,n,o,r,s,i)=>{const l=e.component=function(e,t,n){const o=e.type,r=(t?t.appContext:e.appContext)||cr,s={uid:ar++,vnode:e,type:o,parent:t,appContext:r,root:null,next:null,subTree:null,update:null,scope:new te(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(r.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Zn(o,r),emitsOptions:Bt(o,r),emit:null,emitted:null,propsDefaults:g,inheritAttrs:o.inheritAttrs,ctx:g,data:g,props:g,attrs:g,slots:g,refs:g,setupState:g,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};s.ctx={_:s},s.root=t?t.root:s,s.emit=It.bind(null,s),e.ce&&e.ce(s);return s}(e,o,r);if(gn(e)&&(l.ctx.renderer=ne),function(e,t=!1){vr=t;const{props:n,children:o}=e.vnode,r=hr(e);(function(e,t,n,o=!1){const r={},s={};Z(s,zo,1),e.propsDefaults=Object.create(null),qn(e,t,r,s);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);e.props=n?o?r:ft(r):e.type.props?r:s,e.attrs=s})(e,n,r,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=yt(t),Z(t,"_",n)):ro(t,e.slots={})}else e.slots={},t&&so(e,t);Z(e.slots,zo,1)})(e,o);const s=r?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=bt(new Proxy(e.ctx,ir));const{setup:o}=n;if(o){const n=e.setupContext=o.length>1?Sr(e):null;fr(e),ve();const r=$r(o,e,0,[e.props,n]);if(ye(),dr(),P(r)){if(r.then(dr,dr),t)return r.then((n=>{yr(e,n,t)})).catch((t=>{Or(t,e,0)}));e.asyncDep=r}else yr(e,r,t)}else _r(e,t)}(e,t):void 0;vr=!1}(l),l.asyncDep){if(r&&r.registerDep(l,V),!e.el){const e=l.subTree=Jo(Fo);_(null,e,t,n)}}else V(l,e,t,n,r,s,i)},M=(e,t,n)=>{const o=t.component=e.component;if(function(e,t,n){const{props:o,children:r,component:s}=e,{props:i,children:l,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!r&&!l||l&&l.$stable)||o!==i&&(o?!i||Gt(o,i,a):!!i);if(1024&c)return!0;if(16&c)return o?Gt(o,i,a):!!i;if(8&c){const e=t.dynamicProps;for(let t=0;tPr&&Mr.splice(t,1)}(o.update),o.update()}else t.component=e.component,t.el=e.el,o.vnode=t},V=(e,t,n,o,r,s,i)=>{const l=new de((()=>{if(e.isMounted){let t,{next:n,bu:o,u:c,parent:a,vnode:p}=e,f=n;l.allowRecurse=!1,n?(n.el=p.el,I(e,n,i)):n=p,o&&J(o),(t=n.props&&n.props.onVnodeBeforeUpdate)&&_o(t,a,n,p),l.allowRecurse=!0;const d=Wt(e),h=e.subTree;e.subTree=d,m(h,d,u(h.el),X(h),e,r,s),n.el=d.el,null===f&&qt(e,d.el),c&&mo(c,r),(t=n.props&&n.props.onVnodeUpdated)&&mo((()=>_o(t,a,n,p)),r)}else{let i;const{el:c,props:a}=t,{bm:u,m:p,parent:f}=e,d=hn(t);if(l.allowRecurse=!1,u&&J(u),!d&&(i=a&&a.onVnodeBeforeMount)&&_o(i,f,t),l.allowRecurse=!0,c&&re){const n=()=>{e.subTree=Wt(e),re(c,e.subTree,e,r,null)};d?t.type.__asyncLoader().then((()=>!e.isUnmounted&&n())):n()}else{const i=e.subTree=Wt(e);m(null,i,n,o,e,r,s),t.el=i.el}if(p&&mo(p,r),!d&&(i=a&&a.onVnodeMounted)){const e=t;mo((()=>_o(i,f,e)),r)}256&t.shapeFlag&&e.a&&mo(e.a,r),e.isMounted=!0,t=n=o=null}}),(()=>Kr(e.update)),e.scope),c=e.update=l.run.bind(l);c.id=e.uid,l.allowRecurse=c.allowRecurse=!0,c()},I=(e,t,n)=>{t.component=e;const o=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,o){const{props:r,attrs:s,vnode:{patchFlag:i}}=e,l=yt(r),[c]=e.propsOptions;let a=!1;if(!(o||i>0)||16&i){let o;qn(e,t,r,s)&&(a=!0);for(const s in l)t&&(T(t,s)||(o=z(s))!==s&&T(t,o))||(c?!n||void 0===n[s]&&void 0===n[o]||(r[s]=Jn(c,l,s,void 0,e,!0)):delete r[s]);if(s!==l)for(const e in s)t&&T(t,e)||(delete s[e],a=!0)}else if(8&i){const n=e.vnode.dynamicProps;for(let o=0;o{const{vnode:o,slots:r}=e;let s=!0,i=g;if(32&o.shapeFlag){const e=t._;e?n&&1===e?s=!1:(C(r,t),n||1!==e||delete r._):(s=!t.$stable,ro(t,r)),i=t}else t&&(so(e,t),i={default:1});if(s)for(const l in r)to(l)||l in i||delete r[l]})(e,t.children,n),ve(),Zr(void 0,e.update),ye()},B=(e,t,n,o,r,s,i,l,c=!1)=>{const u=e&&e.children,p=e?e.shapeFlag:0,f=t.children,{patchFlag:d,shapeFlag:h}=t;if(d>0){if(128&d)return void U(u,f,n,o,r,s,i,l,c);if(256&d)return void L(u,f,n,o,r,s,i,l,c)}8&h?(16&p&&Y(u,r,s),f!==u&&a(n,f)):16&p?16&h?U(u,f,n,o,r,s,i,l,c):Y(u,r,s,!0):(8&p&&a(n,""),16&h&&N(f,n,o,r,s,i,l,c))},L=(e,t,n,o,r,s,i,l,c)=>{const a=(e=e||v).length,u=(t=t||v).length,p=Math.min(a,u);let f;for(f=0;fu?Y(e,r,s,!0,!1,p):N(t,n,o,r,s,i,l,c,p)},U=(e,t,n,o,r,s,i,l,c)=>{let a=0;const u=t.length;let p=e.length-1,f=u-1;for(;a<=p&&a<=f;){const o=e[a],u=t[a]=c?er(t[a]):Xo(t[a]);if(!Wo(o,u))break;m(o,u,n,null,r,s,i,l,c),a++}for(;a<=p&&a<=f;){const o=e[p],a=t[f]=c?er(t[f]):Xo(t[f]);if(!Wo(o,a))break;m(o,a,n,null,r,s,i,l,c),p--,f--}if(a>p){if(a<=f){const e=f+1,p=ef)for(;a<=p;)W(e[a],r,s,!0),a++;else{const d=a,h=a,g=new Map;for(a=h;a<=f;a++){const e=t[a]=c?er(t[a]):Xo(t[a]);null!=e.key&&g.set(e.key,a)}let y,b=0;const _=f-h+1;let S=!1,x=0;const C=new Array(_);for(a=0;a<_;a++)C[a]=0;for(a=d;a<=p;a++){const o=e[a];if(b>=_){W(o,r,s,!0);continue}let u;if(null!=o.key)u=g.get(o.key);else for(y=h;y<=f;y++)if(0===C[y-h]&&Wo(o,t[y])){u=y;break}void 0===u?W(o,r,s,!0):(C[u-h]=a+1,u>=x?x=u:S=!0,m(o,t[u],n,null,r,s,i,l,c),b++)}const w=S?function(e){const t=e.slice(),n=[0];let o,r,s,i,l;const c=e.length;for(o=0;o>1,e[n[l]]0&&(t[o]=n[s-1]),n[s]=o)}}s=n.length,i=n[s-1];for(;s-- >0;)n[s]=i,i=t[i];return n}(C):v;for(y=w.length-1,a=_-1;a>=0;a--){const e=h+a,p=t[e],f=e+1{const{el:i,type:l,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void H(e.component.subTree,t,o,r);if(128&u)return void e.suspense.move(t,o,r);if(64&u)return void l.move(e,t,o,ne);if(l===Oo){n(i,t,o);for(let e=0;e{let s;for(;e&&e!==t;)s=p(e),n(e,o,r),e=s;n(t,o,r)})(e,t,o);if(2!==r&&1&u&&c)if(0===r)c.beforeEnter(i),n(i,t,o),mo((()=>c.enter(i)),s);else{const{leave:e,delayLeave:r,afterLeave:s}=c,l=()=>n(i,t,o),a=()=>{e(i,(()=>{l(),s&&s()}))};r?r(i,l,a):a()}else n(i,t,o)},W=(e,t,n,o=!1,r=!1)=>{const{type:s,props:i,ref:l,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:p,dirs:f}=e;if(null!=l&&bo(l,null,n,e,!0),256&u)return void t.ctx.deactivate(e);const d=1&u&&f,h=!hn(e);let m;if(h&&(m=i&&i.onVnodeBeforeUnmount)&&_o(m,t,e),6&u)q(e.component,n,o);else{if(128&u)return void e.suspense.unmount(n,o);d&&io(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,r,ne,o):a&&(s!==Oo||p>0&&64&p)?Y(a,t,n,!1,!0):(s===Oo&&384&p||!r&&16&u)&&Y(c,t,n),o&&K(e)}(h&&(m=i&&i.onVnodeUnmounted)||d)&&mo((()=>{m&&_o(m,t,e),d&&io(e,null,t,"unmounted")}),n)},K=e=>{const{type:t,el:n,anchor:r,transition:s}=e;if(t===Oo)return void G(n,r);if(t===Mo)return void(({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=p(e),o(e),e=n;o(t)})(e);const i=()=>{o(n),s&&!s.persisted&&s.afterLeave&&s.afterLeave()};if(1&e.shapeFlag&&s&&!s.persisted){const{leave:t,delayLeave:o}=s,r=()=>t(n,i);o?o(e.el,i,r):r()}else i()},G=(e,t)=>{let n;for(;e!==t;)n=p(e),o(e),e=n;o(t)},q=(e,t,n)=>{const{bum:o,scope:r,update:s,subTree:i,um:l}=e;o&&J(o),r.stop(),s&&(s.active=!1,W(i,e,t,n)),l&&mo(l,t),mo((()=>{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},Y=(e,t,n,o=!1,r=!1,s=0)=>{for(let i=s;i6&e.shapeFlag?X(e.component.subTree):128&e.shapeFlag?e.suspense.next():p(e.anchor||e.el),ee=(e,t,n)=>{null==e?t._vnode&&W(t._vnode,null,null,!0):m(t._vnode||null,e,t,null,null,null,n),Yr(),t._vnode=e},ne={p:m,um:W,m:H,r:K,mt:F,mc:N,pc:B,pbc:$,n:X,o:e};let oe,re;return t&&([oe,re]=t(ne)),{render:ee,hydrate:oe,createApp:ao(ee,oe)}}function bo(e,t,n,o,r=!1){if(N(e))return void e.forEach(((e,s)=>bo(e,t&&(N(t)?t[s]:t),n,o,r)));if(hn(o)&&!r)return;const s=4&o.shapeFlag?xr(o.component)||o.component.proxy:o.el,i=r?null:s,{i:l,r:c}=e,a=t&&t.r,u=l.refs===g?l.refs={}:l.refs,p=l.setupState;if(null!=a&&a!==c&&(A(a)?(u[a]=null,T(p,a)&&(p[a]=null)):wt(a)&&(a.value=null)),A(c)){const e=()=>{u[c]=i,T(p,c)&&(p[c]=i)};i?(e.id=-1,mo(e,n)):e()}else if(wt(c)){const e=()=>{c.value=i};i?(e.id=-1,mo(e,n)):e()}else O(c)&&$r(c,l,12,[i,u])}function _o(e,t,n,o=null){Rr(e,t,7,[n,o])}function So(e,t,n=!1){const o=e.children,r=t.children;if(N(o)&&N(r))for(let s=0;se&&(e.disabled||""===e.disabled),Co=e=>"undefined"!=typeof SVGElement&&e instanceof SVGElement,wo=(e,t)=>{const n=e&&e.to;if(A(n)){if(t){return t(n)}return null}return n};function ko(e,t,n,{o:{insert:o},m:r},s=2){0===s&&o(e.targetAnchor,t,n);const{el:i,anchor:l,shapeFlag:c,children:a,props:u}=e,p=2===s;if(p&&o(i,t,n),(!p||xo(u))&&16&c)for(let f=0;f{16&v&&u(y,e,t,r,s,i,l,c)};g?b(n,a):p&&b(p,f)}else{t.el=e.el;const o=t.anchor=e.anchor,u=t.target=e.target,d=t.targetAnchor=e.targetAnchor,m=xo(e.props),v=m?n:u,y=m?o:d;if(i=i||Co(u),b?(f(e.dynamicChildren,b,v,r,s,i,l),So(e,t,!0)):c||p(e,t,v,y,r,s,i,l,!1),g)m||ko(t,n,o,a,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const e=t.target=wo(t.props,h);e&&ko(t,e,null,a,0)}else m&&ko(t,u,d,a,1)}},remove(e,t,n,o,{um:r,o:{remove:s}},i){const{shapeFlag:l,children:c,anchor:a,targetAnchor:u,target:p,props:f}=e;if(p&&s(u),(i||!xo(f))&&(s(a),16&l))for(let d=0;d0?Vo||v:null,Bo(),Lo>0&&Vo&&Vo.push(e),e}function Ho(e,t,n,o,r){return Uo(Jo(e,t,n,o,r,!0))}function Do(e){return!!e&&!0===e.__v_isVNode}function Wo(e,t){return e.type===t.type&&e.key===t.key}const zo="__vInternal",Ko=({key:e})=>null!=e?e:null,Go=({ref:e})=>null!=e?A(e)||wt(e)||O(e)?{i:jt,r:e}:e:null;function qo(e,t=null,n=null,o=0,r=null,s=(e===Oo?0:1),i=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ko(t),ref:t&&Go(t),scopeId:Ut,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:o,dynamicProps:r,dynamicChildren:null,appContext:null};return l?(tr(c,n),128&s&&e.normalize(c)):n&&(c.shapeFlag|=A(n)?8:16),Lo>0&&!i&&Vo&&(c.patchFlag>0||6&s)&&32!==c.patchFlag&&Vo.push(c),c}const Jo=function(e,t=null,n=null,o=0,r=null,i=!1){e&&e!==Eo||(e=Fo);if(Do(e)){const o=Yo(e,t,!0);return n&&tr(o,n),o}l=e,O(l)&&"__vccOpts"in l&&(e=e.__vccOpts);var l;if(t){t=Zo(t);let{class:e,style:n}=t;e&&!A(e)&&(t.class=a(e)),M(n)&&(vt(n)&&!N(n)&&(n=C({},n)),t.style=s(n))}const c=A(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:M(e)?4:O(e)?2:0;return qo(e,t,n,o,r,c,i,!0)};function Zo(e){return e?vt(e)||zo in e?C({},e):e:null}function Yo(e,t,n=!1){const{props:o,ref:r,patchFlag:s,children:i}=e,l=t?nr(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Ko(l),ref:t&&t.ref?n&&r?N(r)?r.concat(Go(t)):[r,Go(t)]:Go(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Oo?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Yo(e.ssContent),ssFallback:e.ssFallback&&Yo(e.ssFallback),el:e.el,anchor:e.anchor}}function Qo(e=" ",t=0){return Jo(Ao,null,e,t)}function Xo(e){return null==e||"boolean"==typeof e?Jo(Fo):N(e)?Jo(Oo,null,e.slice()):"object"==typeof e?er(e):Jo(Ao,null,String(e))}function er(e){return null===e.el||e.memo?e:Yo(e)}function tr(e,t){let n=0;const{shapeFlag:o}=e;if(null==t)t=null;else if(N(t))n=16;else if("object"==typeof t){if(65&o){const n=t.default;return void(n&&(n._c&&(n._d=!1),tr(e,n()),n._c&&(n._d=!0)))}{n=32;const o=t._;o||zo in t?3===o&&jt&&(1===jt.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=jt}}else O(t)?(t={default:t,_ctx:jt},n=32):(t=String(t),64&o?(n=16,t=[Qo(t)]):n=8);e.children=t,e.shapeFlag|=n}function nr(...e){const t={};for(let n=0;n!Do(e)||e.type!==Fo&&!(e.type===Oo&&!or(e.children))))?e:null}const rr=e=>e?hr(e)?xr(e)||e.proxy:rr(e.parent):null,sr=C(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>rr(e.parent),$root:e=>rr(e.root),$emit:e=>e.emit,$options:e=>Un(e),$forceUpdate:e=>()=>Kr(e.update),$nextTick:e=>zr.bind(e.proxy),$watch:e=>rs.bind(e)}),ir={get({_:e},t){const{ctx:n,setupState:o,data:r,props:s,accessCache:i,type:l,appContext:c}=e;let a;if("$"!==t[0]){const l=i[t];if(void 0!==l)switch(l){case 0:return o[t];case 1:return r[t];case 3:return n[t];case 2:return s[t]}else{if(o!==g&&T(o,t))return i[t]=0,o[t];if(r!==g&&T(r,t))return i[t]=1,r[t];if((a=e.propsOptions[0])&&T(a,t))return i[t]=2,s[t];if(n!==g&&T(n,t))return i[t]=3,n[t];In&&(i[t]=4)}}const u=sr[t];let p,f;return u?("$attrs"===t&&be(e,0,t),u(e)):(p=l.__cssModules)&&(p=p[t])?p:n!==g&&T(n,t)?(i[t]=3,n[t]):(f=c.config.globalProperties,T(f,t)?f[t]:void 0)},set({_:e},t,n){const{data:o,setupState:r,ctx:s}=e;if(r!==g&&T(r,t))r[t]=n;else if(o!==g&&T(o,t))o[t]=n;else if(T(e.props,t))return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:r,propsOptions:s}},i){let l;return void 0!==n[i]||e!==g&&T(e,i)||t!==g&&T(t,i)||(l=s[0])&&T(l,i)||T(o,i)||T(sr,i)||T(r.config.globalProperties,i)}},lr=C({},ir,{get(e,t){if(t!==Symbol.unscopables)return ir.get(e,t,e)},has:(e,t)=>"_"!==t[0]&&!n(t)}),cr=lo();let ar=0;let ur=null;const pr=()=>ur||jt,fr=e=>{ur=e,e.scope.on()},dr=()=>{ur&&ur.scope.off(),ur=null};function hr(e){return 4&e.vnode.shapeFlag}let mr,gr,vr=!1;function yr(e,t,n){O(t)?e.render=t:M(t)&&(e.setupState=Rt(t)),_r(e,n)}function br(e){mr=e,gr=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,lr))}}function _r(e,t,n){const o=e.type;if(!e.render){if(!t&&mr&&!o.render){const t=o.template;if(t){const{isCustomElement:n,compilerOptions:r}=e.appContext.config,{delimiters:s,compilerOptions:i}=o,l=C(C({isCustomElement:n,delimiters:s},r),i);o.render=mr(t,l)}}e.render=o.render||y,gr&&gr(e)}fr(e),ve(),Bn(e),ye(),dr()}function Sr(e){const t=t=>{e.exposed=t||{}};let n;return{get attrs(){return n||(n=function(e){return new Proxy(e.attrs,{get:(t,n)=>(be(e,0,"$attrs"),t[n])})}(e))},slots:e.slots,emit:e.emit,expose:t}}function xr(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Rt(bt(e.exposed)),{get:(t,n)=>n in t?t[n]:n in sr?sr[n](e):void 0}))}const Cr=/(?:^|[-_])(\w)/g;function wr(e){return O(e)&&e.displayName||e.name}function kr(e,t,n=!1){let o=wr(t);if(!o&&t.__file){const e=t.__file.match(/([^/\\]+)\.\w+$/);e&&(o=e[1])}if(!o&&e&&e.parent){const n=e=>{for(const n in e)if(e[n]===t)return n};o=n(e.components||e.parent.type.components)||n(e.appContext.components)}return o?o.replace(Cr,(e=>e.toUpperCase())).replace(/[-_]/g,""):n?"App":"Anonymous"}const Tr=[];function Nr(e){const t=[],n=Object.keys(e);return n.slice(0,3).forEach((n=>{t.push(...Er(n,e[n]))})),n.length>3&&t.push(" ..."),t}function Er(e,t,n){return A(t)?(t=JSON.stringify(t),n?t:[`${e}=${t}`]):"number"==typeof t||"boolean"==typeof t||null==t?n?t:[`${e}=${t}`]:wt(t)?(t=Er(e,yt(t.value),!0),n?t:[`${e}=Ref<`,t,">"]):O(t)?[`${e}=fn${t.name?`<${t.name}>`:""}`]:(t=yt(t),n?t:[`${e}=`,t])}function $r(e,t,n,o){let r;try{r=o?e(...o):e()}catch(s){Or(s,t,n)}return r}function Rr(e,t,n,o){if(O(e)){const r=$r(e,t,n,o);return r&&P(r)&&r.catch((e=>{Or(e,t,n)})),r}const r=[];for(let s=0;s>>1;Qr(Mr[o])Qr(e)-Qr(t))),Ur=0;Urnull==e.id?1/0:e.id;function Xr(e){Fr=!1,Ar=!0,Zr(e),Mr.sort(((e,t)=>Qr(e)-Qr(t)));try{for(Pr=0;Pre.value,c=!!e._shallow):mt(e)?(i=()=>e,o=!0):N(e)?(a=!0,c=e.some(mt),i=()=>e.map((e=>wt(e)?e.value:mt(e)?is(e):O(e)?$r(e,s,2):void 0))):i=O(e)?t?()=>$r(e,s,2):()=>{if(!s||!s.isUnmounted)return l&&l(),Rr(e,s,3,[u])}:y,t&&o){const e=i;i=()=>is(e())}let u=e=>{l=h.onStop=()=>{$r(e,s,4)}},p=a?[]:ts;const f=()=>{if(h.active)if(t){const e=h.run();(o||c||(a?e.some(((e,t)=>q(e,p[t]))):q(e,p)))&&(l&&l(),Rr(t,s,3,[e,p===ts?void 0:p,u]),p=e)}else h.run()};let d;f.allowRecurse=!!t,d="sync"===r?f:"post"===r?()=>mo(f,s&&s.suspense):()=>{!s||s.isMounted?function(e){qr(e,Ir,Vr,Br)}(f):f()};const h=new de(i,d);return t?n?f():p=h.run():"post"===r?mo(h.run.bind(h),s&&s.suspense):h.run(),()=>{h.stop(),s&&s.scope&&w(s.scope.effects,h)}}function rs(e,t,n){const o=this.proxy,r=A(e)?e.includes(".")?ss(o,e):()=>o[e]:e.bind(o,o);let s;O(t)?s=t:(s=t.handler,n=t);const i=ur;fr(this);const l=os(r,s.bind(o),n);return i?fr(i):dr(),l}function ss(e,t){const n=t.split(".");return()=>{let t=e;for(let e=0;e{is(e,t)}));else if(B(e))for(const n in e)is(e[n],t);return e}function ls(){const e=pr();return e.setupContext||(e.setupContext=Sr(e))}function cs(e,t,n){const o=arguments.length;return 2===o?M(t)&&!N(t)?Do(t)?Jo(e,null,[t]):Jo(e,t):Jo(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):3===o&&Do(n)&&(n=[n]),Jo(e,t,n))}const as=Symbol("");function us(e,t){const n=e.memo;if(n.length!=t.length)return!1;for(let o=0;o0&&Vo&&Vo.push(e),!0}const ps="3.2.18",fs="undefined"!=typeof document?document:null,ds=new Map,hs={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?fs.createElementNS("http://www.w3.org/2000/svg",e):fs.createElement(e,n?{is:n}:void 0);return"select"===e&&o&&null!=o.multiple&&r.setAttribute("multiple",o.multiple),r},createText:e=>fs.createTextNode(e),createComment:e=>fs.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>fs.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,o){const r=n?n.previousSibling:t.lastChild;let s=ds.get(e);if(!s){const t=fs.createElement("template");if(t.innerHTML=o?`${e}`:e,s=t.content,o){const e=s.firstChild;for(;e.firstChild;)s.appendChild(e.firstChild);s.removeChild(e)}ds.set(e,s)}return t.insertBefore(s.cloneNode(!0),n),[r?r.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};const ms=/\s*!important$/;function gs(e,t,n){if(N(n))n.forEach((n=>gs(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const o=function(e,t){const n=ys[t];if(n)return n;let o=D(t);if("filter"!==o&&o in e)return ys[t]=o;o=K(o);for(let r=0;rdocument.createEvent("Event").timeStamp&&(_s=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);Ss=!!(e&&Number(e[1])<=53)}let xs=0;const Cs=Promise.resolve(),ws=()=>{xs=0};function ks(e,t,n,o){e.addEventListener(t,n,o)}function Ts(e,t,n,o,r=null){const s=e._vei||(e._vei={}),i=s[t];if(o&&i)i.value=o;else{const[n,l]=function(e){let t;if(Ns.test(e)){let n;for(t={};n=e.match(Ns);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[z(e.slice(2)),t]}(t);if(o){ks(e,n,s[t]=function(e,t){const n=e=>{const o=e.timeStamp||_s();(Ss||o>=n.attached-1)&&Rr(function(e,t){if(N(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>xs||(Cs.then(ws),xs=_s()))(),n}(o,r),l)}else i&&(!function(e,t,n,o){e.removeEventListener(t,n,o)}(e,n,i,l),s[t]=void 0)}}const Ns=/(?:Once|Passive|Capture)$/;const Es=/^on[a-z]/;function $s(e,t){const n=dn(e);class o extends Os{constructor(e){super(n,e,t)}}return o.def=n,o}const Rs="undefined"!=typeof HTMLElement?HTMLElement:class{};class Os extends Rs{constructor(e,t={},n){super(),this._def=e,this._props=t,this._instance=null,this._connected=!1,this._resolved=!1,this._numberProps=null,this.shadowRoot&&n?n(this._createVNode(),this.shadowRoot):this.attachShadow({mode:"open"});for(let o=0;o{for(const t of e)this._setAttr(t.attributeName)})).observe(this,{attributes:!0})}connectedCallback(){this._connected=!0,this._instance||(this._resolveDef(),this._update())}disconnectedCallback(){this._connected=!1,zr((()=>{this._connected||(Ni(null,this.shadowRoot),this._instance=null)}))}_resolveDef(){if(this._resolved)return;const e=e=>{this._resolved=!0;const{props:t,styles:n}=e,o=!N(t),r=t?o?Object.keys(t):t:[];let s;if(o)for(const i in this._props){const e=t[i];(e===Number||e&&e.type===Number)&&(this._props[i]=Y(this._props[i]),(s||(s=Object.create(null)))[i]=!0)}s&&(this._numberProps=s,this._update());for(const i of Object.keys(this))"_"!==i[0]&&this._setProp(i,this[i]);for(const i of r.map(D))Object.defineProperty(this,i,{get(){return this._getProp(i)},set(e){this._setProp(i,e)}});this._applyStyles(n)},t=this._def.__asyncLoader;t?t().then(e):e(this._def)}_setAttr(e){let t=this.getAttribute(e);this._numberProps&&this._numberProps[e]&&(t=Y(t)),this._setProp(D(e),t,!1)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0){t!==this._props[e]&&(this._props[e]=t,this._instance&&this._update(),n&&(!0===t?this.setAttribute(z(e),""):"string"==typeof t||"number"==typeof t?this.setAttribute(z(e),t+""):t||this.removeAttribute(z(e))))}_update(){Ni(this._createVNode(),this.shadowRoot)}_createVNode(){const e=Jo(this._def,C({},this._props));return this._instance||(e.ce=e=>{this._instance=e,e.isCE=!0,e.emit=(e,...t)=>{this.dispatchEvent(new CustomEvent(e,{detail:t}))};let t=this;for(;t=t&&(t.parentNode||t.host);)if(t instanceof Os){e.parent=t._instance;break}}),e}_applyStyles(e){e&&e.forEach((e=>{const t=document.createElement("style");t.textContent=e,this.shadowRoot.appendChild(t)}))}}function As(e,t){if(128&e.shapeFlag){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push((()=>{As(n.activeBranch,t)}))}for(;e.component;)e=e.component.subTree;if(1&e.shapeFlag&&e.el)Fs(e.el,t);else if(e.type===Oo)e.children.forEach((e=>As(e,t)));else if(e.type===Mo){let{el:n,anchor:o}=e;for(;n&&(Fs(n,t),n!==o);)n=n.nextSibling}}function Fs(e,t){if(1===e.nodeType){const n=e.style;for(const e in t)n.setProperty(`--${e}`,t[e])}}const Ms="transition",Ps="animation",Vs=(e,{slots:t})=>cs(sn,Us(e),t);Vs.displayName="Transition";const Is={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Bs=Vs.props=C({},sn.props,Is),Ls=(e,t=[])=>{N(e)?e.forEach((e=>e(...t))):e&&e(...t)},js=e=>!!e&&(N(e)?e.some((e=>e.length>1)):e.length>1);function Us(e){const t={};for(const C in e)C in Is||(t[C]=e[C]);if(!1===e.css)return t;const{name:n="v",type:o,duration:r,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=s,appearActiveClass:a=i,appearToClass:u=l,leaveFromClass:p=`${n}-leave-from`,leaveActiveClass:f=`${n}-leave-active`,leaveToClass:d=`${n}-leave-to`}=e,h=function(e){if(null==e)return null;if(M(e))return[Hs(e.enter),Hs(e.leave)];{const t=Hs(e);return[t,t]}}(r),m=h&&h[0],g=h&&h[1],{onBeforeEnter:v,onEnter:y,onEnterCancelled:b,onLeave:_,onLeaveCancelled:S,onBeforeAppear:x=v,onAppear:w=y,onAppearCancelled:k=b}=t,T=(e,t,n)=>{Ws(e,t?u:l),Ws(e,t?a:i),n&&n()},N=(e,t)=>{Ws(e,d),Ws(e,f),t&&t()},E=e=>(t,n)=>{const r=e?w:y,i=()=>T(t,e,n);Ls(r,[t,i]),zs((()=>{Ws(t,e?c:s),Ds(t,e?u:l),js(r)||Gs(t,o,m,i)}))};return C(t,{onBeforeEnter(e){Ls(v,[e]),Ds(e,s),Ds(e,i)},onBeforeAppear(e){Ls(x,[e]),Ds(e,c),Ds(e,a)},onEnter:E(!1),onAppear:E(!0),onLeave(e,t){const n=()=>N(e,t);Ds(e,p),Ys(),Ds(e,f),zs((()=>{Ws(e,p),Ds(e,d),js(_)||Gs(e,o,g,n)})),Ls(_,[e,n])},onEnterCancelled(e){T(e,!1),Ls(b,[e])},onAppearCancelled(e){T(e,!0),Ls(k,[e])},onLeaveCancelled(e){N(e),Ls(S,[e])}})}function Hs(e){return Y(e)}function Ds(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.add(t))),(e._vtc||(e._vtc=new Set)).add(t)}function Ws(e,t){t.split(/\s+/).forEach((t=>t&&e.classList.remove(t)));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function zs(e){requestAnimationFrame((()=>{requestAnimationFrame(e)}))}let Ks=0;function Gs(e,t,n,o){const r=e._endId=++Ks,s=()=>{r===e._endId&&o()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:c}=qs(e,t);if(!i)return o();const a=i+"end";let u=0;const p=()=>{e.removeEventListener(a,f),s()},f=t=>{t.target===e&&++u>=c&&p()};setTimeout((()=>{u(n[e]||"").split(", "),r=o("transitionDelay"),s=o("transitionDuration"),i=Js(r,s),l=o("animationDelay"),c=o("animationDuration"),a=Js(l,c);let u=null,p=0,f=0;t===Ms?i>0&&(u=Ms,p=i,f=s.length):t===Ps?a>0&&(u=Ps,p=a,f=c.length):(p=Math.max(i,a),u=p>0?i>a?Ms:Ps:null,f=u?u===Ms?s.length:c.length:0);return{type:u,timeout:p,propCount:f,hasTransform:u===Ms&&/\b(transform|all)(,|$)/.test(n.transitionProperty)}}function Js(e,t){for(;e.lengthZs(t)+Zs(e[n]))))}function Zs(e){return 1e3*Number(e.slice(0,-1).replace(",","."))}function Ys(){return document.body.offsetHeight}const Qs=new WeakMap,Xs=new WeakMap,ei={name:"TransitionGroup",props:C({},Bs,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=pr(),o=on();let r,s;return Rn((()=>{if(!r.length)return;const t=e.moveClass||`${e.name||"v"}-move`;if(!function(e,t,n){const o=e.cloneNode();e._vtc&&e._vtc.forEach((e=>{e.split(/\s+/).forEach((e=>e&&o.classList.remove(e)))}));n.split(/\s+/).forEach((e=>e&&o.classList.add(e))),o.style.display="none";const r=1===t.nodeType?t:t.parentNode;r.appendChild(o);const{hasTransform:s}=qs(o);return r.removeChild(o),s}(r[0].el,n.vnode.el,t))return;r.forEach(ti),r.forEach(ni);const o=r.filter(oi);Ys(),o.forEach((e=>{const n=e.el,o=n.style;Ds(n,t),o.transform=o.webkitTransform=o.transitionDuration="";const r=n._moveCb=e=>{e&&e.target!==n||e&&!/transform$/.test(e.propertyName)||(n.removeEventListener("transitionend",r),n._moveCb=null,Ws(n,t))};n.addEventListener("transitionend",r)}))})),()=>{const i=yt(e),l=Us(i);let c=i.tag||Oo;r=s,s=t.default?fn(t.default()):[];for(let e=0;e{const t=e.props["onUpdate:modelValue"];return N(t)?e=>J(t,e):t};function si(e){e.target.composing=!0}function ii(e){const t=e.target;t.composing&&(t.composing=!1,function(e,t){const n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}(t,"input"))}const li={created(e,{modifiers:{lazy:t,trim:n,number:o}},r){e._assign=ri(r);const s=o||r.props&&"number"===r.props.type;ks(e,t?"change":"input",(t=>{if(t.target.composing)return;let o=e.value;n?o=o.trim():s&&(o=Y(o)),e._assign(o)})),n&&ks(e,"change",(()=>{e.value=e.value.trim()})),t||(ks(e,"compositionstart",si),ks(e,"compositionend",ii),ks(e,"change",ii))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,modifiers:{lazy:n,trim:o,number:r}},s){if(e._assign=ri(s),e.composing)return;if(document.activeElement===e){if(n)return;if(o&&e.value.trim()===t)return;if((r||"number"===e.type)&&Y(e.value)===t)return}const i=null==t?"":t;e.value!==i&&(e.value=i)}},ci={deep:!0,created(e,t,n){e._assign=ri(n),ks(e,"change",(()=>{const t=e._modelValue,n=di(e),o=e.checked,r=e._assign;if(N(t)){const e=h(t,n),s=-1!==e;if(o&&!s)r(t.concat(n));else if(!o&&s){const n=[...t];n.splice(e,1),r(n)}}else if($(t)){const e=new Set(t);o?e.add(n):e.delete(n),r(e)}else r(hi(e,o))}))},mounted:ai,beforeUpdate(e,t,n){e._assign=ri(n),ai(e,t,n)}};function ai(e,{value:t,oldValue:n},o){e._modelValue=t,N(t)?e.checked=h(t,o.props.value)>-1:$(t)?e.checked=t.has(o.props.value):t!==n&&(e.checked=d(t,hi(e,!0)))}const ui={created(e,{value:t},n){e.checked=d(t,n.props.value),e._assign=ri(n),ks(e,"change",(()=>{e._assign(di(e))}))},beforeUpdate(e,{value:t,oldValue:n},o){e._assign=ri(o),t!==n&&(e.checked=d(t,o.props.value))}},pi={deep:!0,created(e,{value:t,modifiers:{number:n}},o){const r=$(t);ks(e,"change",(()=>{const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?Y(di(e)):di(e)));e._assign(e.multiple?r?new Set(t):t:t[0])})),e._assign=ri(o)},mounted(e,{value:t}){fi(e,t)},beforeUpdate(e,t,n){e._assign=ri(n)},updated(e,{value:t}){fi(e,t)}};function fi(e,t){const n=e.multiple;if(!n||N(t)||$(t)){for(let o=0,r=e.options.length;o-1:t.has(s);else if(d(di(r),t))return void(e.selectedIndex!==o&&(e.selectedIndex=o))}n||-1===e.selectedIndex||(e.selectedIndex=-1)}}function di(e){return"_value"in e?e._value:e.value}function hi(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const mi={created(e,t,n){gi(e,t,n,null,"created")},mounted(e,t,n){gi(e,t,n,null,"mounted")},beforeUpdate(e,t,n,o){gi(e,t,n,o,"beforeUpdate")},updated(e,t,n,o){gi(e,t,n,o,"updated")}};function gi(e,t,n,o,r){let s;switch(e.tagName){case"SELECT":s=pi;break;case"TEXTAREA":s=li;break;default:switch(n.props&&n.props.type){case"checkbox":s=ci;break;case"radio":s=ui;break;default:s=li}}const i=s[r];i&&i(e,t,n,o)}const vi=["ctrl","shift","alt","meta"],yi={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>vi.some((n=>e[`${n}Key`]&&!t.includes(n)))},bi={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},_i={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):Si(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),Si(e,!0),o.enter(e)):o.leave(e,(()=>{Si(e,!1)})):Si(e,t))},beforeUnmount(e,{value:t}){Si(e,t)}};function Si(e,t){e.style.display=t?e._vod:"none"}const xi=C({patchProp:(e,t,n,s,i=!1,l,c,a,u)=>{"class"===t?function(e,t,n){const o=e._vtc;o&&(t=(t?[t,...o]:[...o]).join(" ")),null==t?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}(e,s,i):"style"===t?function(e,t,n){const o=e.style,r=o.display;if(n)if(A(n))t!==n&&(o.cssText=n);else{for(const e in n)gs(o,e,n[e]);if(t&&!A(t))for(const e in t)null==n[e]&&gs(o,e,"")}else e.removeAttribute("style");"_vod"in e&&(o.display=r)}(e,n,s):S(t)?x(t)||Ts(e,t,0,s,c):("."===t[0]?(t=t.slice(1),1):"^"===t[0]?(t=t.slice(1),0):function(e,t,n,o){if(o)return"innerHTML"===t||"textContent"===t||!!(t in e&&Es.test(t)&&O(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if(Es.test(t)&&A(n))return!1;return t in e}(e,t,s,i))?function(e,t,n,o,s,i,l){if("innerHTML"===t||"textContent"===t)return o&&l(o,s,i),void(e[t]=null==n?"":n);if("value"===t&&"PROGRESS"!==e.tagName){e._value=n;const o=null==n?"":n;return e.value!==o&&(e.value=o),void(null==n&&e.removeAttribute(t))}if(""===n||null==n){const o=typeof e[t];if("boolean"===o)return void(e[t]=r(n));if(null==n&&"string"===o)return e[t]="",void e.removeAttribute(t);if("number"===o){try{e[t]=0}catch(c){}return void e.removeAttribute(t)}}try{e[t]=n}catch(a){}}(e,t,s,l,c,a,u):("true-value"===t?e._trueValue=s:"false-value"===t&&(e._falseValue=s),function(e,t,n,s,i){if(s&&t.startsWith("xlink:"))null==n?e.removeAttributeNS(bs,t.slice(6,t.length)):e.setAttributeNS(bs,t,n);else{const s=o(t);null==n||s&&!r(n)?e.removeAttribute(t):e.setAttribute(t,s?"":n)}}(e,t,s,i))}},hs);let Ci,wi=!1;function ki(){return Ci||(Ci=go(xi))}function Ti(){return Ci=wi?Ci:vo(xi),wi=!0,Ci}const Ni=(...e)=>{ki().render(...e)},Ei=(...e)=>{Ti().hydrate(...e)};function $i(e){if(A(e)){return document.querySelector(e)}return e}const Ri=y;function Oi(e){throw e}function Ai(e){}function Fi(e,t,n,o){const r=new SyntaxError(String(e));return r.code=e,r.loc=t,r}const Mi=Symbol(""),Pi=Symbol(""),Vi=Symbol(""),Ii=Symbol(""),Bi=Symbol(""),Li=Symbol(""),ji=Symbol(""),Ui=Symbol(""),Hi=Symbol(""),Di=Symbol(""),Wi=Symbol(""),zi=Symbol(""),Ki=Symbol(""),Gi=Symbol(""),qi=Symbol(""),Ji=Symbol(""),Zi=Symbol(""),Yi=Symbol(""),Qi=Symbol(""),Xi=Symbol(""),el=Symbol(""),tl=Symbol(""),nl=Symbol(""),ol=Symbol(""),rl=Symbol(""),sl=Symbol(""),il=Symbol(""),ll=Symbol(""),cl=Symbol(""),al=Symbol(""),ul=Symbol(""),pl=Symbol(""),fl=Symbol(""),dl=Symbol(""),hl=Symbol(""),ml=Symbol(""),gl=Symbol(""),vl=Symbol(""),yl=Symbol(""),bl={[Mi]:"Fragment",[Pi]:"Teleport",[Vi]:"Suspense",[Ii]:"KeepAlive",[Bi]:"BaseTransition",[Li]:"openBlock",[ji]:"createBlock",[Ui]:"createElementBlock",[Hi]:"createVNode",[Di]:"createElementVNode",[Wi]:"createCommentVNode",[zi]:"createTextVNode",[Ki]:"createStaticVNode",[Gi]:"resolveComponent",[qi]:"resolveDynamicComponent",[Ji]:"resolveDirective",[Zi]:"resolveFilter",[Yi]:"withDirectives",[Qi]:"renderList",[Xi]:"renderSlot",[el]:"createSlots",[tl]:"toDisplayString",[nl]:"mergeProps",[ol]:"normalizeClass",[rl]:"normalizeStyle",[sl]:"normalizeProps",[il]:"guardReactiveProps",[ll]:"toHandlers",[cl]:"camelize",[al]:"capitalize",[ul]:"toHandlerKey",[pl]:"setBlockTracking",[fl]:"pushScopeId",[dl]:"popScopeId",[hl]:"withCtx",[ml]:"unref",[gl]:"isRef",[vl]:"withMemo",[yl]:"isMemoSame"};const _l={source:"",start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0}};function Sl(e,t,n,o,r,s,i,l=!1,c=!1,a=!1,u=_l){return e&&(l?(e.helper(Li),e.helper(Zl(e.inSSR,a))):e.helper(Jl(e.inSSR,a)),i&&e.helper(Yi)),{type:13,tag:t,props:n,children:o,patchFlag:r,dynamicProps:s,directives:i,isBlock:l,disableTracking:c,isComponent:a,loc:u}}function xl(e,t=_l){return{type:17,loc:t,elements:e}}function Cl(e,t=_l){return{type:15,loc:t,properties:e}}function wl(e,t){return{type:16,loc:_l,key:A(e)?kl(e,!0):e,value:t}}function kl(e,t=!1,n=_l,o=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:o}}function Tl(e,t=_l){return{type:8,loc:t,children:e}}function Nl(e,t=[],n=_l){return{type:14,loc:n,callee:e,arguments:t}}function El(e,t,n=!1,o=!1,r=_l){return{type:18,params:e,returns:t,newline:n,isSlot:o,loc:r}}function $l(e,t,n,o=!0){return{type:19,test:e,consequent:t,alternate:n,newline:o,loc:_l}}const Rl=e=>4===e.type&&e.isStatic,Ol=(e,t)=>e===t||e===z(t);function Al(e){return Ol(e,"Teleport")?Pi:Ol(e,"Suspense")?Vi:Ol(e,"KeepAlive")?Ii:Ol(e,"BaseTransition")?Bi:void 0}const Fl=/^\d|[^\$\w]/,Ml=e=>!Fl.test(e),Pl=/[A-Za-z_$\xA0-\uFFFF]/,Vl=/[\.\?\w$\xA0-\uFFFF]/,Il=/\s+[.[]\s*|\s*[.[]\s+/g,Bl=e=>{e=e.trim().replace(Il,(e=>e.trim()));let t=0,n=[],o=0,r=0,s=null;for(let i=0;i4===e.key.type&&e.key.content===n))}e||s.properties.unshift(t),o=s}else o=Nl(n.helper(nl),[Cl([t]),s]),r&&r.callee===il&&(r=i[i.length-2]);13===e.type?r?r.arguments[0]=o:e.props=o:r?r.arguments[0]=o:e.arguments[2]=o}function ec(e,t){return`_${t}_${e.replace(/[^\w]/g,((t,n)=>"-"===t?"_":e.charCodeAt(n).toString()))}`}function tc(e,{helper:t,removeHelper:n,inSSR:o}){e.isBlock||(e.isBlock=!0,n(Jl(o,e.isComponent)),t(Li),t(Zl(o,e.isComponent)))}const nc=/&(gt|lt|amp|apos|quot);/g,oc={gt:">",lt:"<",amp:"&",apos:"'",quot:'"'},rc={delimiters:["{{","}}"],getNamespace:()=>0,getTextMode:()=>0,isVoidTag:b,isPreTag:b,isCustomElement:b,decodeEntities:e=>e.replace(nc,((e,t)=>oc[t])),onError:Oi,onWarn:Ai,comments:!1};function sc(e,t={}){const n=function(e,t){const n=C({},rc);let o;for(o in t)n[o]=void 0===t[o]?rc[o]:t[o];return{options:n,column:1,line:1,offset:0,originalSource:e,source:e,inPre:!1,inVPre:!1,onWarn:n.onWarn}}(e,t),o=bc(n);return function(e,t=_l){return{type:0,children:e,helpers:[],components:[],directives:[],hoists:[],imports:[],cached:0,temps:0,codegenNode:void 0,loc:t}}(ic(n,0,[]),_c(n,o))}function ic(e,t,n){const o=Sc(n),r=o?o.ns:0,s=[];for(;!Tc(e,t,n);){const i=e.source;let l;if(0===t||1===t)if(!e.inVPre&&xc(i,e.options.delimiters[0]))l=gc(e,t);else if(0===t&&"<"===i[0])if(1===i.length);else if("!"===i[1])l=xc(i,"\x3c!--")?ac(e):xc(i,""===i[2]){Cc(e,3);continue}if(/[a-z]/i.test(i[2])){dc(e,1,o);continue}l=uc(e)}else/[a-z]/i.test(i[1])?l=pc(e,n):"?"===i[1]&&(l=uc(e));if(l||(l=vc(e,t)),N(l))for(let e=0;e/.exec(e.source);if(o){n=e.source.slice(4,o.index);const t=e.source.slice(0,o.index);let r=1,s=0;for(;-1!==(s=t.indexOf("\x3c!--",r));)Cc(e,s-r+1),r=s+1;Cc(e,o.index+o[0].length-r+1)}else n=e.source.slice(4),Cc(e,e.source.length);return{type:3,content:n,loc:_c(e,t)}}function uc(e){const t=bc(e),n="?"===e.source[1]?1:2;let o;const r=e.source.indexOf(">");return-1===r?(o=e.source.slice(n),Cc(e,e.source.length)):(o=e.source.slice(n,r),Cc(e,r+1)),{type:3,content:o,loc:_c(e,t)}}function pc(e,t){const n=e.inPre,o=e.inVPre,r=Sc(t),s=dc(e,0,r),i=e.inPre&&!n,l=e.inVPre&&!o;if(s.isSelfClosing||e.options.isVoidTag(s.tag))return i&&(e.inPre=!1),l&&(e.inVPre=!1),s;t.push(s);const c=e.options.getTextMode(s,r),a=ic(e,c,t);if(t.pop(),s.children=a,Nc(e.source,s.tag))dc(e,1,r);else if(0===e.source.length&&"script"===s.tag.toLowerCase()){const e=a[0];e&&xc(e.loc.source,"\x3c!--")}return s.loc=_c(e,s.loc.start),i&&(e.inPre=!1),l&&(e.inVPre=!1),s}const fc=t("if,else,else-if,for,slot");function dc(e,t,n){const o=bc(e),r=/^<\/?([a-z][^\t\r\n\f />]*)/i.exec(e.source),s=r[1],i=e.options.getNamespace(s,n);Cc(e,r[0].length),wc(e);const l=bc(e),c=e.source;e.options.isPreTag(s)&&(e.inPre=!0);let a=hc(e,t);0===t&&!e.inVPre&&a.some((e=>7===e.type&&"pre"===e.name))&&(e.inVPre=!0,C(e,l),e.source=c,a=hc(e,t).filter((e=>"v-pre"!==e.name)));let u=!1;if(0===e.source.length||(u=xc(e.source,"/>"),Cc(e,u?2:1)),1===t)return;let p=0;return e.inVPre||("slot"===s?p=2:"template"===s?a.some((e=>7===e.type&&fc(e.name)))&&(p=3):function(e,t,n){const o=n.options;if(o.isCustomElement(e))return!1;if("component"===e||/^[A-Z]/.test(e)||Al(e)||o.isBuiltInComponent&&o.isBuiltInComponent(e)||o.isNativeTag&&!o.isNativeTag(e))return!0;for(let r=0;r0&&!xc(e.source,">")&&!xc(e.source,"/>");){if(xc(e.source,"/")){Cc(e,1),wc(e);continue}const r=mc(e,o);6===r.type&&r.value&&"class"===r.name&&(r.value.content=r.value.content.replace(/\s+/g," ").trim()),0===t&&n.push(r),/^[^\t\r\n\f />]/.test(e.source),wc(e)}return n}function mc(e,t){const n=bc(e),o=/^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(e.source)[0];t.has(o),t.add(o);{const e=/["'<]/g;let t;for(;t=e.exec(o););}let r;Cc(e,o.length),/^[\t\r\n\f ]*=/.test(e.source)&&(wc(e),Cc(e,1),wc(e),r=function(e){const t=bc(e);let n;const o=e.source[0],r='"'===o||"'"===o;if(r){Cc(e,1);const t=e.source.indexOf(o);-1===t?n=yc(e,e.source.length,4):(n=yc(e,t,4),Cc(e,1))}else{const t=/^[^\t\r\n\f >]+/.exec(e.source);if(!t)return;const o=/["'<=`]/g;let r;for(;r=o.exec(t[0]););n=yc(e,t[0].length,4)}return{content:n,isQuoted:r,loc:_c(e,t)}}(e));const s=_c(e,n);if(!e.inVPre&&/^(v-[A-Za-z0-9-]|:|\.|@|#)/.test(o)){const t=/(?:^v-([a-z0-9-]+))?(?:(?::|^\.|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(o);let i,l=xc(o,"."),c=t[1]||(l||xc(o,":")?"bind":xc(o,"@")?"on":"slot");if(t[2]){const r="slot"===c,s=o.lastIndexOf(t[2]),l=_c(e,kc(e,n,s),kc(e,n,s+t[2].length+(r&&t[3]||"").length));let a=t[2],u=!0;a.startsWith("[")?(u=!1,a=a.endsWith("]")?a.substr(1,a.length-2):a.substr(1)):r&&(a+=t[3]||""),i={type:4,content:a,isStatic:u,constType:u?3:0,loc:l}}if(r&&r.isQuoted){const e=r.loc;e.start.offset++,e.start.column++,e.end=jl(e.start,r.content),e.source=e.source.slice(1,-1)}const a=t[3]?t[3].substr(1).split("."):[];return l&&a.push("prop"),{type:7,name:c,exp:r&&{type:4,content:r.content,isStatic:!1,constType:0,loc:r.loc},arg:i,modifiers:a,loc:s}}return!e.inVPre&&xc(o,"v-"),{type:6,name:o,value:r&&{type:2,content:r.content,loc:r.loc},loc:s}}function gc(e,t){const[n,o]=e.options.delimiters,r=e.source.indexOf(o,n.length);if(-1===r)return;const s=bc(e);Cc(e,n.length);const i=bc(e),l=bc(e),c=r-n.length,a=e.source.slice(0,c),u=yc(e,c,t),p=u.trim(),f=u.indexOf(p);f>0&&Ul(i,a,f);return Ul(l,a,c-(u.length-p.length-f)),Cc(e,o.length),{type:5,content:{type:4,isStatic:!1,constType:0,content:p,loc:_c(e,i,l)},loc:_c(e,s)}}function vc(e,t){const n=3===t?["]]>"]:["<",e.options.delimiters[0]];let o=e.source.length;for(let s=0;st&&(o=t)}const r=bc(e);return{type:2,content:yc(e,o,t),loc:_c(e,r)}}function yc(e,t,n){const o=e.source.slice(0,t);return Cc(e,t),2===n||3===n||-1===o.indexOf("&")?o:e.options.decodeEntities(o,4===n)}function bc(e){const{column:t,line:n,offset:o}=e;return{column:t,line:n,offset:o}}function _c(e,t,n){return{start:t,end:n=n||bc(e),source:e.originalSource.slice(t.offset,n.offset)}}function Sc(e){return e[e.length-1]}function xc(e,t){return e.startsWith(t)}function Cc(e,t){const{source:n}=e;Ul(e,n,t),e.source=n.slice(t)}function wc(e){const t=/^[\t\r\n\f ]+/.exec(e.source);t&&Cc(e,t[0].length)}function kc(e,t,n){return jl(t,e.originalSource.slice(t.offset,n),n)}function Tc(e,t,n){const o=e.source;switch(t){case 0:if(xc(o,"=0;--e)if(Nc(o,n[e].tag))return!0;break;case 1:case 2:{const e=Sc(n);if(e&&Nc(o,e.tag))return!0;break}case 3:if(xc(o,"]]>"))return!0}return!o}function Nc(e,t){return xc(e,"]/.test(e[2+t.length]||">")}function Ec(e,t){Rc(e,t,$c(e,e.children[0]))}function $c(e,t){const{children:n}=e;return 1===n.length&&1===t.type&&!ql(t)}function Rc(e,t,n=!1){let o=!0;const{children:r}=e,s=r.length;let i=0;for(let l=0;l0){if(r<3&&(o=!1),r>=2){e.codegenNode.patchFlag="-1",e.codegenNode=t.hoist(e.codegenNode),i++;continue}}else{const n=e.codegenNode;if(13===n.type){const o=Vc(n);if((!o||512===o||1===o)&&Mc(e,t)>=2){const o=Pc(e);o&&(n.props=t.hoist(o))}n.dynamicProps&&(n.dynamicProps=t.hoist(n.dynamicProps))}}}else if(12===e.type){const n=Oc(e.content,t);n>0&&(n<3&&(o=!1),n>=2&&(e.codegenNode=t.hoist(e.codegenNode),i++))}if(1===e.type){const n=1===e.tagType;n&&t.scopes.vSlot++,Rc(e,t),n&&t.scopes.vSlot--}else if(11===e.type)Rc(e,t,1===e.children.length);else if(9===e.type)for(let n=0;n1)for(let r=0;r`_${bl[k.helper(e)]}`,replaceNode(e){k.parent.children[k.childIndex]=k.currentNode=e},removeNode(e){const t=e?k.parent.children.indexOf(e):k.currentNode?k.childIndex:-1;e&&e!==k.currentNode?k.childIndex>t&&(k.childIndex--,k.onNodeRemoved()):(k.currentNode=null,k.onNodeRemoved()),k.parent.children.splice(t,1)},onNodeRemoved:()=>{},addIdentifiers(e){},removeIdentifiers(e){},hoist(e){A(e)&&(e=kl(e)),k.hoists.push(e);const t=kl(`_hoisted_${k.hoists.length}`,!1,e.loc,2);return t.hoisted=e,t},cache:(e,t=!1)=>function(e,t,n=!1){return{type:20,index:e,value:t,isVNode:n,loc:_l}}(k.cached++,e,t)};return k}function Bc(e,t){const n=Ic(e,t);Lc(e,n),t.hoistStatic&&Ec(e,n),t.ssr||function(e,t){const{helper:n}=t,{children:o}=e;if(1===o.length){const n=o[0];if($c(e,n)&&n.codegenNode){const o=n.codegenNode;13===o.type&&tc(o,t),e.codegenNode=o}else e.codegenNode=n}else if(o.length>1){let o=64;e.codegenNode=Sl(t,n(Mi),void 0,e.children,o+"",void 0,void 0,!0,void 0,!1)}}(e,n),e.helpers=[...n.helpers.keys()],e.components=[...n.components],e.directives=[...n.directives],e.imports=n.imports,e.hoists=n.hoists,e.temps=n.temps,e.cached=n.cached}function Lc(e,t){t.currentNode=e;const{nodeTransforms:n}=t,o=[];for(let s=0;s{n--};for(;nt===e:t=>e.test(t);return(e,o)=>{if(1===e.type){const{props:r}=e;if(3===e.tagType&&r.some(Kl))return;const s=[];for(let i=0;i`_${bl[e]}`,push(e,t){d.code+=e},indent(){h(++d.indentLevel)},deindent(e=!1){e?--d.indentLevel:h(--d.indentLevel)},newline(){h(d.indentLevel)}};function h(e){d.push("\n"+" ".repeat(e))}return d}(e,t);t.onContextCreated&&t.onContextCreated(n);const{mode:o,push:r,prefixIdentifiers:s,indent:i,deindent:l,newline:c,ssr:a}=n,u=e.helpers.length>0,p=!s&&"module"!==o;!function(e,t){const{push:n,newline:o,runtimeGlobalName:r}=t,s=r,i=e=>`${bl[e]}: _${bl[e]}`;if(e.helpers.length>0&&(n(`const _Vue = ${s}\n`),e.hoists.length)){n(`const { ${[Hi,Di,Wi,zi,Ki].filter((t=>e.helpers.includes(t))).map(i).join(", ")} } = _Vue\n`)}(function(e,t){if(!e.length)return;t.pure=!0;const{push:n,newline:o}=t;o();for(let r=0;r`${bl[e]}: _${bl[e]}`)).join(", ")} } = _Vue`),r("\n"),c())),e.components.length&&(Dc(e.components,"component",n),(e.directives.length||e.temps>0)&&c()),e.directives.length&&(Dc(e.directives,"directive",n),e.temps>0&&c()),e.temps>0){r("let ");for(let t=0;t0?", ":""}_temp${t}`)}return(e.components.length||e.directives.length||e.temps)&&(r("\n"),c()),a||r("return "),e.codegenNode?Kc(e.codegenNode,n):r("null"),p&&(l(),r("}")),l(),r("}"),{ast:e,code:n.code,preamble:"",map:n.map?n.map.toJSON():void 0}}function Dc(e,t,{helper:n,push:o,newline:r,isTS:s}){const i=n("component"===t?Gi:Ji);for(let l=0;l3||!1;t.push("["),n&&t.indent(),zc(e,t,n),n&&t.deindent(),t.push("]")}function zc(e,t,n=!1,o=!0){const{push:r,newline:s}=t;for(let i=0;ie||"null"))}([s,i,l,c,a]),t),n(")"),p&&n(")");u&&(n(", "),Kc(u,t),n(")"))}(e,t);break;case 14:!function(e,t){const{push:n,helper:o,pure:r}=t,s=A(e.callee)?e.callee:o(e.callee);r&&n(Uc);n(s+"(",e),zc(e.arguments,t),n(")")}(e,t);break;case 15:!function(e,t){const{push:n,indent:o,deindent:r,newline:s}=t,{properties:i}=e;if(!i.length)return void n("{}",e);const l=i.length>1||!1;n(l?"{":"{ "),l&&o();for(let c=0;c "),(c||l)&&(n("{"),o());i?(c&&n("return "),N(i)?Wc(i,t):Kc(i,t)):l&&Kc(l,t);(c||l)&&(r(),n("}"));a&&n(")")}(e,t);break;case 19:!function(e,t){const{test:n,consequent:o,alternate:r,newline:s}=e,{push:i,indent:l,deindent:c,newline:a}=t;if(4===n.type){const e=!Ml(n.content);e&&i("("),Gc(n,t),e&&i(")")}else i("("),Kc(n,t),i(")");s&&l(),t.indentLevel++,s||i(" "),i("? "),Kc(o,t),t.indentLevel--,s&&a(),s||i(" "),i(": ");const u=19===r.type;u||t.indentLevel++;Kc(r,t),u||t.indentLevel--;s&&c(!0)}(e,t);break;case 20:!function(e,t){const{push:n,helper:o,indent:r,deindent:s,newline:i}=t;n(`_cache[${e.index}] || (`),e.isVNode&&(r(),n(`${o(pl)}(-1),`),i());n(`_cache[${e.index}] = `),Kc(e.value,t),e.isVNode&&(n(","),i(),n(`${o(pl)}(1),`),i(),n(`_cache[${e.index}]`),s());n(")")}(e,t);break;case 21:zc(e.body,t,!0,!1)}}function Gc(e,t){const{content:n,isStatic:o}=e;t.push(o?JSON.stringify(n):n,e)}function qc(e,t){for(let n=0;nfunction(e,t,n,o){if(!("else"===t.name||t.exp&&t.exp.content.trim())){t.exp=kl("true",!1,t.exp?t.exp.loc:e.loc)}if("if"===t.name){const r=Yc(e,t),s={type:9,loc:e.loc,branches:[r]};if(n.replaceNode(s),o)return o(s,r,!0)}else{const r=n.parent.children;let s=r.indexOf(e);for(;s-- >=-1;){const i=r[s];if(!i||2!==i.type||i.content.trim().length){if(i&&9===i.type){n.removeNode();const r=Yc(e,t);i.branches.push(r);const s=o&&o(i,r,!1);Lc(r,n),s&&s(),n.currentNode=null}break}n.removeNode(i)}}}(e,t,n,((e,t,o)=>{const r=n.parent.children;let s=r.indexOf(e),i=0;for(;s-- >=0;){const e=r[s];e&&9===e.type&&(i+=e.branches.length)}return()=>{if(o)e.codegenNode=Qc(t,i,n);else{(function(e){for(;;)if(19===e.type){if(19!==e.alternate.type)return e;e=e.alternate}else 20===e.type&&(e=e.value)}(e.codegenNode)).alternate=Qc(t,i+e.branches.length-1,n)}}}))));function Yc(e,t){return{type:10,loc:e.loc,condition:"else"===t.name?void 0:t.exp,children:3!==e.tagType||Hl(e,"for")?[e]:e.children,userKey:Dl(e,"key")}}function Qc(e,t,n){return e.condition?$l(e.condition,Xc(e,t,n),Nl(n.helper(Wi),['""',"true"])):Xc(e,t,n)}function Xc(e,t,n){const{helper:o}=n,r=wl("key",kl(`${t}`,!1,_l,2)),{children:s}=e,i=s[0];if(1!==s.length||1!==i.type){if(1===s.length&&11===i.type){const e=i.codegenNode;return Xl(e,r,n),e}{let t=64;return Sl(n,o(Mi),Cl([r]),s,t+"",void 0,void 0,!0,!1,!1,e.loc)}}{const e=i.codegenNode,t=14===(l=e).type&&l.callee===vl?l.arguments[1].returns:l;return 13===t.type&&tc(t,n),Xl(t,r,n),e}var l}const ea=jc("for",((e,t,n)=>{const{helper:o,removeHelper:r}=n;return function(e,t,n,o){if(!t.exp)return;const r=ra(t.exp);if(!r)return;const{scopes:s}=n,{source:i,value:l,key:c,index:a}=r,u={type:11,loc:t.loc,source:i,valueAlias:l,keyAlias:c,objectIndexAlias:a,parseResult:r,children:Gl(e)?e.children:[e]};n.replaceNode(u),s.vFor++;const p=o&&o(u);return()=>{s.vFor--,p&&p()}}(e,t,n,(t=>{const s=Nl(o(Qi),[t.source]),i=Hl(e,"memo"),l=Dl(e,"key"),c=l&&(6===l.type?kl(l.value.content,!0):l.exp),a=l?wl("key",c):null,u=4===t.source.type&&t.source.constType>0,p=u?64:l?128:256;return t.codegenNode=Sl(n,o(Mi),void 0,s,p+"",void 0,void 0,!0,!u,!1,e.loc),()=>{let l;const p=Gl(e),{children:f}=t,d=1!==f.length||1!==f[0].type,h=ql(e)?e:p&&1===e.children.length&&ql(e.children[0])?e.children[0]:null;if(h?(l=h.codegenNode,p&&a&&Xl(l,a,n)):d?l=Sl(n,o(Mi),a?Cl([a]):void 0,e.children,"64",void 0,void 0,!0,void 0,!1):(l=f[0].codegenNode,p&&a&&Xl(l,a,n),l.isBlock!==!u&&(l.isBlock?(r(Li),r(Zl(n.inSSR,l.isComponent))):r(Jl(n.inSSR,l.isComponent))),l.isBlock=!u,l.isBlock?(o(Li),o(Zl(n.inSSR,l.isComponent))):o(Jl(n.inSSR,l.isComponent))),i){const e=El(ia(t.parseResult,[kl("_cached")]));e.body={type:21,body:[Tl(["const _memo = (",i.exp,")"]),Tl(["if (_cached",...c?[" && _cached.key === ",c]:[],` && ${n.helperString(yl)}(_cached, _memo)) return _cached`]),Tl(["const _item = ",l]),kl("_item.memo = _memo"),kl("return _item")],loc:_l},s.arguments.push(e,kl("_cache"),kl(String(n.cached++)))}else s.arguments.push(El(ia(t.parseResult),l,!0))}}))}));const ta=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,na=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,oa=/^\(|\)$/g;function ra(e,t){const n=e.loc,o=e.content,r=o.match(ta);if(!r)return;const[,s,i]=r,l={source:sa(n,i.trim(),o.indexOf(i,s.length)),value:void 0,key:void 0,index:void 0};let c=s.trim().replace(oa,"").trim();const a=s.indexOf(c),u=c.match(na);if(u){c=c.replace(na,"").trim();const e=u[1].trim();let t;if(e&&(t=o.indexOf(e,a+c.length),l.key=sa(n,e,t)),u[2]){const r=u[2].trim();r&&(l.index=sa(n,r,o.indexOf(r,l.key?t+e.length:a+c.length)))}}return c&&(l.value=sa(n,c,a)),l}function sa(e,t,n){return kl(t,!1,Ll(e,n,t.length))}function ia({value:e,key:t,index:n},o=[]){return function(e){let t=e.length;for(;t--&&!e[t];);return e.slice(0,t+1).map(((e,t)=>e||kl("_".repeat(t+1),!1)))}([e,t,n,...o])}const la=kl("undefined",!1),ca=(e,t)=>{if(1===e.type&&(1===e.tagType||3===e.tagType)){const n=Hl(e,"slot");if(n)return t.scopes.vSlot++,()=>{t.scopes.vSlot--}}},aa=(e,t,n)=>El(e,t,!1,!0,t.length?t[0].loc:n);function ua(e,t,n=aa){t.helper(hl);const{children:o,loc:r}=e,s=[],i=[];let l=t.scopes.vSlot>0||t.scopes.vFor>0;const c=Hl(e,"slot",!0);if(c){const{arg:e,exp:t}=c;e&&!Rl(e)&&(l=!0),s.push(wl(e||kl("default",!0),n(t,o,r)))}let a=!1,u=!1;const p=[],f=new Set;for(let m=0;mwl("default",n(e,t,r));a?p.length&&p.some((e=>da(e)))&&(u||s.push(e(void 0,p))):s.push(e(void 0,o))}const d=l?2:fa(e.children)?3:1;let h=Cl(s.concat(wl("_",kl(d+"",!1))),r);return i.length&&(h=Nl(t.helper(el),[h,xl(i)])),{slots:h,hasDynamicSlots:l}}function pa(e,t){return Cl([wl("name",e),wl("fn",t)])}function fa(e){for(let t=0;tfunction(){if(1!==(e=t.currentNode).type||0!==e.tagType&&1!==e.tagType)return;const{tag:n,props:o}=e,r=1===e.tagType;let s=r?function(e,t,n=!1){let{tag:o}=e;const r=ba(o),s=Dl(e,"is");if(s)if(r){const e=6===s.type?s.value&&kl(s.value.content,!0):s.exp;if(e)return Nl(t.helper(qi),[e])}else 6===s.type&&s.value.content.startsWith("vue:")&&(o=s.value.content.slice(4));const i=!r&&Hl(e,"is");if(i&&i.exp)return Nl(t.helper(qi),[i.exp]);const l=Al(o)||t.isBuiltInComponent(o);if(l)return n||t.helper(l),l;return t.helper(Gi),t.components.add(o),ec(o,"component")}(e,t):`"${n}"`;let i,l,c,a,u,p,f=0,d=M(s)&&s.callee===qi||s===Pi||s===Vi||!r&&("svg"===n||"foreignObject"===n||Dl(e,"key",!0));if(o.length>0){const n=ga(e,t);i=n.props,f=n.patchFlag,u=n.dynamicPropNames;const o=n.directives;p=o&&o.length?xl(o.map((e=>function(e,t){const n=[],o=ha.get(e);o?n.push(t.helperString(o)):(t.helper(Ji),t.directives.add(e.name),n.push(ec(e.name,"directive")));const{loc:r}=e;e.exp&&n.push(e.exp);e.arg&&(e.exp||n.push("void 0"),n.push(e.arg));if(Object.keys(e.modifiers).length){e.arg||(e.exp||n.push("void 0"),n.push("void 0"));const t=kl("true",!1,r);n.push(Cl(e.modifiers.map((e=>wl(e,t))),r))}return xl(n,e.loc)}(e,t)))):void 0}if(e.children.length>0){s===Ii&&(d=!0,f|=1024);if(r&&s!==Pi&&s!==Ii){const{slots:n,hasDynamicSlots:o}=ua(e,t);l=n,o&&(f|=1024)}else if(1===e.children.length&&s!==Pi){const n=e.children[0],o=n.type,r=5===o||8===o;r&&0===Oc(n,t)&&(f|=1),l=r||2===o?n:e.children}else l=e.children}0!==f&&(c=String(f),u&&u.length&&(a=function(e){let t="[";for(let n=0,o=e.length;n{if(Rl(e)){const o=e.content,r=S(o);if(i||!r||"onclick"===o.toLowerCase()||"onUpdate:modelValue"===o||j(o)||(h=!0),r&&j(o)&&(g=!0),20===n.type||(4===n.type||8===n.type)&&Oc(n,t)>0)return;"ref"===o?p=!0:"class"===o?f=!0:"style"===o?d=!0:"key"===o||v.includes(o)||v.push(o),!i||"class"!==o&&"style"!==o||v.includes(o)||v.push(o)}else m=!0};for(let _=0;_1?Nl(t.helper(nl),c,s):c[0]):l.length&&(b=Cl(va(l),s)),m?u|=16:(f&&!i&&(u|=2),d&&!i&&(u|=4),v.length&&(u|=8),h&&(u|=32)),0!==u&&32!==u||!(p||g||a.length>0)||(u|=512),!t.inSSR&&b)switch(b.type){case 15:let e=-1,n=-1,o=!1;for(let t=0;t{if(ql(e)){const{children:n,loc:o}=e,{slotName:r,slotProps:s}=function(e,t){let n,o='"default"';const r=[];for(let s=0;s0){const{props:o,directives:s}=ga(e,t,r);n=o}return{slotName:o,slotProps:n}}(e,t),i=[t.prefixIdentifiers?"_ctx.$slots":"$slots",r,"{}","undefined","true"];let l=2;s&&(i[2]=s,l=3),n.length&&(i[3]=El([],n,!1,!1,o),l=4),t.scopeId&&!t.slotted&&(l=5),i.splice(l),e.codegenNode=Nl(t.helper(Xi),i,o)}};const Sa=/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/,xa=(e,t,n,o)=>{const{loc:r,modifiers:s,arg:i}=e;let l;if(4===i.type)if(i.isStatic){l=kl(G(D(i.content)),!0,i.loc)}else l=Tl([`${n.helperString(ul)}(`,i,")"]);else l=i,l.children.unshift(`${n.helperString(ul)}(`),l.children.push(")");let c=e.exp;c&&!c.content.trim()&&(c=void 0);let a=n.cacheHandlers&&!c&&!n.inVOnce;if(c){const e=Bl(c.content),t=!(e||Sa.test(c.content)),n=c.content.includes(";");(t||a&&e)&&(c=Tl([`${t?"$event":"(...args)"} => ${n?"{":"("}`,c,n?"}":")"]))}let u={props:[wl(l,c||kl("() => {}",!1,r))]};return o&&(u=o(u)),a&&(u.props[0].value=n.cache(u.props[0].value)),u.props.forEach((e=>e.key.isHandlerKey=!0)),u},Ca=(e,t,n)=>{const{exp:o,modifiers:r,loc:s}=e,i=e.arg;return 4!==i.type?(i.children.unshift("("),i.children.push(') || ""')):i.isStatic||(i.content=`${i.content} || ""`),r.includes("camel")&&(4===i.type?i.content=i.isStatic?D(i.content):`${n.helperString(cl)}(${i.content})`:(i.children.unshift(`${n.helperString(cl)}(`),i.children.push(")"))),n.inSSR||(r.includes("prop")&&wa(i,"."),r.includes("attr")&&wa(i,"^")),!o||4===o.type&&!o.content.trim()?{props:[wl(i,kl("",!0,s))]}:{props:[wl(i,o)]}},wa=(e,t)=>{4===e.type?e.content=e.isStatic?t+e.content:`\`${t}\${${e.content}}\``:(e.children.unshift(`'${t}' + (`),e.children.push(")"))},ka=(e,t)=>{if(0===e.type||1===e.type||11===e.type||10===e.type)return()=>{const n=e.children;let o,r=!1;for(let e=0;e7===e.type&&!t.directiveTransforms[e.name])))))for(let e=0;e{if(1===e.type&&Hl(e,"once",!0)){if(Ta.has(e)||t.inVOnce)return;return Ta.add(e),t.inVOnce=!0,t.helper(pl),()=>{t.inVOnce=!1;const e=t.currentNode;e.codegenNode&&(e.codegenNode=t.cache(e.codegenNode,!0))}}},Ea=(e,t,n)=>{const{exp:o,arg:r}=e;if(!o)return $a();const s=o.loc.source,i=4===o.type?o.content:s;if(!i.trim()||!Bl(i))return $a();const l=r||kl("modelValue",!0),c=r?Rl(r)?`onUpdate:${r.content}`:Tl(['"onUpdate:" + ',r]):"onUpdate:modelValue";let a;a=Tl([`${n.isTS?"($event: any)":"$event"} => ((`,o,") = $event)"]);const u=[wl(l,e.exp),wl(c,a)];if(e.modifiers.length&&1===t.tagType){const t=e.modifiers.map((e=>(Ml(e)?e:JSON.stringify(e))+": true")).join(", "),n=r?Rl(r)?`${r.content}Modifiers`:Tl([r,' + "Modifiers"']):"modelModifiers";u.push(wl(n,kl(`{ ${t} }`,!1,e.loc,2)))}return $a(u)};function $a(e=[]){return{props:e}}const Ra=new WeakSet,Oa=(e,t)=>{if(1===e.type){const n=Hl(e,"memo");if(!n||Ra.has(e))return;return Ra.add(e),()=>{const o=e.codegenNode||t.currentNode.codegenNode;o&&13===o.type&&(1!==e.tagType&&tc(o,t),e.codegenNode=Nl(t.helper(vl),[n.exp,El(void 0,o),"_cache",String(t.cached++)]))}}};function Aa(e,t={}){const n=t.onError||Oi,o="module"===t.mode;!0===t.prefixIdentifiers?n(Fi(46)):o&&n(Fi(47));t.cacheHandlers&&n(Fi(48)),t.scopeId&&!o&&n(Fi(49));const r=A(e)?sc(e,t):e,[s,i]=[[Na,Zc,Oa,ea,_a,ma,ca,ka],{on:xa,bind:Ca,model:Ea}];return Bc(r,C({},t,{prefixIdentifiers:false,nodeTransforms:[...s,...t.nodeTransforms||[]],directiveTransforms:C({},i,t.directiveTransforms||{})})),Hc(r,C({},t,{prefixIdentifiers:false}))}const Fa=Symbol(""),Ma=Symbol(""),Pa=Symbol(""),Va=Symbol(""),Ia=Symbol(""),Ba=Symbol(""),La=Symbol(""),ja=Symbol(""),Ua=Symbol(""),Ha=Symbol("");var Da;let Wa;Da={[Fa]:"vModelRadio",[Ma]:"vModelCheckbox",[Pa]:"vModelText",[Va]:"vModelSelect",[Ia]:"vModelDynamic",[Ba]:"withModifiers",[La]:"withKeys",[ja]:"vShow",[Ua]:"Transition",[Ha]:"TransitionGroup"},Object.getOwnPropertySymbols(Da).forEach((e=>{bl[e]=Da[e]}));const za=t("style,iframe,script,noscript",!0),Ka={isVoidTag:f,isNativeTag:e=>u(e)||p(e),isPreTag:e=>"pre"===e,decodeEntities:function(e,t=!1){return Wa||(Wa=document.createElement("div")),t?(Wa.innerHTML=`
`,Wa.children[0].getAttribute("foo")):(Wa.innerHTML=e,Wa.textContent)},isBuiltInComponent:e=>Ol(e,"Transition")?Ua:Ol(e,"TransitionGroup")?Ha:void 0,getNamespace(e,t){let n=t?t.ns:0;if(t&&2===n)if("annotation-xml"===t.tag){if("svg"===e)return 1;t.props.some((e=>6===e.type&&"encoding"===e.name&&null!=e.value&&("text/html"===e.value.content||"application/xhtml+xml"===e.value.content)))&&(n=0)}else/^m(?:[ions]|text)$/.test(t.tag)&&"mglyph"!==e&&"malignmark"!==e&&(n=0);else t&&1===n&&("foreignObject"!==t.tag&&"desc"!==t.tag&&"title"!==t.tag||(n=0));if(0===n){if("svg"===e)return 1;if("math"===e)return 2}return n},getTextMode({tag:e,ns:t}){if(0===t){if("textarea"===e||"title"===e)return 1;if(za(e))return 2}return 0}},Ga=(e,t)=>{const n=c(e);return kl(JSON.stringify(n),!1,t,3)};const qa=t("passive,once,capture"),Ja=t("stop,prevent,self,ctrl,shift,alt,meta,exact,middle"),Za=t("left,right"),Ya=t("onkeyup,onkeydown,onkeypress",!0),Qa=(e,t)=>Rl(e)&&"onclick"===e.content.toLowerCase()?kl(t,!0):4!==e.type?Tl(["(",e,`) === "onClick" ? "${t}" : (`,e,")"]):e,Xa=(e,t)=>{1!==e.type||0!==e.tagType||"script"!==e.tag&&"style"!==e.tag||t.removeNode()},eu=[e=>{1===e.type&&e.props.forEach(((t,n)=>{6===t.type&&"style"===t.name&&t.value&&(e.props[n]={type:7,name:"bind",arg:kl("style",!0,t.loc),exp:Ga(t.value.content,t.loc),modifiers:[],loc:t.loc})}))}],tu={cloak:()=>({props:[]}),html:(e,t,n)=>{const{exp:o,loc:r}=e;return t.children.length&&(t.children.length=0),{props:[wl(kl("innerHTML",!0,r),o||kl("",!0))]}},text:(e,t,n)=>{const{exp:o,loc:r}=e;return t.children.length&&(t.children.length=0),{props:[wl(kl("textContent",!0),o?Nl(n.helperString(tl),[o],r):kl("",!0))]}},model:(e,t,n)=>{const o=Ea(e,t,n);if(!o.props.length||1===t.tagType)return o;const{tag:r}=t,s=n.isCustomElement(r);if("input"===r||"textarea"===r||"select"===r||s){let e=Pa,i=!1;if("input"===r||s){const n=Dl(t,"type");if(n){if(7===n.type)e=Ia;else if(n.value)switch(n.value.content){case"radio":e=Fa;break;case"checkbox":e=Ma;break;case"file":i=!0}}else(function(e){return e.props.some((e=>!(7!==e.type||"bind"!==e.name||e.arg&&4===e.arg.type&&e.arg.isStatic)))})(t)&&(e=Ia)}else"select"===r&&(e=Va);i||(o.needRuntime=n.helper(e))}return o.props=o.props.filter((e=>!(4===e.key.type&&"modelValue"===e.key.content))),o},on:(e,t,n)=>xa(e,0,n,(t=>{const{modifiers:o}=e;if(!o.length)return t;let{key:r,value:s}=t.props[0];const{keyModifiers:i,nonKeyModifiers:l,eventOptionModifiers:c}=((e,t,n,o)=>{const r=[],s=[],i=[];for(let l=0;l({props:[],needRuntime:n.helper(ja)})};const nu=Object.create(null);function ou(e,t){if(!A(e)){if(!e.nodeType)return y;e=e.innerHTML}const n=e,o=nu[n];if(o)return o;if("#"===e[0]){const t=document.querySelector(e);e=t?t.innerHTML:""}const{code:r}=function(e,t={}){return Aa(e,C({},Ka,t,{nodeTransforms:[Xa,...eu,...t.nodeTransforms||[]],directiveTransforms:C({},tu,t.directiveTransforms||{}),transformHoist:null}))}(e,C({hoistStatic:!0,onError:void 0,onWarn:y},t)),s=new Function(r)();return s._rc=!0,nu[n]=s}return br(ou),e.BaseTransition=sn,e.Comment=Fo,e.EffectScope=te,e.Fragment=Oo,e.KeepAlive=vn,e.ReactiveEffect=de,e.Static=Mo,e.Suspense=Jt,e.Teleport=To,e.Text=Ao,e.Transition=Vs,e.TransitionGroup=ei,e.VueElement=Os,e.callWithAsyncErrorHandling=Rr,e.callWithErrorHandling=$r,e.camelize=D,e.capitalize=K,e.cloneVNode=Yo,e.compatUtils=null,e.compile=ou,e.computed=Pt,e.createApp=(...e)=>{const t=ki().createApp(...e),{mount:n}=t;return t.mount=e=>{const o=$i(e);if(!o)return;const r=t._component;O(r)||r.render||r.template||(r.template=o.innerHTML),o.innerHTML="";const s=n(o,!1,o instanceof SVGElement);return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),s},t},e.createBlock=Ho,e.createCommentVNode=function(e="",t=!1){return t?(Io(),Ho(Fo,null,e)):Jo(Fo,null,e)},e.createElementBlock=function(e,t,n,o,r,s){return Uo(qo(e,t,n,o,r,s,!0))},e.createElementVNode=qo,e.createHydrationRenderer=vo,e.createRenderer=go,e.createSSRApp=(...e)=>{const t=Ti().createApp(...e),{mount:n}=t;return t.mount=e=>{const t=$i(e);if(t)return n(t,!0,t instanceof SVGElement)},t},e.createSlots=function(e,t){for(let n=0;n{let e;return a||(e=a=t().catch((e=>{if(e=e instanceof Error?e:new Error(String(e)),l)return new Promise(((t,n)=>{l(e,(()=>t((u++,a=null,p()))),(()=>n(e)),u+1)}));throw e})).then((t=>e!==a&&a?a:(t&&(t.__esModule||"Module"===t[Symbol.toStringTag])&&(t=t.default),c=t,t))))};return dn({name:"AsyncComponentWrapper",__asyncLoader:p,get __asyncResolved(){return c},setup(){const e=ur;if(c)return()=>mn(c,e);const t=t=>{a=null,Or(t,e,13,!o)};if(i&&e.suspense)return p().then((t=>()=>mn(t,e))).catch((e=>(t(e),()=>o?Jo(o,{error:e}):null)));const l=kt(!1),u=kt(),f=kt(!!r);return r&&setTimeout((()=>{f.value=!1}),r),null!=s&&setTimeout((()=>{if(!l.value&&!u.value){const e=new Error(`Async component timed out after ${s}ms.`);t(e),u.value=e}}),s),p().then((()=>{l.value=!0,e.parent&&gn(e.parent.vnode)&&Kr(e.parent.update)})).catch((e=>{t(e),u.value=e})),()=>l.value&&c?mn(c,e):u.value&&o?Jo(o,{error:u.value}):n&&!f.value?Jo(n):void 0}})},e.defineComponent=dn,e.defineCustomElement=$s,e.defineEmits=function(){return null},e.defineExpose=function(e){},e.defineProps=function(){return null},e.defineSSRCustomElement=e=>$s(e,Ei),e.effect=function(e,t){e.effect&&(e=e.effect.fn);const n=new de(e);t&&(C(n,t),t.scope&&ne(n,t.scope)),t&&t.lazy||n.run();const o=n.run.bind(n);return o.effect=n,o},e.effectScope=function(e){return new te(e)},e.getCurrentInstance=pr,e.getCurrentScope=function(){return X},e.getTransitionRawChildren=fn,e.guardReactiveProps=Zo,e.h=cs,e.handleError=Or,e.hydrate=Ei,e.initCustomFormatter=function(){},e.initDirectivesForSSR=Ri,e.inject=nn,e.isMemoSame=us,e.isProxy=vt,e.isReactive=mt,e.isReadonly=gt,e.isRef=wt,e.isRuntimeOnly=()=>!mr,e.isVNode=Do,e.markRaw=bt,e.mergeDefaults=function(e,t){for(const n in t){const o=e[n];o?o.default=t[n]:null===o&&(e[n]={default:t[n]})}return e},e.mergeProps=nr,e.nextTick=zr,e.normalizeClass=a,e.normalizeProps=function(e){if(!e)return null;let{class:t,style:n}=e;return t&&!A(t)&&(e.class=a(t)),n&&(e.style=s(n)),e},e.normalizeStyle=s,e.onActivated=bn,e.onBeforeMount=Nn,e.onBeforeUnmount=On,e.onBeforeUpdate=$n,e.onDeactivated=_n,e.onErrorCaptured=Vn,e.onMounted=En,e.onRenderTracked=Pn,e.onRenderTriggered=Mn,e.onScopeDispose=function(e){X&&X.cleanups.push(e)},e.onServerPrefetch=Fn,e.onUnmounted=An,e.onUpdated=Rn,e.openBlock=Io,e.popScopeId=function(){Ut=null},e.provide=tn,e.proxyRefs=Rt,e.pushScopeId=function(e){Ut=e},e.queuePostFlushCb=Jr,e.reactive=pt,e.readonly=dt,e.ref=kt,e.registerRuntimeCompiler=br,e.render=Ni,e.renderList=function(e,t,n,o){let r;const s=n&&n[o];if(N(e)||A(e)){r=new Array(e.length);for(let n=0,o=e.length;nt(e,n,void 0,s&&s[n])));else{const n=Object.keys(e);r=new Array(n.length);for(let o=0,i=n.length;oe.devtools.emit(t,...n))),Vt=[];else{(o.__VUE_DEVTOOLS_HOOK_REPLAY__=o.__VUE_DEVTOOLS_HOOK_REPLAY__||[]).push((e=>{t(e,o)}))}},e.setTransitionHooks=pn,e.shallowReactive=ft,e.shallowReadonly=function(e){return ht(e,!0,Ie,st,at)},e.shallowRef=function(e){return Tt(e,!0)},e.ssrContextKey=as,e.ssrUtils=null,e.stop=function(e){e.effect.stop()},e.toDisplayString=e=>null==e?"":N(e)||M(e)&&(e.toString===V||!O(e.toString))?JSON.stringify(e,m,2):String(e),e.toHandlerKey=G,e.toHandlers=function(e){const t={};for(const n in e)t[G(n)]=e[n];return t},e.toRaw=yt,e.toRef=Ft,e.toRefs=function(e){const t=N(e)?new Array(e.length):{};for(const n in e)t[n]=Ft(e,n);return t},e.transformVNodeArgs=function(e){},e.triggerRef=function(e){Ct(e)},e.unref=Et,e.useAttrs=function(){return ls().attrs},e.useCssModule=function(e="$style"){return g},e.useCssVars=function(e){const t=pr();if(!t)return;const n=()=>As(t.subTree,e(t.proxy));es(n),En((()=>{const e=new MutationObserver(n);e.observe(t.subTree.el.parentNode,{childList:!0}),An((()=>e.disconnect()))}))},e.useSSRContext=()=>{},e.useSlots=function(){return ls().slots},e.useTransitionState=on,e.vModelCheckbox=ci,e.vModelDynamic=mi,e.vModelRadio=ui,e.vModelSelect=pi,e.vModelText=li,e.vShow=_i,e.version=ps,e.warn=function(e,...t){ve();const n=Tr.length?Tr[Tr.length-1].component:null,o=n&&n.appContext.config.warnHandler,r=function(){let e=Tr[Tr.length-1];if(!e)return[];const t=[];for(;e;){const n=t[0];n&&n.vnode===e?n.recurseCount++:t.push({vnode:e,recurseCount:0});const o=e.component&&e.component.parent;e=o&&o.vnode}return t}();if(o)$r(o,n,11,[e+t.join(""),n&&n.proxy,r.map((({vnode:e})=>`at <${kr(n,e.type)}>`)).join("\n"),r]);else{const n=[`[Vue warn]: ${e}`,...t];r.length&&n.push("\n",...function(e){const t=[];return e.forEach(((e,n)=>{t.push(...0===n?[]:["\n"],...function({vnode:e,recurseCount:t}){const n=t>0?`... (${t} recursive calls)`:"",o=` at <${kr(e.component,e.type,!!e.component&&null==e.component.parent)}`,r=">"+n;return e.props?[o,...Nr(e.props),r]:[o+r]}(e))})),t}(r)),console.warn(...n)}ye()},e.watch=ns,e.watchEffect=function(e,t){return os(e,null,t)},e.watchPostEffect=es,e.watchSyncEffect=function(e,t){return os(e,null,{flush:"sync"})},e.withAsyncContext=function(e){const t=pr();let n=e();return dr(),P(n)&&(n=n.catch((e=>{throw fr(t),e}))),[n,()=>fr(t)]},e.withCtx=Dt,e.withDefaults=function(e,t){return null},e.withDirectives=function(e,t){if(null===jt)return e;const n=jt.proxy,o=e.dirs||(e.dirs=[]);for(let r=0;rn=>{if(!("key"in n))return;const o=z(n.key);return t.some((e=>e===o||bi[e]===o))?e(n):void 0},e.withMemo=function(e,t,n,o){const r=n[o];if(r&&us(r,e))return r;const s=t();return s.memo=e.slice(),n[o]=s},e.withModifiers=(e,t)=>(n,...o)=>{for(let e=0;eDt,Object.defineProperty(e,"__esModule",{value:!0}),e}({}); diff --git a/core/static/core/style.scss b/core/static/core/style.scss index a9e19f1a..d6f784f4 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -1224,7 +1224,7 @@ u, .underline { text-decoration: underline; } -#bar_ui { +#bar-ui { padding: 0.4em; display: flex; flex-wrap: wrap; diff --git a/counter/models.py b/counter/models.py index 564d6a3b..af83d492 100644 --- a/counter/models.py +++ b/counter/models.py @@ -451,11 +451,11 @@ class Counter(models.Model): Show if the counter authorize the refilling with physic money """ - if ( - self.id in SITH_COUNTER_OFFICES - ): # If the counter is the counters 'AE' or 'BdF', the refiling are authorized + 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 - is_ae_member = False ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"]) for barman in self.get_barmen_list(): diff --git a/counter/static/counter/js/counter_click.js b/counter/static/counter/js/counter_click.js new file mode 100644 index 00000000..46f22e93 --- /dev/null +++ b/counter/static/counter/js/counter_click.js @@ -0,0 +1,78 @@ +document.addEventListener('alpine:init', () => { + Alpine.data('counter', () => ({ + basket: basket, + errors: [], + + sum_basket() { + 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 handle_code(event) { + const code = $(event.target).find("#code_field").val().toUpperCase(); + if(["FIN", "ANN"].includes(code)) { + $(event.target).submit(); + } else { + await this.handle_action(event); + } + }, + + async handle_action(event) { + const payload = $(event.target).serialize(); + let request = new Request(click_api_url, { + method: "POST", + body: payload, + headers: { + 'Accept': 'application/json', + 'X-CSRFToken': csrf_token, + } + }) + 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(); + } + })) +}) + +$(function () { + /* Autocompletion in the code field */ + const code_field = $("#code_field"); + + let quantity = ""; + let search = ""; + code_field.autocomplete({ + select: function (event, ui) { + event.preventDefault(); + code_field.val(quantity + ui.item.value); + }, + focus: function (event, ui) { + event.preventDefault(); + code_field.val(quantity + ui.item.value); + }, + source: function (request, response) { + // by the dark magic of JS, parseInt("123abc") === 123 + quantity = parseInt(request.term); + search = request.term.slice(quantity.toString().length) + let matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i"); + response($.grep(products_autocomplete, function (value) { + value = value.tags; + return matcher.test(value); + })); + }, + }); + + /* Accordion UI between basket and refills */ + $("#click_form").accordion({ + heightStyle: "content", + activate: () => $(".focus").focus(), + }); + $("#products").tabs(); + + code_field.focus(); +}); \ No newline at end of file diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index b9238b89..1a44c7f3 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -2,266 +2,195 @@ {% from "core/macros.jinja" import user_mini_profile, user_subscription %} {% block title %} -{{ counter }} + {{ counter }} +{% endblock %} + +{% block additional_js %} + + {% endblock %} {% block info_boxes %} {% endblock %} + {% block nav %} {% endblock %} {% block content %} -

{{ counter }}

+

{{ counter }}

-
- - -
-
{% 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 %} - - {% if request.session['not_valid_student_card_uid'] %} -

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

- {% endif %} - - -
{% trans %}Registered cards{% endtrans %}
- {% if customer.student_cards.exists() %} -
    - {% for card in customer.student_cards.all() %} -
  • {{ card.uid }}
  • - {% endfor %} -
- {% else %} - {% trans %}No card registered{% endtrans %} - {% endif %} -
- -
-
{% trans %}Selling{% endtrans %}
-
- - {% raw %} -
-

{{ error }}

-
- {% endraw %} - -
- {% csrf_token %} - - - -
-

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

- - {% raw %} -
    -
  • - -
    - - - - -
    - - {{ p_info["qty"] + p_info["bonus_qty"] }} - -
    - - - - -
    - - {{ products[p_id].name }}: {{ (p_info["qty"]*p_info["price"]/100).toLocaleString(undefined, { minimumFractionDigits: 2 }) }} € P -
  • -
-

- Total: {{ sum_basket().toLocaleString(undefined, { minimumFractionDigits: 2 }) }} € -

- -
-

{{ error }}

-
- {% endraw %} +
+ +
+
{% trans %}Customer{% endtrans %}
+ {{ user_mini_profile(customer.user) }} + {{ user_subscription(customer.user) }} +

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

{% csrf_token %} - - -
-
- {% csrf_token %} - - -
-
- {% if (counter.type == 'BAR' and barmens_can_refill) %} -
{% trans %}Refilling{% endtrans %}
-
-
- {% csrf_token %} - {{ refill_form.as_p() }} - - -
-
- {% endif %} -
- -
-
    - {% for category in categories.keys() -%} -
  • {{ category }}
  • - {%- endfor %} -
- {% for category in categories.keys() -%} -
-
{{ category }}
- {% for p in categories[category] -%} - {% set file = None %} - {% if p.icon %} - {% set file = p.icon.url %} - {% else %} - {% set file = static('core/img/na.gif') %} + + {% trans %}Add a student card{% endtrans %} + + {% if request.session['not_valid_student_card_uid'] %} +

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

{% endif %} -
+ +
+
{% trans %}Registered cards{% endtrans %}
+ {% if customer.student_cards.exists() %} +
    + {% for card in customer.student_cards.all() %} +
  • {{ card.uid }}
  • + {% endfor %} +
+ {% else %} + {% trans %}No card registered{% endtrans %} + {% endif %} +
+ +
+
{% trans %}Selling{% endtrans %}
+
+ {% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user.id) %} + + {# Formulaire pour rechercher un produit en tapant son code dans une barre de recherche #} +
{% csrf_token %} - - - + + + +
+ + +

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

+ +
    + +
+

+ Total: + + +

+ +
+ {% csrf_token %} + + +
+
+ {% csrf_token %} + + +
+
+ {% if (counter.type == 'BAR' and barmens_can_refill) %} +
{% trans %}Refilling{% endtrans %}
+
+
+ {% csrf_token %} + {{ refill_form.as_p() }} + + +
+
+ {% endif %} +
+ +
+
    + {% for category in categories.keys() -%} +
  • {{ category }}
  • + {%- endfor %} +
+ {% for category in categories.keys() -%} +
+
{{ category }}
+ {% for p in categories[category] -%} +
+ {% csrf_token %} + + + +
+ {%- endfor %} +
{%- endfor %}
- {%- endfor %}
-
- {% endblock %} {% block script %} -{{ super() }} - - + {{ super() }} + {% endblock %} diff --git a/counter/tests.py b/counter/tests.py index 0a25a9ff..af2403e5 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -42,7 +42,7 @@ class CounterTest(TestCase): self.foyer = Counter.objects.get(id=2) def test_full_click(self): - response = self.client.post( + self.client.post( reverse("counter:login", kwargs={"counter_id": self.mde.id}), {"username": self.skia.username, "password": "plop"}, ) @@ -62,13 +62,12 @@ class CounterTest(TestCase): reverse("counter:details", kwargs={"counter_id": self.mde.id}), {"code": "4000k", "counter_token": counter_token}, ) - location = response.get("location") - + counter_url = response.get("location") response = self.client.get(response.get("location")) self.assertTrue(">Richard Batsbak2 x Barbar" in str(response_content)) - self.assertTrue("
  • 2 x Déconsigne Eco-cup" in str(response_content)) + self.assertTrue("2 x Barbar" in str(response_content)) + self.assertTrue("2 x Déconsigne Eco-cup" in str(response_content)) self.assertTrue( "

    Client : Richard Batsbak - Nouveau montant : 3.60" in str(response_content) @@ -98,7 +107,7 @@ class CounterTest(TestCase): ) response = self.client.post( - location, + counter_url, { "action": "refill", "amount": "5", @@ -108,7 +117,7 @@ class CounterTest(TestCase): ) self.assertTrue(response.status_code == 200) - response = self.client.post( + self.client.post( reverse("counter:login", kwargs={"counter_id": self.foyer.id}), {"username": self.krophil.username, "password": "plop"}, ) @@ -125,10 +134,10 @@ class CounterTest(TestCase): reverse("counter:details", kwargs={"counter_id": self.foyer.id}), {"code": "4000k", "counter_token": counter_token}, ) - location = response.get("location") + counter_url = response.get("location") response = self.client.post( - location, + counter_url, { "action": "refill", "amount": "5", @@ -144,7 +153,7 @@ class CounterStatsTest(TestCase): call_command("populate") self.counter = Counter.objects.filter(id=2).first() - def test_unothorized_user_fail(self): + def test_unauthorised_user_fail(self): # Test with not login user response = self.client.get(reverse("counter:stats", args=[self.counter.id])) self.assertTrue(response.status_code == 403) diff --git a/counter/views.py b/counter/views.py index ca012688..c195ae36 100644 --- a/counter/views.py +++ b/counter/views.py @@ -22,8 +22,10 @@ # # import json +from urllib.parse import parse_qs from django.contrib.auth.decorators import login_required +from django.db.models import F from django.shortcuts import get_object_or_404 from django.http import Http404 from django.core.exceptions import PermissionDenied @@ -300,7 +302,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): current_tab = "counter" def render_to_response(self, *args, **kwargs): - if self.request.is_ajax(): # JSON response for AJAX requests + if self.is_ajax(self.request): response = {"errors": []} status = HTTPStatus.OK @@ -395,42 +397,40 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["not_valid_student_card_uid"] = False if self.object.type != "BAR": self.operator = request.user - elif self.is_barman_price(): + elif self.customer_is_barman(): self.operator = self.customer.user else: self.operator = self.object.get_random_barman() - - if "add_product" in request.POST["action"]: + 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 "add_student_card" in request.POST["action"]: + elif action == "add_student_card": self.add_student_card(request) - elif "del_product" in request.POST["action"]: + elif action == "del_product": self.del_product(request) - elif "refill" in request.POST["action"]: + elif action == "refill": self.refill(request) - elif "code" in request.POST["action"]: + elif action == "code": return self.parse_code(request) - elif "cancel" in request.POST["action"]: + elif action == "cancel": return self.cancel(request) - elif "finish" in request.POST["action"]: + elif action == "finish": return self.finish(request) context = self.get_context_data(object=self.object) return self.render_to_response(context) - def is_barman_price(self): - if self.object.type == "BAR" and self.customer.user.id in [ - s.id for s in self.object.get_barmen_list() - ]: - return True - else: - return False + 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.is_barman_price(): + if self.customer_is_barman(): price = p.special_selling_price else: price = p.selling_price @@ -475,13 +475,22 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): 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 request.POST["product_id"] + pid = p or parse_qs(request.body.decode())["product_id"][0] pid = str(pid) price = self.get_price(pid) total = self.sum_basket(request) @@ -563,7 +572,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def del_product(self, request): """Delete a product from the basket""" - pid = str(request.POST["product_id"]) + pid = parse_qs(request.body.decode())["product_id"][0] product = self.get_product(pid) if pid in request.session["basket"]: if ( @@ -576,30 +585,29 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["basket"][pid]["qty"] -= 1 if request.session["basket"][pid]["qty"] <= 0: del request.session["basket"][pid] - else: - request.session["basket"][pid] = None request.session.modified = True def parse_code(self, request): - """Parse the string entered by the barman""" - string = str(request.POST["code"]).upper() - if string == _("END"): + """ + 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())["code"][0].upper() + if string == "FIN": return self.finish(request) - elif string == _("CAN"): + 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") - if nb is None: - nb = 1 - else: - nb = int(nb) + nb = int(nb) if nb is not None else 1 p = self.object.products.filter(code=code).first() if p is not None: - while nb > 0 and not self.add_product(request, nb, p.id): - nb -= 1 + self.add_product(request, nb, p.id) context = self.get_context_data(object=self.object) return self.render_to_response(context) @@ -613,7 +621,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): 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.is_barman_price(): + if self.customer_is_barman(): uprice = p.special_selling_price else: uprice = p.selling_price @@ -665,22 +673,26 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def refill(self, request): """Refill the customer's account""" - if self.get_object().type == "BAR" and self.object.can_refill(): - 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 - else: + 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(CounterClick, self).get_context_data(**kwargs) - kwargs["products"] = self.object.products.select_related("product_type") + 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: diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index b2ff6681..529fd067 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -111,7 +111,7 @@ {% endif %}

    -

    {{ p.name }}

    +

    {{ p.name }}

    {{ p.selling_price }} €

    diff --git a/sith/settings.py b/sith/settings.py index 1b0998b7..67dbac38 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -689,6 +689,5 @@ SITH_FRONT_DEP_VERSIONS = { "https://github.com/viralpatel/jquery.shorten/": "", "https://github.com/getsentry/sentry-javascript/": "4.0.6", "https://github.com/jhuckaby/webcamjs/": "1.0.0", - "https://github.com/vuejs/vue-next": "3.2.18", "https://github.com/alpinejs/alpine": "3.10.5", } From 394e17d599fe099322ad0ad0b1e54de568e7dc1f Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Fri, 13 Jan 2023 02:22:53 +0100 Subject: [PATCH 22/95] resolved importError (#565) --- counter/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/counter/models.py b/counter/models.py index 587ac554..cdc28896 100644 --- a/counter/models.py +++ b/counter/models.py @@ -25,9 +25,8 @@ from __future__ import annotations from typing import Tuple -from __future__ import annotations from django.db import models -from django.db.models import OuterRef, Exists, QuerySet +from django.db.models import OuterRef, Exists from django.db.models.functions import Length from django.utils.translation import gettext_lazy as _ from django.utils import timezone From 585923c82718b5b3e6796c63bb8d23fd3af58b94 Mon Sep 17 00:00:00 2001 From: Skia Date: Tue, 7 Feb 2023 12:08:25 +0100 Subject: [PATCH 23/95] Add galaxy (#562) * style.scss: lint * style.scss: add 'th' padding * core: populate: add much more data for development * Add galaxy --- .../images/{6.jpg => sas/Family/krophil.jpg} | Bin .../images/{8.jpg => sas/Family/richard.jpg} | Bin .../images/{3.jpg => sas/Family/skia.jpg} | Bin core/fixtures/images/sas/Family/skia_sli.jpg | Bin 0 -> 58159 bytes .../images/sas/Family/skia_sli_krophil.jpg | Bin 0 -> 98308 bytes .../images/{5.jpg => sas/Family/sli.jpg} | Bin .../2022-03-02_In-Bloom_by-David-Revoy.jpg | Bin 0 -> 59334 bytes .../2022-12-06_Rain-City_by-David-Revoy.jpg | Bin 0 -> 27752 bytes ...ffron-steampunk-clothes_by-David-Revoy.jpg | Bin 0 -> 24396 bytes .../2022-06-30_Asleep_by-David-Revoy.jpg | Bin 0 -> 31281 bytes core/management/commands/check_front.py | 9 +- core/management/commands/populate.py | 195 +++- core/static/core/style.scss | 915 +++++++++--------- core/views/user.py | 70 +- galaxy/__init__.py | 0 galaxy/apps.py | 30 + galaxy/management/commands/rule_galaxy.py | 61 ++ galaxy/migrations/0001_initial.py | 113 +++ galaxy/migrations/__init__.py | 0 galaxy/models.py | 377 ++++++++ galaxy/static/galaxy/js/3d-force-graph.min.js | 5 + galaxy/static/galaxy/js/d3-force-3d.min.js | 2 + .../static/galaxy/js/three-spritetext.min.js | 2 + galaxy/static/galaxy/js/three.min.js | 6 + galaxy/templates/galaxy/user.jinja | 108 +++ galaxy/tests.py | 129 +++ galaxy/urls.py | 40 + galaxy/views.py | 97 ++ sith/settings.py | 28 + sith/urls.py | 1 + 30 files changed, 1691 insertions(+), 497 deletions(-) rename core/fixtures/images/{6.jpg => sas/Family/krophil.jpg} (100%) rename core/fixtures/images/{8.jpg => sas/Family/richard.jpg} (100%) rename core/fixtures/images/{3.jpg => sas/Family/skia.jpg} (100%) create mode 100644 core/fixtures/images/sas/Family/skia_sli.jpg create mode 100644 core/fixtures/images/sas/Family/skia_sli_krophil.jpg rename core/fixtures/images/{5.jpg => sas/Family/sli.jpg} (100%) create mode 100644 core/fixtures/images/sas/Pepper/2022-03-02_In-Bloom_by-David-Revoy.jpg create mode 100644 core/fixtures/images/sas/Pepper/2022-12-06_Rain-City_by-David-Revoy.jpg create mode 100644 core/fixtures/images/sas/Saffron/2020-05-09_Saffron-steampunk-clothes_by-David-Revoy.jpg create mode 100644 core/fixtures/images/sas/Shichimi/2022-06-30_Asleep_by-David-Revoy.jpg create mode 100644 galaxy/__init__.py create mode 100644 galaxy/apps.py create mode 100644 galaxy/management/commands/rule_galaxy.py create mode 100644 galaxy/migrations/0001_initial.py create mode 100644 galaxy/migrations/__init__.py create mode 100644 galaxy/models.py create mode 100644 galaxy/static/galaxy/js/3d-force-graph.min.js create mode 100644 galaxy/static/galaxy/js/d3-force-3d.min.js create mode 100644 galaxy/static/galaxy/js/three-spritetext.min.js create mode 100644 galaxy/static/galaxy/js/three.min.js create mode 100644 galaxy/templates/galaxy/user.jinja create mode 100644 galaxy/tests.py create mode 100644 galaxy/urls.py create mode 100644 galaxy/views.py diff --git a/core/fixtures/images/6.jpg b/core/fixtures/images/sas/Family/krophil.jpg similarity index 100% rename from core/fixtures/images/6.jpg rename to core/fixtures/images/sas/Family/krophil.jpg diff --git a/core/fixtures/images/8.jpg b/core/fixtures/images/sas/Family/richard.jpg similarity index 100% rename from core/fixtures/images/8.jpg rename to core/fixtures/images/sas/Family/richard.jpg diff --git a/core/fixtures/images/3.jpg b/core/fixtures/images/sas/Family/skia.jpg similarity index 100% rename from core/fixtures/images/3.jpg rename to core/fixtures/images/sas/Family/skia.jpg diff --git a/core/fixtures/images/sas/Family/skia_sli.jpg b/core/fixtures/images/sas/Family/skia_sli.jpg new file mode 100644 index 0000000000000000000000000000000000000000..817b320ca1156f5e10b40475715409682c6e4810 GIT binary patch literal 58159 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%0D(9b7s%tJjus}e>vuf$e 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 K^8*VbQ~%lLqbZO8 literal 0 HcmV?d00001 diff --git a/core/fixtures/images/sas/Family/skia_sli_krophil.jpg b/core/fixtures/images/sas/Family/skia_sli_krophil.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a8feba4ea66505921946654c96145feb890cf21b GIT binary patch literal 98308 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%0A%8M(x!zRqcKFpeQ&+T`>TA|eReiQ$L`?(;s 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 Mka~?Y$^JC|+5bYF7XSbN literal 0 HcmV?d00001 diff --git a/core/fixtures/images/5.jpg b/core/fixtures/images/sas/Family/sli.jpg similarity index 100% rename from core/fixtures/images/5.jpg rename to core/fixtures/images/sas/Family/sli.jpg diff --git a/core/fixtures/images/sas/Pepper/2022-03-02_In-Bloom_by-David-Revoy.jpg b/core/fixtures/images/sas/Pepper/2022-03-02_In-Bloom_by-David-Revoy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..31c45e38f61250c64ad2955a532eed7cb7eccbeb GIT binary patch literal 59334 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

    &*Psw>&--ZCk}FLJdb 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 literal 0 HcmV?d00001 diff --git a/core/management/commands/check_front.py b/core/management/commands/check_front.py index 181de23c..b6e24479 100644 --- a/core/management/commands/check_front.py +++ b/core/management/commands/check_front.py @@ -6,8 +6,15 @@ from django.core.management.base import BaseCommand # see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string # added "v?" +# Please note that this does not match the version of the three.js library. +# Hence, you shall have to check this one by yourself semver_regex = re.compile( - """^v?(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""" + r"^v?" + r"(?P\d+)" + r"\.(?P\d+)" + r"\.(?P\d+)" + r"(?:-(?P(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" ) diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index ae83008c..8df2bfff 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -1,7 +1,7 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2016,2017,2023 +# - Skia # # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, # http://ae.utbm.fr. @@ -25,6 +25,7 @@ import os from datetime import date, datetime, timedelta from io import StringIO, BytesIO +from pathlib import Path from django.contrib.auth.models import Permission from django.core.management.base import BaseCommand @@ -54,6 +55,7 @@ from com.models import Sith, Weekmail, News, NewsDate from election.models import Election, Role, Candidature, ElectionList from forum.models import Forum, ForumTopic from pedagogy.models import UV +from sas.models import Album, Picture, PeoplePictureRelation class Command(BaseCommand): @@ -71,9 +73,7 @@ class Command(BaseCommand): def handle(self, *args, **options): os.environ["DJANGO_COLORS"] = "nocolor" Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save() - root_path = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - ) + root_path = Path(__file__).parent.parent.parent.parent root_group, _ = Group.objects.get_or_create(name="Root") Group(name="Public").save() Group(name="Subscribers").save() @@ -84,7 +84,7 @@ class Command(BaseCommand): Group(name="Banned from buying alcohol").save() Group(name="Banned from counters").save() Group(name="Banned to subscribe").save() - Group(name="SAS admin").save() + sas_admin, _ = Group.objects.get_or_create(name="SAS admin") Group(name="Forum admin").save() Group(name="Pedagogy admin").save() self.reset_index("core", "auth") @@ -119,7 +119,8 @@ class Command(BaseCommand): club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) club_root.save() - SithFile(parent=None, name="SAS", is_folder=True, owner=root).save() + sas = SithFile(parent=None, name="SAS", is_folder=True, owner=root) + sas.save() main_club = Club( id=1, name=settings.SITH_MAIN_CLUB["name"], @@ -223,7 +224,15 @@ Welcome to the wiki page! Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id ] skia.save() - skia_profile_path = os.path.join(root_path, "core/fixtures/images/3.jpg") + skia_profile_path = ( + root_path + / "core" + / "fixtures" + / "images" + / "sas" + / "Family" + / "skia.jpg" + ) with open(skia_profile_path, "rb") as f: name = str(skia.id) + "_profile.jpg" skia_profile = SithFile( @@ -233,7 +242,7 @@ Welcome to the wiki page! owner=skia, is_folder=False, mime_type="image/jpeg", - size=os.path.getsize(skia_profile_path), + size=skia_profile_path.stat().st_size, ) skia_profile.file.name = name skia_profile.save() @@ -351,23 +360,48 @@ Welcome to the wiki page! ] u.save() # Adding user Richard Batsbak - r = User( + richard = User( username="rbatsbak", last_name="Batsbak", first_name="Richard", email="richard@git.an", date_of_birth="1982-06-12", ) - r.set_password("plop") - r.save() - r.view_groups = [ + richard.set_password("plop") + richard.save() + richard.godfathers.add(comptable) + richard_profile_path = ( + root_path + / "core" + / "fixtures" + / "images" + / "sas" + / "Family" + / "richard.jpg" + ) + with open(richard_profile_path, "rb") as f: + name = f"{richard.id}_profile.jpg" + richard_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=richard, + is_folder=False, + mime_type="image/jpeg", + size=richard_profile_path.stat().st_size, + ) + richard_profile.file.name = name + richard_profile.save() + richard.profile_pict = richard_profile + richard.save() + richard.view_groups = [ Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id ] - r.save() + richard.save() # Adding syntax help page p = Page(name="Aide_sur_la_syntaxe") p.save(force_lock=True) - with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as rm: + with open(root_path / "doc" / "SYNTAX.md", "r") as rm: PageRev( page=p, title="Aide sur la syntaxe", author=skia, content=rm.read() ).save() @@ -442,7 +476,7 @@ Welcome to the wiki page! s.save() # Richard s = Subscription( - member=User.objects.filter(pk=r.pk).first(), + member=User.objects.filter(pk=richard.pk).first(), subscription_type=default_subscription, payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], ) @@ -514,7 +548,7 @@ Welcome to the wiki page! subscribers = Group.objects.get(name="Subscribers") old_subscribers = Group.objects.get(name="Old subscribers") Customer(user=skia, account_id="6568j", amount=0).save() - Customer(user=r, account_id="4000k", amount=0).save() + Customer(user=richard, account_id="4000k", amount=0).save() p = ProductType(name="Bières bouteilles") p.save() c = ProductType(name="Cotisations") @@ -825,7 +859,15 @@ Welcome to the wiki page! Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id ] sli.save() - sli_profile_path = os.path.join(root_path, "core/fixtures/images/5.jpg") + sli_profile_path = ( + root_path + / "core" + / "fixtures" + / "images" + / "sas" + / "Family" + / "sli.jpg" + ) with open(sli_profile_path, "rb") as f: name = str(sli.id) + "_profile.jpg" sli_profile = SithFile( @@ -835,7 +877,7 @@ Welcome to the wiki page! owner=sli, is_folder=False, mime_type="image/jpeg", - size=os.path.getsize(sli_profile_path), + size=sli_profile_path.stat().st_size, ) sli_profile.file.name = name sli_profile.save() @@ -851,7 +893,15 @@ Welcome to the wiki page! ) krophil.set_password("plop") krophil.save() - krophil_profile_path = os.path.join(root_path, "core/fixtures/images/6.jpg") + krophil_profile_path = ( + root_path + / "core" + / "fixtures" + / "images" + / "sas" + / "Family" + / "krophil.jpg" + ) with open(krophil_profile_path, "rb") as f: name = str(krophil.id) + "_profile.jpg" krophil_profile = SithFile( @@ -861,7 +911,7 @@ Welcome to the wiki page! owner=krophil, is_folder=False, mime_type="image/jpeg", - size=os.path.getsize(krophil_profile_path), + size=krophil_profile_path.stat().st_size, ) krophil_profile.file.name = name krophil_profile.save() @@ -1164,3 +1214,106 @@ Welcome to the wiki page! hours_THE=121, hours_TE=4, ).save() + + # SAS + skia.groups.add(sas_admin.id) + sas_fixtures_path = root_path / "core" / "fixtures" / "images" / "sas" + for f in sas_fixtures_path.glob("*"): + if f.is_dir(): + album = Album( + parent=sas, + name=f.name, + owner=root, + is_folder=True, + is_in_sas=True, + is_moderated=True, + ) + album.clean() + album.save() + for p in f.iterdir(): + pict = Picture( + parent=album, + name=p.name, + file=resize_image( + Image.open(BytesIO(p.read_bytes())), 1000, "JPEG" + ), + owner=root, + is_folder=False, + is_in_sas=True, + is_moderated=True, + mime_type="image/jpeg", + size=p.stat().st_size, + ) + pict.file.name = p.name + pict.clean() + pict.generate_thumbnails() + pict.save() + + p = Picture.objects.get(name="skia.jpg") + PeoplePictureRelation(user=skia, picture=p).save() + p = Picture.objects.get(name="sli.jpg") + PeoplePictureRelation(user=sli, picture=p).save() + p = Picture.objects.get(name="krophil.jpg") + PeoplePictureRelation(user=krophil, picture=p).save() + p = Picture.objects.get(name="skia_sli.jpg") + PeoplePictureRelation(user=skia, picture=p).save() + PeoplePictureRelation(user=sli, picture=p).save() + p = Picture.objects.get(name="skia_sli_krophil.jpg") + PeoplePictureRelation(user=skia, picture=p).save() + PeoplePictureRelation(user=sli, picture=p).save() + PeoplePictureRelation(user=krophil, picture=p).save() + p = Picture.objects.get(name="richard.jpg") + PeoplePictureRelation(user=richard, picture=p).save() + + with open(skia_profile_path, "rb") as f: + name = str(skia.id) + "_profile.jpg" + skia_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=skia, + is_folder=False, + mime_type="image/jpeg", + size=skia_profile_path.stat().st_size, + ) + skia_profile.file.name = name + skia_profile.save() + skia.profile_pict = skia_profile + skia.save() + + # Create some additional data for galaxy to work with + root.godfathers.add(skia) + skia.godfathers.add(root) + sli.godfathers.add(skia) + richard.godchildren.add(subscriber) + richard.godchildren.add(public) + Membership( + user=sli, + club=troll, + role=9, + description="Padawan Troll", + start_date=timezone.now() - timedelta(days=17), + ).save() + Membership( + user=krophil, + club=troll, + role=10, + description="Maitre Troll", + start_date=timezone.now() - timedelta(days=200), + ).save() + Membership( + user=skia, + club=troll, + role=2, + description="Grand Ancien Troll", + start_date=timezone.now() - timedelta(days=400), + end_date=timezone.now() - timedelta(days=86), + ).save() + Membership( + user=richard, + club=troll, + role=2, + description="", + start_date=timezone.now() - timedelta(days=200), + end_date=timezone.now() - timedelta(days=100), + ).save() diff --git a/core/static/core/style.scss b/core/static/core/style.scss index d6f784f4..3279d0d7 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -11,7 +11,6 @@ $primary-dark-color: hsl(203, 75%, 40%); $secondary-light-color: hsl(40, 68%, 65%); $secondary-dark-color: hsl(40, 68%, 35%); - $primary-neutral-color: hsl(219.6, 20.8%, 50%); $primary-neutral-light-color: hsl(0, 0%, 94%); $primary-neutral-dark-color: hsl(210, 29%, 29%); @@ -29,7 +28,7 @@ $pinktober: #ff5674; $pinktober-secondary: #8a2536; $pinktober-primary-text: white; $pinktober-bar-closed: $pinktober-secondary; -$pinktober-bar-opened: #388E3C; +$pinktober-bar-opened: #388e3c; $shadow-color: rgb(223, 223, 223); @@ -49,7 +48,11 @@ body { font-family: sans-serif; } -button, input[type=button], input[type=submit], input[type=reset],input[type=file] { +button, +input[type="button"], +input[type="submit"], +input[type="reset"], +input[type="file"] { border: none; text-decoration: none; background-color: $background-button-color; @@ -62,15 +65,24 @@ button, input[type=button], input[type=submit], input[type=reset],input[type=fil } } -input[type=button], input[type=submit], input[type=reset],input[type=file] { +input[type="button"], +input[type="submit"], +input[type="reset"], +input[type="file"] { font-weight: bold; } -button:not(:disabled), input[type=button]:not(:disabled), input[type=submit]:not(:disabled), input[type=reset]:not(:disabled),input[type=file]:not(:disabled) { +button:not(:disabled), +input[type="button"]:not(:disabled), +input[type="submit"]:not(:disabled), +input[type="reset"]:not(:disabled), +input[type="file"]:not(:disabled) { cursor: pointer; } -input,textarea[type=text],[type=number]{ +input, +textarea[type="text"], +[type="number"] { border: none; text-decoration: none; background-color: $background-button-color; @@ -80,7 +92,7 @@ input,textarea[type=text],[type=number]{ border-radius: 5px; max-width: 95%; } -textarea{ +textarea { border: none; text-decoration: none; background-color: $background-button-color; @@ -88,7 +100,7 @@ textarea{ font-size: 1.2em; border-radius: 5px; } -select{ +select { border: none; text-decoration: none; font-size: 1.2em; @@ -143,7 +155,8 @@ a { } .shadow { - box-shadow: rgba(60, 64, 67, .3) 0 1px 3px 0, rgba(60, 64, 67, .15) 0 4px 8px 3px; + box-shadow: rgba(60, 64, 67, 0.3) 0 1px 3px 0, + rgba(60, 64, 67, 0.15) 0 4px 8px 3px; } .w_big { @@ -162,7 +175,9 @@ a { cursor: pointer; } -[x-cloak] { display: none !important; } +[x-cloak] { + display: none !important; +} /*--------------------------------HEADER-------------------------------*/ @@ -195,7 +210,6 @@ header { background-color: $primary-neutral-dark-color; border-radius: 0px 0px 10px 10px; - #header_logo { background-color: $white-color; padding: 0.2em; @@ -209,10 +223,10 @@ header { height: 100%; img { - max-width: 70%; - max-height: 100%; - margin: auto; - display: block; + max-width: 70%; + max-height: 100%; + margin: auto; + display: block; } } } @@ -259,7 +273,7 @@ header { flex: auto; margin: 0.8em 0; input { - width: 14ch; + width: 14ch; } } @@ -338,13 +352,14 @@ header { flex-wrap: wrap; width: 90%; margin: 1em auto; - #alert_box, #info_box { + #alert_box, + #info_box { flex: 49%; font-size: 0.9em; margin: 0.2em; border-radius: 0.6em; .markdown { - margin: 0.5em; + margin: 0.5em; } &:before { font-family: FontAwesome; @@ -369,7 +384,6 @@ header { } } - #page { width: 90%; margin: 20px auto 0; @@ -470,11 +484,11 @@ header { } &.btn-grey.clickable:not(:disabled):hover { - background-color:hsl(210,5%,30%); + background-color: hsl(210, 5%, 30%); } } -/*--------------------------------CONTENT------------------------------*/ + /*--------------------------------CONTENT------------------------------*/ #quick_notif { width: 100%; margin: 0 auto; @@ -485,7 +499,6 @@ header { } } - #content { padding: 1em 1%; box-shadow: $shadow-color 0 5px 10px; @@ -510,7 +523,7 @@ header { } &.alert-red { - background-color: rgb(255,245,245); + background-color: rgb(255, 245, 245); color: #c53030; border: #fc8181 1px solid; } @@ -554,7 +567,7 @@ header { } } -/*---------------------------------NEWS--------------------------------*/ + /*---------------------------------NEWS--------------------------------*/ #news { display: flex; flex-wrap: wrap; @@ -586,20 +599,23 @@ header { } } } - @media screen and (max-width: $small-devices){ - #left_column, #right_column { + @media screen and (max-width: $small-devices) { + #left_column, + #right_column { flex: 100%; } } -/* AGENDA/BIRTHDAYS */ - #agenda,#birthdays { + /* AGENDA/BIRTHDAYS */ + #agenda, + #birthdays { display: block; width: 100%; background: white; font-size: 70%; margin-bottom: 1em; - #agenda_title,#birthdays_title { + #agenda_title, + #birthdays_title { margin: 0em; border-radius: 5px 5px 0 0; box-shadow: $shadow-color 1px 1px 1px; @@ -615,7 +631,8 @@ header { box-shadow: $shadow-color 1px 1px 1px; height: 20em; } - #agenda_content,#birthdays_content { + #agenda_content, + #birthdays_content { .agenda_item { padding: 0.5em; margin-bottom: 0.5em; @@ -636,7 +653,7 @@ header { margin: 0em; list-style-type: none; font-weight: bold; - >li { + > li { padding: 0.5em; &:nth-child(even) { background: $secondary-neutral-light-color; @@ -652,9 +669,9 @@ header { } } } -/* END AGENDA/BIRTHDAYS */ + /* END AGENDA/BIRTHDAYS */ -/* EVENTS TODAY AND NEXT FEW DAYS */ + /* EVENTS TODAY AND NEXT FEW DAYS */ .news_events_group { box-shadow: $shadow-color 1px 1px 1px; margin-left: 1em; @@ -721,10 +738,10 @@ header { .news_content { clear: left; .button_bar { - text-align: right; + text-align: right; .fb { - color: $faceblue; - } + color: $faceblue; + } .twitter { color: $twitblue; } @@ -733,9 +750,9 @@ header { } } } -/* END EVENTS TODAY AND NEXT FEW DAYS */ + /* END EVENTS TODAY AND NEXT FEW DAYS */ -/* COMING SOON */ + /* COMING SOON */ .news_coming_soon { display: list-item; list-style-type: square; @@ -750,9 +767,9 @@ header { font-size: 0.9em; } } -/* END COMING SOON */ + /* END COMING SOON */ -/* NOTICES */ + /* NOTICES */ .news_notice { margin: 0em 0em 1em 1em; padding: 0.4em; @@ -767,9 +784,9 @@ header { margin-left: 1em; } } -/* END NOTICES */ + /* END NOTICES */ -/* CALLS */ + /* CALLS */ .news_call { margin: 0em 0em 1em 1em; padding: 0.4em; @@ -787,7 +804,7 @@ header { margin-left: 1em; } } -/* END CALLS */ + /* END CALLS */ .news_empty { margin-left: 1em; @@ -798,7 +815,7 @@ header { } } -@media screen and (max-width: $small-devices){ +@media screen and (max-width: $small-devices) { #page { width: 98%; } @@ -861,204 +878,211 @@ header { } .helptext { - margin-top: 10px; - display: block; + margin-top: 10px; + display: block; } /*---------------------------POSTERS----------------------------*/ -#poster_list, #screen_list, #poster_edit, #screen_edit{ +#poster_list, +#screen_list, +#poster_edit, +#screen_edit { + position: relative; + #title { position: relative; - #title{ - position: relative; - padding: 10px; - margin: 10px; - border-bottom: 2px solid black; - h3{ - display: flex; - justify-content: center; - align-items: center; - } - #links{ - position: absolute; - display: flex; - bottom: 5px; - &.left{ - left: 0; - } - &.right{ - right: 0; - } - .link{ - padding: 5px; - padding-left: 20px; - padding-right: 20px; - margin-left: 5px; - border-radius: 20px; - background-color: hsl(40, 100%, 50%); - color: black; - &:hover{ - color: black; - background-color: hsl(40, 58%, 50%); - } - &.delete{ - background-color: hsl(0, 100%, 40%); - } - } - } + padding: 10px; + margin: 10px; + border-bottom: 2px solid black; + h3 { + display: flex; + justify-content: center; + align-items: center; } - #posters, #screens{ - position: relative; - display: flex; - flex-wrap: wrap; - #no-posters, #no-screens{ - display: flex; - justify-content: center; - align-items: center; + #links { + position: absolute; + display: flex; + bottom: 5px; + &.left { + left: 0; + } + &.right { + right: 0; + } + .link { + padding: 5px; + padding-left: 20px; + padding-right: 20px; + margin-left: 5px; + border-radius: 20px; + background-color: hsl(40, 100%, 50%); + color: black; + &:hover { + color: black; + background-color: hsl(40, 58%, 50%); } - .poster, .screen{ - min-width: 10%; - max-width: 20%; - display: flex; - flex-direction: column; - margin: 10px; - border: 2px solid darkgrey; - border-radius: 4px; - padding: 10px; - background-color: lightgrey; - *{ - display: flex; - justify-content: center; - align-items: center; - } - .name{ - padding-bottom: 5px; - margin-bottom: 5px; - border-bottom: 1px solid whitesmoke; - } - .image{ - flex-grow: 1; - position: relative; - padding-bottom: 5px; - margin-bottom: 5px; - border-bottom: 1px solid whitesmoke; - img{ - max-height: 20vw; - max-width: 100%; - } - &:hover{ - &::before{ - position: absolute; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - flex-wrap: wrap; - top: 0; - left: 0; - z-index: 10; - content: "Click to expand"; - color: white; - background-color: rgba(black, 0.5); - } - } - } - .dates{ - padding-bottom: 5px; - margin-bottom: 5px; - border-bottom: 1px solid whitesmoke; - *{ - display: flex; - justify-content: center; - align-items: center; - flex-wrap: wrap; - margin-left: 5px; - margin-right: 5px; - } - .begin, .end{ - width: 48%; - } - .begin{ - border-right: 1px solid whitesmoke; - padding-right: 2%; - } - } - .edit, .moderate, .slideshow{ - padding: 5px; - border-radius: 20px; - background-color: hsl(40, 100%, 50%); - color: black; - &:hover{ - color: black; - background-color: hsl(40, 58%, 50%); - } - &:nth-child(2n){ - margin-top: 5px; - margin-bottom: 5px; - } - } - .tooltip{ - visibility: hidden; - width: 120px; - background-color: hsl(210, 20%, 98%); - color: hsl(0, 0%, 0%); - text-align: center; - padding: 5px 0; - border-radius: 6px; - position: absolute; - z-index: 10; - ul{ - margin-left: 0; - display: inline-block; - li{ - display: list-item; - list-style-type: none; - } - } - } - &.not_moderated - { - border: 1px solid red; - } - &:hover .tooltip{ - visibility: visible; - } + &.delete { + background-color: hsl(0, 100%, 40%); } + } } - #view{ - position: fixed; - width: 100vw; - height: 100vh; + } + #posters, + #screens { + position: relative; + display: flex; + flex-wrap: wrap; + #no-posters, + #no-screens { + display: flex; + justify-content: center; + align-items: center; + } + .poster, + .screen { + min-width: 10%; + max-width: 20%; + display: flex; + flex-direction: column; + margin: 10px; + border: 2px solid darkgrey; + border-radius: 4px; + padding: 10px; + background-color: lightgrey; + * { display: flex; justify-content: center; align-items: center; - top: 0; - left: 0; - z-index: 10; - visibility: hidden; - background-color: rgba(10, 10, 10, 0.9); - overflow: hidden; - &.active{ - visibility: visible; + } + .name { + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid whitesmoke; + } + .image { + flex-grow: 1; + position: relative; + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid whitesmoke; + img { + max-height: 20vw; + max-width: 100%; } - #placeholder{ - width: 80vw; - height: 80vh; + &:hover { + &::before { + position: absolute; + width: 100%; + height: 100%; display: flex; justify-content: center; align-items: center; + flex-wrap: wrap; top: 0; left: 0; - img{ - max-width: 100%; - max-height: 100%; - } + z-index: 10; + content: "Click to expand"; + color: white; + background-color: rgba(black, 0.5); + } } + } + .dates { + padding-bottom: 5px; + margin-bottom: 5px; + border-bottom: 1px solid whitesmoke; + * { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + margin-left: 5px; + margin-right: 5px; + } + .begin, + .end { + width: 48%; + } + .begin { + border-right: 1px solid whitesmoke; + padding-right: 2%; + } + } + .edit, + .moderate, + .slideshow { + padding: 5px; + border-radius: 20px; + background-color: hsl(40, 100%, 50%); + color: black; + &:hover { + color: black; + background-color: hsl(40, 58%, 50%); + } + &:nth-child(2n) { + margin-top: 5px; + margin-bottom: 5px; + } + } + .tooltip { + visibility: hidden; + width: 120px; + background-color: hsl(210, 20%, 98%); + color: hsl(0, 0%, 0%); + text-align: center; + padding: 5px 0; + border-radius: 6px; + position: absolute; + z-index: 10; + ul { + margin-left: 0; + display: inline-block; + li { + display: list-item; + list-style-type: none; + } + } + } + &.not_moderated { + border: 1px solid red; + } + &:hover .tooltip { + visibility: visible; + } } + } + #view { + position: fixed; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + z-index: 10; + visibility: hidden; + background-color: rgba(10, 10, 10, 0.9); + overflow: hidden; + &.active { + visibility: visible; + } + #placeholder { + width: 80vw; + height: 80vh; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + img { + max-width: 100%; + max-height: 100%; + } + } + } } - /*---------------------------ACCOUNTING----------------------------*/ #accounting { .journal-table { @@ -1084,7 +1108,12 @@ header { } /*-----------------------------GENERAL-----------------------------*/ -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { font-weight: bold; margin-top: 0.5em; } @@ -1119,19 +1148,22 @@ h6 { margin-left: 50px; } -p, pre { +p, +pre { margin-top: 0.8em; margin-left: 0; } -ul, ol, dl { +ul, +ol, +dl { margin-top: 1em; margin-bottom: 1em; margin-left: 25px; } dt { - margin-top: 25px; + margin-top: 25px; } code { @@ -1167,7 +1199,11 @@ blockquote h5:first-child { table { width: 100%; - font-size: 0.90em; + font-size: 0.9em; +} + +th { + padding: 4px; } td { @@ -1207,11 +1243,13 @@ sub { font-size: smaller; } -b, strong { +b, +strong { font-weight: bold; } -i, em { +i, +em { font-style: italic; } @@ -1220,153 +1258,157 @@ i, em { font-weight: bold; } -u, .underline { +u, +.underline { text-decoration: underline; } #bar-ui { - padding: 0.4em; - display: flex; - flex-wrap: wrap; - flex-direction: row-reverse; + padding: 0.4em; + display: flex; + flex-wrap: wrap; + flex-direction: row-reverse; - #products { - flex-basis: 100%; - margin: 0.2em; - overflow: auto; - } + #products { + flex-basis: 100%; + margin: 0.2em; + overflow: auto; + } - #click_form { - flex: auto; - margin: 0.2em; - } + #click_form { + flex: auto; + margin: 0.2em; + } - #user_info { - flex: auto; - padding: 0.5em; - margin: 0.2em; - height: 100%; - background: $secondary-neutral-light-color; - img { - max-width: 70%; - } - input { - background: white; - } + #user_info { + flex: auto; + padding: 0.5em; + margin: 0.2em; + height: 100%; + background: $secondary-neutral-light-color; + img { + max-width: 70%; } + input { + background: white; + } + } } /*-----------------------------USER PROFILE----------------------------*/ #user_profile_page { - #user_profile { + #user_profile { + display: flex; + justify-content: center; + margin-top: 2em; + margin-bottom: 4em; + #user_profile_infos { + flex-basis: 30%; + border-right: solid 1px grey; + div { + margin: 0.5em; + } + #user_profile_infos_items { + margin-top: 3em; + } + .user_profile_infos_item, + .user_profile_infos_item_value { + vertical-align: top; + display: inline-block; + width: 49%; + } + .user_profile_infos_item { + color: grey; + } + #user_profile_infos_promo { display: flex; - justify-content: center; - margin-top: 2em; - margin-bottom: 4em; - #user_profile_infos { - flex-basis: 30%; - border-right: solid 1px grey; - div { - margin: 0.5em; - } - #user_profile_infos_items { - margin-top: 3em; - } - .user_profile_infos_item, .user_profile_infos_item_value { - vertical-align: top; - display: inline-block; - width: 49%; - } - .user_profile_infos_item { - color: grey; - } - #user_profile_infos_promo { - display: flex; - align-items: center; - img { - width: 5em; - margin: 0.5em; - } - } - #user_profile_infos_quote { - text-align: right; - color: grey; - font-style: italic; - &:after, &:before { - content: "\201C"; - vertical-align: middle; - } - } + align-items: center; + img { + width: 5em; + margin: 0.5em; } - #user_profile_pictures { - height: 20em; - flex-basis: 30%; - display: flex; - justify-content: flex-end; - #user_profile_pictures_bigone { - flex-grow: 9; - flex-basis: 20em; - display: flex; - justify-content: center; - align-items: center; - img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - } - } - #user_profile_pictures_thumbnails { - flex-grow: 1; - flex-basis: 50px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - img { - margin: 0.1em; - width: 50px; - } - } - } - @media screen and (max-width: $small-devices){ - #user_profile_infos, #user_profile_pictures { - flex-basis: 50%; - } + } + #user_profile_infos_quote { + text-align: right; + color: grey; + font-style: italic; + &:after, + &:before { + content: "\201C"; + vertical-align: middle; } + } } -} - -.user_mini_profile { - height: 100%; - width: 100%; - img { - max-width: 100%; - max-height: 100%; - } - .user_mini_profile_infos { - padding: 0.2em; - height: 20%; - display: flex; - flex-wrap: nowrap; - justify-content: space-around; - font-size: 0.9em; - div { - max-height: 100%; - } - .user_mini_profile_infos_text { - text-align: center; - .user_mini_profile_nick { - font-style: italic; - } - } - } - .user_mini_profile_picture { - height: 80%; + #user_profile_pictures { + height: 20em; + flex-basis: 30%; + display: flex; + justify-content: flex-end; + #user_profile_pictures_bigone { + flex-grow: 9; + flex-basis: 20em; display: flex; justify-content: center; align-items: center; + img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + } + } + #user_profile_pictures_thumbnails { + flex-grow: 1; + flex-basis: 50px; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + img { + margin: 0.1em; + width: 50px; + } + } } + @media screen and (max-width: $small-devices) { + #user_profile_infos, + #user_profile_pictures { + flex-basis: 50%; + } + } + } +} + +.user_mini_profile { + height: 100%; + width: 100%; + img { + max-width: 100%; + max-height: 100%; + } + .user_mini_profile_infos { + padding: 0.2em; + height: 20%; + display: flex; + flex-wrap: nowrap; + justify-content: space-around; + font-size: 0.9em; + div { + max-height: 100%; + } + .user_mini_profile_infos_text { + text-align: center; + .user_mini_profile_nick { + font-style: italic; + } + } + } + .user_mini_profile_picture { + height: 80%; + display: flex; + justify-content: center; + align-items: center; + } } .mini_profile_link { @@ -1415,31 +1457,30 @@ u, .underline { } /*--------------------------------MATMAT-------------------------------*/ .matmat_results { - display: flex; - flex-wrap: wrap; - .matmat_user { - flex-basis: 14em; - align-self: flex-start; - margin: 0.5em; - height: 18em; - overflow: hidden; - border: 1px solid black; - box-shadow: $shadow-color 1px 1px 1px; - &:hover { - box-shadow: 1px 1px 5px $second-color; - } - } - .matmat_user a { - color: $primary-neutral-dark-color; - height: 100%; - width: 100%; - margin: 0em; - padding: 0em; - display: block; + display: flex; + flex-wrap: wrap; + .matmat_user { + flex-basis: 14em; + align-self: flex-start; + margin: 0.5em; + height: 18em; + overflow: hidden; + border: 1px solid black; + box-shadow: $shadow-color 1px 1px 1px; + &:hover { + box-shadow: 1px 1px 5px $second-color; } + } + .matmat_user a { + color: $primary-neutral-dark-color; + height: 100%; + width: 100%; + margin: 0em; + padding: 0em; + display: block; + } } - /*---------------------------------PAGE--------------------------------*/ .page_content { @@ -1510,7 +1551,7 @@ textarea { .last_message span { white-space: nowrap; text-overflow: ellipsis; - overflow:hidden; + overflow: hidden; width: 100%; display: block; } @@ -1720,7 +1761,8 @@ label { } } -#cash_summary_form label, .inline { +#cash_summary_form label, +.inline { display: inline; } @@ -1769,19 +1811,25 @@ label { text-decoration: underline; } .footnotes { - font-size: 85%; + font-size: 85%; } } /*--------------------------------JQuery-------------------------------*/ -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header -.ui-state-active, a.ui-button:active, .ui-button:active, +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active, +a.ui-button:active, +.ui-button:active, .ui-button.ui-state-active:hover { background: $primary-color; border-color: $primary-color; } -.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-top, +.ui-corner-all, +.ui-corner-bottom, +.ui-corner-right, +.ui-corner-top, .ui-corner-left { border-radius: 0; } @@ -1795,7 +1843,6 @@ label { max-width: 10em; } } - } /* --------------------------------------pedagogy-----------------------------------*/ @@ -1807,40 +1854,40 @@ $pedagogy-light-blue: #caf0ff; $pedagogy-white-text: #f0f0f0; .pedagogy { + &.star-not-checked { + color: #f7f7f7; + margin-bottom: 0; + margin-top: 0; + } + &.star-checked { + color: $pedagogy-orange; + margin-bottom: 0; + margin-top: 0; + } + + &.grade-without-star { + display: none; + } + + @media screen and (max-width: $large-devices) { &.star-not-checked { - color : #f7f7f7; - margin-bottom: 0; - margin-top: 0; + margin-left: 5px; + margin-right: 5px; } &.star-checked { - color: $pedagogy-orange; - margin-bottom: 0; - margin-top: 0; + margin-left: 5px; + margin-right: 5px; } + } + @media screen and (max-width: $small-devices) { &.grade-without-star { - display: none + display: block; } - - @media screen and (max-width: $large-devices){ - &.star-not-checked { - margin-left: 5px; - margin-right: 5px; - } - &.star-checked { - margin-left: 5px; - margin-right: 5px; - } - } - - @media screen and (max-width: $small-devices){ - &.grade-without-star { - display: block; - } - &.grade-with-star { - display: none; - } + &.grade-with-star { + display: none; } + } #dynamic_view { font-size: 1.1em; @@ -1851,20 +1898,18 @@ $pedagogy-white-text: #f0f0f0; text-align: center; border: none; } - } #search_form { - .search-form-container { display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto auto; grid-template-areas: - "action-bar action-bar" - "search-bar search-bar" - "radio-department radio-department" - "radio-credit-type radio-semester"; + "action-bar action-bar" + "search-bar search-bar" + "radio-department radio-department" + "radio-credit-type radio-semester"; } .action-bar { @@ -1879,13 +1924,13 @@ $pedagogy-white-text: #f0f0f0; grid-template-rows: auto; grid-template-areas: "search-bar-input search-bar-button"; - @media screen and (max-width: $medium-devices){ + @media screen and (max-width: $medium-devices) { grid-template-columns: auto auto; grid-template-rows: auto; grid-template-areas: "search-bar-input search-bar-button"; } - @media screen and (max-width: $small-devices){ + @media screen and (max-width: $small-devices) { grid-template-columns: auto; grid-template-rows: auto; grid-template-areas: "search-bar-input"; @@ -1921,8 +1966,9 @@ $pedagogy-white-text: #f0f0f0; grid-area: radio-semester; } - .radio-guide input[type="radio"],input[type="checkbox"] { - display:none; + .radio-guide input[type="radio"], + input[type="checkbox"] { + display: none; } .radio-guide { margin-top: 10px; @@ -1942,7 +1988,7 @@ $pedagogy-white-text: #f0f0f0; .radio-guide input[type="checkbox"]:checked + label { background-color: $pedagogy-orange; } - .radio-guide label:hover { + .radio-guide label:hover { background-color: $pedagogy-hover-blue; } } @@ -1956,7 +2002,7 @@ $pedagogy-white-text: #f0f0f0; grid-template-rows: auto auto; grid-template-areas: "hours-cm hours-td hours-tp hours-te hours-the" - "department credit-type semester . ." ; + "department credit-type semester . ."; } .department { @@ -2005,7 +2051,7 @@ $pedagogy-white-text: #f0f0f0; grid-template-rows: 100%; grid-template-areas: "stars comment"; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { grid-template-columns: 100%; grid-template-rows: auto auto; grid-template-areas: @@ -2033,7 +2079,7 @@ $pedagogy-white-text: #f0f0f0; color: $pedagogy-white-text; clip-path: polygon(0 0%, 0 100%, 30% 100%, 33% 0); - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { clip-path: none; } } @@ -2060,7 +2106,7 @@ $pedagogy-white-text: #f0f0f0; "grade grade-stars uv-infos" ". . uv-infos"; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { grid-template-columns: 50% 50%; grid-template-rows: auto auto; grid-template-areas: @@ -2077,7 +2123,7 @@ $pedagogy-white-text: #f0f0f0; > p { text-align: right; - font-weight: bold; + font-weight: bold; } } @@ -2104,14 +2150,14 @@ $pedagogy-white-text: #f0f0f0; margin-bottom: 30px; margin-top: 10px; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { grid-template-columns: auto; grid-template-rows: auto auto auto auto; grid-template-areas: "grade-block" "comment" "info" - "comment-end-bar" + "comment-end-bar"; } .grade-block { @@ -2131,10 +2177,10 @@ $pedagogy-white-text: #f0f0f0; background-color: $pedagogy-blue; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { grid-template-columns: 50% auto; grid-template-rows: auto; - grid-template-areas:"grade-type grade-stars"; + grid-template-areas: "grade-type grade-stars"; width: auto; clip-path: none; align-content: space-evenly; @@ -2171,7 +2217,7 @@ $pedagogy-white-text: #f0f0f0; "anchor" "markdown"; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { border-left: solid; border-right: solid; border-color: $pedagogy-blue; @@ -2199,7 +2245,7 @@ $pedagogy-white-text: #f0f0f0; grid-area: info; padding-bottom: 10px; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { border-left: solid; border-right: solid; border-color: $pedagogy-blue; @@ -2227,7 +2273,7 @@ $pedagogy-white-text: #f0f0f0; background-color: $pedagogy-blue; margin-top: -1px; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { grid-template-columns: auto; grid-template-rows: auto auto auto; grid-template-areas: @@ -2247,7 +2293,7 @@ $pedagogy-white-text: #f0f0f0; background-color: $pedagogy-orange; clip-path: polygon(0 10px, 0 100%, 350px 200%, 300px 10px); - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { clip-path: none; padding: 0; padding-bottom: 7px; @@ -2261,14 +2307,13 @@ $pedagogy-white-text: #f0f0f0; a:hover { color: $pedagogy-hover-blue; } - } .date { grid-area: date; color: $pedagogy-white-text; - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { padding-bottom: 7px; } } @@ -2287,7 +2332,7 @@ $pedagogy-white-text: #f0f0f0; color: $pedagogy-hover-blue; } - @media screen and (max-width: $large-devices){ + @media screen and (max-width: $large-devices) { text-align: center; justify-self: inherit; padding-bottom: 7px; diff --git a/core/views/user.py b/core/views/user.py index 8a8a97cd..d4ccb135 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -189,78 +189,70 @@ class UserTabsMixin(TabedViewMixin): return self.object.get_display_name() def get_list_of_tabs(self): - tab_list = [] - tab_list.append( + user: User = self.object + tab_list = [ { - "url": reverse("core:user_profile", kwargs={"user_id": self.object.id}), + "url": reverse("core:user_profile", kwargs={"user_id": user.id}), "slug": "infos", "name": _("Infos"), - } - ) - tab_list.append( + }, { - "url": reverse( - "core:user_godfathers", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_godfathers", kwargs={"user_id": user.id}), "slug": "godfathers", "name": _("Family"), - } - ) - tab_list.append( + }, { - "url": reverse( - "core:user_pictures", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_pictures", kwargs={"user_id": user.id}), "slug": "pictures", "name": _("Pictures"), - } - ) - if self.request.user == self.object: + }, + ] + if self.request.user.was_subscribed: + tab_list.append( + { + "url": reverse("galaxy:user", kwargs={"user_id": user.id}), + "slug": "galaxy", + "name": _("Galaxy"), + } + ) + if self.request.user == user: tab_list.append( {"url": reverse("core:user_tools"), "slug": "tools", "name": _("Tools")} ) - if self.request.user.can_edit(self.object): + if self.request.user.can_edit(user): tab_list.append( { - "url": reverse( - "core:user_edit", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_edit", kwargs={"user_id": user.id}), "slug": "edit", "name": _("Edit"), } ) tab_list.append( { - "url": reverse( - "core:user_prefs", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_prefs", kwargs={"user_id": user.id}), "slug": "prefs", "name": _("Preferences"), } ) - if self.request.user.can_view(self.object): + if self.request.user.can_view(user): tab_list.append( { - "url": reverse( - "core:user_clubs", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_clubs", kwargs={"user_id": user.id}), "slug": "clubs", "name": _("Clubs"), } ) - if self.request.user.is_owner(self.object): + if self.request.user.is_owner(user): tab_list.append( { - "url": reverse( - "core:user_groups", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_groups", kwargs={"user_id": user.id}), "slug": "groups", "name": _("Groups"), } ) try: - if self.object.customer and ( - self.object == self.request.user + if user.customer and ( + user == self.request.user or self.request.user.is_in_group( settings.SITH_GROUP_ACCOUNTING_ADMIN_ID ) @@ -271,9 +263,7 @@ class UserTabsMixin(TabedViewMixin): ): tab_list.append( { - "url": reverse( - "core:user_stats", kwargs={"user_id": self.object.id} - ), + "url": reverse("core:user_stats", kwargs={"user_id": user.id}), "slug": "stats", "name": _("Stats"), } @@ -281,10 +271,10 @@ class UserTabsMixin(TabedViewMixin): tab_list.append( { "url": reverse( - "core:user_account", kwargs={"user_id": self.object.id} + "core:user_account", kwargs={"user_id": user.id} ), "slug": "account", - "name": _("Account") + " (%s €)" % self.object.customer.amount, + "name": _("Account") + " (%s €)" % user.customer.amount, } ) except: diff --git a/galaxy/__init__.py b/galaxy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/galaxy/apps.py b/galaxy/apps.py new file mode 100644 index 00000000..d3b3e849 --- /dev/null +++ b/galaxy/apps.py @@ -0,0 +1,30 @@ +# -*- coding:utf-8 -* +# +# Copyright 2023 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.apps import AppConfig + + +class GalaxyConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "galaxy" diff --git a/galaxy/management/commands/rule_galaxy.py b/galaxy/management/commands/rule_galaxy.py new file mode 100644 index 00000000..1db3c975 --- /dev/null +++ b/galaxy/management/commands/rule_galaxy.py @@ -0,0 +1,61 @@ +# -*- coding:utf-8 -* +# +# Copyright 2023 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.core.management.base import BaseCommand +from django.db import connection + +from galaxy.models import Galaxy + +import logging + + +class Command(BaseCommand): + help = ( + "Rule the Galaxy! " + "Reset the whole galaxy and compute once again all the relation scores of all users. " + "As the sith's users are rather numerous, this command might be quite expensive in memory " + "and CPU time. Please keep this fact in mind when scheduling calls to this command in a production " + "environment." + ) + + def handle(self, *args, **options): + logger = logging.getLogger("main") + if options["verbosity"] > 1: + logger.setLevel(logging.DEBUG) + elif options["verbosity"] > 0: + logger.setLevel(logging.INFO) + else: + logger.setLevel(logging.NOTSET) + + logger.info("The Galaxy is being ruled by the Sith.") + Galaxy.rule() + logger.info( + "Caching current Galaxy state for a quicker display of the Empire's power." + ) + Galaxy.make_state() + + logger.info("Ruled the galaxy in {} queries.".format(len(connection.queries))) + if options["verbosity"] > 2: + for q in connection.queries: + logger.debug(q) diff --git a/galaxy/migrations/0001_initial.py b/galaxy/migrations/0001_initial.py new file mode 100644 index 00000000..8d35cccf --- /dev/null +++ b/galaxy/migrations/0001_initial.py @@ -0,0 +1,113 @@ +# Generated by Django 3.2.16 on 2023-02-03 10:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Galaxy", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("state", models.JSONField(verbose_name="current state")), + ], + ), + migrations.CreateModel( + name="GalaxyStar", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "mass", + models.PositiveIntegerField(default=0, verbose_name="star mass"), + ), + ( + "owner", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="galaxy_user", + to=settings.AUTH_USER_MODEL, + verbose_name="galaxy user", + ), + ), + ], + ), + migrations.CreateModel( + name="GalaxyLane", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "distance", + models.PositiveIntegerField( + default=0, + help_text="Distance separating star1 and star2", + verbose_name="distance", + ), + ), + ( + "family", + models.PositiveIntegerField(default=0, verbose_name="family score"), + ), + ( + "pictures", + models.PositiveIntegerField( + default=0, verbose_name="pictures score" + ), + ), + ( + "clubs", + models.PositiveIntegerField(default=0, verbose_name="clubs score"), + ), + ( + "star1", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lanes1", + to="galaxy.galaxystar", + verbose_name="galaxy lanes 1", + ), + ), + ( + "star2", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lanes2", + to="galaxy.galaxystar", + verbose_name="galaxy lanes 2", + ), + ), + ], + ), + ] diff --git a/galaxy/migrations/__init__.py b/galaxy/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/galaxy/models.py b/galaxy/models.py new file mode 100644 index 00000000..e5e2cf41 --- /dev/null +++ b/galaxy/models.py @@ -0,0 +1,377 @@ +# -*- coding:utf-8 -* +# +# Copyright 2023 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +import math +import logging + +from django.db import models +from django.db.models import Q, Case, F, Value, When, Count +from django.db.models.functions import Concat +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from typing import List, TypedDict + +from core.models import User +from club.models import Club +from sas.models import Picture + + +class GalaxyStar(models.Model): + """ + This class defines a star (vertex -> user) in the galaxy graph, storing a reference to its owner citizen, and being + referenced by GalaxyLane. + + It also stores the individual mass of this star, used to push it towards the center of the galaxy. + """ + + owner = models.OneToOneField( + User, + verbose_name=_("galaxy user"), + related_name="galaxy_user", + on_delete=models.CASCADE, + ) + mass = models.PositiveIntegerField( + _("star mass"), + default=0, + ) + + def __str__(self): + return str(self.owner) + + +class GalaxyLane(models.Model): + """ + This class defines a lane (edge -> link between galaxy citizen) in the galaxy map, storing a reference to both its + ends and the distance it covers. + Score details between citizen owning the stars is also stored here. + """ + + star1 = models.ForeignKey( + GalaxyStar, + verbose_name=_("galaxy lanes 1"), + related_name="lanes1", + on_delete=models.CASCADE, + ) + star2 = models.ForeignKey( + GalaxyStar, + verbose_name=_("galaxy lanes 2"), + related_name="lanes2", + on_delete=models.CASCADE, + ) + distance = models.PositiveIntegerField( + _("distance"), + default=0, + help_text=_("Distance separating star1 and star2"), + ) + family = models.PositiveIntegerField( + _("family score"), + default=0, + ) + pictures = models.PositiveIntegerField( + _("pictures score"), + default=0, + ) + clubs = models.PositiveIntegerField( + _("clubs score"), + default=0, + ) + + +class StarDict(TypedDict): + id: int + name: str + mass: int + + +class GalaxyDict(TypedDict): + nodes: List[StarDict] + links: List + + +class Galaxy(models.Model): + logger = logging.getLogger("main") + + GALAXY_SCALE_FACTOR = 2_000 + FAMILY_LINK_POINTS = 366 # Equivalent to a leap year together in a club, because. + PICTURE_POINTS = 2 # Equivalent to two days as random members of a club. + CLUBS_POINTS = 1 # One day together as random members in a club is one point. + + state = models.JSONField("current state") + + @staticmethod + def make_state() -> GalaxyDict: + """ + Compute JSON structure to send to 3d-force-graph: https://github.com/vasturiano/3d-force-graph/ + """ + without_nickname = Concat( + F("owner__first_name"), Value(" "), F("owner__last_name") + ) + with_nickname = Concat( + F("owner__first_name"), + Value(" "), + F("owner__last_name"), + Value(" ("), + F("owner__nick_name"), + Value(")"), + ) + stars = GalaxyStar.objects.annotate( + owner_name=Case( + When(owner__nick_name=None, then=without_nickname), + default=with_nickname, + ) + ) + lanes = GalaxyLane.objects.annotate( + star1_owner=F("star1__owner__id"), + star2_owner=F("star2__owner__id"), + ) + json = GalaxyDict( + nodes=[ + StarDict(id=star.owner_id, name=star.owner_name, mass=star.mass) + for star in stars + ], + links=[], + ) + # Make bidirectional links + # TODO: see if this impacts performance with a big graph + for path in lanes: + json["links"].append( + { + "source": path.star1_owner, + "target": path.star2_owner, + "value": path.distance, + } + ) + json["links"].append( + { + "source": path.star2_owner, + "target": path.star1_owner, + "value": path.distance, + } + ) + Galaxy.objects.all().delete() + Galaxy(state=json).save() + + ################### + # User self score # + ################### + + @classmethod + def compute_user_score(cls, user): + """ + This compute an individual score for each citizen. It will later be used by the graph algorithm to push + higher scores towards the center of the galaxy. + + Idea: This could be added to the computation: + - Forum posts + - Picture count + - Counter consumption + - Barman time + - ... + """ + user_score = 1 + user_score += cls.query_user_score(user) + + # TODO: + # Scale that value with some magic number to accommodate to typical data + # Really active galaxy citizen after 5 years typically have a score of about XXX + # Citizen that were seen regularly without taking much part in organizations typically have a score of about XXX + # Citizen that only went to a few events typically score about XXX + user_score = int(math.log2(user_score)) + + return user_score + + @classmethod + def query_user_score(cls, user): + score_query = ( + User.objects.filter(id=user.id) + .annotate( + godchildren_count=Count("godchildren", distinct=True) + * cls.FAMILY_LINK_POINTS, + godfathers_count=Count("godfathers", distinct=True) + * cls.FAMILY_LINK_POINTS, + pictures_score=Count("pictures", distinct=True) * cls.PICTURE_POINTS, + clubs_score=Count("memberships", distinct=True) * cls.CLUBS_POINTS, + ) + .aggregate( + score=models.Sum( + F("godchildren_count") + + F("godfathers_count") + + F("pictures_score") + + F("clubs_score") + ) + ) + ) + return score_query.get("score") + + #################### + # Inter-user score # + #################### + + @classmethod + def compute_users_score(cls, user1, user2): + family = cls.compute_users_family_score(user1, user2) + pictures = cls.compute_users_pictures_score(user1, user2) + clubs = cls.compute_users_clubs_score(user1, user2) + score = family + pictures + clubs + return score, family, pictures, clubs + + @classmethod + def compute_users_family_score(cls, user1, user2): + link_count = User.objects.filter( + Q(id=user1.id, godfathers=user2) | Q(id=user2.id, godfathers=user1) + ).count() + if link_count: + cls.logger.debug( + f"\t\t- '{user1}' and '{user2}' have {link_count} direct family link" + ) + return link_count * cls.FAMILY_LINK_POINTS + + @classmethod + def compute_users_pictures_score(cls, user1, user2): + picture_count = ( + Picture.objects.filter(people__user__in=(user1,)) + .filter(people__user__in=(user2,)) + .count() + ) + if picture_count: + cls.logger.debug( + f"\t\t- '{user1}' was pictured with '{user2}' {picture_count} times" + ) + return picture_count * cls.PICTURE_POINTS + + @classmethod + def compute_users_clubs_score(cls, user1, user2): + common_clubs = Club.objects.filter(members__in=user1.memberships.all()).filter( + members__in=user2.memberships.all() + ) + user1_memberships = user1.memberships.filter(club__in=common_clubs) + user2_memberships = user2.memberships.filter(club__in=common_clubs) + + score = 0 + for user1_membership in user1_memberships: + if user1_membership.end_date is None: + user1_membership.end_date = timezone.now().date() + query = Q( # start2 <= start1 <= end2 + start_date__lte=user1_membership.start_date, + end_date__gte=user1_membership.start_date, + ) + query |= Q( # start2 <= start1 <= now + start_date__lte=user1_membership.start_date, end_date=None + ) + query |= Q( # start1 <= start2 <= end2 + start_date__gte=user1_membership.start_date, + start_date__lte=user1_membership.end_date, + ) + for user2_membership in user2_memberships.filter( + query, club=user1_membership.club + ): + if user2_membership.end_date is None: + user2_membership.end_date = timezone.now().date() + latest_start = max( + user1_membership.start_date, user2_membership.start_date + ) + earliest_end = min(user1_membership.end_date, user2_membership.end_date) + cls.logger.debug( + "\t\t- '%s' was with '%s' in %s starting on %s until %s (%s days)" + % ( + user1, + user2, + user2_membership.club, + latest_start, + earliest_end, + (earliest_end - latest_start).days, + ) + ) + score += cls.CLUBS_POINTS * (earliest_end - latest_start).days + return score + + ################### + # Rule the galaxy # + ################### + + @classmethod + def rule(cls): + GalaxyStar.objects.all().delete() + # The following is a no-op thanks to cascading, but in case that changes in the future, better keep it anyway. + GalaxyLane.objects.all().delete() + rulable_users = ( + User.objects.filter(subscriptions__isnull=False) + .filter( + Q(godchildren__isnull=False) + | Q(godfathers__isnull=False) + | Q(pictures__isnull=False) + | Q(memberships__isnull=False) + ) + .distinct() + ) + # force fetch of the whole query to make sure there won't + # be any more db hits + # this is memory expensive but prevents a lot of db hits, therefore + # is far more time efficient + rulable_users = list(rulable_users) + while len(rulable_users) > 0: + user1 = rulable_users.pop() + for user2 in rulable_users: + cls.logger.debug("") + cls.logger.debug(f"\t> Ruling '{user1}' against '{user2}'") + star1, _ = GalaxyStar.objects.get_or_create(owner=user1) + star2, _ = GalaxyStar.objects.get_or_create(owner=user2) + if star1.mass == 0: + star1.mass = cls.compute_user_score(user1) + star1.save() + if star2.mass == 0: + star2.mass = cls.compute_user_score(user2) + star2.save() + users_score, family, pictures, clubs = cls.compute_users_score( + user1, user2 + ) + if users_score > 0: + GalaxyLane( + star1=star1, + star2=star2, + distance=cls.scale_distance(users_score), + family=family, + pictures=pictures, + clubs=clubs, + ).save() + + @classmethod + def scale_distance(cls, value): + # TODO: this will need adjustements with the real, typical data on Taiste + + cls.logger.debug(f"\t\t> Score: {value}") + # Invert score to draw close users together + value = 1 / value # Cannot be 0 + value += 2 # We use log2 just below and need to stay above 1 + value = ( # Let's get something in the range ]0; log2(3)-1≈0.58[ that we can multiply later + math.log2(value) - 1 + ) + value *= ( # Scale that value with a magic number to accommodate to typical data + # Really close galaxy citizen after 5 years typically have a score of about XXX + # Citizen that were in the same year without being really friends typically have a score of about XXX + # Citizen that have met once or twice only have a couple of pictures together typically score about XXX + cls.GALAXY_SCALE_FACTOR + ) + cls.logger.debug(f"\t\t> Scaled distance: {value}") + return int(value) diff --git a/galaxy/static/galaxy/js/3d-force-graph.min.js b/galaxy/static/galaxy/js/3d-force-graph.min.js new file mode 100644 index 00000000..fef0d61b --- /dev/null +++ b/galaxy/static/galaxy/js/3d-force-graph.min.js @@ -0,0 +1,5 @@ +// Version 1.70.19 3d-force-graph - https://github.com/vasturiano/3d-force-graph +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).ForceGraph3D=e()}(this,(function(){"use strict";function t(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function e(e){for(var i=1;it.length)&&(e=t.length);for(var n=0,i=new Array(e);n>8&255]+G[t>>16&255]+G[t>>24&255]+"-"+G[255&e]+G[e>>8&255]+"-"+G[e>>16&15|64]+G[e>>24&255]+"-"+G[63&n|128]+G[n>>8&255]+"-"+G[n>>16&255]+G[n>>24&255]+G[255&i]+G[i>>8&255]+G[i>>16&255]+G[i>>24&255]).toLowerCase()}function q(t,e,n){return Math.max(e,Math.min(n,t))}function X(t,e,n){return(1-n)*t+n*e}function Y(t){return 0==(t&t-1)&&0!==t}function $(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function Z(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function J(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}class K{constructor(t=0,e=0){K.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,a=this.y-t.y;return this.x=r*n-a*i+t.x,this.y=r*i+a*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Q{constructor(){Q.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1]}set(t,e,n,i,r,a,o,s,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=o,c[3]=e,c[4]=r,c[5]=s,c[6]=n,c[7]=a,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,a=n[0],o=n[3],s=n[6],l=n[1],c=n[4],u=n[7],h=n[2],d=n[5],p=n[8],f=i[0],m=i[3],g=i[6],v=i[1],_=i[4],y=i[7],x=i[2],b=i[5],w=i[8];return r[0]=a*f+o*v+s*x,r[3]=a*m+o*_+s*b,r[6]=a*g+o*y+s*w,r[1]=l*f+c*v+u*x,r[4]=l*m+c*_+u*b,r[7]=l*g+c*y+u*w,r[2]=h*f+d*v+p*x,r[5]=h*m+d*_+p*b,r[8]=h*g+d*y+p*w,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],a=t[4],o=t[5],s=t[6],l=t[7],c=t[8];return e*a*c-e*o*l-n*r*c+n*o*s+i*r*l-i*a*s}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],a=t[4],o=t[5],s=t[6],l=t[7],c=t[8],u=c*a-o*l,h=o*s-c*r,d=l*r-a*s,p=e*u+n*h+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const f=1/p;return t[0]=u*f,t[1]=(i*l-c*n)*f,t[2]=(o*n-i*a)*f,t[3]=h*f,t[4]=(c*e-i*s)*f,t[5]=(i*r-o*e)*f,t[6]=d*f,t[7]=(n*s-l*e)*f,t[8]=(a*e-n*r)*f,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,a,o){const s=Math.cos(r),l=Math.sin(r);return this.set(n*s,n*l,-n*(s*a+l*o)+a+t,-i*l,i*s,-i*(-l*a+s*o)+o+e,0,0,1),this}scale(t,e){return this.premultiply(tt.makeScale(t,e)),this}rotate(t){return this.premultiply(tt.makeRotation(-t)),this}translate(t,e){return this.premultiply(tt.makeTranslation(t,e)),this}makeTranslation(t,e){return this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,n,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const tt=new Q;function et(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}function nt(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function it(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function rt(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}const at={[N]:{[z]:it},[z]:{[N]:rt}},ot={legacyMode:!0,get workingColorSpace(){return z},set workingColorSpace(t){console.warn("THREE.ColorManagement: .workingColorSpace is readonly.")},convert:function(t,e,n){if(this.legacyMode||e===n||!e||!n)return t;if(at[e]&&void 0!==at[e][n]){const i=at[e][n];return t.r=i(t.r),t.g=i(t.g),t.b=i(t.b),t}throw new Error("Unsupported color space conversion.")},fromWorkingColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this.workingColorSpace)}},st={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},lt={r:0,g:0,b:0},ct={h:0,s:0,l:0},ut={h:0,s:0,l:0};function ht(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}function dt(t,e){return e.r=t.r,e.g=t.g,e.b=t.b,e}class pt{constructor(t,e,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,void 0===e&&void 0===n?this.set(t):this.setRGB(t,e,n)}set(t){return t&&t.isColor?this.copy(t):"number"==typeof t?this.setHex(t):"string"==typeof t&&this.setStyle(t),this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=N){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,ot.toWorkingColorSpace(this,e),this}setRGB(t,e,n,i=ot.workingColorSpace){return this.r=t,this.g=e,this.b=n,ot.toWorkingColorSpace(this,i),this}setHSL(t,e,n,i=ot.workingColorSpace){if(t=function(t,e){return(t%e+e)%e}(t,1),e=q(e,0,1),n=q(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=ht(r,i,t+1/3),this.g=ht(r,i,t),this.b=ht(r,i,t-1/3)}return ot.toWorkingColorSpace(this,i),this}setStyle(t,e=N){function n(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(t)){let t;const r=i[1],a=i[2];switch(r){case"rgb":case"rgba":if(t=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return this.r=Math.min(255,parseInt(t[1],10))/255,this.g=Math.min(255,parseInt(t[2],10))/255,this.b=Math.min(255,parseInt(t[3],10))/255,ot.toWorkingColorSpace(this,e),n(t[4]),this;if(t=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return this.r=Math.min(100,parseInt(t[1],10))/100,this.g=Math.min(100,parseInt(t[2],10))/100,this.b=Math.min(100,parseInt(t[3],10))/100,ot.toWorkingColorSpace(this,e),n(t[4]),this;break;case"hsl":case"hsla":if(t=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a)){const i=parseFloat(t[1])/360,r=parseFloat(t[2])/100,a=parseFloat(t[3])/100;return n(t[4]),this.setHSL(i,r,a,e)}}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const t=i[1],n=t.length;if(3===n)return this.r=parseInt(t.charAt(0)+t.charAt(0),16)/255,this.g=parseInt(t.charAt(1)+t.charAt(1),16)/255,this.b=parseInt(t.charAt(2)+t.charAt(2),16)/255,ot.toWorkingColorSpace(this,e),this;if(6===n)return this.r=parseInt(t.charAt(0)+t.charAt(1),16)/255,this.g=parseInt(t.charAt(2)+t.charAt(3),16)/255,this.b=parseInt(t.charAt(4)+t.charAt(5),16)/255,ot.toWorkingColorSpace(this,e),this}return t&&t.length>0?this.setColorName(t,e):this}setColorName(t,e=N){const n=st[t.toLowerCase()];return void 0!==n?this.setHex(n,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=it(t.r),this.g=it(t.g),this.b=it(t.b),this}copyLinearToSRGB(t){return this.r=rt(t.r),this.g=rt(t.g),this.b=rt(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=N){return ot.fromWorkingColorSpace(dt(this,lt),t),q(255*lt.r,0,255)<<16^q(255*lt.g,0,255)<<8^q(255*lt.b,0,255)<<0}getHexString(t=N){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=ot.workingColorSpace){ot.fromWorkingColorSpace(dt(this,lt),e);const n=lt.r,i=lt.g,r=lt.b,a=Math.max(n,i,r),o=Math.min(n,i,r);let s,l;const c=(o+a)/2;if(o===a)s=0,l=0;else{const t=a-o;switch(l=c<=.5?t/(a+o):t/(2-a-o),a){case n:s=(i-r)/t+(i2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=nt("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n.drawImage(t,0,0,t.width,t.height);const i=n.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t0&&(n.userData=this.userData),e||(t.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(300!==this.mapping)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case m:t.x=t.x-Math.floor(t.x);break;case g:t.x=t.x<0?0:1;break;case v:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case m:t.y=t.y-Math.floor(t.y);break;case g:t.y=t.y<0?0:1;break;case v:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}}yt.DEFAULT_IMAGE=null,yt.DEFAULT_MAPPING=300,yt.DEFAULT_ANISOTROPY=1;class xt{constructor(t=0,e=0,n=0,i=1){xt.prototype.isVector4=!0,this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,a=t.elements;return this.x=a[0]*e+a[4]*n+a[8]*i+a[12]*r,this.y=a[1]*e+a[5]*n+a[9]*i+a[13]*r,this.z=a[2]*e+a[6]*n+a[10]*i+a[14]*r,this.w=a[3]*e+a[7]*n+a[11]*i+a[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const a=.01,o=.1,s=t.elements,l=s[0],c=s[4],u=s[8],h=s[1],d=s[5],p=s[9],f=s[2],m=s[6],g=s[10];if(Math.abs(c-h)s&&t>v?tv?s=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),a=Math.atan2(r,e*n);t=Math.sin(t*a)/r,o=Math.sin(o*a)/r}const r=o*n;if(s=s*t+h*r,l=l*t+d*r,c=c*t+p*r,u=u*t+f*r,t===1-o){const t=1/Math.sqrt(s*s+l*l+c*c+u*u);s*=t,l*=t,c*=t,u*=t}}t[e]=s,t[e+1]=l,t[e+2]=c,t[e+3]=u}static multiplyQuaternionsFlat(t,e,n,i,r,a){const o=n[i],s=n[i+1],l=n[i+2],c=n[i+3],u=r[a],h=r[a+1],d=r[a+2],p=r[a+3];return t[e]=o*p+c*u+s*d-l*h,t[e+1]=s*p+c*h+l*u-o*d,t[e+2]=l*p+c*d+o*h-s*u,t[e+3]=c*p-o*u-s*h-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){const n=t._x,i=t._y,r=t._z,a=t._order,o=Math.cos,s=Math.sin,l=o(n/2),c=o(i/2),u=o(r/2),h=s(n/2),d=s(i/2),p=s(r/2);switch(a){case"XYZ":this._x=h*c*u+l*d*p,this._y=l*d*u-h*c*p,this._z=l*c*p+h*d*u,this._w=l*c*u-h*d*p;break;case"YXZ":this._x=h*c*u+l*d*p,this._y=l*d*u-h*c*p,this._z=l*c*p-h*d*u,this._w=l*c*u+h*d*p;break;case"ZXY":this._x=h*c*u-l*d*p,this._y=l*d*u+h*c*p,this._z=l*c*p+h*d*u,this._w=l*c*u-h*d*p;break;case"ZYX":this._x=h*c*u-l*d*p,this._y=l*d*u+h*c*p,this._z=l*c*p-h*d*u,this._w=l*c*u+h*d*p;break;case"YZX":this._x=h*c*u+l*d*p,this._y=l*d*u+h*c*p,this._z=l*c*p-h*d*u,this._w=l*c*u-h*d*p;break;case"XZY":this._x=h*c*u-l*d*p,this._y=l*d*u-h*c*p,this._z=l*c*p+h*d*u,this._w=l*c*u+h*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+a)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],a=e[1],o=e[5],s=e[9],l=e[2],c=e[6],u=e[10],h=n+o+u;if(h>0){const t=.5/Math.sqrt(h+1);this._w=.25/t,this._x=(c-s)*t,this._y=(r-l)*t,this._z=(a-i)*t}else if(n>o&&n>u){const t=2*Math.sqrt(1+n-o-u);this._w=(c-s)/t,this._x=.25*t,this._y=(i+a)/t,this._z=(r+l)/t}else if(o>u){const t=2*Math.sqrt(1+o-n-u);this._w=(r-l)/t,this._x=(i+a)/t,this._y=.25*t,this._z=(s+c)/t}else{const t=2*Math.sqrt(1+u-n-o);this._w=(a-i)/t,this._x=(r+l)/t,this._y=(s+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(q(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,a=t._w,o=e._x,s=e._y,l=e._z,c=e._w;return this._x=n*c+a*o+i*l-r*s,this._y=i*c+a*s+r*o-n*l,this._z=r*c+a*l+n*s-i*o,this._w=a*c-n*o-i*s-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,a=this._w;let o=a*t._w+n*t._x+i*t._y+r*t._z;if(o<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,o=-o):this.copy(t),o>=1)return this._w=a,this._x=n,this._y=i,this._z=r,this;const s=1-o*o;if(s<=Number.EPSILON){const t=1-e;return this._w=t*a+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(s),c=Math.atan2(l,o),u=Math.sin((1-e)*c)/l,h=Math.sin(e*c)/l;return this._w=a*u+this._w*h,this._x=n*u+this._x*h,this._y=i*u+this._y*h,this._z=r*u+this._z*h,this._onChangeCallback(),this}slerpQuaternions(t,e,n){return this.copy(t).slerp(e,n)}random(){const t=Math.random(),e=Math.sqrt(1-t),n=Math.sqrt(t),i=2*Math.PI*Math.random(),r=2*Math.PI*Math.random();return this.set(e*Math.cos(i),n*Math.sin(r),n*Math.cos(r),e*Math.sin(i))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class Et{constructor(t=0,e=0,n=0){Et.prototype.isVector3=!0,this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(At.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(At.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,a=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*a,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*a,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*a,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,a=t.y,o=t.z,s=t.w,l=s*e+a*i-o*n,c=s*n+o*e-r*i,u=s*i+r*n-a*e,h=-r*e-a*n-o*i;return this.x=l*s+h*-r+c*-o-u*-a,this.y=c*s+h*-a+u*-r-l*-o,this.z=u*s+h*-o+l*-a-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,a=e.x,o=e.y,s=e.z;return this.x=i*s-r*o,this.y=r*a-n*s,this.z=n*o-i*a,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return Tt.copy(this).projectOnVector(t),this.sub(Tt)}reflect(t){return this.sub(Tt.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(q(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=2*(Math.random()-.5),e=Math.random()*Math.PI*2,n=Math.sqrt(1-t**2);return this.x=n*Math.cos(e),this.y=n*Math.sin(e),this.z=t,this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const Tt=new Et,At=new St;class Ct{constructor(t=new Et(1/0,1/0,1/0),e=new Et(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){let e=1/0,n=1/0,i=1/0,r=-1/0,a=-1/0,o=-1/0;for(let s=0,l=t.length;sr&&(r=l),c>a&&(a=c),u>o&&(o=u)}return this.min.set(e,n,i),this.max.set(r,a,o),this}setFromBufferAttribute(t){let e=1/0,n=1/0,i=1/0,r=-1/0,a=-1/0,o=-1/0;for(let s=0,l=t.count;sr&&(r=l),c>a&&(a=c),u>o&&(o=u)}return this.min.set(e,n,i),this.max.set(r,a,o),this}setFromPoints(t){this.makeEmpty();for(let e=0,n=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,Pt),Pt.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Ut),Ft.subVectors(this.max,Ut),Dt.subVectors(t.a,Ut),Ot.subVectors(t.b,Ut),It.subVectors(t.c,Ut),kt.subVectors(Ot,Dt),Nt.subVectors(It,Ot),zt.subVectors(Dt,It);let e=[0,-kt.z,kt.y,0,-Nt.z,Nt.y,0,-zt.z,zt.y,kt.z,0,-kt.x,Nt.z,0,-Nt.x,zt.z,0,-zt.x,-kt.y,kt.x,0,-Nt.y,Nt.x,0,-zt.y,zt.x,0];return!!Gt(e,Dt,Ot,It,Ft)&&(e=[1,0,0,0,1,0,0,0,1],!!Gt(e,Dt,Ot,It,Ft)&&(Bt.crossVectors(kt,Nt),e=[Bt.x,Bt.y,Bt.z],Gt(e,Dt,Ot,It,Ft)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return Pt.copy(t).clamp(this.min,this.max).sub(t).length()}getBoundingSphere(t){return this.getCenter(t.center),t.radius=.5*this.getSize(Pt).length(),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Lt[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Lt[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Lt[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Lt[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Lt[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Lt[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Lt[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Lt[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Lt)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Lt=[new Et,new Et,new Et,new Et,new Et,new Et,new Et,new Et],Pt=new Et,Rt=new Ct,Dt=new Et,Ot=new Et,It=new Et,kt=new Et,Nt=new Et,zt=new Et,Ut=new Et,Ft=new Et,Bt=new Et,jt=new Et;function Gt(t,e,n,i,r){for(let a=0,o=t.length-3;a<=o;a+=3){jt.fromArray(t,a);const o=r.x*Math.abs(jt.x)+r.y*Math.abs(jt.y)+r.z*Math.abs(jt.z),s=e.dot(jt),l=n.dot(jt),c=i.dot(jt);if(Math.max(-Math.max(s,l,c),Math.min(s,l,c))>o)return!1}return!0}const Ht=new Ct,Vt=new Et,Wt=new Et;class qt{constructor(t=new Et,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):Ht.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Vt.subVectors(t,this.center);const e=Vt.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.addScaledVector(Vt,n/t),this.radius+=n}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(Wt.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Vt.copy(t.center).add(Wt)),this.expandByPoint(Vt.copy(t.center).sub(Wt))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Xt=new Et,Yt=new Et,$t=new Et,Zt=new Et,Jt=new Et,Kt=new Et,Qt=new Et;class te{constructor(t=new Et,e=new Et(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.direction).multiplyScalar(t).add(this.origin)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Xt)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.direction).multiplyScalar(n).add(this.origin)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Xt.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Xt.copy(this.direction).multiplyScalar(e).add(this.origin),Xt.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){Yt.copy(t).add(e).multiplyScalar(.5),$t.copy(e).sub(t).normalize(),Zt.copy(this.origin).sub(Yt);const r=.5*t.distanceTo(e),a=-this.direction.dot($t),o=Zt.dot(this.direction),s=-Zt.dot($t),l=Zt.lengthSq(),c=Math.abs(1-a*a);let u,h,d,p;if(c>0)if(u=a*s-o,h=a*o-s,p=r*c,u>=0)if(h>=-p)if(h<=p){const t=1/c;u*=t,h*=t,d=u*(u+a*h+2*o)+h*(a*u+h+2*s)+l}else h=r,u=Math.max(0,-(a*h+o)),d=-u*u+h*(h+2*s)+l;else h=-r,u=Math.max(0,-(a*h+o)),d=-u*u+h*(h+2*s)+l;else h<=-p?(u=Math.max(0,-(-a*r+o)),h=u>0?-r:Math.min(Math.max(-r,-s),r),d=-u*u+h*(h+2*s)+l):h<=p?(u=0,h=Math.min(Math.max(-r,-s),r),d=h*(h+2*s)+l):(u=Math.max(0,-(a*r+o)),h=u>0?r:Math.min(Math.max(-r,-s),r),d=-u*u+h*(h+2*s)+l);else h=a>0?-r:r,u=Math.max(0,-(a*h+o)),d=-u*u+h*(h+2*s)+l;return n&&n.copy(this.direction).multiplyScalar(u).add(this.origin),i&&i.copy($t).multiplyScalar(h).add(Yt),d}intersectSphere(t,e){Xt.subVectors(t.center,this.origin);const n=Xt.dot(this.direction),i=Xt.dot(Xt)-n*n,r=t.radius*t.radius;if(i>r)return null;const a=Math.sqrt(r-i),o=n-a,s=n+a;return o<0&&s<0?null:o<0?this.at(s,e):this.at(o,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,a,o,s;const l=1/this.direction.x,c=1/this.direction.y,u=1/this.direction.z,h=this.origin;return l>=0?(n=(t.min.x-h.x)*l,i=(t.max.x-h.x)*l):(n=(t.max.x-h.x)*l,i=(t.min.x-h.x)*l),c>=0?(r=(t.min.y-h.y)*c,a=(t.max.y-h.y)*c):(r=(t.max.y-h.y)*c,a=(t.min.y-h.y)*c),n>a||r>i?null:((r>n||isNaN(n))&&(n=r),(a=0?(o=(t.min.z-h.z)*u,s=(t.max.z-h.z)*u):(o=(t.max.z-h.z)*u,s=(t.min.z-h.z)*u),n>s||o>i?null:((o>n||n!=n)&&(n=o),(s=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Xt)}intersectTriangle(t,e,n,i,r){Jt.subVectors(e,t),Kt.subVectors(n,t),Qt.crossVectors(Jt,Kt);let a,o=this.direction.dot(Qt);if(o>0){if(i)return null;a=1}else{if(!(o<0))return null;a=-1,o=-o}Zt.subVectors(this.origin,t);const s=a*this.direction.dot(Kt.crossVectors(Zt,Kt));if(s<0)return null;const l=a*this.direction.dot(Jt.cross(Zt));if(l<0)return null;if(s+l>o)return null;const c=-a*Zt.dot(Qt);return c<0?null:this.at(c/o,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class ee{constructor(){ee.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}set(t,e,n,i,r,a,o,s,l,c,u,h,d,p,f,m){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=a,g[9]=o,g[13]=s,g[2]=l,g[6]=c,g[10]=u,g[14]=h,g[3]=d,g[7]=p,g[11]=f,g[15]=m,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new ee).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/ne.setFromMatrixColumn(t,0).length(),r=1/ne.setFromMatrixColumn(t,1).length(),a=1/ne.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*a,e[9]=n[9]*a,e[10]=n[10]*a,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,n=t.x,i=t.y,r=t.z,a=Math.cos(n),o=Math.sin(n),s=Math.cos(i),l=Math.sin(i),c=Math.cos(r),u=Math.sin(r);if("XYZ"===t.order){const t=a*c,n=a*u,i=o*c,r=o*u;e[0]=s*c,e[4]=-s*u,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-o*s,e[2]=r-t*l,e[6]=i+n*l,e[10]=a*s}else if("YXZ"===t.order){const t=s*c,n=s*u,i=l*c,r=l*u;e[0]=t+r*o,e[4]=i*o-n,e[8]=a*l,e[1]=a*u,e[5]=a*c,e[9]=-o,e[2]=n*o-i,e[6]=r+t*o,e[10]=a*s}else if("ZXY"===t.order){const t=s*c,n=s*u,i=l*c,r=l*u;e[0]=t-r*o,e[4]=-a*u,e[8]=i+n*o,e[1]=n+i*o,e[5]=a*c,e[9]=r-t*o,e[2]=-a*l,e[6]=o,e[10]=a*s}else if("ZYX"===t.order){const t=a*c,n=a*u,i=o*c,r=o*u;e[0]=s*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=s*u,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=o*s,e[10]=a*s}else if("YZX"===t.order){const t=a*s,n=a*l,i=o*s,r=o*l;e[0]=s*c,e[4]=r-t*u,e[8]=i*u+n,e[1]=u,e[5]=a*c,e[9]=-o*c,e[2]=-l*c,e[6]=n*u+i,e[10]=t-r*u}else if("XZY"===t.order){const t=a*s,n=a*l,i=o*s,r=o*l;e[0]=s*c,e[4]=-u,e[8]=l*c,e[1]=t*u+r,e[5]=a*c,e[9]=n*u-i,e[2]=i*u-n,e[6]=o*c,e[10]=r*u+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(re,t,ae)}lookAt(t,e,n){const i=this.elements;return le.subVectors(t,e),0===le.lengthSq()&&(le.z=1),le.normalize(),oe.crossVectors(n,le),0===oe.lengthSq()&&(1===Math.abs(n.z)?le.x+=1e-4:le.z+=1e-4,le.normalize(),oe.crossVectors(n,le)),oe.normalize(),se.crossVectors(le,oe),i[0]=oe.x,i[4]=se.x,i[8]=le.x,i[1]=oe.y,i[5]=se.y,i[9]=le.y,i[2]=oe.z,i[6]=se.z,i[10]=le.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,a=n[0],o=n[4],s=n[8],l=n[12],c=n[1],u=n[5],h=n[9],d=n[13],p=n[2],f=n[6],m=n[10],g=n[14],v=n[3],_=n[7],y=n[11],x=n[15],b=i[0],w=i[4],M=i[8],S=i[12],E=i[1],T=i[5],A=i[9],C=i[13],L=i[2],P=i[6],R=i[10],D=i[14],O=i[3],I=i[7],k=i[11],N=i[15];return r[0]=a*b+o*E+s*L+l*O,r[4]=a*w+o*T+s*P+l*I,r[8]=a*M+o*A+s*R+l*k,r[12]=a*S+o*C+s*D+l*N,r[1]=c*b+u*E+h*L+d*O,r[5]=c*w+u*T+h*P+d*I,r[9]=c*M+u*A+h*R+d*k,r[13]=c*S+u*C+h*D+d*N,r[2]=p*b+f*E+m*L+g*O,r[6]=p*w+f*T+m*P+g*I,r[10]=p*M+f*A+m*R+g*k,r[14]=p*S+f*C+m*D+g*N,r[3]=v*b+_*E+y*L+x*O,r[7]=v*w+_*T+y*P+x*I,r[11]=v*M+_*A+y*R+x*k,r[15]=v*S+_*C+y*D+x*N,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],a=t[1],o=t[5],s=t[9],l=t[13],c=t[2],u=t[6],h=t[10],d=t[14];return t[3]*(+r*s*u-i*l*u-r*o*h+n*l*h+i*o*d-n*s*d)+t[7]*(+e*s*d-e*l*h+r*a*h-i*a*d+i*l*c-r*s*c)+t[11]*(+e*l*u-e*o*d-r*a*u+n*a*d+r*o*c-n*l*c)+t[15]*(-i*o*c-e*s*u+e*o*h+i*a*u-n*a*h+n*s*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],a=t[4],o=t[5],s=t[6],l=t[7],c=t[8],u=t[9],h=t[10],d=t[11],p=t[12],f=t[13],m=t[14],g=t[15],v=u*m*l-f*h*l+f*s*d-o*m*d-u*s*g+o*h*g,_=p*h*l-c*m*l-p*s*d+a*m*d+c*s*g-a*h*g,y=c*f*l-p*u*l+p*o*d-a*f*d-c*o*g+a*u*g,x=p*u*s-c*f*s-p*o*h+a*f*h+c*o*m-a*u*m,b=e*v+n*_+i*y+r*x;if(0===b)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const w=1/b;return t[0]=v*w,t[1]=(f*h*r-u*m*r-f*i*d+n*m*d+u*i*g-n*h*g)*w,t[2]=(o*m*r-f*s*r+f*i*l-n*m*l-o*i*g+n*s*g)*w,t[3]=(u*s*r-o*h*r-u*i*l+n*h*l+o*i*d-n*s*d)*w,t[4]=_*w,t[5]=(c*m*r-p*h*r+p*i*d-e*m*d-c*i*g+e*h*g)*w,t[6]=(p*s*r-a*m*r-p*i*l+e*m*l+a*i*g-e*s*g)*w,t[7]=(a*h*r-c*s*r+c*i*l-e*h*l-a*i*d+e*s*d)*w,t[8]=y*w,t[9]=(p*u*r-c*f*r-p*n*d+e*f*d+c*n*g-e*u*g)*w,t[10]=(a*f*r-p*o*r+p*n*l-e*f*l-a*n*g+e*o*g)*w,t[11]=(c*o*r-a*u*r-c*n*l+e*u*l+a*n*d-e*o*d)*w,t[12]=x*w,t[13]=(c*f*i-p*u*i+p*n*h-e*f*h-c*n*m+e*u*m)*w,t[14]=(p*o*i-a*f*i-p*n*s+e*f*s+a*n*m-e*o*m)*w,t[15]=(a*u*i-c*o*i+c*n*s-e*u*s-a*n*h+e*o*h)*w,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,a=t.x,o=t.y,s=t.z,l=r*a,c=r*o;return this.set(l*a+n,l*o-i*s,l*s+i*o,0,l*o+i*s,c*o+n,c*s-i*a,0,l*s-i*o,c*s+i*a,r*s*s+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,a){return this.set(1,n,r,0,t,1,a,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,a=e._y,o=e._z,s=e._w,l=r+r,c=a+a,u=o+o,h=r*l,d=r*c,p=r*u,f=a*c,m=a*u,g=o*u,v=s*l,_=s*c,y=s*u,x=n.x,b=n.y,w=n.z;return i[0]=(1-(f+g))*x,i[1]=(d+y)*x,i[2]=(p-_)*x,i[3]=0,i[4]=(d-y)*b,i[5]=(1-(h+g))*b,i[6]=(m+v)*b,i[7]=0,i[8]=(p+_)*w,i[9]=(m-v)*w,i[10]=(1-(h+f))*w,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=ne.set(i[0],i[1],i[2]).length();const a=ne.set(i[4],i[5],i[6]).length(),o=ne.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],ie.copy(this);const s=1/r,l=1/a,c=1/o;return ie.elements[0]*=s,ie.elements[1]*=s,ie.elements[2]*=s,ie.elements[4]*=l,ie.elements[5]*=l,ie.elements[6]*=l,ie.elements[8]*=c,ie.elements[9]*=c,ie.elements[10]*=c,e.setFromRotationMatrix(ie),n.x=r,n.y=a,n.z=o,this}makePerspective(t,e,n,i,r,a){const o=this.elements,s=2*r/(e-t),l=2*r/(n-i),c=(e+t)/(e-t),u=(n+i)/(n-i),h=-(a+r)/(a-r),d=-2*a*r/(a-r);return o[0]=s,o[4]=0,o[8]=c,o[12]=0,o[1]=0,o[5]=l,o[9]=u,o[13]=0,o[2]=0,o[6]=0,o[10]=h,o[14]=d,o[3]=0,o[7]=0,o[11]=-1,o[15]=0,this}makeOrthographic(t,e,n,i,r,a){const o=this.elements,s=1/(e-t),l=1/(n-i),c=1/(a-r),u=(e+t)*s,h=(n+i)*l,d=(a+r)*c;return o[0]=2*s,o[4]=0,o[8]=0,o[12]=-u,o[1]=0,o[5]=2*l,o[9]=0,o[13]=-h,o[2]=0,o[6]=0,o[10]=-2*c,o[14]=-d,o[3]=0,o[7]=0,o[11]=0,o[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}const ne=new Et,ie=new ee,re=new Et(0,0,0),ae=new Et(1,1,1),oe=new Et,se=new Et,le=new Et,ce=new ee,ue=new St;class he{constructor(t=0,e=0,n=0,i=he.DefaultOrder){this.isEuler=!0,this._x=t,this._y=e,this._z=n,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,n,i=this._order){return this._x=t,this._y=e,this._z=n,this._order=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,n=!0){const i=t.elements,r=i[0],a=i[4],o=i[8],s=i[1],l=i[5],c=i[9],u=i[2],h=i[6],d=i[10];switch(e){case"XYZ":this._y=Math.asin(q(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-a,r)):(this._x=Math.atan2(h,l),this._z=0);break;case"YXZ":this._x=Math.asin(-q(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(o,d),this._z=Math.atan2(s,l)):(this._y=Math.atan2(-u,r),this._z=0);break;case"ZXY":this._x=Math.asin(q(h,-1,1)),Math.abs(h)<.9999999?(this._y=Math.atan2(-u,d),this._z=Math.atan2(-a,l)):(this._y=0,this._z=Math.atan2(s,r));break;case"ZYX":this._y=Math.asin(-q(u,-1,1)),Math.abs(u)<.9999999?(this._x=Math.atan2(h,d),this._z=Math.atan2(s,r)):(this._x=0,this._z=Math.atan2(-a,l));break;case"YZX":this._z=Math.asin(q(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-u,r)):(this._x=0,this._y=Math.atan2(o,d));break;case"XZY":this._z=Math.asin(-q(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(h,l),this._y=Math.atan2(o,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===n&&this._onChangeCallback(),this}setFromQuaternion(t,e,n){return ce.makeRotationFromQuaternion(t),this.setFromRotationMatrix(ce,e,n)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return ue.setFromEuler(this),this.setFromQuaternion(ue,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}toVector3(){console.error("THREE.Euler: .toVector3() has been removed. Use Vector3.setFromEuler() instead")}}he.DefaultOrder="XYZ",he.RotationOrders=["XYZ","YZX","ZXY","XZY","YXZ","ZYX"];class de{constructor(){this.mask=1}set(t){this.mask=(1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(n=n.concat(r))}return n}getWorldPosition(t){return this.updateWorldMatrix(!0,!1),t.setFromMatrixPosition(this.matrixWorld)}getWorldQuaternion(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(_e,t,ye),t}getWorldScale(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(_e,xe,t),t}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(e[8],e[9],e[10]).normalize()}raycast(){}traverse(t){t(this);const e=this.children;for(let n=0,i=e.length;n0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const n=e.shapes;if(Array.isArray(n))for(let e=0,i=n.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),o.length>0&&(n.images=o),s.length>0&&(n.shapes=s),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c),u.length>0&&(n.nodes=u)}return n.object=i,n;function a(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){Ae.subVectors(i,e),Ce.subVectors(n,e),Le.subVectors(t,e);const a=Ae.dot(Ae),o=Ae.dot(Ce),s=Ae.dot(Le),l=Ce.dot(Ce),c=Ce.dot(Le),u=a*l-o*o;if(0===u)return r.set(-2,-1,-1);const h=1/u,d=(l*s-o*c)*h,p=(a*c-o*s)*h;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return this.getBarycoord(t,e,n,i,Pe),Pe.x>=0&&Pe.y>=0&&Pe.x+Pe.y<=1}static getUV(t,e,n,i,r,a,o,s){return this.getBarycoord(t,e,n,i,Pe),s.set(0,0),s.addScaledVector(r,Pe.x),s.addScaledVector(a,Pe.y),s.addScaledVector(o,Pe.z),s}static isFrontFacing(t,e,n,i){return Ae.subVectors(n,e),Ce.subVectors(t,e),Ae.cross(Ce).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,n,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,n),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Ae.subVectors(this.c,this.b),Ce.subVectors(this.a,this.b),.5*Ae.cross(Ce).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return ze.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return ze.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,n,i,r){return ze.getUV(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return ze.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return ze.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let a,o;Re.subVectors(i,n),De.subVectors(r,n),Ie.subVectors(t,n);const s=Re.dot(Ie),l=De.dot(Ie);if(s<=0&&l<=0)return e.copy(n);ke.subVectors(t,i);const c=Re.dot(ke),u=De.dot(ke);if(c>=0&&u<=c)return e.copy(i);const h=s*u-c*l;if(h<=0&&s>=0&&c<=0)return a=s/(s-c),e.copy(n).addScaledVector(Re,a);Ne.subVectors(t,r);const d=Re.dot(Ne),p=De.dot(Ne);if(p>=0&&d<=p)return e.copy(r);const f=d*l-s*p;if(f<=0&&l>=0&&p<=0)return o=l/(l-p),e.copy(n).addScaledVector(De,o);const m=c*p-d*u;if(m<=0&&u-c>=0&&d-p>=0)return Oe.subVectors(r,i),o=(u-c)/(u-c+(d-p)),e.copy(i).addScaledVector(Oe,o);const g=1/(m+f+h);return a=f*g,o=h*g,e.copy(n).addScaledVector(Re,a).addScaledVector(De,o)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let Ue=0;class Fe extends j{constructor(){super(),this.isMaterial=!0,Object.defineProperty(this,"id",{value:Ue++}),this.uuid=W(),this.name="",this.type="Material",this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=h,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=U,this.stencilZFail=U,this.stencilZPass=U,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0,this._alphaTest=0}get alphaTest(){return this._alphaTest}set alphaTest(t){this._alphaTest>0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn("THREE.Material: '"+e+"' parameter is undefined.");continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn("THREE."+this.type+": '"+e+"' is not a property of this material.")}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.5,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),void 0!==this.sheen&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.iridescence&&(n.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(n.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(n.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=this.transparent),n.depthFunc=this.depthFunc,n.depthTest=this.depthTest,n.depthWrite=this.depthWrite,n.colorWrite=this.colorWrite,n.stencilWrite=this.stencilWrite,n.stencilWriteMask=this.stencilWriteMask,n.stencilFunc=this.stencilFunc,n.stencilRef=this.stencilRef,n.stencilFuncMask=this.stencilFuncMask,n.stencilFail=this.stencilFail,n.stencilZFail=this.stencilZFail,n.stencilZPass=this.stencilZPass,void 0!==this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(n.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=this.premultipliedAlpha),!0===this.wireframe&&(n.wireframe=this.wireframe),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=this.flatShading),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),!1===this.fog&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}class Be extends Fe{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new pt(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const je=new Et,Ge=new K;class He{constructor(t,e,n=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=n,this.usage=35044,this.updateRange={offset:0,count:-1},this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this}copyAt(t,e,n){t*=this.itemSize,n*=e.itemSize;for(let i=0,r=this.itemSize;i0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],a=[];for(let e=0,i=n.length;e0&&(i[e]=a,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const a=this.groups;a.length>0&&(t.data.groups=JSON.parse(JSON.stringify(a)));const o=this.boundingSphere;return null!==o&&(t.data.boundingSphere={center:o.center.toArray(),radius:o.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;tn.far?null:{distance:c,point:fn.clone(),object:t}}(t,e,n,i,an,on,sn,pn);if(c){r&&(un.fromBufferAttribute(r,o),hn.fromBufferAttribute(r,s),dn.fromBufferAttribute(r,l),c.uv=ze.getUV(pn,an,on,sn,un,hn,dn,new K)),a&&(un.fromBufferAttribute(a,o),hn.fromBufferAttribute(a,s),dn.fromBufferAttribute(a,l),c.uv2=ze.getUV(pn,an,on,sn,un,hn,dn,new K));const t={a:o,b:s,c:l,normal:new Et,materialIndex:0};ze.getNormal(an,on,sn,t.normal),c.face=t}return c}class vn extends tn{constructor(t=1,e=1,n=1,i=1,r=1,a=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:n,widthSegments:i,heightSegments:r,depthSegments:a};const o=this;i=Math.floor(i),r=Math.floor(r),a=Math.floor(a);const s=[],l=[],c=[],u=[];let h=0,d=0;function p(t,e,n,i,r,a,p,f,m,g,v){const _=a/m,y=p/g,x=a/2,b=p/2,w=f/2,M=m+1,S=g+1;let E=0,T=0;const A=new Et;for(let a=0;a0?1:-1,c.push(A.x,A.y,A.z),u.push(s/m),u.push(1-a/g),E+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}class Mn extends Te{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new ee,this.projectionMatrix=new ee,this.projectionMatrixInverse=new ee}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}class Sn extends Mn{constructor(t=50,e=1,n=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*V*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*H*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*V*Math.atan(Math.tan(.5*H*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,n,i,r,a){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=a,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*H*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const a=this.view;if(null!==this.view&&this.view.enabled){const t=a.fullWidth,o=a.fullHeight;r+=a.offsetX*i/t,e-=a.offsetY*n/o,i*=a.width/t,n*=a.height/o}const o=this.filmOffset;0!==o&&(r+=t*o/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const En=-90;class Tn extends Te{constructor(t,e,n){super(),this.type="CubeCamera",this.renderTarget=n;const i=new Sn(En,1,t,e);i.layers=this.layers,i.up.set(0,1,0),i.lookAt(1,0,0),this.add(i);const r=new Sn(En,1,t,e);r.layers=this.layers,r.up.set(0,1,0),r.lookAt(-1,0,0),this.add(r);const a=new Sn(En,1,t,e);a.layers=this.layers,a.up.set(0,0,-1),a.lookAt(0,1,0),this.add(a);const o=new Sn(En,1,t,e);o.layers=this.layers,o.up.set(0,0,1),o.lookAt(0,-1,0),this.add(o);const s=new Sn(En,1,t,e);s.layers=this.layers,s.up.set(0,1,0),s.lookAt(0,0,1),this.add(s);const l=new Sn(En,1,t,e);l.layers=this.layers,l.up.set(0,1,0),l.lookAt(0,0,-1),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const n=this.renderTarget,[i,r,a,o,s,l]=this.children,c=t.getRenderTarget(),u=t.toneMapping,h=t.xr.enabled;t.toneMapping=0,t.xr.enabled=!1;const d=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0),t.render(e,i),t.setRenderTarget(n,1),t.render(e,r),t.setRenderTarget(n,2),t.render(e,a),t.setRenderTarget(n,3),t.render(e,o),t.setRenderTarget(n,4),t.render(e,s),n.texture.generateMipmaps=d,t.setRenderTarget(n,5),t.render(e,l),t.setRenderTarget(c),t.toneMapping=u,t.xr.enabled=h,n.texture.needsPMREMUpdate=!0}}class An extends yt{constructor(t,e,n,i,r,a,o,s,l,c){super(t=void 0!==t?t:[],e=void 0!==e?e:d,n,i,r,a,o,s,l,c),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class Cn extends bt{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const n={width:t,height:t,depth:1},i=[n,n,n,n,n,n];this.texture=new An(i,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.encoding),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:x}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.encoding=e.encoding,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new vn(5,5,5),r=new wn({name:"CubemapFromEquirect",uniforms:_n(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const a=new mn(i,r),o=e.minFilter;e.minFilter===b&&(e.minFilter=x);return new Tn(1,10,this).update(t,a),e.minFilter=o,a.geometry.dispose(),a.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}const Ln=new Et,Pn=new Et,Rn=new Q;class Dn{constructor(t=new Et(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=Ln.subVectors(n,e).cross(Pn.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(this.normal).multiplyScalar(-this.distanceToPoint(t)).add(t)}intersectLine(t,e){const n=t.delta(Ln),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(n).multiplyScalar(r).add(t.start)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||Rn.getNormalMatrix(t),i=this.coplanarPoint(Ln).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const On=new qt,In=new Et;class kn{constructor(t=new Dn,e=new Dn,n=new Dn,i=new Dn,r=new Dn,a=new Dn){this.planes=[t,e,n,i,r,a]}set(t,e,n,i,r,a){const o=this.planes;return o[0].copy(t),o[1].copy(e),o[2].copy(n),o[3].copy(i),o[4].copy(r),o[5].copy(a),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t){const e=this.planes,n=t.elements,i=n[0],r=n[1],a=n[2],o=n[3],s=n[4],l=n[5],c=n[6],u=n[7],h=n[8],d=n[9],p=n[10],f=n[11],m=n[12],g=n[13],v=n[14],_=n[15];return e[0].setComponents(o-i,u-s,f-h,_-m).normalize(),e[1].setComponents(o+i,u+s,f+h,_+m).normalize(),e[2].setComponents(o+r,u+l,f+d,_+g).normalize(),e[3].setComponents(o-r,u-l,f-d,_-g).normalize(),e[4].setComponents(o-a,u-c,f-p,_-v).normalize(),e[5].setComponents(o+a,u+c,f+p,_+v).normalize(),this}intersectsObject(t){const e=t.geometry;return null===e.boundingSphere&&e.computeBoundingSphere(),On.copy(e.boundingSphere).applyMatrix4(t.matrixWorld),this.intersectsSphere(On)}intersectsSprite(t){return On.center.set(0,0,0),On.radius=.7071067811865476,On.applyMatrix4(t.matrixWorld),this.intersectsSphere(On)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,In.y=i.normal.y>0?t.max.y:t.min.y,In.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(In)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function Nn(){let t=null,e=!1,n=null,i=null;function r(e,a){n(e,a),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function zn(t,e){const n=e.isWebGL2,i=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),i.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const n=i.get(e);n&&(t.deleteBuffer(n.buffer),i.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=i.get(e);return void((!t||t.version 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif",iridescence_fragment:"#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\t return vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat R21 = R12;\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = dFdx( surf_pos.xyz );\n\t\tvec3 vSigmaY = dFdy( surf_pos.xyz );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#if defined( USE_ENVMAP )\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#else\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULARINTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;\n\t\t#endif\n\t\t#ifdef USE_SPECULARCOLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vUv ).rgb;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vUv ).a;\n\t#endif\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX_Iridescence( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness );\n\t#else\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * faceDirection;\n\t\t\tbitangent = bitangent * faceDirection;\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( - vViewPosition, normal, mapN, faceDirection );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n\t\treturn normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN, faceDirection );\n\t#endif\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",output_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha + 0.1;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n varying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n uniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n uniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n varying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if defined( USE_SHADOWMAP ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_COORDS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmission = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmission.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmission.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\t#ifdef texture2DLodEXT\n\t\t\treturn texture2DLodEXT( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#else\n\t\t\treturn texture2D( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#endif\n\t}\n\tvec3 applyVolumeAttenuation( const in vec3 radiance, const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn radiance;\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance * radiance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );\n\t}\n#endif",uv_pars_fragment:"#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif",uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULARINTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n\t#ifdef USE_SPECULARCOLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},Bn={common:{diffuse:{value:new pt(16777215)},opacity:{value:1},map:{value:null},uvTransform:{value:new Q},uv2Transform:{value:new Q},alphaMap:{value:null},alphaTest:{value:0}},specularmap:{specularMap:{value:null}},envmap:{envMap:{value:null},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1}},emissivemap:{emissiveMap:{value:null}},bumpmap:{bumpMap:{value:null},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalScale:{value:new K(1,1)}},displacementmap:{displacementMap:{value:null},displacementScale:{value:1},displacementBias:{value:0}},roughnessmap:{roughnessMap:{value:null}},metalnessmap:{metalnessMap:{value:null}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new pt(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new pt(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new Q}},sprite:{diffuse:{value:new pt(16777215)},opacity:{value:1},center:{value:new K(.5,.5)},rotation:{value:0},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new Q}}},jn={basic:{uniforms:yn([Bn.common,Bn.specularmap,Bn.envmap,Bn.aomap,Bn.lightmap,Bn.fog]),vertexShader:Fn.meshbasic_vert,fragmentShader:Fn.meshbasic_frag},lambert:{uniforms:yn([Bn.common,Bn.specularmap,Bn.envmap,Bn.aomap,Bn.lightmap,Bn.emissivemap,Bn.bumpmap,Bn.normalmap,Bn.displacementmap,Bn.fog,Bn.lights,{emissive:{value:new pt(0)}}]),vertexShader:Fn.meshlambert_vert,fragmentShader:Fn.meshlambert_frag},phong:{uniforms:yn([Bn.common,Bn.specularmap,Bn.envmap,Bn.aomap,Bn.lightmap,Bn.emissivemap,Bn.bumpmap,Bn.normalmap,Bn.displacementmap,Bn.fog,Bn.lights,{emissive:{value:new pt(0)},specular:{value:new pt(1118481)},shininess:{value:30}}]),vertexShader:Fn.meshphong_vert,fragmentShader:Fn.meshphong_frag},standard:{uniforms:yn([Bn.common,Bn.envmap,Bn.aomap,Bn.lightmap,Bn.emissivemap,Bn.bumpmap,Bn.normalmap,Bn.displacementmap,Bn.roughnessmap,Bn.metalnessmap,Bn.fog,Bn.lights,{emissive:{value:new pt(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:Fn.meshphysical_vert,fragmentShader:Fn.meshphysical_frag},toon:{uniforms:yn([Bn.common,Bn.aomap,Bn.lightmap,Bn.emissivemap,Bn.bumpmap,Bn.normalmap,Bn.displacementmap,Bn.gradientmap,Bn.fog,Bn.lights,{emissive:{value:new pt(0)}}]),vertexShader:Fn.meshtoon_vert,fragmentShader:Fn.meshtoon_frag},matcap:{uniforms:yn([Bn.common,Bn.bumpmap,Bn.normalmap,Bn.displacementmap,Bn.fog,{matcap:{value:null}}]),vertexShader:Fn.meshmatcap_vert,fragmentShader:Fn.meshmatcap_frag},points:{uniforms:yn([Bn.points,Bn.fog]),vertexShader:Fn.points_vert,fragmentShader:Fn.points_frag},dashed:{uniforms:yn([Bn.common,Bn.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:Fn.linedashed_vert,fragmentShader:Fn.linedashed_frag},depth:{uniforms:yn([Bn.common,Bn.displacementmap]),vertexShader:Fn.depth_vert,fragmentShader:Fn.depth_frag},normal:{uniforms:yn([Bn.common,Bn.bumpmap,Bn.normalmap,Bn.displacementmap,{opacity:{value:1}}]),vertexShader:Fn.meshnormal_vert,fragmentShader:Fn.meshnormal_frag},sprite:{uniforms:yn([Bn.sprite,Bn.fog]),vertexShader:Fn.sprite_vert,fragmentShader:Fn.sprite_frag},background:{uniforms:{uvTransform:{value:new Q},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:Fn.background_vert,fragmentShader:Fn.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1}},vertexShader:Fn.backgroundCube_vert,fragmentShader:Fn.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:Fn.cube_vert,fragmentShader:Fn.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:Fn.equirect_vert,fragmentShader:Fn.equirect_frag},distanceRGBA:{uniforms:yn([Bn.common,Bn.displacementmap,{referencePosition:{value:new Et},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:Fn.distanceRGBA_vert,fragmentShader:Fn.distanceRGBA_frag},shadow:{uniforms:yn([Bn.lights,Bn.fog,{color:{value:new pt(0)},opacity:{value:1}}]),vertexShader:Fn.shadow_vert,fragmentShader:Fn.shadow_frag}};jn.physical={uniforms:yn([jn.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatNormalScale:{value:new K(1,1)},clearcoatNormalMap:{value:null},iridescence:{value:0},iridescenceMap:{value:null},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},sheen:{value:0},sheenColor:{value:new pt(0)},sheenColorMap:{value:null},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},transmission:{value:0},transmissionMap:{value:null},transmissionSamplerSize:{value:new K},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},attenuationDistance:{value:0},attenuationColor:{value:new pt(0)},specularIntensity:{value:1},specularIntensityMap:{value:null},specularColor:{value:new pt(1,1,1)},specularColorMap:{value:null}}]),vertexShader:Fn.meshphysical_vert,fragmentShader:Fn.meshphysical_frag};const Gn={r:0,b:0,g:0};function Hn(t,e,n,i,r,a,o){const s=new pt(0);let l,c,u=!0===a?0:1,h=null,d=0,p=null;function m(e,n){e.getRGB(Gn,xn(t)),i.buffers.color.setClear(Gn.r,Gn.g,Gn.b,n,o)}return{getClearColor:function(){return s},setClearColor:function(t,e=1){s.set(t),u=e,m(s,u)},getClearAlpha:function(){return u},setClearAlpha:function(t){u=t,m(s,u)},render:function(i,a){let o=!1,g=!0===a.isScene?a.background:null;if(g&&g.isTexture){g=(a.backgroundBlurriness>0?n:e).get(g)}const v=t.xr,_=v.getSession&&v.getSession();_&&"additive"===_.environmentBlendMode&&(g=null),null===g?m(s,u):g&&g.isColor&&(m(g,1),o=!0),(t.autoClear||o)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),g&&(g.isCubeTexture||g.mapping===f)?(void 0===c&&(c=new mn(new vn(1,1,1),new wn({name:"BackgroundCubeMaterial",uniforms:_n(jn.backgroundCube.uniforms),vertexShader:jn.backgroundCube.vertexShader,fragmentShader:jn.backgroundCube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1})),c.geometry.deleteAttribute("normal"),c.geometry.deleteAttribute("uv"),c.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(c.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(c)),c.material.uniforms.envMap.value=g,c.material.uniforms.flipEnvMap.value=g.isCubeTexture&&!1===g.isRenderTargetTexture?-1:1,c.material.uniforms.backgroundBlurriness.value=a.backgroundBlurriness,c.material.uniforms.backgroundIntensity.value=a.backgroundIntensity,c.material.toneMapped=g.encoding!==k,h===g&&d===g.version&&p===t.toneMapping||(c.material.needsUpdate=!0,h=g,d=g.version,p=t.toneMapping),c.layers.enableAll(),i.unshift(c,c.geometry,c.material,0,0,null)):g&&g.isTexture&&(void 0===l&&(l=new mn(new Un(2,2),new wn({name:"BackgroundMaterial",uniforms:_n(jn.background.uniforms),vertexShader:jn.background.vertexShader,fragmentShader:jn.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1})),l.geometry.deleteAttribute("normal"),Object.defineProperty(l.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(l)),l.material.uniforms.t2D.value=g,l.material.uniforms.backgroundIntensity.value=a.backgroundIntensity,l.material.toneMapped=g.encoding!==k,!0===g.matrixAutoUpdate&&g.updateMatrix(),l.material.uniforms.uvTransform.value.copy(g.matrix),h===g&&d===g.version&&p===t.toneMapping||(l.material.needsUpdate=!0,h=g,d=g.version,p=t.toneMapping),l.layers.enableAll(),i.unshift(l,l.geometry,l.material,0,0,null))}}}function Vn(t,e,n,i){const r=t.getParameter(34921),a=i.isWebGL2?null:e.get("OES_vertex_array_object"),o=i.isWebGL2||null!==a,s={},l=p(null);let c=l,u=!1;function h(e){return i.isWebGL2?t.bindVertexArray(e):a.bindVertexArrayOES(e)}function d(e){return i.isWebGL2?t.deleteVertexArray(e):a.deleteVertexArrayOES(e)}function p(t){const e=[],n=[],i=[];for(let t=0;t=0){const n=r[e];let i=a[e];if(void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor)),void 0===n)return!0;if(n.attribute!==i)return!0;if(i&&n.data!==i.data)return!0;o++}}return c.attributesNum!==o||c.index!==i}(r,y,d,x),b&&function(t,e,n,i){const r={},a=e.attributes;let o=0;const s=n.getAttributes();for(const e in s){if(s[e].location>=0){let n=a[e];void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor));const i={};i.attribute=n,n&&n.data&&(i.data=n.data),r[e]=i,o++}}c.attributes=r,c.attributesNum=o,c.index=i}(r,y,d,x)}else{const t=!0===l.wireframe;c.geometry===y.id&&c.program===d.id&&c.wireframe===t||(c.geometry=y.id,c.program=d.id,c.wireframe=t,b=!0)}null!==x&&n.update(x,34963),(b||u)&&(u=!1,function(r,a,o,s){if(!1===i.isWebGL2&&(r.isInstancedMesh||s.isInstancedBufferGeometry)&&null===e.get("ANGLE_instanced_arrays"))return;f();const l=s.attributes,c=o.getAttributes(),u=a.defaultAttributeValues;for(const e in c){const i=c[e];if(i.location>=0){let a=l[e];if(void 0===a&&("instanceMatrix"===e&&r.instanceMatrix&&(a=r.instanceMatrix),"instanceColor"===e&&r.instanceColor&&(a=r.instanceColor)),void 0!==a){const e=a.normalized,o=a.itemSize,l=n.get(a);if(void 0===l)continue;const c=l.buffer,u=l.type,h=l.bytesPerElement;if(a.isInterleavedBufferAttribute){const n=a.data,l=n.stride,d=a.offset;if(n.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(35632,36338).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(35633,36337).precision>0&&t.getShaderPrecisionFormat(35632,36337).precision>0?"mediump":"lowp"}const a="undefined"!=typeof WebGL2RenderingContext&&t instanceof WebGL2RenderingContext||"undefined"!=typeof WebGL2ComputeRenderingContext&&t instanceof WebGL2ComputeRenderingContext;let o=void 0!==n.precision?n.precision:"highp";const s=r(o);s!==o&&(console.warn("THREE.WebGLRenderer:",o,"not supported, using",s,"instead."),o=s);const l=a||e.has("WEBGL_draw_buffers"),c=!0===n.logarithmicDepthBuffer,u=t.getParameter(34930),h=t.getParameter(35660),d=t.getParameter(3379),p=t.getParameter(34076),f=t.getParameter(34921),m=t.getParameter(36347),g=t.getParameter(36348),v=t.getParameter(36349),_=h>0,y=a||e.has("OES_texture_float");return{isWebGL2:a,drawBuffers:l,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");i=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:r,precision:o,logarithmicDepthBuffer:c,maxTextures:u,maxVertexTextures:h,maxTextureSize:d,maxCubemapSize:p,maxAttributes:f,maxVertexUniforms:m,maxVaryings:g,maxFragmentUniforms:v,vertexTextures:_,floatFragmentTextures:y,floatVertexTextures:_&&y,maxSamples:a?t.getParameter(36183):0}}function Xn(t){const e=this;let n=null,i=0,r=!1,a=!1;const o=new Dn,s=new Q,l={value:null,needsUpdate:!1};function c(){l.value!==n&&(l.value=n,l.needsUpdate=i>0),e.numPlanes=i,e.numIntersection=0}function u(t,n,i,r){const a=null!==t?t.length:0;let c=null;if(0!==a){if(c=l.value,!0!==r||null===c){const e=i+4*a,r=n.matrixWorldInverse;s.getNormalMatrix(r),(null===c||c.length0){const o=new Cn(a.height/2);return o.fromEquirectangularTexture(t,r),e.set(r,o),r.addEventListener("dispose",i),n(o.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class $n extends Mn{constructor(t=-1,e=1,n=1,i=-1,r=.1,a=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=a,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,a){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=a,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,a=n+t,o=i+e,s=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,a=r+t*this.view.width,o-=e*this.view.offsetY,s=o-e*this.view.height}this.projectionMatrix.makeOrthographic(r,a,o,s,this.near,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const Zn=[.125,.215,.35,.446,.526,.582],Jn=20,Kn=new $n,Qn=new pt;let ti=null;const ei=(1+Math.sqrt(5))/2,ni=1/ei,ii=[new Et(1,1,1),new Et(-1,1,1),new Et(1,1,-1),new Et(-1,1,-1),new Et(0,ei,ni),new Et(0,ei,-ni),new Et(ni,0,ei),new Et(-ni,0,ei),new Et(ei,ni,0),new Et(-ei,ni,0)];class ri{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){ti=this._renderer.getRenderTarget(),this._setSize(256);const r=this._allocateTargets();return r.depthBuffer=!0,this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=li(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=si(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?s=Zn[o-t+4-1]:0===o&&(s=0),i.push(s);const l=1/(a-2),c=-l,u=1+l,h=[c,c,u,c,u,u,c,c,u,u,c,u],d=6,p=6,f=3,m=2,g=1,v=new Float32Array(f*p*d),_=new Float32Array(m*p*d),y=new Float32Array(g*p*d);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];v.set(i,f*p*t),_.set(h,m*p*t);const r=[t,t,t,t,t,t];y.set(r,g*p*t)}const x=new tn;x.setAttribute("position",new He(v,f)),x.setAttribute("uv",new He(_,m)),x.setAttribute("faceIndex",new He(y,g)),e.push(x),r>4&&r--}return{lodPlanes:e,sizeLods:n,sigmas:i}}(i)),this._blurMaterial=function(t,e,n){const i=new Float32Array(Jn),r=new Et(0,1,0),a=new wn({name:"SphericalGaussianBlur",defines:{n:Jn,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:i},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:ci(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return a}(i,t,e)}return i}_compileMaterial(t){const e=new mn(this._lodPlanes[0],t);this._renderer.compile(e,Kn)}_sceneToCubeUV(t,e,n,i){const r=new Sn(90,1,e,n),a=[1,-1,1,1,1,1],o=[1,1,1,-1,-1,-1],s=this._renderer,l=s.autoClear,c=s.toneMapping;s.getClearColor(Qn),s.toneMapping=0,s.autoClear=!1;const u=new Be({name:"PMREM.Background",side:1,depthWrite:!1,depthTest:!1}),h=new mn(new vn,u);let d=!1;const p=t.background;p?p.isColor&&(u.color.copy(p),t.background=null,d=!0):(u.color.copy(Qn),d=!0);for(let e=0;e<6;e++){const n=e%3;0===n?(r.up.set(0,a[e],0),r.lookAt(o[e],0,0)):1===n?(r.up.set(0,0,a[e]),r.lookAt(0,o[e],0)):(r.up.set(0,a[e],0),r.lookAt(0,0,o[e]));const l=this._cubeSize;oi(i,n*l,e>2?l:0,l,l),s.setRenderTarget(i),d&&s.render(h,r),s.render(t,r)}h.geometry.dispose(),h.material.dispose(),s.toneMapping=c,s.autoClear=l,t.background=p}_textureToCubeUV(t,e){const n=this._renderer,i=t.mapping===d||t.mapping===p;i?(null===this._cubemapMaterial&&(this._cubemapMaterial=li()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=si());const r=i?this._cubemapMaterial:this._equirectMaterial,a=new mn(this._lodPlanes[0],r);r.uniforms.envMap.value=t;const o=this._cubeSize;oi(e,0,0,3*o,2*o),n.setRenderTarget(e),n.render(a,Kn)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;for(let e=1;eJn&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${f} samples when the maximum is set to 20`);const m=[];let g=0;for(let t=0;tv-4?i-v+4:0),4*(this._cubeSize-_),3*_,2*_),s.setRenderTarget(e),s.render(c,Kn)}}function ai(t,e,n){const i=new bt(t,e,n);return i.texture.mapping=f,i.texture.name="PMREM.cubeUv",i.scissorTest=!0,i}function oi(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function si(){return new wn({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:ci(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function li(){return new wn({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:ci(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function ci(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function ui(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const a=r.mapping,o=303===a||304===a,s=a===d||a===p;if(o||s){if(r.isRenderTargetTexture&&!0===r.needsPMREMUpdate){r.needsPMREMUpdate=!1;let i=e.get(r);return null===n&&(n=new ri(t)),i=o?n.fromEquirectangular(r,i):n.fromCubemap(r,i),e.set(r,i),i.texture}if(e.has(r))return e.get(r).texture;{const a=r.image;if(o&&a&&a.height>0||s&&a&&function(t){let e=0;const n=6;for(let i=0;ie.maxTextureSize&&(A=Math.ceil(T/e.maxTextureSize),T=e.maxTextureSize);const C=new Float32Array(T*A*4*f),L=new wt(C,T,A,f);L.type=S,L.needsUpdate=!0;const P=4*E;for(let D=0;D0)return t;const r=e*n;let a=Mi[r];if(void 0===a&&(a=new Float32Array(r),Mi[r]=a),0!==e){i.toArray(a,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(a,r)}return a}function Li(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n":" "} ${r}: ${n[t]}`)}return i.join("\n")}(t.getShaderSource(e),i)}return r}function Tr(t,e){const n=function(t){switch(t){case I:return["Linear","( value )"];case k:return["sRGB","( value )"];default:return console.warn("THREE.WebGLProgram: Unsupported encoding:",t),["Linear","( value )"]}}(e);return"vec4 "+t+"( vec4 value ) { return LinearTo"+n[0]+n[1]+"; }"}function Ar(t,e){let n;switch(e){case 1:n="Linear";break;case 2:n="Reinhard";break;case 3:n="OptimizedCineon";break;case 4:n="ACESFilmic";break;case 5:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),n="Linear"}return"vec3 "+t+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}function Cr(t){return""!==t}function Lr(t,e){const n=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function Pr(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const Rr=/^[ \t]*#include +<([\w\d./]+)>/gm;function Dr(t){return t.replace(Rr,Or)}function Or(t,e){const n=Fn[e];if(void 0===n)throw new Error("Can not resolve #include <"+e+">");return Dr(n)}const Ir=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function kr(t){return t.replace(Ir,Nr)}function Nr(t,e,n,i){let r="";for(let t=parseInt(e);t0&&(y+="\n"),x=[g,v].filter(Cr).join("\n"),x.length>0&&(x+="\n")):(y=[zr(n),"#define SHADER_NAME "+n.shaderName,v,n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.supportsVertexTextures?"#define VERTEX_TEXTURES":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+u:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.displacementMap&&n.supportsVertexTextures?"#define USE_DISPLACEMENTMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularIntensityMap?"#define USE_SPECULARINTENSITYMAP":"",n.specularColorMap?"#define USE_SPECULARCOLORMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEENCOLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEENROUGHNESSMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors&&n.isWebGL2?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Cr).join("\n"),x=[g,zr(n),"#define SHADER_NAME "+n.shaderName,v,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+c:"",n.envMap?"#define "+u:"",n.envMap?"#define "+h:"",m?"#define CUBEUV_TEXEL_WIDTH "+m.texelWidth:"",m?"#define CUBEUV_TEXEL_HEIGHT "+m.texelHeight:"",m?"#define CUBEUV_MAX_MIP "+m.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMap&&n.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",n.normalMap&&n.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularIntensityMap?"#define USE_SPECULARINTENSITYMAP":"",n.specularColorMap?"#define USE_SPECULARCOLORMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEENCOLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEENROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUvs?"#define USE_UV":"",n.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.physicallyCorrectLights?"#define PHYSICALLY_CORRECT_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?Fn.tonemapping_pars_fragment:"",0!==n.toneMapping?Ar("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",Fn.encodings_pars_fragment,Tr("linearToOutputTexel",n.outputEncoding),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Cr).join("\n")),o=Dr(o),o=Lr(o,n),o=Pr(o,n),s=Dr(s),s=Lr(s,n),s=Pr(s,n),o=kr(o),s=kr(s),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(b="#version 300 es\n",y=["precision mediump sampler2DArray;","#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+y,x=["#define varying in",n.glslVersion===F?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===F?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+x);const w=b+x+s,M=Mr(r,35633,b+y+o),S=Mr(r,35632,w);if(r.attachShader(_,M),r.attachShader(_,S),void 0!==n.index0AttributeName?r.bindAttribLocation(_,0,n.index0AttributeName):!0===n.morphTargets&&r.bindAttribLocation(_,0,"position"),r.linkProgram(_),t.debug.checkShaderErrors){const t=r.getProgramInfoLog(_).trim(),e=r.getShaderInfoLog(M).trim(),n=r.getShaderInfoLog(S).trim();let i=!0,a=!0;if(!1===r.getProgramParameter(_,35714)){i=!1;const e=Er(r,M,"vertex"),n=Er(r,S,"fragment");console.error("THREE.WebGLProgram: Shader Error "+r.getError()+" - VALIDATE_STATUS "+r.getProgramParameter(_,35715)+"\n\nProgram Info Log: "+t+"\n"+e+"\n"+n)}else""!==t?console.warn("THREE.WebGLProgram: Program Info Log:",t):""!==e&&""!==n||(a=!1);a&&(this.diagnostics={runnable:i,programLog:t,vertexShader:{log:e,prefix:y},fragmentShader:{log:n,prefix:x}})}let E,T;return r.deleteShader(M),r.deleteShader(S),this.getUniforms=function(){return void 0===E&&(E=new wr(r,_)),E},this.getAttributes=function(){return void 0===T&&(T=function(t,e){const n={},i=t.getProgramParameter(e,35721);for(let r=0;r0,O=a.clearcoat>0,N=a.iridescence>0;return{isWebGL2:u,shaderID:M,shaderName:a.type,vertexShader:T,fragmentShader:A,defines:a.defines,customVertexShaderID:C,customFragmentShaderID:L,isRawShaderMaterial:!0===a.isRawShaderMaterial,glslVersion:a.glslVersion,precision:p,instancing:!0===v.isInstancedMesh,instancingColor:!0===v.isInstancedMesh&&null!==v.instanceColor,supportsVertexTextures:d,outputEncoding:null===R?t.outputEncoding:!0===R.isXRRenderTarget?R.texture.encoding:I,map:!!a.map,matcap:!!a.matcap,envMap:!!b,envMapMode:b&&b.mapping,envMapCubeUVHeight:w,lightMap:!!a.lightMap,aoMap:!!a.aoMap,emissiveMap:!!a.emissiveMap,bumpMap:!!a.bumpMap,normalMap:!!a.normalMap,objectSpaceNormalMap:1===a.normalMapType,tangentSpaceNormalMap:0===a.normalMapType,decodeVideoTexture:!!a.map&&!0===a.map.isVideoTexture&&a.map.encoding===k,clearcoat:O,clearcoatMap:O&&!!a.clearcoatMap,clearcoatRoughnessMap:O&&!!a.clearcoatRoughnessMap,clearcoatNormalMap:O&&!!a.clearcoatNormalMap,iridescence:N,iridescenceMap:N&&!!a.iridescenceMap,iridescenceThicknessMap:N&&!!a.iridescenceThicknessMap,displacementMap:!!a.displacementMap,roughnessMap:!!a.roughnessMap,metalnessMap:!!a.metalnessMap,specularMap:!!a.specularMap,specularIntensityMap:!!a.specularIntensityMap,specularColorMap:!!a.specularColorMap,opaque:!1===a.transparent&&1===a.blending,alphaMap:!!a.alphaMap,alphaTest:D,gradientMap:!!a.gradientMap,sheen:a.sheen>0,sheenColorMap:!!a.sheenColorMap,sheenRoughnessMap:!!a.sheenRoughnessMap,transmission:a.transmission>0,transmissionMap:!!a.transmissionMap,thicknessMap:!!a.thicknessMap,combine:a.combine,vertexTangents:!!a.normalMap&&!!y.attributes.tangent,vertexColors:a.vertexColors,vertexAlphas:!0===a.vertexColors&&!!y.attributes.color&&4===y.attributes.color.itemSize,vertexUvs:!!(a.map||a.bumpMap||a.normalMap||a.specularMap||a.alphaMap||a.emissiveMap||a.roughnessMap||a.metalnessMap||a.clearcoatMap||a.clearcoatRoughnessMap||a.clearcoatNormalMap||a.iridescenceMap||a.iridescenceThicknessMap||a.displacementMap||a.transmissionMap||a.thicknessMap||a.specularIntensityMap||a.specularColorMap||a.sheenColorMap||a.sheenRoughnessMap),uvsVertexOnly:!(a.map||a.bumpMap||a.normalMap||a.specularMap||a.alphaMap||a.emissiveMap||a.roughnessMap||a.metalnessMap||a.clearcoatNormalMap||a.iridescenceMap||a.iridescenceThicknessMap||a.transmission>0||a.transmissionMap||a.thicknessMap||a.specularIntensityMap||a.specularColorMap||a.sheen>0||a.sheenColorMap||a.sheenRoughnessMap||!a.displacementMap),fog:!!_,useFog:!0===a.fog,fogExp2:_&&_.isFogExp2,flatShading:!!a.flatShading,sizeAttenuation:a.sizeAttenuation,logarithmicDepthBuffer:h,skinning:!0===v.isSkinnedMesh,morphTargets:void 0!==y.morphAttributes.position,morphNormals:void 0!==y.morphAttributes.normal,morphColors:void 0!==y.morphAttributes.color,morphTargetsCount:E,morphTextureStride:P,numDirLights:s.directional.length,numPointLights:s.point.length,numSpotLights:s.spot.length,numSpotLightMaps:s.spotLightMap.length,numRectAreaLights:s.rectArea.length,numHemiLights:s.hemi.length,numDirLightShadows:s.directionalShadowMap.length,numPointLightShadows:s.pointShadowMap.length,numSpotLightShadows:s.spotShadowMap.length,numSpotLightShadowsWithMaps:s.numSpotLightShadowsWithMaps,numClippingPlanes:o.numPlanes,numClipIntersection:o.numIntersection,dithering:a.dithering,shadowMapEnabled:t.shadowMap.enabled&&c.length>0,shadowMapType:t.shadowMap.type,toneMapping:a.toneMapped?t.toneMapping:0,physicallyCorrectLights:t.physicallyCorrectLights,premultipliedAlpha:a.premultipliedAlpha,doubleSided:2===a.side,flipSided:1===a.side,useDepthPacking:!!a.depthPacking,depthPacking:a.depthPacking||0,index0AttributeName:a.index0AttributeName,extensionDerivatives:a.extensions&&a.extensions.derivatives,extensionFragDepth:a.extensions&&a.extensions.fragDepth,extensionDrawBuffers:a.extensions&&a.extensions.drawBuffers,extensionShaderTextureLOD:a.extensions&&a.extensions.shaderTextureLOD,rendererExtensionFragDepth:u||i.has("EXT_frag_depth"),rendererExtensionDrawBuffers:u||i.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:u||i.has("EXT_shader_texture_lod"),customProgramCacheKey:a.customProgramCacheKey()}},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.customVertexShaderID),n.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputEncoding),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.combine),t.push(e.vertexUvs),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(n,e),function(t,e){s.disableAll(),e.isWebGL2&&s.enable(0);e.supportsVertexTextures&&s.enable(1);e.instancing&&s.enable(2);e.instancingColor&&s.enable(3);e.map&&s.enable(4);e.matcap&&s.enable(5);e.envMap&&s.enable(6);e.lightMap&&s.enable(7);e.aoMap&&s.enable(8);e.emissiveMap&&s.enable(9);e.bumpMap&&s.enable(10);e.normalMap&&s.enable(11);e.objectSpaceNormalMap&&s.enable(12);e.tangentSpaceNormalMap&&s.enable(13);e.clearcoat&&s.enable(14);e.clearcoatMap&&s.enable(15);e.clearcoatRoughnessMap&&s.enable(16);e.clearcoatNormalMap&&s.enable(17);e.iridescence&&s.enable(18);e.iridescenceMap&&s.enable(19);e.iridescenceThicknessMap&&s.enable(20);e.displacementMap&&s.enable(21);e.specularMap&&s.enable(22);e.roughnessMap&&s.enable(23);e.metalnessMap&&s.enable(24);e.gradientMap&&s.enable(25);e.alphaMap&&s.enable(26);e.alphaTest&&s.enable(27);e.vertexColors&&s.enable(28);e.vertexAlphas&&s.enable(29);e.vertexUvs&&s.enable(30);e.vertexTangents&&s.enable(31);e.uvsVertexOnly&&s.enable(32);t.push(s.mask),s.disableAll(),e.fog&&s.enable(0);e.useFog&&s.enable(1);e.flatShading&&s.enable(2);e.logarithmicDepthBuffer&&s.enable(3);e.skinning&&s.enable(4);e.morphTargets&&s.enable(5);e.morphNormals&&s.enable(6);e.morphColors&&s.enable(7);e.premultipliedAlpha&&s.enable(8);e.shadowMapEnabled&&s.enable(9);e.physicallyCorrectLights&&s.enable(10);e.doubleSided&&s.enable(11);e.flipSided&&s.enable(12);e.useDepthPacking&&s.enable(13);e.dithering&&s.enable(14);e.specularIntensityMap&&s.enable(15);e.specularColorMap&&s.enable(16);e.transmission&&s.enable(17);e.transmissionMap&&s.enable(18);e.thicknessMap&&s.enable(19);e.sheen&&s.enable(20);e.sheenColorMap&&s.enable(21);e.sheenRoughnessMap&&s.enable(22);e.decodeVideoTexture&&s.enable(23);e.opaque&&s.enable(24);t.push(s.mask)}(n,e),n.push(t.outputEncoding)),n.push(e.customProgramCacheKey),n.join()},getUniforms:function(t){const e=m[t.type];let n;if(e){const t=jn[e];n=bn.clone(t.uniforms)}else n=t.uniforms;return n},acquireProgram:function(e,n){let i;for(let t=0,e=c.length;t0?i.push(u):!0===o.transparent?r.push(u):n.push(u)},unshift:function(t,e,o,s,l,c){const u=a(t,e,o,s,l,c);o.transmission>0?i.unshift(u):!0===o.transparent?r.unshift(u):n.unshift(u)},finish:function(){for(let n=e,i=t.length;n1&&n.sort(t||Vr),i.length>1&&i.sort(e||Wr),r.length>1&&r.sort(e||Wr)}}}function Xr(){let t=new WeakMap;return{get:function(e,n){const i=t.get(e);let r;return void 0===i?(r=new qr,t.set(e,[r])):n>=i.length?(r=new qr,i.push(r)):r=i[n],r},dispose:function(){t=new WeakMap}}}function Yr(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new Et,color:new pt};break;case"SpotLight":n={position:new Et,direction:new Et,color:new pt,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new Et,color:new pt,distance:0,decay:0};break;case"HemisphereLight":n={direction:new Et,skyColor:new pt,groundColor:new pt};break;case"RectAreaLight":n={color:new pt,position:new Et,halfWidth:new Et,halfHeight:new Et}}return t[e.id]=n,n}}}let $r=0;function Zr(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function Jr(t,e){const n=new Yr,i=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new K};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new K,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0};for(let t=0;t<9;t++)r.probe.push(new Et);const a=new Et,o=new ee,s=new ee;return{setup:function(a,o){let s=0,l=0,c=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let u=0,h=0,d=0,p=0,f=0,m=0,g=0,v=0,_=0,y=0;a.sort(Zr);const x=!0!==o?Math.PI:1;for(let t=0,e=a.length;t0&&(e.isWebGL2||!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=Bn.LTC_FLOAT_1,r.rectAreaLTC2=Bn.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=Bn.LTC_HALF_1,r.rectAreaLTC2=Bn.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=s,r.ambient[1]=l,r.ambient[2]=c;const b=r.hash;b.directionalLength===u&&b.pointLength===h&&b.spotLength===d&&b.rectAreaLength===p&&b.hemiLength===f&&b.numDirectionalShadows===m&&b.numPointShadows===g&&b.numSpotShadows===v&&b.numSpotMaps===_||(r.directional.length=u,r.spot.length=d,r.rectArea.length=p,r.point.length=h,r.hemi.length=f,r.directionalShadow.length=m,r.directionalShadowMap.length=m,r.pointShadow.length=g,r.pointShadowMap.length=g,r.spotShadow.length=v,r.spotShadowMap.length=v,r.directionalShadowMatrix.length=m,r.pointShadowMatrix.length=g,r.spotLightMatrix.length=v+_-y,r.spotLightMap.length=_,r.numSpotLightShadowsWithMaps=y,b.directionalLength=u,b.pointLength=h,b.spotLength=d,b.rectAreaLength=p,b.hemiLength=f,b.numDirectionalShadows=m,b.numPointShadows=g,b.numSpotShadows=v,b.numSpotMaps=_,r.version=$r++)},setupView:function(t,e){let n=0,i=0,l=0,c=0,u=0;const h=e.matrixWorldInverse;for(let e=0,d=t.length;e=a.length?(o=new Kr(t,e),a.push(o)):o=a[r],o},dispose:function(){n=new WeakMap}}}class ta extends Fe{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class ea extends Fe{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.referencePosition=new Et,this.nearDistance=1,this.farDistance=1e3,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.referencePosition.copy(t.referencePosition),this.nearDistance=t.nearDistance,this.farDistance=t.farDistance,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function na(t,e,n){let i=new kn;const r=new K,a=new K,o=new xt,s=new ta({depthPacking:3201}),l=new ea,c={},u=n.maxTextureSize,h={0:1,1:0,2:2},d=new wn({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new K},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),p=d.clone();p.defines.HORIZONTAL_PASS=1;const f=new tn;f.setAttribute("position",new He(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const m=new mn(f,d),g=this;function v(n,i){const a=e.update(m);d.defines.VSM_SAMPLES!==n.blurSamples&&(d.defines.VSM_SAMPLES=n.blurSamples,p.defines.VSM_SAMPLES=n.blurSamples,d.needsUpdate=!0,p.needsUpdate=!0),null===n.mapPass&&(n.mapPass=new bt(r.x,r.y)),d.uniforms.shadow_pass.value=n.map.texture,d.uniforms.resolution.value=n.mapSize,d.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,a,d,m,null),p.uniforms.shadow_pass.value=n.mapPass.texture,p.uniforms.resolution.value=n.mapSize,p.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,a,p,m,null)}function y(e,n,i,r,a,o){let u=null;const d=!0===i.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==d)u=d;else if(u=!0===i.isPointLight?l:s,t.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0){const t=u.uuid,e=n.uuid;let i=c[t];void 0===i&&(i={},c[t]=i);let r=i[e];void 0===r&&(r=u.clone(),i[e]=r),u=r}return u.visible=n.visible,u.wireframe=n.wireframe,u.side=3===o?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:h[n.side],u.alphaMap=n.alphaMap,u.alphaTest=n.alphaTest,u.map=n.map,u.clipShadows=n.clipShadows,u.clippingPlanes=n.clippingPlanes,u.clipIntersection=n.clipIntersection,u.displacementMap=n.displacementMap,u.displacementScale=n.displacementScale,u.displacementBias=n.displacementBias,u.wireframeLinewidth=n.wireframeLinewidth,u.linewidth=n.linewidth,!0===i.isPointLight&&!0===u.isMeshDistanceMaterial&&(u.referencePosition.setFromMatrixPosition(i.matrixWorld),u.nearDistance=r,u.farDistance=a),u}function x(n,r,a,o,s){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===s)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(a.matrixWorldInverse,n.matrixWorld);const i=e.update(n),r=n.material;if(Array.isArray(r)){const e=i.groups;for(let l=0,c=e.length;lu||r.y>u)&&(r.x>u&&(a.x=Math.floor(u/p.x),r.x=a.x*p.x,h.mapSize.x=a.x),r.y>u&&(a.y=Math.floor(u/p.y),r.y=a.y*p.y,h.mapSize.y=a.y)),null===h.map){const t=3!==this.type?{minFilter:_,magFilter:_}:{};h.map=new bt(r.x,r.y,t),h.map.texture.name=c.name+".shadowMap",h.camera.updateProjectionMatrix()}t.setRenderTarget(h.map),t.clear();const f=h.getViewportCount();for(let t=0;t=1):-1!==D.indexOf("OpenGL ES")&&(R=parseFloat(/^OpenGL ES (\d)/.exec(D)[1]),P=R>=2);let O=null,I={};const k=t.getParameter(3088),N=t.getParameter(2978),z=(new xt).fromArray(k),U=(new xt).fromArray(N);function F(e,n,i){const r=new Uint8Array(4),a=t.createTexture();t.bindTexture(e,a),t.texParameteri(e,10241,9728),t.texParameteri(e,10240,9728);for(let e=0;ei||t.height>i)&&(r=i/Math.max(t.width,t.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const i=e?$:Math.floor,a=i(r*t.width),o=i(r*t.height);void 0===P&&(P=O(a,o));const s=n?O(a,o):P;s.width=a,s.height=o;return s.getContext("2d").drawImage(t,0,0,a,o),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+t.width+"x"+t.height+") to ("+a+"x"+o+")."),s}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+t.width+"x"+t.height+")."),t}return t}function z(t){return Y(t.width)&&Y(t.height)}function U(t,e){return t.generateMipmaps&&e&&t.minFilter!==_&&t.minFilter!==x}function F(e){t.generateMipmap(e)}function j(n,i,r,a,o=!1){if(!1===s)return i;if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let l=i;return 6403===i&&(5126===r&&(l=33326),5131===r&&(l=33325),5121===r&&(l=33321)),33319===i&&(5126===r&&(l=33328),5131===r&&(l=33327),5121===r&&(l=33323)),6408===i&&(5126===r&&(l=34836),5131===r&&(l=34842),5121===r&&(l=a===k&&!1===o?35907:32856),32819===r&&(l=32854),32820===r&&(l=32855)),33325!==l&&33326!==l&&33327!==l&&33328!==l&&34842!==l&&34836!==l||e.get("EXT_color_buffer_float"),l}function G(t,e,n){return!0===U(t,n)||t.isFramebufferTexture&&t.minFilter!==_&&t.minFilter!==x?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function H(t){return t===_||1004===t||t===y?9728:9729}function V(t){const e=t.target;e.removeEventListener("dispose",V),function(t){const e=i.get(t);if(void 0===e.__webglInit)return;const n=t.source,r=R.get(n);if(r){const i=r[e.__cacheKey];i.usedTimes--,0===i.usedTimes&&q(t),0===Object.keys(r).length&&R.delete(n)}i.remove(t)}(e),e.isVideoTexture&&f.delete(e)}function W(e){const n=e.target;n.removeEventListener("dispose",W),function(e){const n=e.texture,r=i.get(e),a=i.get(n);void 0!==a.__webglTexture&&(t.deleteTexture(a.__webglTexture),o.memory.textures--);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++)t.deleteFramebuffer(r.__webglFramebuffer[e]),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer[e]);else{if(t.deleteFramebuffer(r.__webglFramebuffer),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer),r.__webglMultisampledFramebuffer&&t.deleteFramebuffer(r.__webglMultisampledFramebuffer),r.__webglColorRenderbuffer)for(let e=0;e0&&r.__version!==t.version){const n=t.image;if(null===n)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==n.complete)return void et(r,t,e);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.bindTexture(3553,r.__webglTexture,33984+e)}const J={[m]:10497,[g]:33071,[v]:33648},K={[_]:9728,1004:9984,[y]:9986,[x]:9729,1007:9985,[b]:9987};function Q(n,a,o){if(o?(t.texParameteri(n,10242,J[a.wrapS]),t.texParameteri(n,10243,J[a.wrapT]),32879!==n&&35866!==n||t.texParameteri(n,32882,J[a.wrapR]),t.texParameteri(n,10240,K[a.magFilter]),t.texParameteri(n,10241,K[a.minFilter])):(t.texParameteri(n,10242,33071),t.texParameteri(n,10243,33071),32879!==n&&35866!==n||t.texParameteri(n,32882,33071),a.wrapS===g&&a.wrapT===g||console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),t.texParameteri(n,10240,H(a.magFilter)),t.texParameteri(n,10241,H(a.minFilter)),a.minFilter!==_&&a.minFilter!==x&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),!0===e.has("EXT_texture_filter_anisotropic")){const o=e.get("EXT_texture_filter_anisotropic");if(a.magFilter===_)return;if(a.minFilter!==y&&a.minFilter!==b)return;if(a.type===S&&!1===e.has("OES_texture_float_linear"))return;if(!1===s&&a.type===E&&!1===e.has("OES_texture_half_float_linear"))return;(a.anisotropy>1||i.get(a).__currentAnisotropy)&&(t.texParameterf(n,o.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(a.anisotropy,r.getMaxAnisotropy())),i.get(a).__currentAnisotropy=a.anisotropy)}}function tt(e,n){let i=!1;void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",V));const r=n.source;let a=R.get(r);void 0===a&&(a={},R.set(r,a));const s=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.encoding),e.join()}(n);if(s!==e.__cacheKey){void 0===a[s]&&(a[s]={texture:t.createTexture(),usedTimes:0},o.memory.textures++,i=!0),a[s].usedTimes++;const r=a[e.__cacheKey];void 0!==r&&(a[e.__cacheKey].usedTimes--,0===r.usedTimes&&q(n)),e.__cacheKey=s,e.__webglTexture=a[s].texture}return i}function et(e,r,o){let l=3553;(r.isDataArrayTexture||r.isCompressedArrayTexture)&&(l=35866),r.isData3DTexture&&(l=32879);const c=tt(e,r),h=r.source;n.bindTexture(l,e.__webglTexture,33984+o);const d=i.get(h);if(h.version!==d.__version||!0===c){n.activeTexture(33984+o),t.pixelStorei(37440,r.flipY),t.pixelStorei(37441,r.premultiplyAlpha),t.pixelStorei(3317,r.unpackAlignment),t.pixelStorei(37443,0);const e=function(t){return!s&&(t.wrapS!==g||t.wrapT!==g||t.minFilter!==_&&t.minFilter!==x)}(r)&&!1===z(r.image);let i=N(r.image,e,!1,u);i=lt(r,i);const p=z(i)||s,f=a.convert(r.format,r.encoding);let m,v=a.convert(r.type),y=j(r.internalFormat,f,v,r.encoding,r.isVideoTexture);Q(l,r,p);const b=r.mipmaps,w=s&&!0!==r.isVideoTexture,E=void 0===d.__version||!0===c,P=G(r,i,p);if(r.isDepthTexture)y=6402,s?y=r.type===S?36012:r.type===M?33190:r.type===T?35056:33189:r.type===S&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),r.format===C&&6402===y&&1012!==r.type&&r.type!==M&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),r.type=M,v=a.convert(r.type)),r.format===L&&6402===y&&(y=34041,r.type!==T&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),r.type=T,v=a.convert(r.type))),E&&(w?n.texStorage2D(3553,1,y,i.width,i.height):n.texImage2D(3553,0,y,i.width,i.height,0,f,v,null));else if(r.isDataTexture)if(b.length>0&&p){w&&E&&n.texStorage2D(3553,P,y,b[0].width,b[0].height);for(let t=0,e=b.length;t>=1,e>>=1}}else if(b.length>0&&p){w&&E&&n.texStorage2D(3553,P,y,b[0].width,b[0].height);for(let t=0,e=b.length;t=34069&&l<=34074)&&t.framebufferTexture2D(36160,s,l,i.get(o).__webglTexture,0),n.bindFramebuffer(36160,null)}function rt(e,n,i){if(t.bindRenderbuffer(36161,e),n.depthBuffer&&!n.stencilBuffer){let r=33189;if(i||st(n)){const e=n.depthTexture;e&&e.isDepthTexture&&(e.type===S?r=36012:e.type===M&&(r=33190));const i=ot(n);st(n)?d.renderbufferStorageMultisampleEXT(36161,i,r,n.width,n.height):t.renderbufferStorageMultisample(36161,i,r,n.width,n.height)}else t.renderbufferStorage(36161,r,n.width,n.height);t.framebufferRenderbuffer(36160,36096,36161,e)}else if(n.depthBuffer&&n.stencilBuffer){const r=ot(n);i&&!1===st(n)?t.renderbufferStorageMultisample(36161,r,35056,n.width,n.height):st(n)?d.renderbufferStorageMultisampleEXT(36161,r,35056,n.width,n.height):t.renderbufferStorage(36161,34041,n.width,n.height),t.framebufferRenderbuffer(36160,33306,36161,e)}else{const e=!0===n.isWebGLMultipleRenderTargets?n.texture:[n.texture];for(let r=0;r0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==n.__useRenderToTexture}function lt(t,n){const i=t.encoding,r=t.format,a=t.type;return!0===t.isCompressedTexture||!0===t.isVideoTexture||t.format===B||i!==I&&(i===k?!1===s?!0===e.has("EXT_sRGB")&&r===A?(t.format=B,t.minFilter=x,t.generateMipmaps=!1):n=mt.sRGBToLinear(n):r===A&&a===w||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture encoding:",i)),n}this.allocateTextureUnit=function(){const t=X;return t>=l&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+l),X+=1,t},this.resetTextureUnits=function(){X=0},this.setTexture2D=Z,this.setTexture2DArray=function(t,e){const r=i.get(t);t.version>0&&r.__version!==t.version?et(r,t,e):n.bindTexture(35866,r.__webglTexture,33984+e)},this.setTexture3D=function(t,e){const r=i.get(t);t.version>0&&r.__version!==t.version?et(r,t,e):n.bindTexture(32879,r.__webglTexture,33984+e)},this.setTextureCube=function(e,r){const o=i.get(e);e.version>0&&o.__version!==e.version?function(e,r,o){if(6!==r.image.length)return;const l=tt(e,r),u=r.source;n.bindTexture(34067,e.__webglTexture,33984+o);const h=i.get(u);if(u.version!==h.__version||!0===l){n.activeTexture(33984+o),t.pixelStorei(37440,r.flipY),t.pixelStorei(37441,r.premultiplyAlpha),t.pixelStorei(3317,r.unpackAlignment),t.pixelStorei(37443,0);const e=r.isCompressedTexture||r.image[0].isCompressedTexture,i=r.image[0]&&r.image[0].isDataTexture,d=[];for(let t=0;t<6;t++)d[t]=e||i?i?r.image[t].image:r.image[t]:N(r.image[t],!1,!0,c),d[t]=lt(r,d[t]);const p=d[0],f=z(p)||s,m=a.convert(r.format,r.encoding),g=a.convert(r.type),v=j(r.internalFormat,m,g,r.encoding),_=s&&!0!==r.isVideoTexture,y=void 0===h.__version||!0===l;let x,b=G(r,p,f);if(Q(34067,r,f),e){_&&y&&n.texStorage2D(34067,b,v,p.width,p.height);for(let t=0;t<6;t++){x=d[t].mipmaps;for(let e=0;e0&&b++,n.texStorage2D(34067,b,v,d[0].width,d[0].height));for(let t=0;t<6;t++)if(i){_?n.texSubImage2D(34069+t,0,0,0,d[t].width,d[t].height,m,g,d[t].data):n.texImage2D(34069+t,0,v,d[t].width,d[t].height,0,m,g,d[t].data);for(let e=0;e0&&!1===st(e)){const i=d?l:[l];c.__webglMultisampledFramebuffer=t.createFramebuffer(),c.__webglColorRenderbuffer=[],n.bindFramebuffer(36160,c.__webglMultisampledFramebuffer);for(let n=0;n0&&!1===st(e)){const r=e.isWebGLMultipleRenderTargets?e.texture:[e.texture],a=e.width,o=e.height;let s=16384;const l=[],c=e.stencilBuffer?33306:36096,u=i.get(e),h=!0===e.isWebGLMultipleRenderTargets;if(h)for(let e=0;es+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&o<=s-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==s&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(s.matrix.fromArray(r.transform.matrix),s.matrix.decompose(s.position,s.rotation,s.scale),r.linearVelocity?(s.hasLinearVelocity=!0,s.linearVelocity.copy(r.linearVelocity)):s.hasLinearVelocity=!1,r.angularVelocity?(s.hasAngularVelocity=!0,s.angularVelocity.copy(r.angularVelocity)):s.hasAngularVelocity=!1));null!==o&&(i=e.getPose(t.targetRaySpace,n),null===i&&null!==r&&(i=r),null!==i&&(o.matrix.fromArray(i.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),i.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(i.linearVelocity)):o.hasLinearVelocity=!1,i.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(i.angularVelocity)):o.hasAngularVelocity=!1,this.dispatchEvent(la)))}return null!==o&&(o.visible=null!==i),null!==s&&(s.visible=null!==r),null!==l&&(l.visible=null!==a),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const n=new sa;n.matrixAutoUpdate=!1,n.visible=!1,t.joints[e.jointName]=n,t.add(n)}return t.joints[e.jointName]}}class ua extends yt{constructor(t,e,n,i,r,a,o,s,l,c){if((c=void 0!==c?c:C)!==C&&c!==L)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===n&&c===C&&(n=M),void 0===n&&c===L&&(n=T),super(null,i,r,a,o,s,c,n,l),this.isDepthTexture=!0,this.image={width:t,height:e},this.magFilter=void 0!==o?o:_,this.minFilter=void 0!==s?s:_,this.flipY=!1,this.generateMipmaps=!1}}class ha extends j{constructor(t,e){super();const n=this;let i=null,r=1,a=null,o="local-floor",s=null,l=null,c=null,u=null,h=null,d=null;const p=e.getContextAttributes();let f=null,m=null;const g=[],v=[],_=new Set,y=new Map,x=new Sn;x.layers.enable(1),x.viewport=new xt;const b=new Sn;b.layers.enable(2),b.viewport=new xt;const S=[x,b],E=new oa;E.layers.enable(1),E.layers.enable(2);let P=null,R=null;function D(t){const e=v.indexOf(t.inputSource);if(-1===e)return;const n=g[e];void 0!==n&&n.dispatchEvent({type:t.type,data:t.inputSource})}function O(){i.removeEventListener("select",D),i.removeEventListener("selectstart",D),i.removeEventListener("selectend",D),i.removeEventListener("squeeze",D),i.removeEventListener("squeezestart",D),i.removeEventListener("squeezeend",D),i.removeEventListener("end",O),i.removeEventListener("inputsourceschange",I);for(let t=0;t=0&&(v[i]=null,g[i].disconnect(n))}for(let e=0;e=v.length){v.push(n),i=t;break}if(null===v[t]){v[t]=n,i=t;break}}if(-1===i)break}const r=g[i];r&&r.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(t){let e=g[t];return void 0===e&&(e=new ca,g[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=g[t];return void 0===e&&(e=new ca,g[t]=e),e.getGripSpace()},this.getHand=function(t){let e=g[t];return void 0===e&&(e=new ca,g[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){o=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return s||a},this.setReferenceSpace=function(t){s=t},this.getBaseLayer=function(){return null!==u?u:h},this.getBinding=function(){return c},this.getFrame=function(){return d},this.getSession=function(){return i},this.setSession=async function(l){if(i=l,null!==i){if(f=t.getRenderTarget(),i.addEventListener("select",D),i.addEventListener("selectstart",D),i.addEventListener("selectend",D),i.addEventListener("squeeze",D),i.addEventListener("squeezestart",D),i.addEventListener("squeezeend",D),i.addEventListener("end",O),i.addEventListener("inputsourceschange",I),!0!==p.xrCompatible&&await e.makeXRCompatible(),void 0===i.renderState.layers||!1===t.capabilities.isWebGL2){const n={antialias:void 0!==i.renderState.layers||p.antialias,alpha:p.alpha,depth:p.depth,stencil:p.stencil,framebufferScaleFactor:r};h=new XRWebGLLayer(i,e,n),i.updateRenderState({baseLayer:h}),m=new bt(h.framebufferWidth,h.framebufferHeight,{format:A,type:w,encoding:t.outputEncoding,stencilBuffer:p.stencil})}else{let n=null,a=null,o=null;p.depth&&(o=p.stencil?35056:33190,n=p.stencil?L:C,a=p.stencil?T:M);const s={colorFormat:32856,depthFormat:o,scaleFactor:r};c=new XRWebGLBinding(i,e),u=c.createProjectionLayer(s),i.updateRenderState({layers:[u]}),m=new bt(u.textureWidth,u.textureHeight,{format:A,type:w,depthTexture:new ua(u.textureWidth,u.textureHeight,a,void 0,void 0,void 0,void 0,void 0,void 0,n),stencilBuffer:p.stencil,encoding:t.outputEncoding,samples:p.antialias?4:0});t.properties.get(m).__ignoreDepthValues=u.ignoreDepthValues}m.isXRRenderTarget=!0,this.setFoveation(1),s=null,a=await i.requestReferenceSpace(o),F.setContext(i),F.start(),n.isPresenting=!0,n.dispatchEvent({type:"sessionstart"})}};const k=new Et,N=new Et;function z(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCamera=function(t){if(null===i)return;E.near=b.near=x.near=t.near,E.far=b.far=x.far=t.far,P===E.near&&R===E.far||(i.updateRenderState({depthNear:E.near,depthFar:E.far}),P=E.near,R=E.far);const e=t.parent,n=E.cameras;z(E,e);for(let t=0;te&&(y.set(t,t.lastChangedTime),n.dispatchEvent({type:"planechanged",data:t}))}else _.add(t),y.set(t,i.lastChangedTime),n.dispatchEvent({type:"planeadded",data:t})}d=null})),this.setAnimationLoop=function(t){U=t},this.dispose=function(){}}}function da(t,e){function n(n,i){n.opacity.value=i.opacity,i.color&&n.diffuse.value.copy(i.color),i.emissive&&n.emissive.value.copy(i.emissive).multiplyScalar(i.emissiveIntensity),i.map&&(n.map.value=i.map),i.alphaMap&&(n.alphaMap.value=i.alphaMap),i.bumpMap&&(n.bumpMap.value=i.bumpMap,n.bumpScale.value=i.bumpScale,1===i.side&&(n.bumpScale.value*=-1)),i.displacementMap&&(n.displacementMap.value=i.displacementMap,n.displacementScale.value=i.displacementScale,n.displacementBias.value=i.displacementBias),i.emissiveMap&&(n.emissiveMap.value=i.emissiveMap),i.normalMap&&(n.normalMap.value=i.normalMap,n.normalScale.value.copy(i.normalScale),1===i.side&&n.normalScale.value.negate()),i.specularMap&&(n.specularMap.value=i.specularMap),i.alphaTest>0&&(n.alphaTest.value=i.alphaTest);const r=e.get(i).envMap;if(r&&(n.envMap.value=r,n.flipEnvMap.value=r.isCubeTexture&&!1===r.isRenderTargetTexture?-1:1,n.reflectivity.value=i.reflectivity,n.ior.value=i.ior,n.refractionRatio.value=i.refractionRatio),i.lightMap){n.lightMap.value=i.lightMap;const e=!0!==t.physicallyCorrectLights?Math.PI:1;n.lightMapIntensity.value=i.lightMapIntensity*e}let a,o;i.aoMap&&(n.aoMap.value=i.aoMap,n.aoMapIntensity.value=i.aoMapIntensity),i.map?a=i.map:i.specularMap?a=i.specularMap:i.displacementMap?a=i.displacementMap:i.normalMap?a=i.normalMap:i.bumpMap?a=i.bumpMap:i.roughnessMap?a=i.roughnessMap:i.metalnessMap?a=i.metalnessMap:i.alphaMap?a=i.alphaMap:i.emissiveMap?a=i.emissiveMap:i.clearcoatMap?a=i.clearcoatMap:i.clearcoatNormalMap?a=i.clearcoatNormalMap:i.clearcoatRoughnessMap?a=i.clearcoatRoughnessMap:i.iridescenceMap?a=i.iridescenceMap:i.iridescenceThicknessMap?a=i.iridescenceThicknessMap:i.specularIntensityMap?a=i.specularIntensityMap:i.specularColorMap?a=i.specularColorMap:i.transmissionMap?a=i.transmissionMap:i.thicknessMap?a=i.thicknessMap:i.sheenColorMap?a=i.sheenColorMap:i.sheenRoughnessMap&&(a=i.sheenRoughnessMap),void 0!==a&&(a.isWebGLRenderTarget&&(a=a.texture),!0===a.matrixAutoUpdate&&a.updateMatrix(),n.uvTransform.value.copy(a.matrix)),i.aoMap?o=i.aoMap:i.lightMap&&(o=i.lightMap),void 0!==o&&(o.isWebGLRenderTarget&&(o=o.texture),!0===o.matrixAutoUpdate&&o.updateMatrix(),n.uv2Transform.value.copy(o.matrix))}return{refreshFogUniforms:function(e,n){n.color.getRGB(e.fogColor.value,xn(t)),n.isFog?(e.fogNear.value=n.near,e.fogFar.value=n.far):n.isFogExp2&&(e.fogDensity.value=n.density)},refreshMaterialUniforms:function(t,i,r,a,o){i.isMeshBasicMaterial||i.isMeshLambertMaterial?n(t,i):i.isMeshToonMaterial?(n(t,i),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,i)):i.isMeshPhongMaterial?(n(t,i),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,i)):i.isMeshStandardMaterial?(n(t,i),function(t,n){t.roughness.value=n.roughness,t.metalness.value=n.metalness,n.roughnessMap&&(t.roughnessMap.value=n.roughnessMap);n.metalnessMap&&(t.metalnessMap.value=n.metalnessMap);const i=e.get(n).envMap;i&&(t.envMapIntensity.value=n.envMapIntensity)}(t,i),i.isMeshPhysicalMaterial&&function(t,e,n){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap),e.clearcoatNormalMap&&(t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),t.clearcoatNormalMap.value=e.clearcoatNormalMap,1===e.side&&t.clearcoatNormalScale.value.negate()));e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=n.texture,t.transmissionSamplerSize.value.set(n.width,n.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap);e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap)}(t,i,o)):i.isMeshMatcapMaterial?(n(t,i),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,i)):i.isMeshDepthMaterial?n(t,i):i.isMeshDistanceMaterial?(n(t,i),function(t,e){t.referencePosition.value.copy(e.referencePosition),t.nearDistance.value=e.nearDistance,t.farDistance.value=e.farDistance}(t,i)):i.isMeshNormalMaterial?n(t,i):i.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity}(t,i),i.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,i)):i.isPointsMaterial?function(t,e,n,i){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*n,t.scale.value=.5*i,e.map&&(t.map.value=e.map);e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest);let r;e.map?r=e.map:e.alphaMap&&(r=e.alphaMap);void 0!==r&&(!0===r.matrixAutoUpdate&&r.updateMatrix(),t.uvTransform.value.copy(r.matrix))}(t,i,r,a):i.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map);e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest);let n;e.map?n=e.map:e.alphaMap&&(n=e.alphaMap);void 0!==n&&(!0===n.matrixAutoUpdate&&n.updateMatrix(),t.uvTransform.value.copy(n.matrix))}(t,i):i.isShadowMaterial?(t.color.value.copy(i.color),t.opacity.value=i.opacity):i.isShaderMaterial&&(i.uniformsNeedUpdate=!1)}}}function pa(t,e,n,i){let r={},a={},o=[];const s=n.isWebGL2?t.getParameter(35375):0;function l(t,e,n){const i=t.value;if(void 0===n[e]){if("number"==typeof i)n[e]=i;else{const t=Array.isArray(i)?i:[i],r=[];for(let e=0;e0){r=n%i;0!==r&&i-r-o.boundary<0&&(n+=i-r,a.__offset=n)}n+=o.storage}r=n%i,r>0&&(n+=i-r);t.__size=n,t.__cache={}}(n),d=function(e){const n=function(){for(let t=0;t0&&function(t,e,n){const i=J.isWebGL2;null===H&&(H=new bt(1,1,{generateMipmaps:!0,type:Z.has("EXT_color_buffer_half_float")?E:w,minFilter:b,samples:i&&!0===o?4:0}));g.getDrawingBufferSize(W),i?H.setSize(W.x,W.y):H.setSize($(W.x),$(W.y));const r=g.getRenderTarget();g.setRenderTarget(H),g.clear();const a=g.toneMapping;g.toneMapping=0,Ft(t,e,n),g.toneMapping=a,it.updateMultisampleRenderTarget(H),it.updateRenderTargetMipmap(H),g.setRenderTarget(r)}(r,e,n),i&&Q.viewport(C.copy(i)),r.length>0&&Ft(r,e,n),a.length>0&&Ft(a,e,n),s.length>0&&Ft(s,e,n),Q.buffers.depth.setTest(!0),Q.buffers.depth.setMask(!0),Q.buffers.color.setMask(!0),Q.setPolygonOffset(!1)}function Ft(t,e,n){const i=!0===e.isScene?e.overrideMaterial:null;for(let r=0,a=t.length;r0?m[m.length-1]:null,f.pop(),d=f.length>0?f[f.length-1]:null},this.getActiveCubeFace=function(){return _},this.getActiveMipmapLevel=function(){return y},this.getRenderTarget=function(){return x},this.setRenderTargetTextures=function(t,e,n){et.get(t.texture).__webglTexture=e,et.get(t.depthTexture).__webglTexture=n;const i=et.get(t);i.__hasExternalTextures=!0,i.__hasExternalTextures&&(i.__autoAllocateDepthBuffer=void 0===n,i.__autoAllocateDepthBuffer||!0===Z.has("WEBGL_multisampled_render_to_texture")&&(console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided"),i.__useRenderToTexture=!1))},this.setRenderTargetFramebuffer=function(t,e){const n=et.get(t);n.__webglFramebuffer=e,n.__useDefaultFramebuffer=void 0===e},this.setRenderTarget=function(t,e=0,n=0){x=t,_=e,y=n;let i=!0,r=null,a=!1,o=!1;if(t){const n=et.get(t);void 0!==n.__useDefaultFramebuffer?(Q.bindFramebuffer(36160,null),i=!1):void 0===n.__webglFramebuffer?it.setupRenderTarget(t):n.__hasExternalTextures&&it.rebindTextures(t,et.get(t.texture).__webglTexture,et.get(t.depthTexture).__webglTexture);const s=t.texture;(s.isData3DTexture||s.isDataArrayTexture||s.isCompressedArrayTexture)&&(o=!0);const l=et.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=l[e],a=!0):r=J.isWebGL2&&t.samples>0&&!1===it.useMultisampledRTT(t)?et.get(t).__webglMultisampledFramebuffer:l,C.copy(t.viewport),L.copy(t.scissor),P=t.scissorTest}else C.copy(z).multiplyScalar(O).floor(),L.copy(U).multiplyScalar(O).floor(),P=F;if(Q.bindFramebuffer(36160,r)&&J.drawBuffers&&i&&Q.drawBuffers(t,r),Q.viewport(C),Q.scissor(L),Q.setScissorTest(P),a){const i=et.get(t.texture);St.framebufferTexture2D(36160,36064,34069+e,i.__webglTexture,n)}else if(o){const i=et.get(t.texture),r=e||0;St.framebufferTextureLayer(36160,36064,i.__webglTexture,n||0,r)}M=-1},this.readRenderTargetPixels=function(t,e,n,i,r,a,o){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let s=et.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==o&&(s=s[o]),s){Q.bindFramebuffer(36160,s);try{const o=t.texture,s=o.format,l=o.type;if(s!==A&&yt.convert(s)!==St.getParameter(35739))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=l===E&&(Z.has("EXT_color_buffer_half_float")||J.isWebGL2&&Z.has("EXT_color_buffer_float"));if(!(l===w||yt.convert(l)===St.getParameter(35738)||l===S&&(J.isWebGL2||Z.has("OES_texture_float")||Z.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&St.readPixels(e,n,i,r,yt.convert(s),yt.convert(l),a)}finally{const t=null!==x?et.get(x).__webglFramebuffer:null;Q.bindFramebuffer(36160,t)}}},this.copyFramebufferToTexture=function(t,e,n=0){const i=Math.pow(2,-n),r=Math.floor(e.image.width*i),a=Math.floor(e.image.height*i);it.setTexture2D(e,0),St.copyTexSubImage2D(3553,n,0,0,t.x,t.y,r,a),Q.unbindTexture()},this.copyTextureToTexture=function(t,e,n,i=0){const r=e.image.width,a=e.image.height,o=yt.convert(n.format),s=yt.convert(n.type);it.setTexture2D(n,0),St.pixelStorei(37440,n.flipY),St.pixelStorei(37441,n.premultiplyAlpha),St.pixelStorei(3317,n.unpackAlignment),e.isDataTexture?St.texSubImage2D(3553,i,t.x,t.y,r,a,o,s,e.image.data):e.isCompressedTexture?St.compressedTexSubImage2D(3553,i,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,o,e.mipmaps[0].data):St.texSubImage2D(3553,i,t.x,t.y,o,s,e.image),0===i&&n.generateMipmaps&&St.generateMipmap(3553),Q.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n,i,r=0){if(g.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const a=t.max.x-t.min.x+1,o=t.max.y-t.min.y+1,s=t.max.z-t.min.z+1,l=yt.convert(i.format),c=yt.convert(i.type);let u;if(i.isData3DTexture)it.setTexture3D(i,0),u=32879;else{if(!i.isDataArrayTexture)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");it.setTexture2DArray(i,0),u=35866}St.pixelStorei(37440,i.flipY),St.pixelStorei(37441,i.premultiplyAlpha),St.pixelStorei(3317,i.unpackAlignment);const h=St.getParameter(3314),d=St.getParameter(32878),p=St.getParameter(3316),f=St.getParameter(3315),m=St.getParameter(32877),v=n.isCompressedTexture?n.mipmaps[0]:n.image;St.pixelStorei(3314,v.width),St.pixelStorei(32878,v.height),St.pixelStorei(3316,t.min.x),St.pixelStorei(3315,t.min.y),St.pixelStorei(32877,t.min.z),n.isDataTexture||n.isData3DTexture?St.texSubImage3D(u,r,e.x,e.y,e.z,a,o,s,l,c,v.data):n.isCompressedArrayTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),St.compressedTexSubImage3D(u,r,e.x,e.y,e.z,a,o,s,l,v.data)):St.texSubImage3D(u,r,e.x,e.y,e.z,a,o,s,l,c,v),St.pixelStorei(3314,h),St.pixelStorei(32878,d),St.pixelStorei(3316,p),St.pixelStorei(3315,f),St.pixelStorei(32877,m),0===r&&i.generateMipmaps&&St.generateMipmap(u),Q.unbindTexture()},this.initTexture=function(t){t.isCubeTexture?it.setTextureCube(t,0):t.isData3DTexture?it.setTexture3D(t,0):t.isDataArrayTexture||t.isCompressedArrayTexture?it.setTexture2DArray(t,0):it.setTexture2D(t,0),Q.unbindTexture()},this.resetState=function(){_=0,y=0,x=null,Q.reset(),wt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}(class extends fa{}).prototype.isWebGL1Renderer=!0;class ma extends Fe{constructor(t){super(),this.isLineBasicMaterial=!0,this.type="LineBasicMaterial",this.color=new pt(16777215),this.linewidth=1,this.linecap="round",this.linejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.linewidth=t.linewidth,this.linecap=t.linecap,this.linejoin=t.linejoin,this.fog=t.fog,this}}const ga=new Et,va=new Et,_a=new ee,ya=new te,xa=new qt;class ba{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const n=this.getUtoTmapping(t);return this.getPoint(n,e)}getPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPoint(n/t));return e}getSpacedPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPointAt(n/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let n,i=this.getPoint(0),r=0;e.push(0);for(let a=1;a<=t;a++)n=this.getPoint(a/t),r+=n.distanceTo(i),e.push(r),i=n;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const n=this.getLengths();let i=0;const r=n.length;let a;a=e||t*n[r-1];let o,s=0,l=r-1;for(;s<=l;)if(i=Math.floor(s+(l-s)/2),o=n[i]-a,o<0)s=i+1;else{if(!(o>0)){l=i;break}l=i-1}if(i=l,n[i]===a)return i/(r-1);const c=n[i];return(i+(a-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const a=this.getPoint(i),o=this.getPoint(r),s=e||(a.isVector2?new K:new Et);return s.copy(o).sub(a).normalize(),s}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new Et,i=[],r=[],a=[],o=new Et,s=new ee;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new Et)}r[0]=new Et,a[0]=new Et;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),u=Math.abs(i[0].y),h=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),u<=l&&(l=u,n.set(0,1,0)),h<=l&&n.set(0,0,1),o.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],o),a[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),a[e]=a[e-1].clone(),o.crossVectors(i[e-1],i[e]),o.length()>Number.EPSILON){o.normalize();const t=Math.acos(q(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(s.makeRotationAxis(o,t))}a[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(q(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(o.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(s.makeRotationAxis(i[n],e*n)),a[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:a}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.5,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class wa extends ba{constructor(t=0,e=0,n=1,i=1,r=0,a=2*Math.PI,o=!1,s=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=a,this.aClockwise=o,this.aRotation=s}getPoint(t,e){const n=e||new K,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const a=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?o=i[(l-1)%r]:(Sa.subVectors(i[0],i[1]).add(i[0]),o=Sa);const u=i[l%r],h=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:a+1],u=i[a>i.length-3?i.length-1:a+2];return n.set(Ca(o,s.x,l.x,c.x,u.x),Ca(o,s.y,l.y,c.y,u.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new qe(u,3)),this.setAttribute("normal",new qe(h,3)),this.setAttribute("uv",new qe(d,2))}static fromJSON(t){return new Ia(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class ka extends Ia{constructor(t=1,e=1,n=32,i=1,r=!1,a=0,o=2*Math.PI){super(0,t,e,n,i,r,a,o),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:a,thetaLength:o}}static fromJSON(t){return new ka(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class Na extends tn{constructor(t=1,e=32,n=16,i=0,r=2*Math.PI,a=0,o=Math.PI){super(),this.type="SphereGeometry",this.parameters={radius:t,widthSegments:e,heightSegments:n,phiStart:i,phiLength:r,thetaStart:a,thetaLength:o},e=Math.max(3,Math.floor(e)),n=Math.max(2,Math.floor(n));const s=Math.min(a+o,Math.PI);let l=0;const c=[],u=new Et,h=new Et,d=[],p=[],f=[],m=[];for(let d=0;d<=n;d++){const g=[],v=d/n;let _=0;0==d&&0==a?_=.5/e:d==n&&s==Math.PI&&(_=-.5/e);for(let n=0;n<=e;n++){const s=n/e;u.x=-t*Math.cos(i+s*r)*Math.sin(a+v*o),u.y=t*Math.cos(a+v*o),u.z=t*Math.sin(i+s*r)*Math.sin(a+v*o),p.push(u.x,u.y,u.z),h.copy(u).normalize(),f.push(h.x,h.y,h.z),m.push(s+_,1-v),g.push(l++)}c.push(g)}for(let t=0;t0)&&d.push(e,r,l),(t!==n-1||s0){const t=a[0].object;eo.setFromNormalAndCoplanarPoint(e.getWorldDirection(eo.normal),oo.setFromMatrixPosition(t.matrixWorld)),r!==t&&null!==r&&(o.dispatchEvent({type:"hoveroff",object:r}),n.style.cursor="auto",r=null),r!==t&&(o.dispatchEvent({type:"hoveron",object:t}),n.style.cursor="pointer",r=t)}else null!==r&&(o.dispatchEvent({type:"hoveroff",object:r}),n.style.cursor="auto",r=null)}}function u(r){!1!==o.enabled&&(d(r),a.length=0,no.setFromCamera(io,e),no.intersectObjects(t,!0,a),a.length>0&&(i=!0===o.transformGroup?t[0]:a[0].object,eo.setFromNormalAndCoplanarPoint(e.getWorldDirection(eo.normal),oo.setFromMatrixPosition(i.matrixWorld)),no.ray.intersectPlane(eo,ao)&&(so.copy(i.parent.matrixWorld).invert(),ro.copy(ao).sub(oo.setFromMatrixPosition(i.matrixWorld))),n.style.cursor="move",o.dispatchEvent({type:"dragstart",object:i})))}function h(){!1!==o.enabled&&(i&&(o.dispatchEvent({type:"dragend",object:i}),i=null),n.style.cursor=r?"pointer":"auto")}function d(t){const e=n.getBoundingClientRect();io.x=(t.clientX-e.left)/e.width*2-1,io.y=-(t.clientY-e.top)/e.height*2+1}s(),this.enabled=!0,this.transformGroup=!1,this.activate=s,this.deactivate=l,this.dispose=function(){l()},this.getObjects=function(){return t},this.getRaycaster=function(){return no}}}function co(t,e,n){var i,r=1;function a(){var a,o,s=i.length,l=0,c=0,u=0;for(a=0;a=(r=(h+d)/2))?h=r:d=r,i=c,!(c=c[s=+o]))return i[s]=u,t;if(e===(a=+t._x.call(null,c.data)))return u.next=c,i?i[s]=u:t._root=u,t;do{i=i?i[s]=new Array(2):t._root=new Array(2),(o=e>=(r=(h+d)/2))?h=r:d=r}while((s=+o)==(l=+(a>=r)));return i[l]=c,i[s]=u,t}function ho(t,e,n){this.node=t,this.x0=e,this.x1=n}function po(t){return t[0]}function fo(t,e){var n=new mo(null==e?po:e,NaN,NaN);return null==t?n:n.addAll(t)}function mo(t,e,n){this._x=t,this._x0=e,this._x1=n,this._root=void 0}function go(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var vo=fo.prototype=mo.prototype;function _o(t,e,n,i){if(isNaN(e)||isNaN(n))return t;var r,a,o,s,l,c,u,h,d,p=t._root,f={data:i},m=t._x0,g=t._y0,v=t._x1,_=t._y1;if(!p)return t._root=f,t;for(;p.length;)if((c=e>=(a=(m+v)/2))?m=a:v=a,(u=n>=(o=(g+_)/2))?g=o:_=o,r=p,!(p=p[h=u<<1|c]))return r[h]=f,t;if(s=+t._x.call(null,p.data),l=+t._y.call(null,p.data),e===s&&n===l)return f.next=p,r?r[h]=f:t._root=f,t;do{r=r?r[h]=new Array(4):t._root=new Array(4),(c=e>=(a=(m+v)/2))?m=a:v=a,(u=n>=(o=(g+_)/2))?g=o:_=o}while((h=u<<1|c)==(d=(l>=o)<<1|s>=a));return r[d]=p,r[h]=f,t}function yo(t,e,n,i,r){this.node=t,this.x0=e,this.y0=n,this.x1=i,this.y1=r}function xo(t){return t[0]}function bo(t){return t[1]}function wo(t,e,n){var i=new Mo(null==e?xo:e,null==n?bo:n,NaN,NaN,NaN,NaN);return null==t?i:i.addAll(t)}function Mo(t,e,n,i,r,a){this._x=t,this._y=e,this._x0=n,this._y0=i,this._x1=r,this._y1=a,this._root=void 0}function So(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}vo.copy=function(){var t,e,n=new mo(this._x,this._x0,this._x1),i=this._root;if(!i)return n;if(!i.length)return n._root=go(i),n;for(t=[{source:i,target:n._root=new Array(2)}];i=t.pop();)for(var r=0;r<2;++r)(e=i.source[r])&&(e.length?t.push({source:e,target:i.target[r]=new Array(2)}):i.target[r]=go(e));return n},vo.add=function(t){var e=+this._x.call(null,t);return uo(this.cover(e),e,t)},vo.addAll=function(t){var e,n,i=t.length,r=new Array(i),a=1/0,o=-1/0;for(e=0;eo&&(o=n));if(a>o)return this;for(this.cover(a).cover(o),e=0;et||t>=n;)switch(r=+(tl||(r=a.x1)=h))&&(a=c[c.length-1],c[c.length-1]=c[c.length-1-o],c[c.length-1-o]=a)}else{var d=Math.abs(t-+this._x.call(null,u.data));d=(o=(h+d)/2))?h=o:d=o,e=u,!(u=u[l=+s]))return this;if(!u.length)break;e[l+1&1]&&(n=e,c=l)}for(;u.data!==t;)if(i=u,!(u=u.next))return this;return(r=u.next)&&delete u.next,i?(r?i.next=r:delete i.next,this):e?(r?e[l]=r:delete e[l],(u=e[0]||e[1])&&u===(e[1]||e[0])&&!u.length&&(n?n[c]=u:this._root=u),this):(this._root=r,this)},vo.removeAll=function(t){for(var e=0,n=t.length;e=(o=(y+w)/2))?y=o:w=o,(p=n>=(s=(x+M)/2))?x=s:M=s,(f=i>=(l=(b+S)/2))?b=l:S=l,a=v,!(v=v[m=f<<2|p<<1|d]))return a[m]=_,t;if(c=+t._x.call(null,v.data),u=+t._y.call(null,v.data),h=+t._z.call(null,v.data),e===c&&n===u&&i===h)return _.next=v,a?a[m]=_:t._root=_,t;do{a=a?a[m]=new Array(8):t._root=new Array(8),(d=e>=(o=(y+w)/2))?y=o:w=o,(p=n>=(s=(x+M)/2))?x=s:M=s,(f=i>=(l=(b+S)/2))?b=l:S=l}while((m=f<<2|p<<1|d)==(g=(h>=l)<<2|(u>=s)<<1|c>=o));return a[g]=v,a[m]=_,t}function Ao(t,e,n,i,r,a,o){this.node=t,this.x0=e,this.y0=n,this.z0=i,this.x1=r,this.y1=a,this.z1=o}function Co(t){return t[0]}function Lo(t){return t[1]}function Po(t){return t[2]}function Ro(t,e,n,i){var r=new Do(null==e?Co:e,null==n?Lo:n,null==i?Po:i,NaN,NaN,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Do(t,e,n,i,r,a,o,s,l){this._x=t,this._y=e,this._z=n,this._x0=i,this._y0=r,this._z0=a,this._x1=o,this._y1=s,this._z1=l,this._root=void 0}function Oo(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}Eo.copy=function(){var t,e,n=new Mo(this._x,this._y,this._x0,this._y0,this._x1,this._y1),i=this._root;if(!i)return n;if(!i.length)return n._root=So(i),n;for(t=[{source:i,target:n._root=new Array(4)}];i=t.pop();)for(var r=0;r<4;++r)(e=i.source[r])&&(e.length?t.push({source:e,target:i.target[r]=new Array(4)}):i.target[r]=So(e));return n},Eo.add=function(t){const e=+this._x.call(null,t),n=+this._y.call(null,t);return _o(this.cover(e,n),e,n,t)},Eo.addAll=function(t){var e,n,i,r,a=t.length,o=new Array(a),s=new Array(a),l=1/0,c=1/0,u=-1/0,h=-1/0;for(n=0;nu&&(u=i),rh&&(h=r));if(l>u||c>h)return this;for(this.cover(l,c).cover(u,h),n=0;nt||t>=r||i>e||e>=a;)switch(s=(ed||(a=l.y0)>p||(o=l.x1)=v)<<1|t>=g)&&(l=f[f.length-1],f[f.length-1]=f[f.length-1-c],f[f.length-1-c]=l)}else{var _=t-+this._x.call(null,m.data),y=e-+this._y.call(null,m.data),x=_*_+y*y;if(x=(s=(f+g)/2))?f=s:g=s,(u=o>=(l=(m+v)/2))?m=l:v=l,e=p,!(p=p[h=u<<1|c]))return this;if(!p.length)break;(e[h+1&3]||e[h+2&3]||e[h+3&3])&&(n=e,d=h)}for(;p.data!==t;)if(i=p,!(p=p.next))return this;return(r=p.next)&&delete p.next,i?(r?i.next=r:delete i.next,this):e?(r?e[h]=r:delete e[h],(p=e[0]||e[1]||e[2]||e[3])&&p===(e[3]||e[2]||e[1]||e[0])&&!p.length&&(n?n[d]=p:this._root=p),this):(this._root=r,this)},Eo.removeAll=function(t){for(var e=0,n=t.length;e1&&(v=d.y+d.vy-u.y-u.vy||No(s)),r>2&&(_=d.z+d.vz-u.z-u.vz||No(s)),g*=p=((p=Math.sqrt(g*g+v*v+_*_))-n[m])/p*i*e[m],v*=p,_*=p,d.vx-=g*(f=o[m]),r>1&&(d.vy-=v*f),r>2&&(d.vz-=_*f),u.vx+=g*(f=1-f),r>1&&(u.vy+=v*f),r>2&&(u.vz+=_*f)}function p(){if(i){var r,s,c=i.length,u=t.length,h=new Map(i.map(((t,e)=>[l(t,e,i),t])));for(r=0,a=new Array(c);r"function"==typeof t))||Math.random,r=e.find((t=>[1,2,3].includes(t)))||2,p()},d.links=function(e){return arguments.length?(t=e,p(),d):t},d.id=function(t){return arguments.length?(l=t,d):l},d.iterations=function(t){return arguments.length?(h=+t,d):h},d.strength=function(t){return arguments.length?(c="function"==typeof t?t:ko(+t),f(),d):c},d.distance=function(t){return arguments.length?(u="function"==typeof t?t:ko(+t),m(),d):u},d}Io.copy=function(){var t,e,n=new Do(this._x,this._y,this._z,this._x0,this._y0,this._z0,this._x1,this._y1,this._z1),i=this._root;if(!i)return n;if(!i.length)return n._root=Oo(i),n;for(t=[{source:i,target:n._root=new Array(8)}];i=t.pop();)for(var r=0;r<8;++r)(e=i.source[r])&&(e.length?t.push({source:e,target:i.target[r]=new Array(8)}):i.target[r]=Oo(e));return n},Io.add=function(t){var e=+this._x.call(null,t),n=+this._y.call(null,t),i=+this._z.call(null,t);return To(this.cover(e,n,i),e,n,i,t)},Io.addAll=function(t){var e,n,i,r,a,o=t.length,s=new Array(o),l=new Array(o),c=new Array(o),u=1/0,h=1/0,d=1/0,p=-1/0,f=-1/0,m=-1/0;for(n=0;np&&(p=i),rf&&(f=r),am&&(m=a));if(u>p||h>f||d>m)return this;for(this.cover(u,h,d).cover(p,f,m),n=0;nt||t>=o||r>e||e>=s||a>n||n>=l;)switch(u=(ng||(o=h.y0)>v||(s=h.z0)>_||(l=h.x1)=M)<<2|(e>=w)<<1|t>=b)&&(h=y[y.length-1],y[y.length-1]=y[y.length-1-d],y[y.length-1-d]=h)}else{var S=t-+this._x.call(null,x.data),E=e-+this._y.call(null,x.data),T=n-+this._z.call(null,x.data),A=S*S+E*E+T*T;if(A=(l=(v+x)/2))?v=l:x=l,(d=o>=(c=(_+b)/2))?_=c:b=c,(p=s>=(u=(y+w)/2))?y=u:w=u,e=g,!(g=g[f=p<<2|d<<1|h]))return this;if(!g.length)break;(e[f+1&7]||e[f+2&7]||e[f+3&7]||e[f+4&7]||e[f+5&7]||e[f+6&7]||e[f+7&7])&&(n=e,m=f)}for(;g.data!==t;)if(i=g,!(g=g.next))return this;return(r=g.next)&&delete g.next,i?(r?i.next=r:delete i.next,this):e?(r?e[f]=r:delete e[f],(g=e[0]||e[1]||e[2]||e[3]||e[4]||e[5]||e[6]||e[7])&&g===(e[7]||e[6]||e[5]||e[4]||e[3]||e[2]||e[1]||e[0])&&!g.length&&(n?n[m]=g:this._root=g),this):(this._root=r,this)},Io.removeAll=function(t){for(var e=0,n=t.length;e{}};function jo(){for(var t,e=0,n=arguments.length,i={};e=0&&(n=t.slice(i+1),t=t.slice(0,i)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}function Vo(t,e){for(var n,i=0,r=t.length;i0)for(var n,i,r=new Array(n),a=0;a=0&&e._call.call(void 0,t),e=e._next;--Yo}()}finally{Yo=0,function(){var t,e,n=qo,i=1/0;for(;n;)n._call?(i>n._time&&(i=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:qo=e);Xo=t,ls(i)}(),Ko=0}}function ss(){var t=ts.now(),e=t-Jo;e>1e3&&(Qo-=e,Jo=t)}function ls(t){Yo||($o&&($o=clearTimeout($o)),t-Ko>24?(t<1/0&&($o=setTimeout(os,t-ts.now()-Qo)),Zo&&(Zo=clearInterval(Zo))):(Zo||(Jo=ts.now(),Zo=setInterval(ss,1e3)),Yo=1,es(os)))}rs.prototype=as.prototype={constructor:rs,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?ns():+n)+(null==e?0:+e),this._next||Xo===this||(Xo?Xo._next=this:qo=this,Xo=this),this._call=t,this._time=n,ls()},stop:function(){this._call&&(this._call=null,this._time=1/0,ls())}};const cs=4294967296;function us(t){return t.x}function hs(t){return t.y}function ds(t){return t.z}var ps=Math.PI*(3-Math.sqrt(5)),fs=20*Math.PI/(9+Math.sqrt(221));function ms(t,e){e=e||2;var n,i=Math.min(3,Math.max(1,Math.round(e))),r=1,a=.001,o=1-Math.pow(a,1/300),s=0,l=.6,c=new Map,u=as(p),h=jo("tick","end"),d=function(){let t=1;return()=>(t=(1664525*t+1013904223)%cs)/cs}();function p(){f(),h.call("tick",n),r1&&(null==u.fy?u.y+=u.vy*=l:(u.y=u.fy,u.vy=0)),i>2&&(null==u.fz?u.z+=u.vz*=l:(u.z=u.fz,u.vz=0));return n}function m(){for(var e,n=0,r=t.length;n1&&isNaN(e.y)||i>2&&isNaN(e.z)){var a=10*(i>2?Math.cbrt(.5+n):i>1?Math.sqrt(.5+n):n),o=n*ps,s=n*fs;1===i?e.x=a:2===i?(e.x=a*Math.cos(o),e.y=a*Math.sin(o)):(e.x=a*Math.sin(o)*Math.cos(s),e.y=a*Math.cos(o),e.z=a*Math.sin(o)*Math.sin(s))}(isNaN(e.vx)||i>1&&isNaN(e.vy)||i>2&&isNaN(e.vz))&&(e.vx=0,i>1&&(e.vy=0),i>2&&(e.vz=0))}}function g(e){return e.initialize&&e.initialize(t,d,i),e}return null==t&&(t=[]),m(),n={tick:f,restart:function(){return u.restart(p),n},stop:function(){return u.stop(),n},numDimensions:function(t){return arguments.length?(i=Math.min(3,Math.max(1,Math.round(t))),c.forEach(g),n):i},nodes:function(e){return arguments.length?(t=e,m(),c.forEach(g),n):t},alpha:function(t){return arguments.length?(r=+t,n):r},alphaMin:function(t){return arguments.length?(a=+t,n):a},alphaDecay:function(t){return arguments.length?(o=+t,n):+o},alphaTarget:function(t){return arguments.length?(s=+t,n):s},velocityDecay:function(t){return arguments.length?(l=1-t,n):1-l},randomSource:function(t){return arguments.length?(d=t,c.forEach(g),n):d},force:function(t,e){return arguments.length>1?(null==e?c.delete(t):c.set(t,g(e)),n):c.get(t)},find:function(){var e,n,r,a,o,s,l=Array.prototype.slice.call(arguments),c=l.shift()||0,u=(i>1?l.shift():null)||0,h=(i>2?l.shift():null)||0,d=l.shift()||1/0,p=0,f=t.length;for(d*=d,p=0;p1?(h.on(t,e),n):h.on(t)}}}function gs(){var t,e,n,i,r,a,o=ko(-30),s=1,l=1/0,c=.81;function u(i){var a,o=t.length,s=(1===e?fo(t,us):2===e?wo(t,us,hs):3===e?Ro(t,us,hs,ds):null).visitAfter(d);for(r=i,a=0;a1&&(t.y=o/u),e>2&&(t.z=s/u)}else{(n=t).x=n.data.x,e>1&&(n.y=n.data.y),e>2&&(n.z=n.data.z);do{c+=a[n.data.index]}while(n=n.next)}t.value=c}function p(t,o,u,h,d){if(!t.value)return!0;var p=[u,h,d][e-1],f=t.x-n.x,m=e>1?t.y-n.y:0,g=e>2?t.z-n.z:0,v=p-o,_=f*f+m*m+g*g;if(v*v/c<_)return _1&&0===m&&(_+=(m=No(i))*m),e>2&&0===g&&(_+=(g=No(i))*g),_1&&(n.vy+=m*t.value*r/_),e>2&&(n.vz+=g*t.value*r/_)),!0;if(!(t.length||_>=l)){(t.data!==n||t.next)&&(0===f&&(_+=(f=No(i))*f),e>1&&0===m&&(_+=(m=No(i))*m),e>2&&0===g&&(_+=(g=No(i))*g),_1&&(n.vy+=m*v),e>2&&(n.vz+=g*v))}while(t=t.next)}}return u.initialize=function(n,...r){t=n,i=r.find((t=>"function"==typeof t))||Math.random,e=r.find((t=>[1,2,3].includes(t)))||2,h()},u.strength=function(t){return arguments.length?(o="function"==typeof t?t:ko(+t),h(),u):o},u.distanceMin=function(t){return arguments.length?(s=t*t,u):Math.sqrt(s)},u.distanceMax=function(t){return arguments.length?(l=t*t,u):Math.sqrt(l)},u.theta=function(t){return arguments.length?(c=t*t,u):Math.sqrt(c)},u}var vs=function(t){!function(t){if(!t)throw new Error("Eventify cannot use falsy object as events subject");for(var e=["on","fire","off"],n=0;n1&&(i=Array.prototype.splice.call(arguments,1));for(var a=0;a0&&(h.fire("changed",o),o.length=0)}function S(t){if("function"!=typeof t)throw new Error("Function is expected to iterate over graph nodes. You passed "+t);for(var n=e.values(),i=n.next();!i.done;){if(t(i.value))return!0;i=n.next()}}},ys=vs;function xs(t,e){this.id=t,this.links=null,this.data=e}function bs(t,e){t.links?t.links.add(e):t.links=new Set([e])}function ws(t,e,n,i){this.fromId=t,this.toId=e,this.data=n,this.id=i}function Ms(t,e){return t.toString()+"👉 "+e.toString()}var Ss={},Es={get exports(){return Ss},set exports(t){Ss=t}},Ts={},As=function(t){return 0===t?"x":1===t?"y":2===t?"z":"c"+(t+1)};const Cs=As;var Ls=function(t){return function(e,n){let i=n&&n.indent||0,r=n&&void 0!==n.join?n.join:"\n",a=Array(i+1).join(" "),o=[];for(let n=0;n {var}max) {var}max = pos.{var};",{indent:6})}\n }\n\n // Makes the bounds square.\n var maxSideLength = -Infinity;\n ${e("if ({var}max - {var}min > maxSideLength) maxSideLength = {var}max - {var}min ;",{indent:4})}\n\n currentInCache = 0;\n root = newNode();\n ${e("root.min_{var} = {var}min;",{indent:4})}\n ${e("root.max_{var} = {var}min + maxSideLength;",{indent:4})}\n\n i = bodies.length - 1;\n if (i >= 0) {\n root.body = bodies[i];\n }\n while (i--) {\n insert(bodies[i], root);\n }\n }\n\n function insert(newBody) {\n insertStack.reset();\n insertStack.push(root, newBody);\n\n while (!insertStack.isEmpty()) {\n var stackItem = insertStack.pop();\n var node = stackItem.node;\n var body = stackItem.body;\n\n if (!node.body) {\n // This is internal node. Update the total mass of the node and center-of-mass.\n ${e("var {var} = body.pos.{var};",{indent:8})}\n node.mass += body.mass;\n ${e("node.mass_{var} += body.mass * {var};",{indent:8})}\n\n // Recursively insert the body in the appropriate quadrant.\n // But first find the appropriate quadrant.\n var quadIdx = 0; // Assume we are in the 0's quad.\n ${e("var min_{var} = node.min_{var};",{indent:8})}\n ${e("var max_{var} = (min_{var} + node.max_{var}) / 2;",{indent:8})}\n\n${function(e){let n=[],i=Array(e+1).join(" ");for(let e=0;e max_${Ns(e)}) {`),n.push(i+` quadIdx = quadIdx + ${Math.pow(2,e)};`),n.push(i+` min_${Ns(e)} = max_${Ns(e)};`),n.push(i+` max_${Ns(e)} = node.max_${Ns(e)};`),n.push(i+"}");return n.join("\n")}(8)}\n\n var child = getChild(node, quadIdx);\n\n if (!child) {\n // The node is internal but this quadrant is not taken. Add\n // subnode to it.\n child = newNode();\n ${e("child.min_{var} = min_{var};",{indent:10})}\n ${e("child.max_{var} = max_{var};",{indent:10})}\n child.body = body;\n\n setChild(node, quadIdx, child);\n } else {\n // continue searching in this quadrant.\n insertStack.push(child, body);\n }\n } else {\n // We are trying to add to the leaf node.\n // We have to convert current leaf into internal node\n // and continue adding two nodes.\n var oldBody = node.body;\n node.body = null; // internal nodes do not cary bodies\n\n if (isSamePosition(oldBody.pos, body.pos)) {\n // Prevent infinite subdivision by bumping one node\n // anywhere in this quadrant\n var retriesCount = 3;\n do {\n var offset = random.nextDouble();\n ${e("var d{var} = (node.max_{var} - node.min_{var}) * offset;",{indent:12})}\n\n ${e("oldBody.pos.{var} = node.min_{var} + d{var};",{indent:12})}\n retriesCount -= 1;\n // Make sure we don't bump it out of the box. If we do, next iteration should fix it\n } while (retriesCount > 0 && isSamePosition(oldBody.pos, body.pos));\n\n if (retriesCount === 0 && isSamePosition(oldBody.pos, body.pos)) {\n // This is very bad, we ran out of precision.\n // if we do not return from the method we'll get into\n // infinite loop here. So we sacrifice correctness of layout, and keep the app running\n // Next layout iteration should get larger bounding box in the first step and fix this\n return;\n }\n }\n // Next iteration should subdivide node further.\n insertStack.push(node, oldBody);\n insertStack.push(node, body);\n }\n }\n }\n}\nreturn createQuadTree;\n\n`}function Us(t){let e=ks(t);return`\n function isSamePosition(point1, point2) {\n ${e("var d{var} = Math.abs(point1.{var} - point2.{var});",{indent:2})}\n \n return ${e("d{var} < 1e-8",{join:" && "})};\n } \n`}function Fs(t){var e=Math.pow(2,t);return`\nfunction setChild(node, idx, child) {\n ${function(){let t=[];for(let n=0;n 0) {\n return this.stack[--this.popIdx];\n }\n },\n reset: function () {\n this.popIdx = 0;\n }\n};\n\nfunction InsertStackElement(node, body) {\n this.node = node; // QuadTree node\n this.body = body; // physical body which needs to be inserted to node\n}\n"}({get exports(){return Is},set exports(t){Is=t}}).exports=function(t){let e=zs(t);return new Function(e)()},Is.generateQuadTreeFunctionBody=zs,Is.getInsertStackCode=Gs,Is.getQuadNodeCode=js,Is.isSamePosition=Us,Is.getChildBodyCode=Bs,Is.setChildBodyCode=Fs;var Hs={};({get exports(){return Hs},set exports(t){Hs=t}}).exports=function(t){let e=Ws(t);return new Function("bodies","settings","random",e)},Hs.generateFunctionBody=Ws;const Vs=Ls;function Ws(t){let e=Vs(t);return`\n var boundingBox = {\n ${e("min_{var}: 0, max_{var}: 0,",{indent:4})}\n };\n\n return {\n box: boundingBox,\n\n update: updateBoundingBox,\n\n reset: resetBoundingBox,\n\n getBestNewPosition: function (neighbors) {\n var ${e("base_{var} = 0",{join:", "})};\n\n if (neighbors.length) {\n for (var i = 0; i < neighbors.length; ++i) {\n let neighborPos = neighbors[i].pos;\n ${e("base_{var} += neighborPos.{var};",{indent:10})}\n }\n\n ${e("base_{var} /= neighbors.length;",{indent:8})}\n } else {\n ${e("base_{var} = (boundingBox.min_{var} + boundingBox.max_{var}) / 2;",{indent:8})}\n }\n\n var springLength = settings.springLength;\n return {\n ${e("{var}: base_{var} + (random.nextDouble() - 0.5) * springLength,",{indent:8})}\n };\n }\n };\n\n function updateBoundingBox() {\n var i = bodies.length;\n if (i === 0) return; // No bodies - no borders.\n\n ${e("var max_{var} = -Infinity;",{indent:4})}\n ${e("var min_{var} = Infinity;",{indent:4})}\n\n while(i--) {\n // this is O(n), it could be done faster with quadtree, if we check the root node bounds\n var bodyPos = bodies[i].pos;\n ${e("if (bodyPos.{var} < min_{var}) min_{var} = bodyPos.{var};",{indent:6})}\n ${e("if (bodyPos.{var} > max_{var}) max_{var} = bodyPos.{var};",{indent:6})}\n }\n\n ${e("boundingBox.min_{var} = min_{var};",{indent:4})}\n ${e("boundingBox.max_{var} = max_{var};",{indent:4})}\n }\n\n function resetBoundingBox() {\n ${e("boundingBox.min_{var} = boundingBox.max_{var} = 0;",{indent:4})}\n }\n`}var qs={};const Xs=Ls;function Ys(t){return`\n if (!Number.isFinite(options.dragCoefficient)) throw new Error('dragCoefficient is not a finite number');\n\n return {\n update: function(body) {\n ${Xs(t)("body.force.{var} -= options.dragCoefficient * body.velocity.{var};",{indent:6})}\n }\n };\n`}({get exports(){return qs},set exports(t){qs=t}}).exports=function(t){let e=Ys(t);return new Function("options",e)},qs.generateCreateDragForceFunctionBody=Ys;var $s={};const Zs=Ls;function Js(t){let e=Zs(t);return`\n if (!Number.isFinite(options.springCoefficient)) throw new Error('Spring coefficient is not a number');\n if (!Number.isFinite(options.springLength)) throw new Error('Spring length is not a number');\n\n return {\n /**\n * Updates forces acting on a spring\n */\n update: function (spring) {\n var body1 = spring.from;\n var body2 = spring.to;\n var length = spring.length < 0 ? options.springLength : spring.length;\n ${e("var d{var} = body2.pos.{var} - body1.pos.{var};",{indent:6})}\n var r = Math.sqrt(${e("d{var} * d{var}",{join:" + "})});\n\n if (r === 0) {\n ${e("d{var} = (random.nextDouble() - 0.5) / 50;",{indent:8})}\n r = Math.sqrt(${e("d{var} * d{var}",{join:" + "})});\n }\n\n var d = r - length;\n var coefficient = ((spring.coefficient > 0) ? spring.coefficient : options.springCoefficient) * d / r;\n\n ${e("body1.force.{var} += coefficient * d{var}",{indent:6})};\n body1.springCount += 1;\n body1.springLength += r;\n\n ${e("body2.force.{var} -= coefficient * d{var}",{indent:6})};\n body2.springCount += 1;\n body2.springLength += r;\n }\n };\n`}({get exports(){return $s},set exports(t){$s=t}}).exports=function(t){let e=Js(t);return new Function("options","random",e)},$s.generateCreateSpringForceFunctionBody=Js;var Ks={};const Qs=Ls;function tl(t){let e=Qs(t);return`\n var length = bodies.length;\n if (length === 0) return 0;\n\n ${e("var d{var} = 0, t{var} = 0;",{indent:2})}\n\n for (var i = 0; i < length; ++i) {\n var body = bodies[i];\n if (body.isPinned) continue;\n\n if (adaptiveTimeStepWeight && body.springCount) {\n timeStep = (adaptiveTimeStepWeight * body.springLength/body.springCount);\n }\n\n var coeff = timeStep / body.mass;\n\n ${e("body.velocity.{var} += coeff * body.force.{var};",{indent:4})}\n ${e("var v{var} = body.velocity.{var};",{indent:4})}\n var v = Math.sqrt(${e("v{var} * v{var}",{join:" + "})});\n\n if (v > 1) {\n // We normalize it so that we move within timeStep range. \n // for the case when v <= 1 - we let velocity to fade out.\n ${e("body.velocity.{var} = v{var} / v;",{indent:6})}\n }\n\n ${e("d{var} = timeStep * body.velocity.{var};",{indent:4})}\n\n ${e("body.pos.{var} += d{var};",{indent:4})}\n\n ${e("t{var} += Math.abs(d{var});",{indent:4})}\n }\n\n return (${e("t{var} * t{var}",{join:" + "})})/length;\n`}var el,nl,il,rl;({get exports(){return Ks},set exports(t){Ks=t}}).exports=function(t){let e=tl(t);return new Function("bodies","timeStep","adaptiveTimeStepWeight",e)},Ks.generateIntegratorFunctionBody=tl;var al,ol={},sl={get exports(){return ol},set exports(t){ol=t}};var ll=function(t){var e=nl?el:(nl=1,el=function(t,e,n,i){this.from=t,this.to=e,this.length=n,this.coefficient=i}),n=(rl||(rl=1,il=function t(e,n){var i;if(e||(e={}),n)for(i in n)if(n.hasOwnProperty(i)){var r=e.hasOwnProperty(i),a=typeof n[i];r&&typeof e[i]===a?"object"===a&&(e[i]=t(e[i],n[i])):e[i]=n[i]}return e}),il),i=vs;if(t){if(void 0!==t.springCoeff)throw new Error("springCoeff was renamed to springCoefficient");if(void 0!==t.dragCoeff)throw new Error("dragCoeff was renamed to dragCoefficient")}t=n(t,{springLength:10,springCoefficient:.8,gravity:-12,theta:.8,dragCoefficient:.9,timeStep:.5,adaptiveTimeStepWeight:0,dimensions:2,debug:!1});var r=ml[t.dimensions];if(!r){var a=t.dimensions;r={Body:cl(a,t.debug),createQuadTree:ul(a),createBounds:hl(a),createDragForce:dl(a),createSpringForce:pl(a),integrate:fl(a)},ml[a]=r}var o=r.Body,s=r.createQuadTree,l=r.createBounds,c=r.createDragForce,u=r.createSpringForce,h=r.integrate,d=function(){if(al)return ol;function t(t){return new e("number"==typeof t?t:+new Date)}function e(t){this.seed=t}function n(t){return Math.sqrt(2*Math.PI/t)*Math.pow(1/Math.E*(t+1/(12*t-1/(10*t))),t)}function i(){var t=this.seed;return t=4294967295&(3042594569^(t=4251993797+(t=4294967295&(3550635116+(t=374761393+(t=4294967295&(3345072700^(t=t+2127912214+(t<<12)&4294967295)^t>>>19))+(t<<5)&4294967295)^t<<9))+(t<<3)&4294967295)^t>>>16),this.seed=t,(268435455&t)/268435456}return al=1,sl.exports=t,ol.random=t,ol.randomIterator=function(e,n){var i=n||t();if("function"!=typeof i.next)throw new Error("customRandom does not match expected API: next() function is missing");return{forEach:function(t){var n,r,a;for(n=e.length-1;n>0;--n)r=i.next(n+1),a=e[r],e[r]=e[n],e[n]=a,t(a);e.length&&t(e[0])},shuffle:function(){var t,n,r;for(t=e.length-1;t>0;--t)n=i.next(t+1),r=e[n],e[n]=e[t],e[t]=r;return e}}},e.prototype.next=function(t){return Math.floor(this.nextDouble()*t)},e.prototype.nextDouble=i,e.prototype.uniform=i,e.prototype.gaussian=function(){var t,e,n;do{t=(e=2*this.nextDouble()-1)*e+(n=2*this.nextDouble()-1)*n}while(t>=1||0===t);return e*Math.sqrt(-2*Math.log(t)/t)},e.prototype.levy=function(){var t=1.5,e=Math.pow(n(2.5)*Math.sin(Math.PI*t/2)/(n(1.25)*t*Math.pow(2,.25)),1/t);return this.gaussian()*e/Math.pow(Math.abs(this.gaussian()),1/t)},ol}().random(42),p=[],f=[],m=s(t,d),g=l(p,t,d),v=u(t,d),_=c(t),y=[],x=new Map,b=0;S("nbody",(function(){if(0===p.length)return;m.insertBodies(p);var t=p.length;for(;t--;){var e=p[t];e.isPinned||(e.reset(),m.updateBodyForce(e),_.update(e))}})),S("spring",(function(){var t=f.length;for(;t--;)v.update(f[t])}));var w={bodies:p,quadTree:m,springs:f,settings:t,addForce:S,removeForce:function(t){var e=y.indexOf(x.get(t));if(e<0)return;y.splice(e,1),x.delete(t)},getForces:function(){return x},step:function(){for(var e=0;enew o(t))(t);return p.push(e),e},removeBody:function(t){if(t){var e=p.indexOf(t);if(!(e<0))return p.splice(e,1),0===p.length&&g.reset(),!0}},addSpring:function(t,n,i,r){if(!t||!n)throw new Error("Cannot add null spring to force simulator");"number"!=typeof i&&(i=-1);var a=new e(t,n,i,r>=0?r:-1);return f.push(a),a},getTotalMovement:function(){return 0},removeSpring:function(t){if(t){var e=f.indexOf(t);return e>-1?(f.splice(e,1),!0):void 0}},getBestNewBodyPosition:function(t){return g.getBestNewPosition(t)},getBBox:M,getBoundingBox:M,invalidateBBox:function(){console.warn("invalidateBBox() is deprecated, bounds always recomputed on `getBBox()` call")},gravity:function(e){return void 0!==e?(t.gravity=e,m.options({gravity:e}),this):t.gravity},theta:function(e){return void 0!==e?(t.theta=e,m.options({theta:e}),this):t.theta},random:d};return function(t,e){for(var n in t)gl(t,e,n)}(t,w),i(w),w;function M(){return g.update(),g.box}function S(t,e){if(x.has(t))throw new Error("Force "+t+" is already added");x.set(t,e),y.push(e)}},cl=Ts,ul=Is,hl=Hs,dl=qs,pl=$s,fl=Ks,ml={};function gl(t,e,n){if(t.hasOwnProperty(n)&&"function"!=typeof e[n]){var i=Number.isFinite(t[n]);e[n]=i?function(i){if(void 0!==i){if(!Number.isFinite(i))throw new Error("Value of "+n+" should be a valid number.");return t[n]=i,e}return t[n]}:function(i){return void 0!==i?(t[n]=i,e):t[n]}}}Es.exports=function(t,e){if(!t)throw new Error("Graph structure cannot be undefined");var n=(e&&e.createSimulator||ll)(e);if(Array.isArray(e))throw new Error("Physics settings is expected to be an object");var i=t.version>19?function(e){var n=t.getLinks(e);return n?1+n.size/3:1}:function(e){var n=t.getLinks(e);return n?1+n.length/3:1};e&&"function"==typeof e.nodeMass&&(i=e.nodeMass);var r=new Map,a={},o=0,s=n.settings.springTransform||_l;o=0,t.forEachNode((function(t){p(t.id),o+=1})),t.forEachLink(m),t.on("changed",d);var l=!1,c={step:function(){if(0===o)return u(!0),!0;var t=n.step();c.lastMove=t,c.fire("step");var e=t/o<=.01;return u(e),e},getNodePosition:function(t){return _(t).pos},setNodePosition:function(t){var e=_(t);e.setPosition.apply(e,Array.prototype.slice.call(arguments,1))},getLinkPosition:function(t){var e=a[t];if(e)return{from:e.from.pos,to:e.to.pos}},getGraphRect:function(){return n.getBBox()},forEachBody:h,pinNode:function(t,e){_(t.id).isPinned=!!e},isNodePinned:function(t){return _(t.id).isPinned},dispose:function(){t.off("changed",d),c.fire("disposed")},getBody:function(t){return r.get(t)},getSpring:function(e,n){var i;if(void 0===n)i="object"!=typeof e?e:e.id;else{var r=t.hasLink(e,n);if(!r)return;i=r.id}return a[i]},getForceVectorLength:function(){var t=0,e=0;return h((function(n){t+=Math.abs(n.force.x),e+=Math.abs(n.force.y)})),Math.sqrt(t*t+e*e)},simulator:n,graph:t,lastMove:0};return vl(c),c;function u(t){var e;l!==t&&(l=t,e=t,c.fire("stable",e))}function h(t){r.forEach(t)}function d(e){for(var n=0;n=0?i=setTimeout(l,e-c):(i=null,n||(s=t.apply(a,r),a=r=null))}null==e&&(e=100);var c=function(){a=this,r=arguments,o=Date.now();var c=n&&!i;return i||(i=setTimeout(l,e)),c&&(s=t.apply(a,r),a=r=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c.flush=function(){i&&(s=t.apply(a,r),a=r=null,clearTimeout(i),i=null)},c}yl.debounce=yl;var xl=yl;function bl(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&void 0!==arguments[0]?arguments[0]:{},e=Object.assign({},n instanceof Function?n(t):n,{initialised:!1}),i={};function r(e){return a(e,t),s(),r}var a=function(t,n){u.call(r,t,e,n),e.initialised=!0},s=xl((function(){e.initialised&&(d.call(r,e,i),i={})}),1);return p.forEach((function(t){r[t.name]=function(t){var n=t.name,a=t.triggerUpdate,o=void 0!==a&&a,l=t.onChange,c=void 0===l?function(t,e){}:l,u=t.defaultVal,h=void 0===u?null:u;return function(t){var a=e[n];if(!arguments.length)return a;var l=void 0===t?h:t;return e[n]=l,c.call(r,l,e,a),!i.hasOwnProperty(n)&&(i[n]=a),o&&s(),r}}(t)})),Object.keys(o).forEach((function(t){r[t]=function(){for(var n,i=arguments.length,a=new Array(i),s=0;s=e)&&(n=e);else{let i=-1;for(let r of t)null!=(r=e(r,++i,t))&&(n=r)&&(n=r)}return n}function Dl(t,e){let n;if(void 0===e)for(const e of t)null!=e&&(n>e||void 0===n&&e>=e)&&(n=e);else{let i=-1;for(let r of t)null!=(r=e(r,++i,t))&&(n>r||void 0===n&&r>=r)&&(n=r)}return n}function Ol(t,e){if(null==t)return{};var n,i,r=function(t,e){if(null==t)return{};var n,i,r={},a=Object.keys(t);for(i=0;i=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}function Il(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(t);!(o=(i=n.next()).done)&&(a.push(i.value),!e||a.length!==e);o=!0);}catch(t){s=!0,r=t}finally{try{o||null==n.return||n.return()}finally{if(s)throw r}}return a}(t,e)||Nl(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function kl(t){return function(t){if(Array.isArray(t))return zl(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||Nl(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Nl(t,e){if(t){if("string"==typeof t)return zl(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?zl(t,e):void 0}}function zl(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],i=arguments.length>3&&void 0!==arguments[3]&&arguments[3],r=(e instanceof Array?e.length?e:[void 0]:[e]).map((function(t){return{keyAccessor:t,isProp:!(t instanceof Function)}})),a=t.reduce((function(t,e){var i=t,a=e;return r.forEach((function(t,e){var o,s=t.keyAccessor;if(t.isProp){var l=a,c=l[s],u=Ol(l,[s].map(Ul));o=c,a=u}else o=s(a,e);e+11&&void 0!==arguments[1]?arguments[1]:1;i===r.length?Object.keys(e).forEach((function(t){return e[t]=n(e[t])})):Object.values(e).forEach((function(e){return t(e,i+1)}))}(a);var o=a;return i&&(o=[],function t(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];n.length===r.length?o.push({keys:n,vals:e}):Object.entries(e).forEach((function(e){var i=Il(e,2),r=i[0],a=i[1];return t(a,[].concat(kl(n),[r]))}))}(a),e instanceof Array&&0===e.length&&1===o.length&&(o[0].keys=[])),o};function Bl(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function jl(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function Gl(t,e){if(null==t)return{};var n,i,r=function(t,e){if(null==t)return{};var n,i,r={},a=Object.keys(t);for(i=0;i=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}function Hl(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null==n)return;var i,r,a=[],o=!0,s=!1;try{for(n=n.call(t);!(o=(i=n.next()).done)&&(a.push(i.value),!e||a.length!==e);o=!0);}catch(t){s=!0,r=t}finally{try{o||null==n.return||n.return()}finally{if(s)throw r}}return a}(t,e)||Wl(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Vl(t){return function(t){if(Array.isArray(t))return ql(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||Wl(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Wl(t,e){if(t){if("string"==typeof t)return ql(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?ql(t,e):void 0}}function ql(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t}if(t=P(t,360),e=P(e,100),n=P(n,100),0===e)i=r=a=n;else{var s=n<.5?n*(1+e):n+e-n*e,l=2*n-s;i=o(l,s,t+1/3),r=o(l,s,t),a=o(l,s,t-1/3)}return{r:255*i,g:255*r,b:255*a}}(t.h,l,u),h=!0,d="hsl"),t.hasOwnProperty("a")&&(a=t.a));var p,f,m;return a=L(a),{ok:h,format:t.format||d,r:o(255,s(r.r,0)),g:o(255,s(r.g,0)),b:o(255,s(r.b,0)),a:a}}(t);this._originalInput=t,this._r=u.r,this._g=u.g,this._b=u.b,this._a=u.a,this._roundA=a(100*this._a)/100,this._format=l.format||u.format,this._gradientType=l.gradientType,this._r<1&&(this._r=a(this._r)),this._g<1&&(this._g=a(this._g)),this._b<1&&(this._b=a(this._b)),this._ok=u.ok,this._tc_id=r++}function u(t,e,n){t=P(t,255),e=P(e,255),n=P(n,255);var i,r,a=s(t,e,n),l=o(t,e,n),c=(a+l)/2;if(a==l)i=r=0;else{var u=a-l;switch(r=c>.5?u/(2-a-l):u/(a+l),a){case t:i=(e-n)/u+(e>1)+720)%360;--e;)i.h=(i.h+r)%360,a.push(c(i));return a}function T(t,e){e=e||6;for(var n=c(t).toHsv(),i=n.h,r=n.s,a=n.v,o=[],s=1/e;e--;)o.push(c({h:i,s:r,v:a})),a=(a+s)%1;return o}c.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var t=this.toRgb();return(299*t.r+587*t.g+114*t.b)/1e3},getLuminance:function(){var t,n,i,r=this.toRgb();return t=r.r/255,n=r.g/255,i=r.b/255,.2126*(t<=.03928?t/12.92:e.pow((t+.055)/1.055,2.4))+.7152*(n<=.03928?n/12.92:e.pow((n+.055)/1.055,2.4))+.0722*(i<=.03928?i/12.92:e.pow((i+.055)/1.055,2.4))},setAlpha:function(t){return this._a=L(t),this._roundA=a(100*this._a)/100,this},toHsv:function(){var t=h(this._r,this._g,this._b);return{h:360*t.h,s:t.s,v:t.v,a:this._a}},toHsvString:function(){var t=h(this._r,this._g,this._b),e=a(360*t.h),n=a(100*t.s),i=a(100*t.v);return 1==this._a?"hsv("+e+", "+n+"%, "+i+"%)":"hsva("+e+", "+n+"%, "+i+"%, "+this._roundA+")"},toHsl:function(){var t=u(this._r,this._g,this._b);return{h:360*t.h,s:t.s,l:t.l,a:this._a}},toHslString:function(){var t=u(this._r,this._g,this._b),e=a(360*t.h),n=a(100*t.s),i=a(100*t.l);return 1==this._a?"hsl("+e+", "+n+"%, "+i+"%)":"hsla("+e+", "+n+"%, "+i+"%, "+this._roundA+")"},toHex:function(t){return d(this._r,this._g,this._b,t)},toHexString:function(t){return"#"+this.toHex(t)},toHex8:function(t){return function(t,e,n,i,r){var o=[O(a(t).toString(16)),O(a(e).toString(16)),O(a(n).toString(16)),O(k(i))];if(r&&o[0].charAt(0)==o[0].charAt(1)&&o[1].charAt(0)==o[1].charAt(1)&&o[2].charAt(0)==o[2].charAt(1)&&o[3].charAt(0)==o[3].charAt(1))return o[0].charAt(0)+o[1].charAt(0)+o[2].charAt(0)+o[3].charAt(0);return o.join("")}(this._r,this._g,this._b,this._a,t)},toHex8String:function(t){return"#"+this.toHex8(t)},toRgb:function(){return{r:a(this._r),g:a(this._g),b:a(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+a(this._r)+", "+a(this._g)+", "+a(this._b)+")":"rgba("+a(this._r)+", "+a(this._g)+", "+a(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:a(100*P(this._r,255))+"%",g:a(100*P(this._g,255))+"%",b:a(100*P(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+a(100*P(this._r,255))+"%, "+a(100*P(this._g,255))+"%, "+a(100*P(this._b,255))+"%)":"rgba("+a(100*P(this._r,255))+"%, "+a(100*P(this._g,255))+"%, "+a(100*P(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":!(this._a<1)&&(C[d(this._r,this._g,this._b,!0)]||!1)},toFilter:function(t){var e="#"+p(this._r,this._g,this._b,this._a),n=e,i=this._gradientType?"GradientType = 1, ":"";if(t){var r=c(t);n="#"+p(r._r,r._g,r._b,r._a)}return"progid:DXImageTransform.Microsoft.gradient("+i+"startColorstr="+e+",endColorstr="+n+")"},toString:function(t){var e=!!t;t=t||this._format;var n=!1,i=this._a<1&&this._a>=0;return e||!i||"hex"!==t&&"hex6"!==t&&"hex3"!==t&&"hex4"!==t&&"hex8"!==t&&"name"!==t?("rgb"===t&&(n=this.toRgbString()),"prgb"===t&&(n=this.toPercentageRgbString()),"hex"!==t&&"hex6"!==t||(n=this.toHexString()),"hex3"===t&&(n=this.toHexString(!0)),"hex4"===t&&(n=this.toHex8String(!0)),"hex8"===t&&(n=this.toHex8String()),"name"===t&&(n=this.toName()),"hsl"===t&&(n=this.toHslString()),"hsv"===t&&(n=this.toHsvString()),n||this.toHexString()):"name"===t&&0===this._a?this.toName():this.toRgbString()},clone:function(){return c(this.toString())},_applyModification:function(t,e){var n=t.apply(null,[this].concat([].slice.call(e)));return this._r=n._r,this._g=n._g,this._b=n._b,this.setAlpha(n._a),this},lighten:function(){return this._applyModification(v,arguments)},brighten:function(){return this._applyModification(_,arguments)},darken:function(){return this._applyModification(y,arguments)},desaturate:function(){return this._applyModification(f,arguments)},saturate:function(){return this._applyModification(m,arguments)},greyscale:function(){return this._applyModification(g,arguments)},spin:function(){return this._applyModification(x,arguments)},_applyCombination:function(t,e){return t.apply(null,[this].concat([].slice.call(e)))},analogous:function(){return this._applyCombination(E,arguments)},complement:function(){return this._applyCombination(b,arguments)},monochromatic:function(){return this._applyCombination(T,arguments)},splitcomplement:function(){return this._applyCombination(S,arguments)},triad:function(){return this._applyCombination(w,arguments)},tetrad:function(){return this._applyCombination(M,arguments)}},c.fromRatio=function(t,e){if("object"==typeof t){var n={};for(var i in t)t.hasOwnProperty(i)&&(n[i]="a"===i?t[i]:I(t[i]));t=n}return c(t,e)},c.equals=function(t,e){return!(!t||!e)&&c(t).toRgbString()==c(e).toRgbString()},c.random=function(){return c.fromRatio({r:l(),g:l(),b:l()})},c.mix=function(t,e,n){n=0===n?0:n||50;var i=c(t).toRgb(),r=c(e).toRgb(),a=n/100;return c({r:(r.r-i.r)*a+i.r,g:(r.g-i.g)*a+i.g,b:(r.b-i.b)*a+i.b,a:(r.a-i.a)*a+i.a})}, +// =4.5;break;case"AAlarge":r=a>=3;break;case"AAAsmall":r=a>=7}return r},c.mostReadable=function(t,e,n){var i,r,a,o,s=null,l=0;r=(n=n||{}).includeFallbackColors,a=n.level,o=n.size;for(var u=0;ul&&(l=i,s=c(e[u]));return c.isReadable(t,s,{level:a,size:o})||!r?s:(n.includeFallbackColors=!1,c.mostReadable(t,["#fff","#000"],n))};var A=c.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},C=c.hexNames=function(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[t[n]]=n);return e}(A);function L(t){return t=parseFloat(t),(isNaN(t)||t<0||t>1)&&(t=1),t}function P(t,n){(function(t){return"string"==typeof t&&-1!=t.indexOf(".")&&1===parseFloat(t)})(t)&&(t="100%");var i=function(t){return"string"==typeof t&&-1!=t.indexOf("%")}(t);return t=o(n,s(0,parseFloat(t))),i&&(t=parseInt(t*n,10)/100),e.abs(t-n)<1e-6?1:t%n/parseFloat(n)}function R(t){return o(1,s(0,t))}function D(t){return parseInt(t,16)}function O(t){return 1==t.length?"0"+t:""+t}function I(t){return t<=1&&(t=100*t+"%"),t}function k(t){return e.round(255*parseFloat(t)).toString(16)}function N(t){return D(t)/255}var z,U,F,B=(U="[\\s|\\(]+("+(z="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",F="[\\s|\\(]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")[,|\\s]+("+z+")\\s*\\)?",{CSS_UNIT:new RegExp(z),rgb:new RegExp("rgb"+U),rgba:new RegExp("rgba"+F),hsl:new RegExp("hsl"+U),hsla:new RegExp("hsla"+F),hsv:new RegExp("hsv"+U),hsva:new RegExp("hsva"+F),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function j(t){return!!B.CSS_UNIT.exec(t)}t.exports?t.exports=c:window.tinycolor=c}(Math)}({get exports(){return Ql},set exports(t){Ql=t}});var tc=Ql;function ec(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,i)}return n}function nc(t){for(var e=1;e=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}function fc(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function mc(t,e){if(e&&("object"==typeof e||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return fc(t)}function gc(t){var e=hc();return function(){var n,i=cc(t);if(e){var r=cc(this).constructor;n=Reflect.construct(i,arguments,r)}else n=i.apply(this,arguments);return mc(this,n)}}function vc(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=n){var i,r,a,o,s=[],l=!0,c=!1;try{if(a=(n=n.call(t)).next,0===e){if(Object(n)!==n)return;l=!1}else for(;!(l=(i=a.call(n)).done)&&(s.push(i.value),s.length!==e);l=!0);}catch(t){c=!0,r=t}finally{try{if(!l&&null!=n.return&&(o=n.return(),Object(o)!==o))return}finally{if(c)throw r}}return s}}(t,e)||yc(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function _c(t){return function(t){if(Array.isArray(t))return xc(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||yc(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function yc(t,e){if(t){if("string"==typeof t)return xc(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?xc(t,e):void 0}}function xc(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n2&&void 0!==arguments[2]?arguments[2]:{},i=n.objFilter,r=void 0===i?function(){return!0}:i,a=pc(n,Ec);return $l(t,e.children.filter(r),(function(t){return e.add(t)}),(function(t){e.remove(t),Sc(t)}),nc({objBindAttr:"__threeObj"},a))}var Ac=function(t){return isNaN(t)?parseInt(tc(t).toHex(),16):t},Cc=function(t){return isNaN(t)?tc(t).getAlpha():1},Lc=function t(){var e=new Cl,n=[],i=[],r=Jl;function a(t){let a=e.get(t);if(void 0===a){if(r!==Jl)return r;e.set(t,a=n.push(t)-1)}return i[a%i.length]}return a.domain=function(t){if(!arguments.length)return n.slice();n=[],e=new Cl;for(const i of t)e.has(i)||e.set(i,n.push(i)-1);return a},a.range=function(t){return arguments.length?(i=Array.from(t),a):i.slice()},a.unknown=function(t){return arguments.length?(r=t,a):r},a.copy=function(){return t(n,i).unknown(r)},Zl.apply(a,arguments),a}(Kl);function Pc(t,e,n){e&&"string"==typeof n&&t.filter((function(t){return!t[n]})).forEach((function(t){t[n]=Lc(e(t))}))}var Rc=window.THREE?window.THREE:{Group:sa,Mesh:mn,MeshLambertMaterial:class extends Fe{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new pt(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new pt(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new K(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}},Color:pt,BufferGeometry:tn,BufferAttribute:He,Matrix4:ee,Vector3:Et,SphereGeometry:Na,CylinderGeometry:Ia,TubeGeometry:za,ConeGeometry:ka,Line:class extends Te{constructor(t=new tn,e=new ma){super(),this.isLine=!0,this.type="Line",this.geometry=t,this.material=e,this.updateMorphTargets()}copy(t,e){return super.copy(t,e),this.material=t.material,this.geometry=t.geometry,this}computeLineDistances(){const t=this.geometry;if(null===t.index){const e=t.attributes.position,n=[0];for(let t=1,i=e.count;ts)continue;h.applyMatrix4(this.matrixWorld);const a=t.ray.origin.distanceTo(h);at.far||e.push({distance:a,point:u.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else{for(let n=Math.max(0,a.start),i=Math.min(f.count,a.start+a.count)-1;ns)continue;h.applyMatrix4(this.matrixWorld);const i=t.ray.origin.distanceTo(h);it.far||e.push({distance:i,point:u.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t2?-60:-30),t<3&&i(e.graphData.nodes,"z"),t<2&&i(e.graphData.nodes,"y")}},dagMode:{onChange:function(t,e){!t&&"d3"===e.forceEngine&&(e.graphData.nodes||[]).forEach((function(t){return t.fx=t.fy=t.fz=void 0}))}},dagLevelDistance:{},dagNodeFilter:{default:function(t){return!0}},onDagError:{triggerUpdate:!1},nodeRelSize:{default:4},nodeId:{default:"id"},nodeVal:{default:"val"},nodeResolution:{default:8},nodeColor:{default:"color"},nodeAutoColorBy:{},nodeOpacity:{default:.75},nodeVisibility:{default:!0},nodeThreeObject:{},nodeThreeObjectExtend:{default:!1},linkSource:{default:"source"},linkTarget:{default:"target"},linkVisibility:{default:!0},linkColor:{default:"color"},linkAutoColorBy:{},linkOpacity:{default:.2},linkWidth:{},linkResolution:{default:6},linkCurvature:{default:0,triggerUpdate:!1},linkCurveRotation:{default:0,triggerUpdate:!1},linkMaterial:{},linkThreeObject:{},linkThreeObjectExtend:{default:!1},linkPositionUpdate:{triggerUpdate:!1},linkDirectionalArrowLength:{default:0},linkDirectionalArrowColor:{},linkDirectionalArrowRelPos:{default:.5,triggerUpdate:!1},linkDirectionalArrowResolution:{default:8},linkDirectionalParticles:{default:0},linkDirectionalParticleSpeed:{default:.01,triggerUpdate:!1},linkDirectionalParticleWidth:{default:.5},linkDirectionalParticleColor:{},linkDirectionalParticleResolution:{default:4},forceEngine:{default:"d3"},d3AlphaMin:{default:0},d3AlphaDecay:{default:.0228,triggerUpdate:!1,onChange:function(t,e){e.d3ForceLayout.alphaDecay(t)}},d3AlphaTarget:{default:0,triggerUpdate:!1,onChange:function(t,e){e.d3ForceLayout.alphaTarget(t)}},d3VelocityDecay:{default:.4,triggerUpdate:!1,onChange:function(t,e){e.d3ForceLayout.velocityDecay(t)}},ngraphPhysics:{default:{timeStep:20,gravity:-1.2,theta:.8,springLength:30,springCoefficient:8e-4,dragCoefficient:.02}},warmupTicks:{default:0,triggerUpdate:!1},cooldownTicks:{default:1/0,triggerUpdate:!1},cooldownTime:{default:15e3,triggerUpdate:!1},onLoading:{default:function(){},triggerUpdate:!1},onFinishLoading:{default:function(){},triggerUpdate:!1},onUpdate:{default:function(){},triggerUpdate:!1},onFinishUpdate:{default:function(){},triggerUpdate:!1},onEngineTick:{default:function(){},triggerUpdate:!1},onEngineStop:{default:function(){},triggerUpdate:!1}},methods:{refresh:function(t){return t._flushObjects=!0,t._rerender(),this},d3Force:function(t,e,n){return void 0===n?t.d3ForceLayout.force(e):(t.d3ForceLayout.force(e,n),this)},d3ReheatSimulation:function(t){return t.d3ForceLayout.alpha(1),this.resetCountdown(),this},resetCountdown:function(t){return t.cntTicks=0,t.startTickTime=new Date,t.engineRunning=!0,this},tickFrame:function(t){var e,n,i,r,a="ngraph"!==t.forceEngine;return t.engineRunning&&function(){++t.cntTicks>t.cooldownTicks||new Date-t.startTickTime>t.cooldownTime||a&&t.d3AlphaMin>0&&t.d3ForceLayout.alpha()0){var f=s.x-o.x,m=s.y-o.y||0,g=(new Rc.Vector3).subVectors(h,u),v=g.clone().multiplyScalar(l).cross(0!==f||0!==m?new Rc.Vector3(0,0,1):new Rc.Vector3(0,1,0)).applyAxisAngle(g.normalize(),p).add((new Rc.Vector3).addVectors(u,h).divideScalar(2));c=new Rc.QuadraticBezierCurve3(u,v,h)}else{var _=70*l,y=-p,x=y+Math.PI/2;c=new Rc.CubicBezierCurve3(u,new Rc.Vector3(_*Math.cos(x),_*Math.sin(x),0).add(u),new Rc.Vector3(_*Math.cos(y),_*Math.sin(y),0).add(u),h)}e.__curve=c}else e.__curve=null}}t.graphData.links.forEach((function(n){var i=n.__lineObj;if(i){var s=a?n:t.layout.getLinkPosition(t.layout.graph.getLink(n.source,n.target).id),l=s[a?"source":"from"],c=s[a?"target":"to"];if(l&&c&&l.hasOwnProperty("x")&&c.hasOwnProperty("x")){o(n);var u=r(n);if(!t.linkPositionUpdate||!t.linkPositionUpdate(u?i.children[1]:i,{start:{x:l.x,y:l.y,z:l.z},end:{x:c.x,y:c.y,z:c.z}},n)||u){var h=30,d=n.__curve,p=i.children.length?i.children[0]:i;if("Line"===p.type){if(d)p.geometry.setFromPoints(d.getPoints(h));else{var f=p.geometry.getAttribute("position");f&&f.array&&6===f.array.length||p.geometry[Oc]("position",f=new Rc.BufferAttribute(new Float32Array(6),3)),f.array[0]=l.x,f.array[1]=l.y||0,f.array[2]=l.z||0,f.array[3]=c.x,f.array[4]=c.y||0,f.array[5]=c.z||0,f.needsUpdate=!0}p.geometry.computeBoundingSphere()}else if("Mesh"===p.type)if(d){p.geometry.type.match(/^Tube(Buffer)?Geometry$/)||(p.position.set(0,0,0),p.rotation.set(0,0,0),p.scale.set(1,1,1));var m=Math.ceil(10*e(n))/10/2,g=new Rc.TubeGeometry(d,h,m,t.linkResolution,!1);p.geometry.dispose(),p.geometry=g}else{if(!p.geometry.type.match(/^Cylinder(Buffer)?Geometry$/)){var v=Math.ceil(10*e(n))/10/2,_=new Rc.CylinderGeometry(v,v,1,t.linkResolution,1,!1);_[Ic]((new Rc.Matrix4).makeTranslation(0,.5,0)),_[Ic]((new Rc.Matrix4).makeRotationX(Math.PI/2)),p.geometry.dispose(),p.geometry=_}var y=new Rc.Vector3(l.x,l.y||0,l.z||0),x=new Rc.Vector3(c.x,c.y||0,c.z||0),b=y.distanceTo(x);p.position.x=y.x,p.position.y=y.y,p.position.z=y.z,p.scale.z=b,p.parent.localToWorld(x),p.lookAt(x)}}}}}))}(),e=Al(t.linkDirectionalArrowRelPos),n=Al(t.linkDirectionalArrowLength),i=Al(t.nodeVal),t.graphData.links.forEach((function(r){var o=r.__arrowObj;if(o){var s=a?r:t.layout.getLinkPosition(t.layout.graph.getLink(r.source,r.target).id),l=s[a?"source":"from"],c=s[a?"target":"to"];if(l&&c&&l.hasOwnProperty("x")&&c.hasOwnProperty("x")){var u=Math.cbrt(Math.max(0,i(l)||1))*t.nodeRelSize,h=Math.cbrt(Math.max(0,i(c)||1))*t.nodeRelSize,d=n(r),p=e(r),f=r.__curve?function(t){return r.__curve.getPoint(t)}:function(t){var e=function(t,e,n,i){return e[t]+(n[t]-e[t])*i||0};return{x:e("x",l,c,t),y:e("y",l,c,t),z:e("z",l,c,t)}},m=r.__curve?r.__curve.getLength():Math.sqrt(["x","y","z"].map((function(t){return Math.pow((c[t]||0)-(l[t]||0),2)})).reduce((function(t,e){return t+e}),0)),g=u+d+(m-u-h-d)*p,v=f(g/m),_=f((g-d)/m);["x","y","z"].forEach((function(t){return o.position[t]=_[t]}));var y=dc(Rc.Vector3,_c(["x","y","z"].map((function(t){return v[t]}))));o.parent.localToWorld(y),o.lookAt(y)}}})),r=Al(t.linkDirectionalParticleSpeed),t.graphData.links.forEach((function(e){var n=e.__photonsObj&&e.__photonsObj.children,i=e.__singleHopPhotonsObj&&e.__singleHopPhotonsObj.children;if(i&&i.length||n&&n.length){var o=a?e:t.layout.getLinkPosition(t.layout.graph.getLink(e.source,e.target).id),s=o[a?"source":"from"],l=o[a?"target":"to"];if(s&&l&&s.hasOwnProperty("x")&&l.hasOwnProperty("x")){var c=r(e),u=e.__curve?function(t){return e.__curve.getPoint(t)}:function(t){var e=function(t,e,n,i){return e[t]+(n[t]-e[t])*i||0};return{x:e("x",s,l,t),y:e("y",s,l,t),z:e("z",s,l,t)}};[].concat(_c(n||[]),_c(i||[])).forEach((function(t,e){var i="singleHopPhotons"===t.parent.__linkThreeObjType;if(t.hasOwnProperty("__progressRatio")||(t.__progressRatio=i?0:e/n.length),t.__progressRatio+=c,t.__progressRatio>=1){if(i)return t.parent.remove(t),void Sc(t);t.__progressRatio=t.__progressRatio%1}var r=t.__progressRatio,a=u(r);["x","y","z"].forEach((function(e){return t.position[e]=a[e]}))}))}}})),this},emitParticle:function(t,e){if(e&&t.graphData.links.includes(e)){if(!e.__singleHopPhotonsObj){var n=new Rc.Group;n.__linkThreeObjType="singleHopPhotons",e.__singleHopPhotonsObj=n,t.graphScene.add(n)}var i=Al(t.linkDirectionalParticleWidth),r=Math.ceil(10*i(e))/10/2,a=t.linkDirectionalParticleResolution,o=new Rc.SphereGeometry(r,a,a),s=Al(t.linkColor),l=Al(t.linkDirectionalParticleColor)(e)||s(e)||"#f0f0f0",c=new Rc.Color(Ac(l)),u=3*t.linkOpacity,h=new Rc.MeshLambertMaterial({color:c,transparent:!0,opacity:u});e.__singleHopPhotonsObj.add(new Rc.Mesh(o,h))}return this},getGraphBbox:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){return!0};if(!t.initialised)return null;var n=function t(n){var i=[];if(n.geometry){n.geometry.computeBoundingBox();var r=new Rc.Box3;r.copy(n.geometry.boundingBox).applyMatrix4(n.matrixWorld),i.push(r)}return i.concat.apply(i,_c((n.children||[]).filter((function(t){return!t.hasOwnProperty("__graphObjType")||"node"===t.__graphObjType&&e(t.__data)})).map(t)))}(t.graphScene);return n.length?Object.assign.apply(Object,_c(["x","y","z"].map((function(t){return sc({},t,[Dl(n,(function(e){return e.min[t]})),Rl(n,(function(e){return e.max[t]}))])})))):null}},stateInit:function(){return{d3ForceLayout:ms().force("link",Fo()).force("charge",gs()).force("center",co()).force("dagRadial",null).stop(),engineRunning:!1}},init:function(t,e){e.graphScene=t},update:function(t,e){var n=function(t){return t.some((function(t){return e.hasOwnProperty(t)}))};if(t.engineRunning=!1,t.onUpdate(),null!==t.nodeAutoColorBy&&n(["nodeAutoColorBy","graphData","nodeColor"])&&Pc(t.graphData.nodes,Al(t.nodeAutoColorBy),t.nodeColor),null!==t.linkAutoColorBy&&n(["linkAutoColorBy","graphData","linkColor"])&&Pc(t.graphData.links,Al(t.linkAutoColorBy),t.linkColor),t._flushObjects||n(["graphData","nodeThreeObject","nodeThreeObjectExtend","nodeVal","nodeColor","nodeVisibility","nodeRelSize","nodeResolution","nodeOpacity"])){var i=Al(t.nodeThreeObject),r=Al(t.nodeThreeObjectExtend),a=Al(t.nodeVal),o=Al(t.nodeColor),s=Al(t.nodeVisibility),l={},c={};Tc(t.graphData.nodes.filter(s),t.graphScene,{purge:t._flushObjects||n(["nodeThreeObject","nodeThreeObjectExtend"]),objFilter:function(t){return"node"===t.__graphObjType},createObj:function(e){var n,a=i(e),o=r(e);return a&&t.nodeThreeObject===a&&(a=a.clone()),a&&!o?n=a:((n=new Rc.Mesh).__graphDefaultObj=!0,a&&o&&n.add(a)),n.__graphObjType="node",n},updateObj:function(e,n){if(e.__graphDefaultObj){var i=a(n)||1,r=Math.cbrt(i)*t.nodeRelSize,s=t.nodeResolution;e.geometry.type.match(/^Sphere(Buffer)?Geometry$/)&&e.geometry.parameters.radius===r&&e.geometry.parameters.widthSegments===s||(l.hasOwnProperty(i)||(l[i]=new Rc.SphereGeometry(r,s,s)),e.geometry.dispose(),e.geometry=l[i]);var u=o(n),h=new Rc.Color(Ac(u||"#ffffaa")),d=t.nodeOpacity*Cc(u);"MeshLambertMaterial"===e.material.type&&e.material.color.equals(h)&&e.material.opacity===d||(c.hasOwnProperty(u)||(c[u]=new Rc.MeshLambertMaterial({color:h,transparent:!0,opacity:d})),e.material.dispose(),e.material=c[u])}}})}if(t._flushObjects||n(["graphData","linkThreeObject","linkThreeObjectExtend","linkMaterial","linkColor","linkWidth","linkVisibility","linkResolution","linkOpacity","linkDirectionalArrowLength","linkDirectionalArrowColor","linkDirectionalArrowResolution","linkDirectionalParticles","linkDirectionalParticleWidth","linkDirectionalParticleColor","linkDirectionalParticleResolution"])){var u=Al(t.linkThreeObject),h=Al(t.linkThreeObjectExtend),d=Al(t.linkMaterial),p=Al(t.linkVisibility),f=Al(t.linkColor),m=Al(t.linkWidth),g={},v={},_={},y=t.graphData.links.filter(p);if(Tc(y,t.graphScene,{objBindAttr:"__lineObj",purge:t._flushObjects||n(["linkThreeObject","linkThreeObjectExtend","linkWidth"]),objFilter:function(t){return"link"===t.__graphObjType},exitObj:function(t){var e=t.__data&&t.__data.__singleHopPhotonsObj;e&&(e.parent.remove(e),Sc(e),delete t.__data.__singleHopPhotonsObj)},createObj:function(e){var n,i,r=u(e),a=h(e);if(r&&t.linkThreeObject===r&&(r=r.clone()),!r||a)if(!!m(e))n=new Rc.Mesh;else{var o=new Rc.BufferGeometry;o[Oc]("position",new Rc.BufferAttribute(new Float32Array(6),3)),n=new Rc.Line(o)}return r?a?((i=new Rc.Group).__graphDefaultObj=!0,i.add(n),i.add(r)):i=r:(i=n).__graphDefaultObj=!0,i.renderOrder=10,i.__graphObjType="link",i},updateObj:function(e,n){if(e.__graphDefaultObj){var i=e.children.length?e.children[0]:e,r=Math.ceil(10*m(n))/10,a=!!r;if(a){var o=r/2,s=t.linkResolution;if(!i.geometry.type.match(/^Cylinder(Buffer)?Geometry$/)||i.geometry.parameters.radiusTop!==o||i.geometry.parameters.radialSegments!==s){if(!g.hasOwnProperty(r)){var l=new Rc.CylinderGeometry(o,o,1,s,1,!1);l[Ic]((new Rc.Matrix4).makeTranslation(0,.5,0)),l[Ic]((new Rc.Matrix4).makeRotationX(Math.PI/2)),g[r]=l}i.geometry.dispose(),i.geometry=g[r]}}var c=d(n);if(c)i.material=c;else{var u=f(n),h=new Rc.Color(Ac(u||"#f0f0f0")),p=t.linkOpacity*Cc(u),y=a?"MeshLambertMaterial":"LineBasicMaterial";if(i.material.type!==y||!i.material.color.equals(h)||i.material.opacity!==p){var x=a?v:_;x.hasOwnProperty(u)||(x[u]=new Rc[y]({color:h,transparent:p<1,opacity:p,depthWrite:p>=1})),i.material.dispose(),i.material=x[u]}}}}}),t.linkDirectionalArrowLength||e.hasOwnProperty("linkDirectionalArrowLength")){var x=Al(t.linkDirectionalArrowLength),b=Al(t.linkDirectionalArrowColor);Tc(y.filter(x),t.graphScene,{objBindAttr:"__arrowObj",objFilter:function(t){return"arrow"===t.__linkThreeObjType},createObj:function(){var t=new Rc.Mesh(void 0,new Rc.MeshLambertMaterial({transparent:!0}));return t.__linkThreeObjType="arrow",t},updateObj:function(e,n){var i=x(n),r=t.linkDirectionalArrowResolution;if(!e.geometry.type.match(/^Cone(Buffer)?Geometry$/)||e.geometry.parameters.height!==i||e.geometry.parameters.radialSegments!==r){var a=new Rc.ConeGeometry(.25*i,i,r);a.translate(0,i/2,0),a.rotateX(Math.PI/2),e.geometry.dispose(),e.geometry=a}e.material.color=new Rc.Color(b(n)||f(n)||"#f0f0f0"),e.material.opacity=3*t.linkOpacity}})}if(t.linkDirectionalParticles||e.hasOwnProperty("linkDirectionalParticles")){var w=Al(t.linkDirectionalParticles),M=Al(t.linkDirectionalParticleWidth),S=Al(t.linkDirectionalParticleColor),E={},T={};Tc(y.filter(w),t.graphScene,{objBindAttr:"__photonsObj",objFilter:function(t){return"photons"===t.__linkThreeObjType},createObj:function(){var t=new Rc.Group;return t.__linkThreeObjType="photons",t},updateObj:function(e,n){var i,r=Math.round(Math.abs(w(n))),a=!!e.children.length&&e.children[0],o=Math.ceil(10*M(n))/10/2,s=t.linkDirectionalParticleResolution;a&&a.geometry.parameters.radius===o&&a.geometry.parameters.widthSegments===s?i=a.geometry:(T.hasOwnProperty(o)||(T[o]=new Rc.SphereGeometry(o,s,s)),i=T[o],a&&a.geometry.dispose());var l,c=S(n)||f(n)||"#f0f0f0",u=new Rc.Color(Ac(c)),h=3*t.linkOpacity;a&&a.material.color.equals(u)&&a.material.opacity===h?l=a.material:(E.hasOwnProperty(c)||(E[c]=new Rc.MeshLambertMaterial({color:u,transparent:!0,opacity:h})),l=E[c],a&&a.material.dispose()),Tc(_c(new Array(r)).map((function(t,e){return{idx:e}})),e,{idAccessor:function(t){return t.idx},createObj:function(){return new Rc.Mesh(i,l)},updateObj:function(t){t.geometry=i,t.material=l}})}})}}if(t._flushObjects=!1,n(["graphData","nodeId","linkSource","linkTarget","numDimensions","forceEngine","dagMode","dagNodeFilter","dagLevelDistance"])){t.engineRunning=!1,t.graphData.links.forEach((function(e){e.source=e[t.linkSource],e.target=e[t.linkTarget]}));var A,C="ngraph"!==t.forceEngine;if(C){(A=t.d3ForceLayout).stop().alpha(1).numDimensions(t.numDimensions).nodes(t.graphData.nodes);var L=t.d3ForceLayout.force("link");L&&L.id((function(e){return e[t.nodeId]})).links(t.graphData.links);var P=t.dagMode&&function(t,e){var n=t.nodes,i=t.links,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=r.nodeFilter,o=void 0===a?function(){return!0}:a,s=r.onLoopError,l=void 0===s?function(t){throw"Invalid DAG structure! Found cycle in node path: ".concat(t.join(" -> "),".")}:s,c={};n.forEach((function(t){return c[e(t)]={data:t,out:[],depth:-1,skip:!o(t)}})),i.forEach((function(t){var n=t.source,i=t.target,r=l(n),a=l(i);if(!c.hasOwnProperty(r))throw"Missing source node with id: ".concat(r);if(!c.hasOwnProperty(a))throw"Missing target node with id: ".concat(a);var o=c[r],s=c[a];function l(t){return"object"===ic(t)?e(t):t}o.out.push(s)}));var u=[];d(Object.values(c));var h=Object.assign.apply(Object,[{}].concat(_c(Object.entries(c).filter((function(t){return!vc(t,2)[1].skip})).map((function(t){var e=vc(t,2);return sc({},e[0],e[1].depth)})))));return h;function d(t){for(var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,r=function(){var r=t[a];if(-1!==n.indexOf(r)){var o=[].concat(_c(n.slice(n.indexOf(r))),[r]).map((function(t){return e(t.data)}));return u.some((function(t){return t.length===o.length&&t.every((function(t,e){return t===o[e]}))}))||(u.push(o),l(o)),"continue"}i>r.depth&&(r.depth=i,d(r.out,[].concat(_c(n),[r]),i+(r.skip?0:1)))},a=0,o=t.length;a1&&(u.vy+=d*m),a>2&&(u.vz+=p*m)}}function u(){if(r){var e,n=r.length;for(o=new Array(n),s=new Array(n),e=0;e[1,2,3].includes(t)))||2,u()},c.strength=function(t){return arguments.length?(l="function"==typeof t?t:ko(+t),u(),c):l},c.radius=function(e){return arguments.length?(t="function"==typeof e?e:ko(+e),u(),c):t},c.x=function(t){return arguments.length?(e=+t,c):e},c.y=function(t){return arguments.length?(n=+t,c):n},c.z=function(t){return arguments.length?(i=+t,c):i},c}((function(e){var n=P[e[t.nodeId]]||-1;return("radialin"===t.dagMode?R-n:n)*D})).strength((function(e){return t.dagNodeFilter(e)?1:0})):null)}else{var z=Dc.graph();t.graphData.nodes.forEach((function(e){z.addNode(e[t.nodeId])})),t.graphData.links.forEach((function(t){z.addLink(t.source,t.target)})),(A=Dc.forcelayout(z,nc({dimensions:t.numDimensions},t.ngraphPhysics))).graph=z}for(var U=0;U0&&t.d3ForceLayout.alpha()1&&void 0!==arguments[1]?arguments[1]:Object,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i=function(e){lc(r,e);var i=gc(r);function r(){var e;rc(this,r);for(var a=arguments.length,o=new Array(a),s=0;s0&&(n.object.isPerspectiveCamera?_.multiplyScalar(t):n.object.isOrthographicCamera?(n.object.zoom/=t,n.object.updateProjectionMatrix()):console.warn("THREE.TrackballControls: Unsupported camera type")),n.staticMoving?w.copy(M):w.y+=(M.y-w.y)*this.dynamicDampingFactor)},this.panCamera=function(){const t=new K,e=new Et,i=new Et;return function(){if(t.copy(E).sub(S),t.lengthSq()){if(n.object.isOrthographicCamera){const e=(n.object.right-n.object.left)/n.object.zoom/n.domElement.clientWidth,i=(n.object.top-n.object.bottom)/n.object.zoom/n.domElement.clientWidth;t.x*=e,t.y*=i}t.multiplyScalar(_.length()*n.panSpeed),i.copy(_).cross(n.object.up).setLength(t.x),i.add(e.copy(n.object.up).setLength(t.y)),n.object.position.add(i),n.target.add(i),n.staticMoving?S.copy(E):S.add(t.subVectors(E,S).multiplyScalar(n.dynamicDampingFactor))}}}(),this.checkDistances=function(){n.noZoom&&n.noPan||(_.lengthSq()>n.maxDistance*n.maxDistance&&(n.object.position.addVectors(n.target,_.setLength(n.maxDistance)),w.copy(M)),_.lengthSq()u&&(n.dispatchEvent(zc),h.copy(n.object.position))):n.object.isOrthographicCamera?(n.object.lookAt(n.target),(h.distanceToSquared(n.object.position)>u||d!==n.object.zoom)&&(n.dispatchEvent(zc),h.copy(n.object.position),d=n.object.zoom)):console.warn("THREE.TrackballControls: Unsupported camera type")},this.reset=function(){p=i,f=i,n.target.copy(n.target0),n.object.position.copy(n.position0),n.object.up.copy(n.up0),n.object.zoom=n.zoom0,n.object.updateProjectionMatrix(),_.subVectors(n.object.position,n.target),n.object.lookAt(n.target),n.dispatchEvent(zc),h.copy(n.object.position),d=n.object.zoom},this.dispose=function(){n.domElement.removeEventListener("contextmenu",z),n.domElement.removeEventListener("pointerdown",P),n.domElement.removeEventListener("pointercancel",O),n.domElement.removeEventListener("wheel",N),n.domElement.removeEventListener("pointermove",R),n.domElement.removeEventListener("pointerup",D),window.removeEventListener("keydown",I),window.removeEventListener("keyup",k)},this.domElement.addEventListener("contextmenu",z),this.domElement.addEventListener("pointerdown",P),this.domElement.addEventListener("pointercancel",O),this.domElement.addEventListener("wheel",N,{passive:!1}),window.addEventListener("keydown",I),window.addEventListener("keyup",k),this.handleResize(),this.update()}}const jc={type:"change"},Gc={type:"start"},Hc={type:"end"};class Vc extends j{constructor(t,e){super(),this.object=t,this.domElement=e,this.domElement.style.touchAction="none",this.enabled=!0,this.target=new Et,this.minDistance=0,this.maxDistance=1/0,this.minZoom=0,this.maxZoom=1/0,this.minPolarAngle=0,this.maxPolarAngle=Math.PI,this.minAzimuthAngle=-1/0,this.maxAzimuthAngle=1/0,this.enableDamping=!1,this.dampingFactor=.05,this.enableZoom=!0,this.zoomSpeed=1,this.enableRotate=!0,this.rotateSpeed=1,this.enablePan=!0,this.panSpeed=1,this.screenSpacePanning=!0,this.keyPanSpeed=7,this.autoRotate=!1,this.autoRotateSpeed=2,this.keys={LEFT:"ArrowLeft",UP:"ArrowUp",RIGHT:"ArrowRight",BOTTOM:"ArrowDown"},this.mouseButtons={LEFT:o.ROTATE,MIDDLE:o.DOLLY,RIGHT:o.PAN},this.touches={ONE:s,TWO:c},this.target0=this.target.clone(),this.position0=this.object.position.clone(),this.zoom0=this.object.zoom,this._domElementKeyEvents=null,this.getPolarAngle=function(){return h.phi},this.getAzimuthalAngle=function(){return h.theta},this.getDistance=function(){return this.object.position.distanceTo(this.target)},this.listenToKeyEvents=function(t){t.addEventListener("keydown",Y),this._domElementKeyEvents=t},this.saveState=function(){n.target0.copy(n.target),n.position0.copy(n.object.position),n.zoom0=n.object.zoom},this.reset=function(){n.target.copy(n.target0),n.object.position.copy(n.position0),n.object.zoom=n.zoom0,n.object.updateProjectionMatrix(),n.dispatchEvent(jc),n.update(),r=i.NONE},this.update=function(){const e=new Et,o=(new St).setFromUnitVectors(t.up,new Et(0,1,0)),s=o.clone().invert(),l=new Et,c=new St,u=2*Math.PI;return function(){const t=n.object.position;e.copy(t).sub(n.target),e.applyQuaternion(o),h.setFromVector3(e),n.autoRotate&&r===i.NONE&&C(2*Math.PI/60/60*n.autoRotateSpeed),n.enableDamping?(h.theta+=d.theta*n.dampingFactor,h.phi+=d.phi*n.dampingFactor):(h.theta+=d.theta,h.phi+=d.phi);let g=n.minAzimuthAngle,v=n.maxAzimuthAngle;return isFinite(g)&&isFinite(v)&&(g<-Math.PI?g+=u:g>Math.PI&&(g-=u),v<-Math.PI?v+=u:v>Math.PI&&(v-=u),h.theta=g<=v?Math.max(g,Math.min(v,h.theta)):h.theta>(g+v)/2?Math.max(g,h.theta):Math.min(v,h.theta)),h.phi=Math.max(n.minPolarAngle,Math.min(n.maxPolarAngle,h.phi)),h.makeSafe(),h.radius*=p,h.radius=Math.max(n.minDistance,Math.min(n.maxDistance,h.radius)),!0===n.enableDamping?n.target.addScaledVector(f,n.dampingFactor):n.target.add(f),e.setFromSpherical(h),e.applyQuaternion(s),t.copy(n.target).add(e),n.object.lookAt(n.target),!0===n.enableDamping?(d.theta*=1-n.dampingFactor,d.phi*=1-n.dampingFactor,f.multiplyScalar(1-n.dampingFactor)):(d.set(0,0,0),f.set(0,0,0)),p=1,!!(m||l.distanceToSquared(n.object.position)>a||8*(1-c.dot(n.object.quaternion))>a)&&(n.dispatchEvent(jc),l.copy(n.object.position),c.copy(n.object.quaternion),m=!1,!0)}}(),this.dispose=function(){n.domElement.removeEventListener("contextmenu",$),n.domElement.removeEventListener("pointerdown",H),n.domElement.removeEventListener("pointercancel",q),n.domElement.removeEventListener("wheel",X),n.domElement.removeEventListener("pointermove",V),n.domElement.removeEventListener("pointerup",W),null!==n._domElementKeyEvents&&n._domElementKeyEvents.removeEventListener("keydown",Y)};const n=this,i={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6};let r=i.NONE;const a=1e-6,h=new to,d=new to;let p=1;const f=new Et;let m=!1;const g=new K,v=new K,_=new K,y=new K,x=new K,b=new K,w=new K,M=new K,S=new K,E=[],T={};function A(){return Math.pow(.95,n.zoomSpeed)}function C(t){d.theta-=t}function L(t){d.phi-=t}const P=function(){const t=new Et;return function(e,n){t.setFromMatrixColumn(n,0),t.multiplyScalar(-e),f.add(t)}}(),R=function(){const t=new Et;return function(e,i){!0===n.screenSpacePanning?t.setFromMatrixColumn(i,1):(t.setFromMatrixColumn(i,0),t.crossVectors(n.object.up,t)),t.multiplyScalar(e),f.add(t)}}(),D=function(){const t=new Et;return function(e,i){const r=n.domElement;if(n.object.isPerspectiveCamera){const a=n.object.position;t.copy(a).sub(n.target);let o=t.length();o*=Math.tan(n.object.fov/2*Math.PI/180),P(2*e*o/r.clientHeight,n.object.matrix),R(2*i*o/r.clientHeight,n.object.matrix)}else n.object.isOrthographicCamera?(P(e*(n.object.right-n.object.left)/n.object.zoom/r.clientWidth,n.object.matrix),R(i*(n.object.top-n.object.bottom)/n.object.zoom/r.clientHeight,n.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),n.enablePan=!1)}}();function O(t){n.object.isPerspectiveCamera?p/=t:n.object.isOrthographicCamera?(n.object.zoom=Math.max(n.minZoom,Math.min(n.maxZoom,n.object.zoom*t)),n.object.updateProjectionMatrix(),m=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),n.enableZoom=!1)}function I(t){n.object.isPerspectiveCamera?p*=t:n.object.isOrthographicCamera?(n.object.zoom=Math.max(n.minZoom,Math.min(n.maxZoom,n.object.zoom/t)),n.object.updateProjectionMatrix(),m=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),n.enableZoom=!1)}function k(t){g.set(t.clientX,t.clientY)}function N(t){y.set(t.clientX,t.clientY)}function z(){if(1===E.length)g.set(E[0].pageX,E[0].pageY);else{const t=.5*(E[0].pageX+E[1].pageX),e=.5*(E[0].pageY+E[1].pageY);g.set(t,e)}}function U(){if(1===E.length)y.set(E[0].pageX,E[0].pageY);else{const t=.5*(E[0].pageX+E[1].pageX),e=.5*(E[0].pageY+E[1].pageY);y.set(t,e)}}function F(){const t=E[0].pageX-E[1].pageX,e=E[0].pageY-E[1].pageY,n=Math.sqrt(t*t+e*e);w.set(0,n)}function B(t){if(1==E.length)v.set(t.pageX,t.pageY);else{const e=Q(t),n=.5*(t.pageX+e.x),i=.5*(t.pageY+e.y);v.set(n,i)}_.subVectors(v,g).multiplyScalar(n.rotateSpeed);const e=n.domElement;C(2*Math.PI*_.x/e.clientHeight),L(2*Math.PI*_.y/e.clientHeight),g.copy(v)}function j(t){if(1===E.length)x.set(t.pageX,t.pageY);else{const e=Q(t),n=.5*(t.pageX+e.x),i=.5*(t.pageY+e.y);x.set(n,i)}b.subVectors(x,y).multiplyScalar(n.panSpeed),D(b.x,b.y),y.copy(x)}function G(t){const e=Q(t),i=t.pageX-e.x,r=t.pageY-e.y,a=Math.sqrt(i*i+r*r);M.set(0,a),S.set(0,Math.pow(M.y/w.y,n.zoomSpeed)),O(S.y),w.copy(M)}function H(t){!1!==n.enabled&&(0===E.length&&(n.domElement.setPointerCapture(t.pointerId),n.domElement.addEventListener("pointermove",V),n.domElement.addEventListener("pointerup",W)),function(t){E.push(t)}(t),"touch"===t.pointerType?function(t){switch(J(t),E.length){case 1:switch(n.touches.ONE){case s:if(!1===n.enableRotate)return;z(),r=i.TOUCH_ROTATE;break;case l:if(!1===n.enablePan)return;U(),r=i.TOUCH_PAN;break;default:r=i.NONE}break;case 2:switch(n.touches.TWO){case c:if(!1===n.enableZoom&&!1===n.enablePan)return;n.enableZoom&&F(),n.enablePan&&U(),r=i.TOUCH_DOLLY_PAN;break;case u:if(!1===n.enableZoom&&!1===n.enableRotate)return;n.enableZoom&&F(),n.enableRotate&&z(),r=i.TOUCH_DOLLY_ROTATE;break;default:r=i.NONE}break;default:r=i.NONE}r!==i.NONE&&n.dispatchEvent(Gc)}(t):function(t){let e;switch(t.button){case 0:e=n.mouseButtons.LEFT;break;case 1:e=n.mouseButtons.MIDDLE;break;case 2:e=n.mouseButtons.RIGHT;break;default:e=-1}switch(e){case o.DOLLY:if(!1===n.enableZoom)return;!function(t){w.set(t.clientX,t.clientY)}(t),r=i.DOLLY;break;case o.ROTATE:if(t.ctrlKey||t.metaKey||t.shiftKey){if(!1===n.enablePan)return;N(t),r=i.PAN}else{if(!1===n.enableRotate)return;k(t),r=i.ROTATE}break;case o.PAN:if(t.ctrlKey||t.metaKey||t.shiftKey){if(!1===n.enableRotate)return;k(t),r=i.ROTATE}else{if(!1===n.enablePan)return;N(t),r=i.PAN}break;default:r=i.NONE}r!==i.NONE&&n.dispatchEvent(Gc)}(t))}function V(t){!1!==n.enabled&&("touch"===t.pointerType?function(t){switch(J(t),r){case i.TOUCH_ROTATE:if(!1===n.enableRotate)return;B(t),n.update();break;case i.TOUCH_PAN:if(!1===n.enablePan)return;j(t),n.update();break;case i.TOUCH_DOLLY_PAN:if(!1===n.enableZoom&&!1===n.enablePan)return;!function(t){n.enableZoom&&G(t),n.enablePan&&j(t)}(t),n.update();break;case i.TOUCH_DOLLY_ROTATE:if(!1===n.enableZoom&&!1===n.enableRotate)return;!function(t){n.enableZoom&&G(t),n.enableRotate&&B(t)}(t),n.update();break;default:r=i.NONE}}(t):function(t){switch(r){case i.ROTATE:if(!1===n.enableRotate)return;!function(t){v.set(t.clientX,t.clientY),_.subVectors(v,g).multiplyScalar(n.rotateSpeed);const e=n.domElement;C(2*Math.PI*_.x/e.clientHeight),L(2*Math.PI*_.y/e.clientHeight),g.copy(v),n.update()}(t);break;case i.DOLLY:if(!1===n.enableZoom)return;!function(t){M.set(t.clientX,t.clientY),S.subVectors(M,w),S.y>0?O(A()):S.y<0&&I(A()),w.copy(M),n.update()}(t);break;case i.PAN:if(!1===n.enablePan)return;!function(t){x.set(t.clientX,t.clientY),b.subVectors(x,y).multiplyScalar(n.panSpeed),D(b.x,b.y),y.copy(x),n.update()}(t)}}(t))}function W(t){Z(t),0===E.length&&(n.domElement.releasePointerCapture(t.pointerId),n.domElement.removeEventListener("pointermove",V),n.domElement.removeEventListener("pointerup",W)),n.dispatchEvent(Hc),r=i.NONE}function q(t){Z(t)}function X(t){!1!==n.enabled&&!1!==n.enableZoom&&r===i.NONE&&(t.preventDefault(),n.dispatchEvent(Gc),function(t){t.deltaY<0?I(A()):t.deltaY>0&&O(A()),n.update()}(t),n.dispatchEvent(Hc))}function Y(t){!1!==n.enabled&&!1!==n.enablePan&&function(t){let e=!1;switch(t.code){case n.keys.UP:t.ctrlKey||t.metaKey||t.shiftKey?L(2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):D(0,n.keyPanSpeed),e=!0;break;case n.keys.BOTTOM:t.ctrlKey||t.metaKey||t.shiftKey?L(-2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):D(0,-n.keyPanSpeed),e=!0;break;case n.keys.LEFT:t.ctrlKey||t.metaKey||t.shiftKey?C(2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):D(n.keyPanSpeed,0),e=!0;break;case n.keys.RIGHT:t.ctrlKey||t.metaKey||t.shiftKey?C(-2*Math.PI*n.rotateSpeed/n.domElement.clientHeight):D(-n.keyPanSpeed,0),e=!0}e&&(t.preventDefault(),n.update())}(t)}function $(t){!1!==n.enabled&&t.preventDefault()}function Z(t){delete T[t.pointerId];for(let e=0;e0){const e=this.getContainerDimensions(),n=e.size[0]/2,i=e.size[1]/2;this.moveState.yawLeft=-(t.pageX-e.offset[0]-n)/n,this.moveState.pitchDown=(t.pageY-e.offset[1]-i)/i,this.updateRotationVector()}},this.pointerup=function(t){if(this.dragToLook)this.status--,this.moveState.yawLeft=this.moveState.pitchDown=0;else{switch(t.button){case 0:this.moveState.forward=0;break;case 2:this.moveState.back=0}this.updateMovementVector()}this.updateRotationVector()},this.update=function(t){const e=t*n.movementSpeed,o=t*n.rollSpeed;n.object.translateX(n.moveVector.x*e),n.object.translateY(n.moveVector.y*e),n.object.translateZ(n.moveVector.z*e),n.tmpQuaternion.set(n.rotationVector.x*o,n.rotationVector.y*o,n.rotationVector.z*o,1).normalize(),n.object.quaternion.multiply(n.tmpQuaternion),(a.distanceToSquared(n.object.position)>i||8*(1-r.dot(n.object.quaternion))>i)&&(n.dispatchEvent(Wc),r.copy(n.object.quaternion),a.copy(n.object.position))},this.updateMovementVector=function(){const t=this.moveState.forward||this.autoForward&&!this.moveState.back?1:0;this.moveVector.x=-this.moveState.left+this.moveState.right,this.moveVector.y=-this.moveState.down+this.moveState.up,this.moveVector.z=-t+this.moveState.back},this.updateRotationVector=function(){this.rotationVector.x=-this.moveState.pitchDown+this.moveState.pitchUp,this.rotationVector.y=-this.moveState.yawRight+this.moveState.yawLeft,this.rotationVector.z=-this.moveState.rollRight+this.moveState.rollLeft},this.getContainerDimensions=function(){return this.domElement!=document?{size:[this.domElement.offsetWidth,this.domElement.offsetHeight],offset:[this.domElement.offsetLeft,this.domElement.offsetTop]}:{size:[window.innerWidth,window.innerHeight],offset:[0,0]}},this.dispose=function(){this.domElement.removeEventListener("contextmenu",Xc),this.domElement.removeEventListener("pointerdown",s),this.domElement.removeEventListener("pointermove",o),this.domElement.removeEventListener("pointerup",l),window.removeEventListener("keydown",c),window.removeEventListener("keyup",u)};const o=this.pointermove.bind(this),s=this.pointerdown.bind(this),l=this.pointerup.bind(this),c=this.keydown.bind(this),u=this.keyup.bind(this);this.domElement.addEventListener("contextmenu",Xc),this.domElement.addEventListener("pointerdown",s),this.domElement.addEventListener("pointermove",o),this.domElement.addEventListener("pointerup",l),window.addEventListener("keydown",c),window.addEventListener("keyup",u),this.updateMovementVector(),this.updateRotationVector()}}function Xc(t){t.preventDefault()}const Yc={uniforms:{tDiffuse:{value:null},opacity:{value:1}},vertexShader:"\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvUv = uv;\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n\t\t}",fragmentShader:"\n\n\t\tuniform float opacity;\n\n\t\tuniform sampler2D tDiffuse;\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tgl_FragColor = texture2D( tDiffuse, vUv );\n\t\t\tgl_FragColor.a *= opacity;\n\n\n\t\t}"};class $c{constructor(){this.enabled=!0,this.needsSwap=!0,this.clear=!1,this.renderToScreen=!1}setSize(){}render(){console.error("THREE.Pass: .render() must be implemented in derived pass.")}dispose(){}}const Zc=new $n(-1,1,1,-1,0,1),Jc=new tn;Jc.setAttribute("position",new qe([-1,3,0,-1,-1,0,3,-1,0],3)),Jc.setAttribute("uv",new qe([0,2,0,0,2,0],2));class Kc{constructor(t){this._mesh=new mn(Jc,t)}dispose(){this._mesh.geometry.dispose()}render(t){t.render(this._mesh,Zc)}get material(){return this._mesh.material}set material(t){this._mesh.material=t}}class Qc extends $c{constructor(t,e){super(),this.textureID=void 0!==e?e:"tDiffuse",t instanceof wn?(this.uniforms=t.uniforms,this.material=t):t&&(this.uniforms=bn.clone(t.uniforms),this.material=new wn({defines:Object.assign({},t.defines),uniforms:this.uniforms,vertexShader:t.vertexShader,fragmentShader:t.fragmentShader})),this.fsQuad=new Kc(this.material)}render(t,e,n){this.uniforms[this.textureID]&&(this.uniforms[this.textureID].value=n.texture),this.fsQuad.material=this.material,this.renderToScreen?(t.setRenderTarget(null),this.fsQuad.render(t)):(t.setRenderTarget(e),this.clear&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),this.fsQuad.render(t))}dispose(){this.material.dispose(),this.fsQuad.dispose()}}class tu extends $c{constructor(t,e){super(),this.scene=t,this.camera=e,this.clear=!0,this.needsSwap=!1,this.inverse=!1}render(t,e,n){const i=t.getContext(),r=t.state;let a,o;r.buffers.color.setMask(!1),r.buffers.depth.setMask(!1),r.buffers.color.setLocked(!0),r.buffers.depth.setLocked(!0),this.inverse?(a=0,o=1):(a=1,o=0),r.buffers.stencil.setTest(!0),r.buffers.stencil.setOp(i.REPLACE,i.REPLACE,i.REPLACE),r.buffers.stencil.setFunc(i.ALWAYS,a,4294967295),r.buffers.stencil.setClear(o),r.buffers.stencil.setLocked(!0),t.setRenderTarget(n),this.clear&&t.clear(),t.render(this.scene,this.camera),t.setRenderTarget(e),this.clear&&t.clear(),t.render(this.scene,this.camera),r.buffers.color.setLocked(!1),r.buffers.depth.setLocked(!1),r.buffers.stencil.setLocked(!1),r.buffers.stencil.setFunc(i.EQUAL,1,4294967295),r.buffers.stencil.setOp(i.KEEP,i.KEEP,i.KEEP),r.buffers.stencil.setLocked(!0)}}class eu extends $c{constructor(){super(),this.needsSwap=!1}render(t){t.state.buffers.stencil.setLocked(!1),t.state.buffers.stencil.setTest(!1)}}class nu{constructor(t,e){if(this.renderer=t,void 0===e){const n=t.getSize(new K);this._pixelRatio=t.getPixelRatio(),this._width=n.width,this._height=n.height,(e=new bt(this._width*this._pixelRatio,this._height*this._pixelRatio)).texture.name="EffectComposer.rt1"}else this._pixelRatio=1,this._width=e.width,this._height=e.height;this.renderTarget1=e,this.renderTarget2=e.clone(),this.renderTarget2.texture.name="EffectComposer.rt2",this.writeBuffer=this.renderTarget1,this.readBuffer=this.renderTarget2,this.renderToScreen=!0,this.passes=[],this.copyPass=new Qc(Yc),this.clock=new $a}swapBuffers(){const t=this.readBuffer;this.readBuffer=this.writeBuffer,this.writeBuffer=t}addPass(t){this.passes.push(t),t.setSize(this._width*this._pixelRatio,this._height*this._pixelRatio)}insertPass(t,e){this.passes.splice(e,0,t),t.setSize(this._width*this._pixelRatio,this._height*this._pixelRatio)}removePass(t){const e=this.passes.indexOf(t);-1!==e&&this.passes.splice(e,1)}isLastEnabledPass(t){for(let e=t+1;e1?i-1:0),a=1;a=0&&r<1?(s=a,l=o):r>=1&&r<2?(s=o,l=a):r>=2&&r<3?(l=a,c=o):r>=3&&r<4?(l=o,c=a):r>=4&&r<5?(s=o,c=a):r>=5&&r<6&&(s=a,c=o);var u=n-a/2;return i(s+u,l+u,c+u)}var vu={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"639",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"};var _u=/^#[a-fA-F0-9]{6}$/,yu=/^#[a-fA-F0-9]{8}$/,xu=/^#[a-fA-F0-9]{3}$/,bu=/^#[a-fA-F0-9]{4}$/,wu=/^rgb\(\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*\)$/i,Mu=/^rgb(?:a)?\(\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,|\/)\s*([-+]?\d*[.]?\d+[%]?)\s*\)$/i,Su=/^hsl\(\s*(\d{0,3}[.]?[0-9]+(?:deg)?)\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*\)$/i,Eu=/^hsl(?:a)?\(\s*(\d{0,3}[.]?[0-9]+(?:deg)?)\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,|\/)\s*([-+]?\d*[.]?\d+[%]?)\s*\)$/i;function Tu(t){if("string"!=typeof t)throw new pu(3);var e=function(t){if("string"!=typeof t)return t;var e=t.toLowerCase();return vu[e]?"#"+vu[e]:t}(t);if(e.match(_u))return{red:parseInt(""+e[1]+e[2],16),green:parseInt(""+e[3]+e[4],16),blue:parseInt(""+e[5]+e[6],16)};if(e.match(yu)){var n=parseFloat((parseInt(""+e[7]+e[8],16)/255).toFixed(2));return{red:parseInt(""+e[1]+e[2],16),green:parseInt(""+e[3]+e[4],16),blue:parseInt(""+e[5]+e[6],16),alpha:n}}if(e.match(xu))return{red:parseInt(""+e[1]+e[1],16),green:parseInt(""+e[2]+e[2],16),blue:parseInt(""+e[3]+e[3],16)};if(e.match(bu)){var i=parseFloat((parseInt(""+e[4]+e[4],16)/255).toFixed(2));return{red:parseInt(""+e[1]+e[1],16),green:parseInt(""+e[2]+e[2],16),blue:parseInt(""+e[3]+e[3],16),alpha:i}}var r=wu.exec(e);if(r)return{red:parseInt(""+r[1],10),green:parseInt(""+r[2],10),blue:parseInt(""+r[3],10)};var a=Mu.exec(e.substring(0,50));if(a)return{red:parseInt(""+a[1],10),green:parseInt(""+a[2],10),blue:parseInt(""+a[3],10),alpha:parseFloat(""+a[4])>1?parseFloat(""+a[4])/100:parseFloat(""+a[4])};var o=Su.exec(e);if(o){var s="rgb("+gu(parseInt(""+o[1],10),parseInt(""+o[2],10)/100,parseInt(""+o[3],10)/100)+")",l=wu.exec(s);if(!l)throw new pu(4,e,s);return{red:parseInt(""+l[1],10),green:parseInt(""+l[2],10),blue:parseInt(""+l[3],10)}}var c=Eu.exec(e.substring(0,50));if(c){var u="rgb("+gu(parseInt(""+c[1],10),parseInt(""+c[2],10)/100,parseInt(""+c[3],10)/100)+")",h=wu.exec(u);if(!h)throw new pu(4,e,u);return{red:parseInt(""+h[1],10),green:parseInt(""+h[2],10),blue:parseInt(""+h[3],10),alpha:parseFloat(""+c[4])>1?parseFloat(""+c[4])/100:parseFloat(""+c[4])}}throw new pu(5)}var Au=function(t){return 7===t.length&&t[1]===t[2]&&t[3]===t[4]&&t[5]===t[6]?"#"+t[1]+t[3]+t[5]:t};function Cu(t){var e=t.toString(16);return 1===e.length?"0"+e:e}function Lu(t,e,n){if("number"==typeof t&&"number"==typeof e&&"number"==typeof n)return Au("#"+Cu(t)+Cu(e)+Cu(n));if("object"==typeof t&&void 0===e&&void 0===n)return Au("#"+Cu(t.red)+Cu(t.green)+Cu(t.blue));throw new pu(6)}function Pu(t,e,n){return function(){var i=n.concat(Array.prototype.slice.call(arguments));return i.length>=e?t.apply(this,i):Pu(t,e,i)}}function Ru(t){return Pu(t,t.length,[])}function Du(t,e){if("transparent"===e)return e;var n,i,r,a=Tu(e),o="number"==typeof a.alpha?a.alpha:1;return function(t,e,n,i){if("string"==typeof t&&"number"==typeof e){var r=Tu(t);return"rgba("+r.red+","+r.green+","+r.blue+","+e+")"}if("number"==typeof t&&"number"==typeof e&&"number"==typeof n&&"number"==typeof i)return i>=1?Lu(t,e,n):"rgba("+t+","+e+","+n+","+i+")";if("object"==typeof t&&void 0===e&&void 0===n&&void 0===i)return t.alpha>=1?Lu(t.red,t.green,t.blue):"rgba("+t.red+","+t.green+","+t.blue+","+t.alpha+")";throw new pu(7)}(ru({},a,{alpha:(n=0,i=1,r=(100*o+100*parseFloat(t))/100,Math.max(n,Math.min(i,r)))}))}var Ou=Ru(Du),Iu={Linear:{None:function(t){return t}},Quadratic:{In:function(t){return t*t},Out:function(t){return t*(2-t)},InOut:function(t){return(t*=2)<1?.5*t*t:-.5*(--t*(t-2)-1)}},Cubic:{In:function(t){return t*t*t},Out:function(t){return--t*t*t+1},InOut:function(t){return(t*=2)<1?.5*t*t*t:.5*((t-=2)*t*t+2)}},Quartic:{In:function(t){return t*t*t*t},Out:function(t){return 1- --t*t*t*t},InOut:function(t){return(t*=2)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)}},Quintic:{In:function(t){return t*t*t*t*t},Out:function(t){return--t*t*t*t*t+1},InOut:function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)}},Sinusoidal:{In:function(t){return 1-Math.cos(t*Math.PI/2)},Out:function(t){return Math.sin(t*Math.PI/2)},InOut:function(t){return.5*(1-Math.cos(Math.PI*t))}},Exponential:{In:function(t){return 0===t?0:Math.pow(1024,t-1)},Out:function(t){return 1===t?1:1-Math.pow(2,-10*t)},InOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?.5*Math.pow(1024,t-1):.5*(2-Math.pow(2,-10*(t-1)))}},Circular:{In:function(t){return 1-Math.sqrt(1-t*t)},Out:function(t){return Math.sqrt(1- --t*t)},InOut:function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)}},Elastic:{In:function(t){return 0===t?0:1===t?1:-Math.pow(2,10*(t-1))*Math.sin(5*(t-1.1)*Math.PI)},Out:function(t){return 0===t?0:1===t?1:Math.pow(2,-10*t)*Math.sin(5*(t-.1)*Math.PI)+1},InOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?-.5*Math.pow(2,10*(t-1))*Math.sin(5*(t-1.1)*Math.PI):.5*Math.pow(2,-10*(t-1))*Math.sin(5*(t-1.1)*Math.PI)+1}},Back:{In:function(t){var e=1.70158;return t*t*((e+1)*t-e)},Out:function(t){var e=1.70158;return--t*t*((e+1)*t+e)+1},InOut:function(t){var e=2.5949095;return(t*=2)<1?t*t*((e+1)*t-e)*.5:.5*((t-=2)*t*((e+1)*t+e)+2)}},Bounce:{In:function(t){return 1-Iu.Bounce.Out(1-t)},Out:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},InOut:function(t){return t<.5?.5*Iu.Bounce.In(2*t):.5*Iu.Bounce.Out(2*t-1)+.5}}},ku="undefined"==typeof self&&"undefined"!=typeof process&&process.hrtime?function(){var t=process.hrtime();return 1e3*t[0]+t[1]/1e6}:"undefined"!=typeof self&&void 0!==self.performance&&void 0!==self.performance.now?self.performance.now.bind(self.performance):void 0!==Date.now?Date.now:function(){return(new Date).getTime()},Nu=function(){function t(){this._tweens={},this._tweensAddedDuringUpdate={}}return t.prototype.getAll=function(){var t=this;return Object.keys(this._tweens).map((function(e){return t._tweens[e]}))},t.prototype.removeAll=function(){this._tweens={}},t.prototype.add=function(t){this._tweens[t.getId()]=t,this._tweensAddedDuringUpdate[t.getId()]=t},t.prototype.remove=function(t){delete this._tweens[t.getId()],delete this._tweensAddedDuringUpdate[t.getId()]},t.prototype.update=function(t,e){void 0===t&&(t=ku()),void 0===e&&(e=!1);var n=Object.keys(this._tweens);if(0===n.length)return!1;for(;n.length>0;){this._tweensAddedDuringUpdate={};for(var i=0;i1?a(t[n],t[n-1],n-i):a(t[r],t[r+1>n?n:r+1],i-r)},Bezier:function(t,e){for(var n=0,i=t.length-1,r=Math.pow,a=zu.Utils.Bernstein,o=0;o<=i;o++)n+=r(1-e,i-o)*r(e,o)*t[o]*a(i,o);return n},CatmullRom:function(t,e){var n=t.length-1,i=n*e,r=Math.floor(i),a=zu.Utils.CatmullRom;return t[0]===t[n]?(e<0&&(r=Math.floor(i=n*(1+e))),a(t[(r-1+n)%n],t[r],t[(r+1)%n],t[(r+2)%n],i-r)):e<0?t[0]-(a(t[0],t[0],t[1],t[1],-i)-t[0]):e>1?t[n]-(a(t[n],t[n],t[n-1],t[n-1],i-n)-t[n]):a(t[r?r-1:0],t[r],t[n1;i--)n*=i;return t[e]=n,n}}(),CatmullRom:function(t,e,n,i,r){var a=.5*(n-t),o=.5*(i-e),s=r*r;return(2*e-2*n+a+o)*(r*s)+(-3*e+3*n-2*a-o)*s+a*r+e}}},Uu=function(){function t(){}return t.nextId=function(){return t._nextId++},t._nextId=0,t}(),Fu=new Nu,Bu=function(){function t(t,e){void 0===e&&(e=Fu),this._object=t,this._group=e,this._isPaused=!1,this._pauseStart=0,this._valuesStart={},this._valuesEnd={},this._valuesStartRepeat={},this._duration=1e3,this._initialRepeat=0,this._repeat=0,this._yoyo=!1,this._isPlaying=!1,this._reversed=!1,this._delayTime=0,this._startTime=0,this._easingFunction=Iu.Linear.None,this._interpolationFunction=zu.Linear,this._chainedTweens=[],this._onStartCallbackFired=!1,this._id=Uu.nextId(),this._isChainStopped=!1,this._goToEnd=!1}return t.prototype.getId=function(){return this._id},t.prototype.isPlaying=function(){return this._isPlaying},t.prototype.isPaused=function(){return this._isPaused},t.prototype.to=function(t,e){return this._valuesEnd=Object.create(t),void 0!==e&&(this._duration=e),this},t.prototype.duration=function(t){return this._duration=t,this},t.prototype.start=function(t){if(this._isPlaying)return this;if(this._group&&this._group.add(this),this._repeat=this._initialRepeat,this._reversed)for(var e in this._reversed=!1,this._valuesStartRepeat)this._swapEndStartRepeatValues(e),this._valuesStart[e]=this._valuesStartRepeat[e];return this._isPlaying=!0,this._isPaused=!1,this._onStartCallbackFired=!1,this._isChainStopped=!1,this._startTime=void 0!==t?"string"==typeof t?ku()+parseFloat(t):t:ku(),this._startTime+=this._delayTime,this._setupProperties(this._object,this._valuesStart,this._valuesEnd,this._valuesStartRepeat),this},t.prototype._setupProperties=function(t,e,n,i){for(var r in n){var a=t[r],o=Array.isArray(a),s=o?"array":typeof a,l=!o&&Array.isArray(n[r]);if("undefined"!==s&&"function"!==s){if(l){var c=n[r];if(0===c.length)continue;c=c.map(this._handleRelativeValue.bind(this,a)),n[r]=[a].concat(c)}if("object"!==s&&!o||!a||l)void 0===e[r]&&(e[r]=a),o||(e[r]*=1),i[r]=l?n[r].slice().reverse():e[r]||0;else{for(var u in e[r]=o?[]:{},a)e[r][u]=a[u];i[r]=o?[]:{},this._setupProperties(a,e[r],n[r],i[r])}}}},t.prototype.stop=function(){return this._isChainStopped||(this._isChainStopped=!0,this.stopChainedTweens()),this._isPlaying?(this._group&&this._group.remove(this),this._isPlaying=!1,this._isPaused=!1,this._onStopCallback&&this._onStopCallback(this._object),this):this},t.prototype.end=function(){return this._goToEnd=!0,this.update(1/0),this},t.prototype.pause=function(t){return void 0===t&&(t=ku()),this._isPaused||!this._isPlaying||(this._isPaused=!0,this._pauseStart=t,this._group&&this._group.remove(this)),this},t.prototype.resume=function(t){return void 0===t&&(t=ku()),this._isPaused&&this._isPlaying?(this._isPaused=!1,this._startTime+=t-this._pauseStart,this._pauseStart=0,this._group&&this._group.add(this),this):this},t.prototype.stopChainedTweens=function(){for(var t=0,e=this._chainedTweens.length;tr)return!1;e&&this.start(t)}if(this._goToEnd=!1,t1?1:i;var a=this._easingFunction(i);if(this._updateProperties(this._object,this._valuesStart,this._valuesEnd,a),this._onUpdateCallback&&this._onUpdateCallback(this._object,i),1===i){if(this._repeat>0){for(n in isFinite(this._repeat)&&this._repeat--,this._valuesStartRepeat)this._yoyo||"string"!=typeof this._valuesEnd[n]||(this._valuesStartRepeat[n]=this._valuesStartRepeat[n]+parseFloat(this._valuesEnd[n])),this._yoyo&&this._swapEndStartRepeatValues(n),this._valuesStart[n]=this._valuesStartRepeat[n];return this._yoyo&&(this._reversed=!this._reversed),void 0!==this._repeatDelayTime?this._startTime=t+this._repeatDelayTime:this._startTime=t+this._delayTime,this._onRepeatCallback&&this._onRepeatCallback(this._object),!0}this._onCompleteCallback&&this._onCompleteCallback(this._object);for(var o=0,s=this._chainedTweens.length;ot.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&(e.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.backgroundIntensity=this.backgroundIntensity),e}get autoUpdate(){return console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate}set autoUpdate(t){console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate=t}},PerspectiveCamera:Sn,Raycaster:Ja,TextureLoader:class extends ja{constructor(t){super(t)}load(t,e,n,i){const r=new yt,a=new Ga(this.manager);return a.setCrossOrigin(this.crossOrigin),a.setPath(this.path),a.load(t,(function(t){r.image=t,r.needsUpdate=!0,void 0!==e&&e(r)}),n,i),r}},Vector2:K,Vector3:Et,Box3:Ct,Color:pt,Mesh:mn,SphereGeometry:Na,MeshBasicMaterial:Be,BackSide:1,EventDispatcher:j,MOUSE:o,Quaternion:St,Spherical:to,Clock:$a},eh=Tl({props:{width:{default:window.innerWidth,onChange:function(t,e,n){isNaN(t)&&(e.width=n)}},height:{default:window.innerHeight,onChange:function(t,e,n){isNaN(t)&&(e.height=n)}},backgroundColor:{default:"#000011"},backgroundImageUrl:{},onBackgroundImageLoaded:{},showNavInfo:{default:!0},skyRadius:{default:5e4},objects:{default:[]},enablePointerInteraction:{default:!0,onChange:function(t,e){e.hoverObj=null,e.toolTipElem&&(e.toolTipElem.innerHTML="")},triggerUpdate:!1},lineHoverPrecision:{default:1,triggerUpdate:!1},hoverOrderComparator:{default:function(){return-1},triggerUpdate:!1},hoverFilter:{default:function(){return!0},triggerUpdate:!1},tooltipContent:{triggerUpdate:!1},hoverDuringDrag:{default:!1,triggerUpdate:!1},clickAfterDrag:{default:!1,triggerUpdate:!1},onHover:{default:function(){},triggerUpdate:!1},onClick:{default:function(){},triggerUpdate:!1},onRightClick:{triggerUpdate:!1}},methods:{tick:function(t){if(t.initialised){if(t.controls.update&&t.controls.update(t.clock.getDelta()),t.postProcessingComposer?t.postProcessingComposer.render():t.renderer.render(t.scene,t.camera),t.extraRenderers.forEach((function(e){return e.render(t.scene,t.camera)})),t.enablePointerInteraction){var e=null;if(t.hoverDuringDrag||!t.isPointerDragging){var n=this.intersectingObjects(t.pointerPos.x,t.pointerPos.y).filter((function(e){return t.hoverFilter(e.object)})).sort((function(e,n){return t.hoverOrderComparator(e.object,n.object)})),i=n.length?n[0]:null;e=i?i.object:null,t.intersectionPoint=i?i.point:null}e!==t.hoverObj&&(t.onHover(e,t.hoverObj),t.toolTipElem.innerHTML=e&&Al(t.tooltipContent)(e)||"",t.hoverObj=e)}Yu.update()}return this},getPointerPos:function(t){var e=t.pointerPos;return{x:e.x,y:e.y}},cameraPosition:function(t,e,n,i){var r=t.camera;if(e&&t.initialised){var a=e,o=n||{x:0,y:0,z:0};if(i){var s=Object.assign({},r.position),l=h();new Yu.Tween(s).to(a,i).easing(Yu.Easing.Quadratic.Out).onUpdate(c).start(),new Yu.Tween(l).to(o,i/3).easing(Yu.Easing.Quadratic.Out).onUpdate(u).start()}else c(a),u(o);return this}return Object.assign({},r.position,{lookAt:h()});function c(t){var e=t.x,n=t.y,i=t.z;void 0!==e&&(r.position.x=e),void 0!==n&&(r.position.y=n),void 0!==i&&(r.position.z=i)}function u(e){var n=new th.Vector3(e.x,e.y,e.z);t.controls.target?t.controls.target=n:r.lookAt(n)}function h(){return Object.assign(new th.Vector3(0,0,-1e3).applyQuaternion(r.quaternion).add(r.position))}},zoomToFit:function(t){for(var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:10,i=arguments.length,r=new Array(i>3?i-3:0),a=3;a2&&void 0!==arguments[2]?arguments[2]:0,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:10,r=t.camera;if(e){var a=new th.Vector3(0,0,0),o=2*Math.max.apply(Math,Ju(Object.entries(e).map((function(t){var e=Zu(t,2),n=e[0],i=e[1];return Math.max.apply(Math,Ju(i.map((function(t){return Math.abs(a[n]-t)}))))})))),s=(1-2*i/t.height)*r.fov,l=o/Math.atan(s*Math.PI/180),c=l/r.aspect,u=Math.max(l,c);if(u>0){var h=a.clone().sub(r.position).normalize().multiplyScalar(-u);this.cameraPosition(h,a,n)}}return this},getBbox:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){return!0},n=new th.Box3(new th.Vector3(0,0,0),new th.Vector3(0,0,0)),i=t.objects.filter(e);return i.length?(i.forEach((function(t){return n.expandByObject(t)})),Object.assign.apply(Object,Ju(["x","y","z"].map((function(t){return $u({},t,[n.min[t],n.max[t]])}))))):null},getScreenCoords:function(t,e,n,i){var r=new th.Vector3(e,n,i);return r.project(this.camera()),{x:(r.x+1)*t.width/2,y:-(r.y-1)*t.height/2}},getSceneCoords:function(t,e,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,r=new th.Vector2(e/t.width*2-1,-n/t.height*2+1),a=new th.Raycaster;return a.setFromCamera(r,t.camera),Object.assign({},a.ray.at(i,new th.Vector3))},intersectingObjects:function(t,e,n){var i=new th.Vector2(e/t.width*2-1,-n/t.height*2+1),r=new th.Raycaster;return r.params.Line.threshold=t.lineHoverPrecision,r.setFromCamera(i,t.camera),r.intersectObjects(t.objects,!0)},renderer:function(t){return t.renderer},scene:function(t){return t.scene},camera:function(t){return t.camera},postProcessingComposer:function(t){return t.postProcessingComposer},controls:function(t){return t.controls},tbControls:function(t){return t.controls}},stateInit:function(){return{scene:new th.Scene,camera:new th.PerspectiveCamera,clock:new th.Clock}},init:function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=n.controlType,r=void 0===i?"trackball":i,a=n.rendererConfig,o=void 0===a?{}:a,s=n.extraRenderers,l=void 0===s?[]:s,c=n.waitForLoadComplete,u=void 0===c||c;t.innerHTML="",t.appendChild(e.container=document.createElement("div")),e.container.className="scene-container",e.container.style.position="relative",e.container.appendChild(e.navInfo=document.createElement("div")),e.navInfo.className="scene-nav-info",e.navInfo.textContent={orbit:"Left-click: rotate, Mouse-wheel/middle-click: zoom, Right-click: pan",trackball:"Left-click: rotate, Mouse-wheel/middle-click: zoom, Right-click: pan",fly:"WASD: move, R|F: up | down, Q|E: roll, up|down: pitch, left|right: yaw"}[r]||"",e.navInfo.style.display=e.showNavInfo?null:"none",e.toolTipElem=document.createElement("div"),e.toolTipElem.classList.add("scene-tooltip"),e.container.appendChild(e.toolTipElem),e.pointerPos=new th.Vector2,e.pointerPos.x=-2,e.pointerPos.y=-2,["pointermove","pointerdown"].forEach((function(t){return e.container.addEventListener(t,(function(n){if("pointerdown"===t&&(e.isPointerPressed=!0),!e.isPointerDragging&&"pointermove"===n.type&&(n.pressure>0||e.isPointerPressed)&&("touch"!==n.pointerType||void 0===n.movementX||[n.movementX,n.movementY].some((function(t){return Math.abs(t)>1})))&&(e.isPointerDragging=!0),e.enablePointerInteraction){var i=(r=e.container,a=r.getBoundingClientRect(),o=window.pageXOffset||document.documentElement.scrollLeft,s=window.pageYOffset||document.documentElement.scrollTop,{top:a.top+s,left:a.left+o});e.pointerPos.x=n.pageX-i.left,e.pointerPos.y=n.pageY-i.top,e.toolTipElem.style.top="".concat(e.pointerPos.y,"px"),e.toolTipElem.style.left="".concat(e.pointerPos.x,"px"),e.toolTipElem.style.transform="translate(-".concat(e.pointerPos.x/e.width*100,"%, ").concat(e.height-e.pointerPos.y<100?"calc(-100% - 8px)":"21px",")")}var r,a,o,s}),{passive:!0})})),e.container.addEventListener("pointerup",(function(t){e.isPointerPressed=!1,e.isPointerDragging&&(e.isPointerDragging=!1,!e.clickAfterDrag)||requestAnimationFrame((function(){0===t.button&&e.onClick(e.hoverObj||null,t,e.intersectionPoint),2===t.button&&e.onRightClick&&e.onRightClick(e.hoverObj||null,t,e.intersectionPoint)}))}),{passive:!0,capture:!0}),e.container.addEventListener("contextmenu",(function(t){e.onRightClick&&t.preventDefault()})),e.renderer=new th.WebGLRenderer(Object.assign({antialias:!0,alpha:!0},o)),e.renderer.setPixelRatio(Math.min(2,window.devicePixelRatio)),e.container.appendChild(e.renderer.domElement),e.extraRenderers=l,e.extraRenderers.forEach((function(t){t.domElement.style.position="absolute",t.domElement.style.top="0px",t.domElement.style.pointerEvents="none",e.container.appendChild(t.domElement)})),e.postProcessingComposer=new nu(e.renderer),e.postProcessingComposer.addPass(new iu(e.scene,e.camera)),e.controls=new{trackball:Bc,orbit:Vc,fly:qc}[r](e.camera,e.renderer.domElement),"fly"===r&&(e.controls.movementSpeed=300,e.controls.rollSpeed=Math.PI/6,e.controls.dragToLook=!0),"trackball"!==r&&"orbit"!==r||(e.controls.minDistance=.1,e.controls.maxDistance=e.skyRadius,e.controls.addEventListener("start",(function(){e.controlsEngaged=!0})),e.controls.addEventListener("change",(function(){e.controlsEngaged&&(e.controlsDragging=!0)})),e.controls.addEventListener("end",(function(){e.controlsEngaged=!1,e.controlsDragging=!1}))),[e.renderer,e.postProcessingComposer].concat(Ju(e.extraRenderers)).forEach((function(t){return t.setSize(e.width,e.height)})),e.camera.aspect=e.width/e.height,e.camera.updateProjectionMatrix(),e.camera.position.z=1e3,e.scene.add(e.skysphere=new th.Mesh),e.skysphere.visible=!1,e.loadComplete=e.scene.visible=!u,window.scene=e.scene},update:function(t,e){if(t.width&&t.height&&(e.hasOwnProperty("width")||e.hasOwnProperty("height"))&&(t.container.style.width="".concat(t.width,"px"),t.container.style.height="".concat(t.height,"px"),[t.renderer,t.postProcessingComposer].concat(Ju(t.extraRenderers)).forEach((function(e){return e.setSize(t.width,t.height)})),t.camera.aspect=t.width/t.height,t.camera.updateProjectionMatrix()),e.hasOwnProperty("skyRadius")&&t.skyRadius&&(t.controls.hasOwnProperty("maxDistance")&&e.skyRadius&&(t.controls.maxDistance=Math.min(t.controls.maxDistance,t.skyRadius)),t.camera.far=2.5*t.skyRadius,t.camera.updateProjectionMatrix(),t.skysphere.geometry=new th.SphereGeometry(t.skyRadius)),e.hasOwnProperty("backgroundColor")){var n=Tu(t.backgroundColor).alpha;void 0===n&&(n=1),t.renderer.setClearColor(new th.Color(Ou(1,t.backgroundColor)),n)}function i(){t.loadComplete=t.scene.visible=!0}e.hasOwnProperty("backgroundImageUrl")&&(t.backgroundImageUrl?(new th.TextureLoader).load(t.backgroundImageUrl,(function(e){t.skysphere.material=new th.MeshBasicMaterial({map:e,side:th.BackSide}),t.skysphere.visible=!0,t.onBackgroundImageLoaded&&setTimeout(t.onBackgroundImageLoaded),!t.loadComplete&&i()})):(t.skysphere.visible=!1,t.skysphere.material.map=null,!t.loadComplete&&i())),e.hasOwnProperty("showNavInfo")&&(t.navInfo.style.display=t.showNavInfo?null:"none"),e.hasOwnProperty("objects")&&((e.objects||[]).forEach((function(e){return t.scene.remove(e)})),t.objects.forEach((function(e){return t.scene.add(e)})))}});function nh(t,e){var n=new e;return{linkProp:function(e){return{default:n[e](),onChange:function(n,i){i[t][e](n)},triggerUpdate:!1}},linkMethod:function(e){return function(n){for(var i=n[t],r=arguments.length,a=new Array(r>1?r-1:0),o=1;o3?r-3:0),o=3;o1&&(z=s.y+s.vy),u>2&&(p=s.z+s.vz),o.visit(m);function m(n,t,e,r,i,o,a){var c=[t,e,r,i,o,a],l=c[0],h=c[1],y=c[2],g=c[u],q=c[u+1],N=c[u+2],m=n.data,A=n.r,b=M+A;if(!m)return l>x+b||g1&&(h>z+b||q2&&(y>p+b||Ns.index){var k=x-m.x-m.vx,E=u>1?z-m.y-m.vy:0,j=u>2?p-m.z-m.vz:0,D=k*k+E*E+j*j;D1&&0===E&&(D+=(E=f(v))*E),u>2&&0===j&&(D+=(j=f(v))*j),D=(b-(D=Math.sqrt(D)))/D*d,s.vx+=(k*=D)*(b=(A*=A)/(w+A)),u>1&&(s.vy+=(E*=D)*b),u>2&&(s.vz+=(j*=D)*b),m.vx-=k*(b=1-b),u>1&&(m.vy-=E*b),u>2&&(m.vz-=j*b))}}}function g(n){if(n.data)return n.r=h[n.data.index];for(var t=n.r=0;tn.r&&(n.r=n[t].r)}function x(){if(i){var t,e,r=i.length;for(h=new Array(r),t=0;t"function"==typeof n))||Math.random,u=t.find((n=>[1,2,3].includes(n)))||2,x()},s.iterations=function(n){return arguments.length?(y=+n,s):y},s.strength=function(n){return arguments.length?(d=+n,s):d},s.radius=function(t){return arguments.length?(n="function"==typeof t?t:o(+t),x(),s):n},s},n.forceLink=function(n){var t,e,r,i,u,a,c,l=h,d=function(n){return 1/Math.min(u[n.source.index],u[n.target.index])},y=o(30),s=1;function g(r){for(var u=0,o=n.length;u1&&(z=v.y+v.vy-h.y-h.vy||f(c)),i>2&&(p=v.z+v.vz-h.z-h.vz||f(c)),x*=d=((d=Math.sqrt(x*x+z*z+p*p))-e[g])/d*r*t[g],z*=d,p*=d,v.vx-=x*(y=a[g]),i>1&&(v.vy-=z*y),i>2&&(v.vz-=p*y),h.vx+=x*(y=1-y),i>1&&(h.vy+=z*y),i>2&&(h.vz+=p*y)}function x(){if(r){var i,o,f=r.length,c=n.length,h=new Map(r.map(((n,t)=>[l(n,t,r),n])));for(i=0,u=new Array(f);i"function"==typeof n))||Math.random,i=t.find((n=>[1,2,3].includes(n)))||2,x()},g.links=function(t){return arguments.length?(n=t,x(),g):n},g.id=function(n){return arguments.length?(l=n,g):l},g.iterations=function(n){return arguments.length?(s=+n,g):s},g.strength=function(n){return arguments.length?(d="function"==typeof n?n:o(+n),z(),g):d},g.distance=function(n){return arguments.length?(y="function"==typeof n?n:o(+n),p(),g):y},g},n.forceManyBody=function(){var n,i,u,a,c,l,h=o(-30),v=1,d=1/0,x=.81;function z(o){var f,a=n.length,l=(1===i?t.binarytree(n,y):2===i?e.quadtree(n,y,s):3===i?r.octree(n,y,s,g):null).visitAfter(M);for(c=o,f=0;f1&&(n.y=u/c),i>2&&(n.z=o/c)}else{(t=n).x=t.data.x,i>1&&(t.y=t.data.y),i>2&&(t.z=t.data.z);do{a+=l[t.data.index]}while(t=t.next)}n.value=a}function w(n,t,e,r,o){if(!n.value)return!0;var h=[e,r,o][i-1],y=n.x-u.x,s=i>1?n.y-u.y:0,g=i>2?n.z-u.z:0,z=h-t,p=y*y+s*s+g*g;if(z*z/x1&&0===s&&(p+=(s=f(a))*s),i>2&&0===g&&(p+=(g=f(a))*g),p1&&(u.vy+=s*n.value*c/p),i>2&&(u.vz+=g*n.value*c/p)),!0;if(!(n.length||p>=d)){(n.data!==u||n.next)&&(0===y&&(p+=(y=f(a))*y),i>1&&0===s&&(p+=(s=f(a))*s),i>2&&0===g&&(p+=(g=f(a))*g),p1&&(u.vy+=s*z),i>2&&(u.vz+=g*z))}while(n=n.next)}}return z.initialize=function(t,...e){n=t,a=e.find((n=>"function"==typeof n))||Math.random,i=e.find((n=>[1,2,3].includes(n)))||2,p()},z.strength=function(n){return arguments.length?(h="function"==typeof n?n:o(+n),p(),z):h},z.distanceMin=function(n){return arguments.length?(v=n*n,z):Math.sqrt(v)},z.distanceMax=function(n){return arguments.length?(d=n*n,z):Math.sqrt(d)},z.theta=function(n){return arguments.length?(x=n*n,z):Math.sqrt(x)},z},n.forceRadial=function(n,t,e,r){var i,u,f,a,c=o(.1);function l(n){for(var o=0,c=i.length;o1&&(l.vy+=v*s),u>2&&(l.vz+=d*s)}}function h(){if(i){var t,e=i.length;for(f=new Array(e),a=new Array(e),t=0;t[1,2,3].includes(n)))||2,h()},l.strength=function(n){return arguments.length?(c="function"==typeof n?n:o(+n),h(),l):c},l.radius=function(t){return arguments.length?(n="function"==typeof t?t:o(+t),h(),l):n},l.x=function(n){return arguments.length?(t=+n,l):t},l.y=function(n){return arguments.length?(e=+n,l):e},l.z=function(n){return arguments.length?(r=+n,l):r},l},n.forceSimulation=function(n,t){t=t||2;var e,r=Math.min(3,Math.max(1,Math.round(t))),o=1,f=.001,a=1-Math.pow(f,1/300),c=0,l=.6,h=new Map,v=u.timer(g),y=i.dispatch("tick","end"),s=function(){let n=1;return()=>(n=(1664525*n+1013904223)%d)/d}();function g(){p(),y.call("tick",e),o1&&(null==u.fy?u.y+=u.vy*=l:(u.y=u.fy,u.vy=0)),r>2&&(null==u.fz?u.z+=u.vz*=l:(u.z=u.fz,u.vz=0));return e}function M(){for(var t,e=0,i=n.length;e1&&isNaN(t.y)||r>2&&isNaN(t.z)){var u=10*(r>2?Math.cbrt(.5+e):r>1?Math.sqrt(.5+e):e),o=e*x,f=e*z;1===r?t.x=u:2===r?(t.x=u*Math.cos(o),t.y=u*Math.sin(o)):(t.x=u*Math.sin(o)*Math.cos(f),t.y=u*Math.cos(o),t.z=u*Math.sin(o)*Math.sin(f))}(isNaN(t.vx)||r>1&&isNaN(t.vy)||r>2&&isNaN(t.vz))&&(t.vx=0,r>1&&(t.vy=0),r>2&&(t.vz=0))}}function w(t){return t.initialize&&t.initialize(n,s,r),t}return null==n&&(n=[]),M(),e={tick:p,restart:function(){return v.restart(g),e},stop:function(){return v.stop(),e},numDimensions:function(n){return arguments.length?(r=Math.min(3,Math.max(1,Math.round(n))),h.forEach(w),e):r},nodes:function(t){return arguments.length?(n=t,M(),h.forEach(w),e):n},alpha:function(n){return arguments.length?(o=+n,e):o},alphaMin:function(n){return arguments.length?(f=+n,e):f},alphaDecay:function(n){return arguments.length?(a=+n,e):+a},alphaTarget:function(n){return arguments.length?(c=+n,e):c},velocityDecay:function(n){return arguments.length?(l=1-n,e):1-l},randomSource:function(n){return arguments.length?(s=n,h.forEach(w),e):s},force:function(n,t){return arguments.length>1?(null==t?h.delete(n):h.set(n,w(t)),e):h.get(n)},find:function(){var t,e,i,u,o,f,a=Array.prototype.slice.call(arguments),c=a.shift()||0,l=(r>1?a.shift():null)||0,h=(r>2?a.shift():null)||0,v=a.shift()||1/0,d=0,y=n.length;for(v*=v,d=0;d1?(y.on(n,t),e):y.on(n)}}},n.forceX=function(n){var t,e,r,i=o(.1);function u(n){for(var i,u=0,o=t.length;ut.length)&&(e=t.length);for(var r=0,i=new Array(e);r0&&void 0!==arguments[0]?arguments[0]:"",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:10,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"rgba(255, 255, 255, 1)";return e(this,l),(t=c.call(this,new f.SpriteMaterial))._text="".concat(r),t._textHeight=i,t._color=n,t._backgroundColor=!1,t._padding=0,t._borderWidth=0,t._borderRadius=0,t._borderColor="white",t._strokeWidth=0,t._strokeColor="white",t._fontFace="Arial",t._fontSize=90,t._fontWeight="normal",t._canvas=document.createElement("canvas"),t._genCanvas(),t}return i=l,(o=[{key:"text",get:function(){return this._text},set:function(t){this._text=t,this._genCanvas()}},{key:"textHeight",get:function(){return this._textHeight},set:function(t){this._textHeight=t,this._genCanvas()}},{key:"color",get:function(){return this._color},set:function(t){this._color=t,this._genCanvas()}},{key:"backgroundColor",get:function(){return this._backgroundColor},set:function(t){this._backgroundColor=t,this._genCanvas()}},{key:"padding",get:function(){return this._padding},set:function(t){this._padding=t,this._genCanvas()}},{key:"borderWidth",get:function(){return this._borderWidth},set:function(t){this._borderWidth=t,this._genCanvas()}},{key:"borderRadius",get:function(){return this._borderRadius},set:function(t){this._borderRadius=t,this._genCanvas()}},{key:"borderColor",get:function(){return this._borderColor},set:function(t){this._borderColor=t,this._genCanvas()}},{key:"fontFace",get:function(){return this._fontFace},set:function(t){this._fontFace=t,this._genCanvas()}},{key:"fontSize",get:function(){return this._fontSize},set:function(t){this._fontSize=t,this._genCanvas()}},{key:"fontWeight",get:function(){return this._fontWeight},set:function(t){this._fontWeight=t,this._genCanvas()}},{key:"strokeWidth",get:function(){return this._strokeWidth},set:function(t){this._strokeWidth=t,this._genCanvas()}},{key:"strokeColor",get:function(){return this._strokeColor},set:function(t){this._strokeColor=t,this._genCanvas()}},{key:"_genCanvas",value:function(){var t=this,e=this._canvas,r=e.getContext("2d"),i=Array.isArray(this.borderWidth)?this.borderWidth:[this.borderWidth,this.borderWidth],n=i.map((function(e){return e*t.fontSize*.1})),o=(Array.isArray(this.borderRadius)?this.borderRadius:[this.borderRadius,this.borderRadius,this.borderRadius,this.borderRadius]).map((function(e){return e*t.fontSize*.1})),a=Array.isArray(this.padding)?this.padding:[this.padding,this.padding],u=a.map((function(e){return e*t.fontSize*.1})),c=this.text.split("\n"),l="".concat(this.fontWeight," ").concat(this.fontSize,"px ").concat(this.fontFace);r.font=l;var d=Math.max.apply(Math,s(c.map((function(t){return r.measureText(t).width})))),g=this.fontSize*c.length;if(e.width=d+2*n[0]+2*u[0],e.height=g+2*n[1]+2*u[1],this.borderWidth){if(r.strokeStyle=this.borderColor,n[0]){var p=n[0]/2;r.lineWidth=n[0],r.beginPath(),r.moveTo(p,o[0]),r.lineTo(p,e.height-o[3]),r.moveTo(e.width-p,o[1]),r.lineTo(e.width-p,e.height-o[2]),r.stroke()}if(n[1]){var y=n[1]/2;r.lineWidth=n[1],r.beginPath(),r.moveTo(Math.max(n[0],o[0]),y),r.lineTo(e.width-Math.max(n[0],o[1]),y),r.moveTo(Math.max(n[0],o[3]),e.height-y),r.lineTo(e.width-Math.max(n[0],o[2]),e.height-y),r.stroke()}if(this.borderRadius){var b=Math.max.apply(Math,s(n)),v=b/2;r.lineWidth=b,r.beginPath(),[!!o[0]&&[o[0],v,v,o[0]],!!o[1]&&[e.width-o[1],e.width-v,v,o[1]],!!o[2]&&[e.width-o[2],e.width-v,e.height-v,e.height-o[2]],!!o[3]&&[o[3],v,e.height-v,e.height-o[3]]].filter((function(t){return t})).forEach((function(t){var e=h(t,4),i=e[0],n=e[1],o=e[2],a=e[3];r.moveTo(i,o),r.quadraticCurveTo(n,o,n,a)})),r.stroke()}}this.backgroundColor&&(r.fillStyle=this.backgroundColor,this.borderRadius?(r.beginPath(),r.moveTo(n[0],o[0]),[[n[0],o[0],e.width-o[1],n[1],n[1],n[1]],[e.width-n[0],e.width-n[0],e.width-n[0],n[1],o[1],e.height-o[2]],[e.width-n[0],e.width-o[2],o[3],e.height-n[1],e.height-n[1],e.height-n[1]],[n[0],n[0],n[0],e.height-n[1],e.height-o[3],o[0]]].forEach((function(t){var e=h(t,6),i=e[0],n=e[1],o=e[2],a=e[3],s=e[4],u=e[5];r.quadraticCurveTo(i,a,n,s),r.lineTo(o,u)})),r.closePath(),r.fill()):r.fillRect(n[0],n[1],e.width-2*n[0],e.height-2*n[1])),r.translate.apply(r,s(n)),r.translate.apply(r,s(u)),r.font=l,r.fillStyle=this.color,r.textBaseline="bottom";var _=this.strokeWidth>0;_&&(r.lineWidth=this.strokeWidth*this.fontSize/10,r.strokeStyle=this.strokeColor),c.forEach((function(e,i){var n=(d-r.measureText(e).width)/2,o=(i+1)*t.fontSize;_&&r.strokeText(e,n,o),r.fillText(e,n,o)})),this.material.map&&this.material.map.dispose();var m=this.material.map=new f.Texture(e);m.minFilter=f.LinearFilter,m.needsUpdate=!0;var w=this.textHeight*c.length+2*i[1]+2*a[1];this.scale.set(w*e.width/e.height,w,0)}},{key:"clone",value:function(){return new this.constructor(this.text,this.textHeight,this.color).copy(this)}},{key:"copy",value:function(t){return f.Sprite.prototype.copy.call(this,t),this.color=t.color,this.backgroundColor=t.backgroundColor,this.padding=t.padding,this.borderWidth=t.borderWidth,this.borderColor=t.borderColor,this.fontFace=t.fontFace,this.fontSize=t.fontSize,this.fontWeight=t.fontWeight,this.strokeWidth=t.strokeWidth,this.strokeColor=t.strokeColor,this}}])&&r(i.prototype,o),u&&r(i,u),Object.defineProperty(i,"prototype",{writable:!1}),l}(f.Sprite);return l})); diff --git a/galaxy/static/galaxy/js/three.min.js b/galaxy/static/galaxy/js/three.min.js new file mode 100644 index 00000000..c3724c3a --- /dev/null +++ b/galaxy/static/galaxy/js/three.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2022 Three.js Authors + * SPDX-License-Identifier: MIT + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="148",i=100,n=300,r=301,s=302,a=303,o=304,l=306,c=1e3,h=1001,u=1002,d=1003,p=1004,m=1005,f=1006,g=1007,v=1008,x=1009,_=1012,y=1014,M=1015,b=1016,S=1020,w=1023,T=1026,A=1027,E=33776,C=33777,L=33778,R=33779,P=35840,I=35841,D=35842,N=35843,O=37492,z=37496,U=37808,B=37809,F=37810,k=37811,G=37812,V=37813,H=37814,W=37815,j=37816,q=37817,X=37818,Y=37819,Z=37820,J=37821,K=36492,$=2300,Q=2301,tt=2302,et=2400,it=2401,nt=2402,rt=2500,st=2501,at=3e3,ot=3001,lt="srgb",ct="srgb-linear",ht=7680,ut=35044,dt="300 es",pt=1035;class mt{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const i=this._listeners;void 0===i[t]&&(i[t]=[]),-1===i[t].indexOf(e)&&i[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const i=this._listeners;return void 0!==i[t]&&-1!==i[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const i=this._listeners[t];if(void 0!==i){const t=i.indexOf(e);-1!==t&&i.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const i=e.slice(0);for(let e=0,n=i.length;e>8&255]+ft[t>>16&255]+ft[t>>24&255]+"-"+ft[255&e]+ft[e>>8&255]+"-"+ft[e>>16&15|64]+ft[e>>24&255]+"-"+ft[63&i|128]+ft[i>>8&255]+"-"+ft[i>>16&255]+ft[i>>24&255]+ft[255&n]+ft[n>>8&255]+ft[n>>16&255]+ft[n>>24&255]).toLowerCase()}function yt(t,e,i){return Math.max(e,Math.min(i,t))}function Mt(t,e){return(t%e+e)%e}function bt(t,e,i){return(1-i)*t+i*e}function St(t){return 0==(t&t-1)&&0!==t}function wt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function Tt(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function At(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function Et(t,e){switch(e.constructor){case Float32Array:return t;case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}var Ct=Object.freeze({__proto__:null,DEG2RAD:vt,RAD2DEG:xt,generateUUID:_t,clamp:yt,euclideanModulo:Mt,mapLinear:function(t,e,i,n,r){return n+(t-e)*(r-n)/(i-e)},inverseLerp:function(t,e,i){return t!==e?(i-t)/(e-t):0},lerp:bt,damp:function(t,e,i,n){return bt(t,e,1-Math.exp(-i*n))},pingpong:function(t,e=1){return e-Math.abs(Mt(t,2*e)-e)},smoothstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*(3-2*t)},smootherstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(gt=t);let e=gt+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*vt},radToDeg:function(t){return t*xt},isPowerOfTwo:St,ceilPowerOfTwo:wt,floorPowerOfTwo:Tt,setQuaternionFromProperEuler:function(t,e,i,n,r){const s=Math.cos,a=Math.sin,o=s(i/2),l=a(i/2),c=s((e+n)/2),h=a((e+n)/2),u=s((e-n)/2),d=a((e-n)/2),p=s((n-e)/2),m=a((n-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:Et,denormalize:At});class Lt{constructor(t=0,e=0){Lt.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,i=this.y,n=t.elements;return this.x=n[0]*e+n[3]*i+n[6],this.y=n[1]*e+n[4]*i+n[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const i=this.length();return this.divideScalar(i||1).multiplyScalar(Math.max(t,Math.min(e,i)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,i=this.y-t.y;return e*e+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,i){return this.x=t.x+(e.x-t.x)*i,this.y=t.y+(e.y-t.y)*i,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const i=Math.cos(e),n=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*i-s*n+t.x,this.y=r*n+s*i+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Rt{constructor(){Rt.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1]}set(t,e,i,n,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=n,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=i,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,i=t.elements;return e[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],this}extractBasis(t,e,i){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),i.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const i=t.elements,n=e.elements,r=this.elements,s=i[0],a=i[3],o=i[6],l=i[1],c=i[4],h=i[7],u=i[2],d=i[5],p=i[8],m=n[0],f=n[3],g=n[6],v=n[1],x=n[4],_=n[7],y=n[2],M=n[5],b=n[8];return r[0]=s*m+a*v+o*y,r[3]=s*f+a*x+o*M,r[6]=s*g+a*_+o*b,r[1]=l*m+c*v+h*y,r[4]=l*f+c*x+h*M,r[7]=l*g+c*_+h*b,r[2]=u*m+d*v+p*y,r[5]=u*f+d*x+p*M,r[8]=u*g+d*_+p*b,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],i=t[1],n=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-i*r*c+i*a*o+n*r*l-n*s*o}invert(){const t=this.elements,e=t[0],i=t[1],n=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+i*u+n*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(n*l-c*i)*m,t[2]=(a*i-n*s)*m,t[3]=u*m,t[4]=(c*e-n*o)*m,t[5]=(n*r-a*e)*m,t[6]=d*m,t[7]=(i*o-l*e)*m,t[8]=(s*e-i*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,i,n,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(i*o,i*l,-i*(o*s+l*a)+s+t,-n*l,n*o,-n*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(Pt.makeScale(t,e)),this}rotate(t){return this.premultiply(Pt.makeRotation(-t)),this}translate(t,e){return this.premultiply(Pt.makeTranslation(t,e)),this}makeTranslation(t,e){return this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,-i,0,i,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,i=t.elements;for(let t=0;t<9;t++)if(e[t]!==i[t])return!1;return!0}fromArray(t,e=0){for(let i=0;i<9;i++)this.elements[i]=t[i+e];return this}toArray(t=[],e=0){const i=this.elements;return t[e]=i[0],t[e+1]=i[1],t[e+2]=i[2],t[e+3]=i[3],t[e+4]=i[4],t[e+5]=i[5],t[e+6]=i[6],t[e+7]=i[7],t[e+8]=i[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const Pt=new Rt;function It(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const Dt={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function Nt(t,e){return new Dt[t](e)}function Ot(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function zt(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Ut(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}const Bt={[lt]:{[ct]:zt},[ct]:{[lt]:Ut}},Ft={legacyMode:!0,get workingColorSpace(){return ct},set workingColorSpace(t){console.warn("THREE.ColorManagement: .workingColorSpace is readonly.")},convert:function(t,e,i){if(this.legacyMode||e===i||!e||!i)return t;if(Bt[e]&&void 0!==Bt[e][i]){const n=Bt[e][i];return t.r=n(t.r),t.g=n(t.g),t.b=n(t.b),t}throw new Error("Unsupported color space conversion.")},fromWorkingColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this.workingColorSpace)}},kt={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Gt={r:0,g:0,b:0},Vt={h:0,s:0,l:0},Ht={h:0,s:0,l:0};function Wt(t,e,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+6*(e-t)*(2/3-i):t}function jt(t,e){return e.r=t.r,e.g=t.g,e.b=t.b,e}class qt{constructor(t,e,i){return this.isColor=!0,this.r=1,this.g=1,this.b=1,void 0===e&&void 0===i?this.set(t):this.setRGB(t,e,i)}set(t){return t&&t.isColor?this.copy(t):"number"==typeof t?this.setHex(t):"string"==typeof t&&this.setStyle(t),this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=lt){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,Ft.toWorkingColorSpace(this,e),this}setRGB(t,e,i,n=Ft.workingColorSpace){return this.r=t,this.g=e,this.b=i,Ft.toWorkingColorSpace(this,n),this}setHSL(t,e,i,n=Ft.workingColorSpace){if(t=Mt(t,1),e=yt(e,0,1),i=yt(i,0,1),0===e)this.r=this.g=this.b=i;else{const n=i<=.5?i*(1+e):i+e-i*e,r=2*i-n;this.r=Wt(r,n,t+1/3),this.g=Wt(r,n,t),this.b=Wt(r,n,t-1/3)}return Ft.toWorkingColorSpace(this,n),this}setStyle(t,e=lt){function i(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let n;if(n=/^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec(t)){let t;const r=n[1],s=n[2];switch(r){case"rgb":case"rgba":if(t=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(s))return this.r=Math.min(255,parseInt(t[1],10))/255,this.g=Math.min(255,parseInt(t[2],10))/255,this.b=Math.min(255,parseInt(t[3],10))/255,Ft.toWorkingColorSpace(this,e),i(t[4]),this;if(t=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(s))return this.r=Math.min(100,parseInt(t[1],10))/100,this.g=Math.min(100,parseInt(t[2],10))/100,this.b=Math.min(100,parseInt(t[3],10))/100,Ft.toWorkingColorSpace(this,e),i(t[4]),this;break;case"hsl":case"hsla":if(t=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(s)){const n=parseFloat(t[1])/360,r=parseFloat(t[2])/100,s=parseFloat(t[3])/100;return i(t[4]),this.setHSL(n,r,s,e)}}}else if(n=/^\#([A-Fa-f\d]+)$/.exec(t)){const t=n[1],i=t.length;if(3===i)return this.r=parseInt(t.charAt(0)+t.charAt(0),16)/255,this.g=parseInt(t.charAt(1)+t.charAt(1),16)/255,this.b=parseInt(t.charAt(2)+t.charAt(2),16)/255,Ft.toWorkingColorSpace(this,e),this;if(6===i)return this.r=parseInt(t.charAt(0)+t.charAt(1),16)/255,this.g=parseInt(t.charAt(2)+t.charAt(3),16)/255,this.b=parseInt(t.charAt(4)+t.charAt(5),16)/255,Ft.toWorkingColorSpace(this,e),this}return t&&t.length>0?this.setColorName(t,e):this}setColorName(t,e=lt){const i=kt[t.toLowerCase()];return void 0!==i?this.setHex(i,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=zt(t.r),this.g=zt(t.g),this.b=zt(t.b),this}copyLinearToSRGB(t){return this.r=Ut(t.r),this.g=Ut(t.g),this.b=Ut(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=lt){return Ft.fromWorkingColorSpace(jt(this,Gt),t),yt(255*Gt.r,0,255)<<16^yt(255*Gt.g,0,255)<<8^yt(255*Gt.b,0,255)<<0}getHexString(t=lt){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=Ft.workingColorSpace){Ft.fromWorkingColorSpace(jt(this,Gt),e);const i=Gt.r,n=Gt.g,r=Gt.b,s=Math.max(i,n,r),a=Math.min(i,n,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case i:o=(n-r)/t+(n2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=Ot("canvas");e.width=t.width,e.height=t.height;const i=e.getContext("2d");i.drawImage(t,0,0,t.width,t.height);const n=i.getImageData(0,0,t.width,t.height),r=n.data;for(let t=0;t0&&(i.userData=this.userData),e||(t.textures[this.uuid]=i),i}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==n)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case c:t.x=t.x-Math.floor(t.x);break;case h:t.x=t.x<0?0:1;break;case u:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case c:t.y=t.y-Math.floor(t.y);break;case h:t.y=t.y<0?0:1;break;case u:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}}$t.DEFAULT_IMAGE=null,$t.DEFAULT_MAPPING=n,$t.DEFAULT_ANISOTROPY=1;class Qt{constructor(t=0,e=0,i=0,n=1){Qt.prototype.isVector4=!0,this.x=t,this.y=e,this.z=i,this.w=n}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,i,n){return this.x=t,this.y=e,this.z=i,this.w=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,i=this.y,n=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*i+s[8]*n+s[12]*r,this.y=s[1]*e+s[5]*i+s[9]*n+s[13]*r,this.z=s[2]*e+s[6]*i+s[10]*n+s[14]*r,this.w=s[3]*e+s[7]*i+s[11]*n+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,i,n,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,n=1-e*e;if(n>Number.EPSILON){const r=Math.sqrt(n),s=Math.atan2(r,e*i);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*i;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,i,n,r,s){const a=i[n],o=i[n+1],l=i[n+2],c=i[n+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,i,n){return this._x=t,this._y=e,this._z=i,this._w=n,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){const i=t._x,n=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(i/2),c=a(n/2),h=a(r/2),u=o(i/2),d=o(n/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const i=e/2,n=Math.sin(i);return this._x=t.x*n,this._y=t.y*n,this._z=t.z*n,this._w=Math.cos(i),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,i=e[0],n=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=i+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-n)*t}else if(i>a&&i>h){const t=2*Math.sqrt(1+i-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(n+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-i-h);this._w=(r-l)/t,this._x=(n+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-i-a);this._w=(s-n)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let i=t.dot(e)+1;return iMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=i):(this._x=0,this._y=-t.z,this._z=t.y,this._w=i)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=i),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(yt(this.dot(t),-1,1)))}rotateTowards(t,e){const i=this.angleTo(t);if(0===i)return this;const n=Math.min(1,e/i);return this.slerp(t,n),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const i=t._x,n=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=i*c+s*a+n*l-r*o,this._y=n*c+s*o+r*a-i*l,this._z=r*c+s*l+i*o-n*a,this._w=s*c-i*a-n*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const i=this._x,n=this._y,r=this._z,s=this._w;let a=s*t._w+i*t._x+n*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=i,this._y=n,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*i+e*this._x,this._y=t*n+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=i*h+this._x*u,this._y=n*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,i){return this.copy(t).slerp(e,i)}random(){const t=Math.random(),e=Math.sqrt(1-t),i=Math.sqrt(t),n=2*Math.PI*Math.random(),r=2*Math.PI*Math.random();return this.set(e*Math.cos(n),i*Math.sin(r),i*Math.cos(r),e*Math.sin(n))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class re{constructor(t=0,e=0,i=0){re.prototype.isVector3=!0,this.x=t,this.y=e,this.z=i}set(t,e,i){return void 0===i&&(i=this.z),this.x=t,this.y=e,this.z=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(ae.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(ae.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,i=this.y,n=this.z,r=t.elements;return this.x=r[0]*e+r[3]*i+r[6]*n,this.y=r[1]*e+r[4]*i+r[7]*n,this.z=r[2]*e+r[5]*i+r[8]*n,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,i=this.y,n=this.z,r=t.elements,s=1/(r[3]*e+r[7]*i+r[11]*n+r[15]);return this.x=(r[0]*e+r[4]*i+r[8]*n+r[12])*s,this.y=(r[1]*e+r[5]*i+r[9]*n+r[13])*s,this.z=(r[2]*e+r[6]*i+r[10]*n+r[14])*s,this}applyQuaternion(t){const e=this.x,i=this.y,n=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=o*e+s*n-a*i,c=o*i+a*e-r*n,h=o*n+r*i-s*e,u=-r*e-s*i-a*n;return this.x=l*o+u*-r+c*-a-h*-s,this.y=c*o+u*-s+h*-r-l*-a,this.z=h*o+u*-a+l*-s-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,i=this.y,n=this.z,r=t.elements;return this.x=r[0]*e+r[4]*i+r[8]*n,this.y=r[1]*e+r[5]*i+r[9]*n,this.z=r[2]*e+r[6]*i+r[10]*n,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const i=this.length();return this.divideScalar(i||1).multiplyScalar(Math.max(t,Math.min(e,i)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,i){return this.x=t.x+(e.x-t.x)*i,this.y=t.y+(e.y-t.y)*i,this.z=t.z+(e.z-t.z)*i,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const i=t.x,n=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=n*o-r*a,this.y=r*s-i*o,this.z=i*a-n*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const i=t.dot(this)/e;return this.copy(t).multiplyScalar(i)}projectOnPlane(t){return se.copy(this).projectOnVector(t),this.sub(se)}reflect(t){return this.sub(se.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const i=this.dot(t)/e;return Math.acos(yt(i,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,i=this.y-t.y,n=this.z-t.z;return e*e+i*i+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,i){const n=Math.sin(e)*t;return this.x=n*Math.sin(i),this.y=Math.cos(e)*t,this.z=n*Math.cos(i),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,i){return this.x=t*Math.sin(e),this.y=i,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),i=this.setFromMatrixColumn(t,1).length(),n=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=i,this.z=n,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=2*(Math.random()-.5),e=Math.random()*Math.PI*2,i=Math.sqrt(1-t**2);return this.x=i*Math.cos(e),this.y=i*Math.sin(e),this.z=t,this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const se=new re,ae=new ne;class oe{constructor(t=new re(1/0,1/0,1/0),e=new re(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){let e=1/0,i=1/0,n=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.length;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,i,n),this.max.set(r,s,a),this}setFromBufferAttribute(t){let e=1/0,i=1/0,n=1/0,r=-1/0,s=-1/0,a=-1/0;for(let o=0,l=t.count;or&&(r=l),c>s&&(s=c),h>a&&(a=h)}return this.min.set(e,i,n),this.max.set(r,s,a),this}setFromPoints(t){this.makeEmpty();for(let e=0,i=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,ce),ce.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,i;return t.normal.x>0?(e=t.normal.x*this.min.x,i=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,i=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,i+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,i+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,i+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,i+=t.normal.z*this.min.z),e<=-t.constant&&i>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(ve),xe.subVectors(this.max,ve),ue.subVectors(t.a,ve),de.subVectors(t.b,ve),pe.subVectors(t.c,ve),me.subVectors(de,ue),fe.subVectors(pe,de),ge.subVectors(ue,pe);let e=[0,-me.z,me.y,0,-fe.z,fe.y,0,-ge.z,ge.y,me.z,0,-me.x,fe.z,0,-fe.x,ge.z,0,-ge.x,-me.y,me.x,0,-fe.y,fe.x,0,-ge.y,ge.x,0];return!!Me(e,ue,de,pe,xe)&&(e=[1,0,0,0,1,0,0,0,1],!!Me(e,ue,de,pe,xe)&&(_e.crossVectors(me,fe),e=[_e.x,_e.y,_e.z],Me(e,ue,de,pe,xe)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return ce.copy(t).clamp(this.min,this.max).sub(t).length()}getBoundingSphere(t){return this.getCenter(t.center),t.radius=.5*this.getSize(ce).length(),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(le[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),le[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),le[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),le[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),le[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),le[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),le[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),le[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(le)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const le=[new re,new re,new re,new re,new re,new re,new re,new re],ce=new re,he=new oe,ue=new re,de=new re,pe=new re,me=new re,fe=new re,ge=new re,ve=new re,xe=new re,_e=new re,ye=new re;function Me(t,e,i,n,r){for(let s=0,a=t.length-3;s<=a;s+=3){ye.fromArray(t,s);const a=r.x*Math.abs(ye.x)+r.y*Math.abs(ye.y)+r.z*Math.abs(ye.z),o=e.dot(ye),l=i.dot(ye),c=n.dot(ye);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const be=new oe,Se=new re,we=new re;class Te{constructor(t=new re,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const i=this.center;void 0!==e?i.copy(e):be.setFromPoints(t).getCenter(i);let n=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Se.subVectors(t,this.center);const e=Se.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),i=.5*(t-this.radius);this.center.addScaledVector(Se,i/t),this.radius+=i}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(we.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Se.copy(t.center).add(we)),this.expandByPoint(Se.copy(t.center).sub(we))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Ae=new re,Ee=new re,Ce=new re,Le=new re,Re=new re,Pe=new re,Ie=new re;class De{constructor(t=new re,e=new re(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.direction).multiplyScalar(t).add(this.origin)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Ae)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const i=e.dot(this.direction);return i<0?e.copy(this.origin):e.copy(this.direction).multiplyScalar(i).add(this.origin)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Ae.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Ae.copy(this.direction).multiplyScalar(e).add(this.origin),Ae.distanceToSquared(t))}distanceSqToSegment(t,e,i,n){Ee.copy(t).add(e).multiplyScalar(.5),Ce.copy(e).sub(t).normalize(),Le.copy(this.origin).sub(Ee);const r=.5*t.distanceTo(e),s=-this.direction.dot(Ce),a=Le.dot(this.direction),o=-Le.dot(Ce),l=Le.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return i&&i.copy(this.direction).multiplyScalar(h).add(this.origin),n&&n.copy(Ce).multiplyScalar(u).add(Ee),d}intersectSphere(t,e){Ae.subVectors(t.center,this.origin);const i=Ae.dot(this.direction),n=Ae.dot(Ae)-i*i,r=t.radius*t.radius;if(n>r)return null;const s=Math.sqrt(r-n),a=i-s,o=i+s;return a<0&&o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const i=-(this.origin.dot(t.normal)+t.constant)/e;return i>=0?i:null}intersectPlane(t,e){const i=this.distanceToPlane(t);return null===i?null:this.at(i,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let i,n,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(i=(t.min.x-u.x)*l,n=(t.max.x-u.x)*l):(i=(t.max.x-u.x)*l,n=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),i>s||r>n?null:((r>i||isNaN(i))&&(i=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),i>o||a>n?null:((a>i||i!=i)&&(i=a),(o=0?i:n,e)))}intersectsBox(t){return null!==this.intersectBox(t,Ae)}intersectTriangle(t,e,i,n,r){Re.subVectors(e,t),Pe.subVectors(i,t),Ie.crossVectors(Re,Pe);let s,a=this.direction.dot(Ie);if(a>0){if(n)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}Le.subVectors(this.origin,t);const o=s*this.direction.dot(Pe.crossVectors(Le,Pe));if(o<0)return null;const l=s*this.direction.dot(Re.cross(Le));if(l<0)return null;if(o+l>a)return null;const c=-s*Le.dot(Ie);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class Ne{constructor(){Ne.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]}set(t,e,i,n,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=i,g[12]=n,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new Ne).fromArray(this.elements)}copy(t){const e=this.elements,i=t.elements;return e[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],e[9]=i[9],e[10]=i[10],e[11]=i[11],e[12]=i[12],e[13]=i[13],e[14]=i[14],e[15]=i[15],this}copyPosition(t){const e=this.elements,i=t.elements;return e[12]=i[12],e[13]=i[13],e[14]=i[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,i){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),i.setFromMatrixColumn(this,2),this}makeBasis(t,e,i){return this.set(t.x,e.x,i.x,0,t.y,e.y,i.y,0,t.z,e.z,i.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,i=t.elements,n=1/Oe.setFromMatrixColumn(t,0).length(),r=1/Oe.setFromMatrixColumn(t,1).length(),s=1/Oe.setFromMatrixColumn(t,2).length();return e[0]=i[0]*n,e[1]=i[1]*n,e[2]=i[2]*n,e[3]=0,e[4]=i[4]*r,e[5]=i[5]*r,e[6]=i[6]*r,e[7]=0,e[8]=i[8]*s,e[9]=i[9]*s,e[10]=i[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,i=t.x,n=t.y,r=t.z,s=Math.cos(i),a=Math.sin(i),o=Math.cos(n),l=Math.sin(n),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,i=s*h,n=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=i+n*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=n+i*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,i=o*h,n=l*c,r=l*h;e[0]=t+r*a,e[4]=n*a-i,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=i*a-n,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,i=o*h,n=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=n+i*a,e[1]=i+n*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,i=s*h,n=a*c,r=a*h;e[0]=o*c,e[4]=n*l-i,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=i*l-n,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,i=s*l,n=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=n*h+i,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=i*h+n,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,i=s*l,n=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=i*h-n,e[2]=n*h-i,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(Ue,t,Be)}lookAt(t,e,i){const n=this.elements;return Ge.subVectors(t,e),0===Ge.lengthSq()&&(Ge.z=1),Ge.normalize(),Fe.crossVectors(i,Ge),0===Fe.lengthSq()&&(1===Math.abs(i.z)?Ge.x+=1e-4:Ge.z+=1e-4,Ge.normalize(),Fe.crossVectors(i,Ge)),Fe.normalize(),ke.crossVectors(Ge,Fe),n[0]=Fe.x,n[4]=ke.x,n[8]=Ge.x,n[1]=Fe.y,n[5]=ke.y,n[9]=Ge.y,n[2]=Fe.z,n[6]=ke.z,n[10]=Ge.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const i=t.elements,n=e.elements,r=this.elements,s=i[0],a=i[4],o=i[8],l=i[12],c=i[1],h=i[5],u=i[9],d=i[13],p=i[2],m=i[6],f=i[10],g=i[14],v=i[3],x=i[7],_=i[11],y=i[15],M=n[0],b=n[4],S=n[8],w=n[12],T=n[1],A=n[5],E=n[9],C=n[13],L=n[2],R=n[6],P=n[10],I=n[14],D=n[3],N=n[7],O=n[11],z=n[15];return r[0]=s*M+a*T+o*L+l*D,r[4]=s*b+a*A+o*R+l*N,r[8]=s*S+a*E+o*P+l*O,r[12]=s*w+a*C+o*I+l*z,r[1]=c*M+h*T+u*L+d*D,r[5]=c*b+h*A+u*R+d*N,r[9]=c*S+h*E+u*P+d*O,r[13]=c*w+h*C+u*I+d*z,r[2]=p*M+m*T+f*L+g*D,r[6]=p*b+m*A+f*R+g*N,r[10]=p*S+m*E+f*P+g*O,r[14]=p*w+m*C+f*I+g*z,r[3]=v*M+x*T+_*L+y*D,r[7]=v*b+x*A+_*R+y*N,r[11]=v*S+x*E+_*P+y*O,r[15]=v*w+x*C+_*I+y*z,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],i=t[4],n=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-n*l*h-r*a*u+i*l*u+n*a*d-i*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-n*s*d+n*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+i*s*d+r*a*c-i*l*c)+t[15]*(-n*a*c-e*o*h+e*a*u+n*s*h-i*s*u+i*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,i){const n=this.elements;return t.isVector3?(n[12]=t.x,n[13]=t.y,n[14]=t.z):(n[12]=t,n[13]=e,n[14]=i),this}invert(){const t=this.elements,e=t[0],i=t[1],n=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,x=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,_=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,y=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,M=e*v+i*x+n*_+r*y;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const b=1/M;return t[0]=v*b,t[1]=(m*u*r-h*f*r-m*n*d+i*f*d+h*n*g-i*u*g)*b,t[2]=(a*f*r-m*o*r+m*n*l-i*f*l-a*n*g+i*o*g)*b,t[3]=(h*o*r-a*u*r-h*n*l+i*u*l+a*n*d-i*o*d)*b,t[4]=x*b,t[5]=(c*f*r-p*u*r+p*n*d-e*f*d-c*n*g+e*u*g)*b,t[6]=(p*o*r-s*f*r-p*n*l+e*f*l+s*n*g-e*o*g)*b,t[7]=(s*u*r-c*o*r+c*n*l-e*u*l-s*n*d+e*o*d)*b,t[8]=_*b,t[9]=(p*h*r-c*m*r-p*i*d+e*m*d+c*i*g-e*h*g)*b,t[10]=(s*m*r-p*a*r+p*i*l-e*m*l-s*i*g+e*a*g)*b,t[11]=(c*a*r-s*h*r-c*i*l+e*h*l+s*i*d-e*a*d)*b,t[12]=y*b,t[13]=(c*m*n-p*h*n+p*i*u-e*m*u-c*i*f+e*h*f)*b,t[14]=(p*a*n-s*m*n-p*i*o+e*m*o+s*i*f-e*a*f)*b,t[15]=(s*h*n-c*a*n+c*i*o-e*h*o-s*i*u+e*a*u)*b,this}scale(t){const e=this.elements,i=t.x,n=t.y,r=t.z;return e[0]*=i,e[4]*=n,e[8]*=r,e[1]*=i,e[5]*=n,e[9]*=r,e[2]*=i,e[6]*=n,e[10]*=r,e[3]*=i,e[7]*=n,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],i=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],n=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,i,n))}makeTranslation(t,e,i){return this.set(1,0,0,t,0,1,0,e,0,0,1,i,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),i=Math.sin(t);return this.set(1,0,0,0,0,e,-i,0,0,i,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,0,i,0,0,1,0,0,-i,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,-i,0,0,i,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const i=Math.cos(e),n=Math.sin(e),r=1-i,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+i,l*a-n*o,l*o+n*a,0,l*a+n*o,c*a+i,c*o-n*s,0,l*o-n*a,c*o+n*s,r*o*o+i,0,0,0,0,1),this}makeScale(t,e,i){return this.set(t,0,0,0,0,e,0,0,0,0,i,0,0,0,0,1),this}makeShear(t,e,i,n,r,s){return this.set(1,i,r,0,t,1,s,0,e,n,1,0,0,0,0,1),this}compose(t,e,i){const n=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,x=o*c,_=o*h,y=i.x,M=i.y,b=i.z;return n[0]=(1-(m+g))*y,n[1]=(d+_)*y,n[2]=(p-x)*y,n[3]=0,n[4]=(d-_)*M,n[5]=(1-(u+g))*M,n[6]=(f+v)*M,n[7]=0,n[8]=(p+x)*b,n[9]=(f-v)*b,n[10]=(1-(u+m))*b,n[11]=0,n[12]=t.x,n[13]=t.y,n[14]=t.z,n[15]=1,this}decompose(t,e,i){const n=this.elements;let r=Oe.set(n[0],n[1],n[2]).length();const s=Oe.set(n[4],n[5],n[6]).length(),a=Oe.set(n[8],n[9],n[10]).length();this.determinant()<0&&(r=-r),t.x=n[12],t.y=n[13],t.z=n[14],ze.copy(this);const o=1/r,l=1/s,c=1/a;return ze.elements[0]*=o,ze.elements[1]*=o,ze.elements[2]*=o,ze.elements[4]*=l,ze.elements[5]*=l,ze.elements[6]*=l,ze.elements[8]*=c,ze.elements[9]*=c,ze.elements[10]*=c,e.setFromRotationMatrix(ze),i.x=r,i.y=s,i.z=a,this}makePerspective(t,e,i,n,r,s){const a=this.elements,o=2*r/(e-t),l=2*r/(i-n),c=(e+t)/(e-t),h=(i+n)/(i-n),u=-(s+r)/(s-r),d=-2*s*r/(s-r);return a[0]=o,a[4]=0,a[8]=c,a[12]=0,a[1]=0,a[5]=l,a[9]=h,a[13]=0,a[2]=0,a[6]=0,a[10]=u,a[14]=d,a[3]=0,a[7]=0,a[11]=-1,a[15]=0,this}makeOrthographic(t,e,i,n,r,s){const a=this.elements,o=1/(e-t),l=1/(i-n),c=1/(s-r),h=(e+t)*o,u=(i+n)*l,d=(s+r)*c;return a[0]=2*o,a[4]=0,a[8]=0,a[12]=-h,a[1]=0,a[5]=2*l,a[9]=0,a[13]=-u,a[2]=0,a[6]=0,a[10]=-2*c,a[14]=-d,a[3]=0,a[7]=0,a[11]=0,a[15]=1,this}equals(t){const e=this.elements,i=t.elements;for(let t=0;t<16;t++)if(e[t]!==i[t])return!1;return!0}fromArray(t,e=0){for(let i=0;i<16;i++)this.elements[i]=t[i+e];return this}toArray(t=[],e=0){const i=this.elements;return t[e]=i[0],t[e+1]=i[1],t[e+2]=i[2],t[e+3]=i[3],t[e+4]=i[4],t[e+5]=i[5],t[e+6]=i[6],t[e+7]=i[7],t[e+8]=i[8],t[e+9]=i[9],t[e+10]=i[10],t[e+11]=i[11],t[e+12]=i[12],t[e+13]=i[13],t[e+14]=i[14],t[e+15]=i[15],t}}const Oe=new re,ze=new Ne,Ue=new re(0,0,0),Be=new re(1,1,1),Fe=new re,ke=new re,Ge=new re,Ve=new Ne,He=new ne;class We{constructor(t=0,e=0,i=0,n=We.DefaultOrder){this.isEuler=!0,this._x=t,this._y=e,this._z=i,this._order=n}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,i,n=this._order){return this._x=t,this._y=e,this._z=i,this._order=n,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,i=!0){const n=t.elements,r=n[0],s=n[4],a=n[8],o=n[1],l=n[5],c=n[9],h=n[2],u=n[6],d=n[10];switch(e){case"XYZ":this._y=Math.asin(yt(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-yt(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(yt(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-yt(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(yt(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-yt(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===i&&this._onChangeCallback(),this}setFromQuaternion(t,e,i){return Ve.makeRotationFromQuaternion(t),this.setFromRotationMatrix(Ve,e,i)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return He.setFromEuler(this),this.setFromQuaternion(He,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}toVector3(){console.error("THREE.Euler: .toVector3() has been removed. Use Vector3.setFromEuler() instead")}}We.DefaultOrder="XYZ",We.RotationOrders=["XYZ","YZX","ZXY","XZY","YXZ","ZYX"];class je{constructor(){this.mask=1}set(t){this.mask=(1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(i=i.concat(r))}return i}getWorldPosition(t){return this.updateWorldMatrix(!0,!1),t.setFromMatrixPosition(this.matrixWorld)}getWorldQuaternion(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(Ke,t,$e),t}getWorldScale(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(Ke,Qe,t),t}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(e[8],e[9],e[10]).normalize()}raycast(){}traverse(t){t(this);const e=this.children;for(let i=0,n=e.length;i0&&(n.userData=this.userData),n.layers=this.layers.mask,n.matrix=this.matrix.toArray(),!1===this.matrixAutoUpdate&&(n.matrixAutoUpdate=!1),this.isInstancedMesh&&(n.type="InstancedMesh",n.count=this.count,n.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(n.instanceColor=this.instanceColor.toJSON())),this.isScene)this.background&&(this.background.isColor?n.background=this.background.toJSON():this.background.isTexture&&(n.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(n.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){n.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const i=e.shapes;if(Array.isArray(i))for(let e=0,n=i.length;e0){n.children=[];for(let e=0;e0){n.animations=[];for(let e=0;e0&&(i.geometries=e),n.length>0&&(i.materials=n),r.length>0&&(i.textures=r),a.length>0&&(i.images=a),o.length>0&&(i.shapes=o),l.length>0&&(i.skeletons=l),c.length>0&&(i.animations=c),h.length>0&&(i.nodes=h)}return i.object=n,i;function s(t){const e=[];for(const i in t){const n=t[i];delete n.metadata,e.push(n)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?n.multiplyScalar(1/Math.sqrt(r)):n.set(0,0,0)}static getBarycoord(t,e,i,n,r){ai.subVectors(n,e),oi.subVectors(i,e),li.subVectors(t,e);const s=ai.dot(ai),a=ai.dot(oi),o=ai.dot(li),l=oi.dot(oi),c=oi.dot(li),h=s*l-a*a;if(0===h)return r.set(-2,-1,-1);const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,i,n){return this.getBarycoord(t,e,i,n,ci),ci.x>=0&&ci.y>=0&&ci.x+ci.y<=1}static getUV(t,e,i,n,r,s,a,o){return this.getBarycoord(t,e,i,n,ci),o.set(0,0),o.addScaledVector(r,ci.x),o.addScaledVector(s,ci.y),o.addScaledVector(a,ci.z),o}static isFrontFacing(t,e,i,n){return ai.subVectors(i,e),oi.subVectors(t,e),ai.cross(oi).dot(n)<0}set(t,e,i){return this.a.copy(t),this.b.copy(e),this.c.copy(i),this}setFromPointsAndIndices(t,e,i,n){return this.a.copy(t[e]),this.b.copy(t[i]),this.c.copy(t[n]),this}setFromAttributeAndIndices(t,e,i,n){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,i),this.c.fromBufferAttribute(t,n),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return ai.subVectors(this.c,this.b),oi.subVectors(this.a,this.b),.5*ai.cross(oi).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return gi.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return gi.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,i,n,r){return gi.getUV(t,this.a,this.b,this.c,e,i,n,r)}containsPoint(t){return gi.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return gi.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const i=this.a,n=this.b,r=this.c;let s,a;hi.subVectors(n,i),ui.subVectors(r,i),pi.subVectors(t,i);const o=hi.dot(pi),l=ui.dot(pi);if(o<=0&&l<=0)return e.copy(i);mi.subVectors(t,n);const c=hi.dot(mi),h=ui.dot(mi);if(c>=0&&h<=c)return e.copy(n);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(i).addScaledVector(hi,s);fi.subVectors(t,r);const d=hi.dot(fi),p=ui.dot(fi);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(i).addScaledVector(ui,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return di.subVectors(r,n),a=(h-c)/(h-c+(d-p)),e.copy(n).addScaledVector(di,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(i).addScaledVector(hi,s).addScaledVector(ui,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let vi=0;class xi extends mt{constructor(){super(),this.isMaterial=!0,Object.defineProperty(this,"id",{value:vi++}),this.uuid=_t(),this.name="",this.type="Material",this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=i,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=ht,this.stencilZFail=ht,this.stencilZPass=ht,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0,this._alphaTest=0}get alphaTest(){return this._alphaTest}set alphaTest(t){this._alphaTest>0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const i=t[e];if(void 0===i){console.warn("THREE.Material: '"+e+"' parameter is undefined.");continue}const n=this[e];void 0!==n?n&&n.isColor?n.set(i):n&&n.isVector3&&i&&i.isVector3?n.copy(i):this[e]=i:console.warn("THREE."+this.type+": '"+e+"' is not a property of this material.")}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const i={metadata:{version:4.5,type:"Material",generator:"Material.toJSON"}};function n(t){const e=[];for(const i in t){const n=t[i];delete n.metadata,e.push(n)}return e}if(i.uuid=this.uuid,i.type=this.type,""!==this.name&&(i.name=this.name),this.color&&this.color.isColor&&(i.color=this.color.getHex()),void 0!==this.roughness&&(i.roughness=this.roughness),void 0!==this.metalness&&(i.metalness=this.metalness),void 0!==this.sheen&&(i.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(i.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(i.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(i.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(i.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(i.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(i.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(i.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(i.shininess=this.shininess),void 0!==this.clearcoat&&(i.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(i.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(i.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(i.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(i.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,i.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.iridescence&&(i.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(i.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(i.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(i.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(i.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(i.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(i.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(i.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(i.lightMap=this.lightMap.toJSON(t).uuid,i.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(i.aoMap=this.aoMap.toJSON(t).uuid,i.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(i.bumpMap=this.bumpMap.toJSON(t).uuid,i.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(i.normalMap=this.normalMap.toJSON(t).uuid,i.normalMapType=this.normalMapType,i.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(i.displacementMap=this.displacementMap.toJSON(t).uuid,i.displacementScale=this.displacementScale,i.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(i.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(i.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(i.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(i.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(i.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(i.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(i.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(i.combine=this.combine)),void 0!==this.envMapIntensity&&(i.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(i.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(i.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(i.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(i.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(i.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(i.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(i.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(i.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(i.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(i.size=this.size),null!==this.shadowSide&&(i.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(i.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(i.blending=this.blending),0!==this.side&&(i.side=this.side),this.vertexColors&&(i.vertexColors=!0),this.opacity<1&&(i.opacity=this.opacity),!0===this.transparent&&(i.transparent=this.transparent),i.depthFunc=this.depthFunc,i.depthTest=this.depthTest,i.depthWrite=this.depthWrite,i.colorWrite=this.colorWrite,i.stencilWrite=this.stencilWrite,i.stencilWriteMask=this.stencilWriteMask,i.stencilFunc=this.stencilFunc,i.stencilRef=this.stencilRef,i.stencilFuncMask=this.stencilFuncMask,i.stencilFail=this.stencilFail,i.stencilZFail=this.stencilZFail,i.stencilZPass=this.stencilZPass,void 0!==this.rotation&&0!==this.rotation&&(i.rotation=this.rotation),!0===this.polygonOffset&&(i.polygonOffset=!0),0!==this.polygonOffsetFactor&&(i.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(i.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(i.linewidth=this.linewidth),void 0!==this.dashSize&&(i.dashSize=this.dashSize),void 0!==this.gapSize&&(i.gapSize=this.gapSize),void 0!==this.scale&&(i.scale=this.scale),!0===this.dithering&&(i.dithering=!0),this.alphaTest>0&&(i.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(i.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(i.premultipliedAlpha=this.premultipliedAlpha),!0===this.wireframe&&(i.wireframe=this.wireframe),this.wireframeLinewidth>1&&(i.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(i.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(i.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(i.flatShading=this.flatShading),!1===this.visible&&(i.visible=!1),!1===this.toneMapped&&(i.toneMapped=!1),!1===this.fog&&(i.fog=!1),Object.keys(this.userData).length>0&&(i.userData=this.userData),e){const e=n(t.textures),r=n(t.images);e.length>0&&(i.textures=e),r.length>0&&(i.images=r)}return i}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let i=null;if(null!==e){const t=e.length;i=new Array(t);for(let n=0;n!==t;++n)i[n]=e[n].clone()}return this.clippingPlanes=i,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}class _i extends xi{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new qt(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const yi=new re,Mi=new Lt;class bi{constructor(t,e,i=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=i,this.usage=ut,this.updateRange={offset:0,count:-1},this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this}copyAt(t,e,i){t*=this.itemSize,i*=e.itemSize;for(let n=0,r=this.itemSize;n0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const i in e)void 0!==e[i]&&(t[i]=e[i]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const i=this.attributes;for(const e in i){const n=i[e];t.data.attributes[e]=n.toJSON(t.data)}const n={};let r=!1;for(const e in this.morphAttributes){const i=this.morphAttributes[e],s=[];for(let e=0,n=i.length;e0&&(n[e]=s,r=!0)}r&&(t.data.morphAttributes=n,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const i=t.index;null!==i&&this.setIndex(i.clone(e));const n=t.attributes;for(const t in n){const i=n[t];this.setAttribute(t,i.clone(e))}const r=t.morphAttributes;for(const t in r){const i=[],n=r[t];for(let t=0,r=n.length;t0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;ti.far?null:{distance:c,point:qi.clone(),object:t}}(t,e,i,n,Ui,Bi,Fi,ji);if(c){r&&(Vi.fromBufferAttribute(r,a),Hi.fromBufferAttribute(r,o),Wi.fromBufferAttribute(r,l),c.uv=gi.getUV(ji,Ui,Bi,Fi,Vi,Hi,Wi,new Lt)),s&&(Vi.fromBufferAttribute(s,a),Hi.fromBufferAttribute(s,o),Wi.fromBufferAttribute(s,l),c.uv2=gi.getUV(ji,Ui,Bi,Fi,Vi,Hi,Wi,new Lt));const t={a:a,b:o,c:l,normal:new re,materialIndex:0};gi.getNormal(Ui,Bi,Fi,t.normal),c.face=t}return c}class Zi extends Di{constructor(t=1,e=1,i=1,n=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:i,widthSegments:n,heightSegments:r,depthSegments:s};const a=this;n=Math.floor(n),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,i,n,r,s,p,m,f,g,v){const x=s/f,_=p/g,y=s/2,M=p/2,b=m/2,S=f+1,w=g+1;let T=0,A=0;const E=new re;for(let s=0;s0?1:-1,c.push(E.x,E.y,E.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader;const i={};for(const t in this.extensions)!0===this.extensions[t]&&(i[t]=!0);return Object.keys(i).length>0&&(e.extensions=i),e}}class en extends si{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new Ne,this.projectionMatrix=new Ne,this.projectionMatrixInverse=new Ne}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}class nn extends en{constructor(t=50,e=1,i=.1,n=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=i,this.far=n,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*xt*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*vt*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*xt*Math.atan(Math.tan(.5*vt*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,i,n,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=i,this.view.offsetY=n,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*vt*this.fov)/this.zoom,i=2*e,n=this.aspect*i,r=-.5*n;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*n/t,e-=s.offsetY*i/a,n*=s.width/t,i*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+n,e,e-i,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const rn=-90;class sn extends si{constructor(t,e,i){super(),this.type="CubeCamera",this.renderTarget=i;const n=new nn(rn,1,t,e);n.layers=this.layers,n.up.set(0,1,0),n.lookAt(1,0,0),this.add(n);const r=new nn(rn,1,t,e);r.layers=this.layers,r.up.set(0,1,0),r.lookAt(-1,0,0),this.add(r);const s=new nn(rn,1,t,e);s.layers=this.layers,s.up.set(0,0,-1),s.lookAt(0,1,0),this.add(s);const a=new nn(rn,1,t,e);a.layers=this.layers,a.up.set(0,0,1),a.lookAt(0,-1,0),this.add(a);const o=new nn(rn,1,t,e);o.layers=this.layers,o.up.set(0,1,0),o.lookAt(0,0,1),this.add(o);const l=new nn(rn,1,t,e);l.layers=this.layers,l.up.set(0,1,0),l.lookAt(0,0,-1),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const i=this.renderTarget,[n,r,s,a,o,l]=this.children,c=t.getRenderTarget(),h=t.toneMapping,u=t.xr.enabled;t.toneMapping=0,t.xr.enabled=!1;const d=i.texture.generateMipmaps;i.texture.generateMipmaps=!1,t.setRenderTarget(i,0),t.render(e,n),t.setRenderTarget(i,1),t.render(e,r),t.setRenderTarget(i,2),t.render(e,s),t.setRenderTarget(i,3),t.render(e,a),t.setRenderTarget(i,4),t.render(e,o),i.texture.generateMipmaps=d,t.setRenderTarget(i,5),t.render(e,l),t.setRenderTarget(c),t.toneMapping=h,t.xr.enabled=u,i.texture.needsPMREMUpdate=!0}}class an extends $t{constructor(t,e,i,n,s,a,o,l,c,h){super(t=void 0!==t?t:[],e=void 0!==e?e:r,i,n,s,a,o,l,c,h),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class on extends te{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const i={width:t,height:t,depth:1},n=[i,i,i,i,i,i];this.texture=new an(n,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.encoding),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:f}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.encoding=e.encoding,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const i={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},n=new Zi(5,5,5),r=new tn({name:"CubemapFromEquirect",uniforms:Ji(i.uniforms),vertexShader:i.vertexShader,fragmentShader:i.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new Xi(n,r),a=e.minFilter;e.minFilter===v&&(e.minFilter=f);return new sn(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,i,n){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,i,n);t.setRenderTarget(r)}}const ln=new re,cn=new re,hn=new Rt;class un{constructor(t=new re(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,i,n){return this.normal.set(t,e,i),this.constant=n,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,i){const n=ln.subVectors(i,e).cross(cn.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(n,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(this.normal).multiplyScalar(-this.distanceToPoint(t)).add(t)}intersectLine(t,e){const i=t.delta(ln),n=this.normal.dot(i);if(0===n)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/n;return r<0||r>1?null:e.copy(i).multiplyScalar(r).add(t.start)}intersectsLine(t){const e=this.distanceToPoint(t.start),i=this.distanceToPoint(t.end);return e<0&&i>0||i<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const i=e||hn.getNormalMatrix(t),n=this.coplanarPoint(ln).applyMatrix4(t),r=this.normal.applyMatrix3(i).normalize();return this.constant=-n.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const dn=new Te,pn=new re;class mn{constructor(t=new un,e=new un,i=new un,n=new un,r=new un,s=new un){this.planes=[t,e,i,n,r,s]}set(t,e,i,n,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(i),a[3].copy(n),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let i=0;i<6;i++)e[i].copy(t.planes[i]);return this}setFromProjectionMatrix(t){const e=this.planes,i=t.elements,n=i[0],r=i[1],s=i[2],a=i[3],o=i[4],l=i[5],c=i[6],h=i[7],u=i[8],d=i[9],p=i[10],m=i[11],f=i[12],g=i[13],v=i[14],x=i[15];return e[0].setComponents(a-n,h-o,m-u,x-f).normalize(),e[1].setComponents(a+n,h+o,m+u,x+f).normalize(),e[2].setComponents(a+r,h+l,m+d,x+g).normalize(),e[3].setComponents(a-r,h-l,m-d,x-g).normalize(),e[4].setComponents(a-s,h-c,m-p,x-v).normalize(),e[5].setComponents(a+s,h+c,m+p,x+v).normalize(),this}intersectsObject(t){const e=t.geometry;return null===e.boundingSphere&&e.computeBoundingSphere(),dn.copy(e.boundingSphere).applyMatrix4(t.matrixWorld),this.intersectsSphere(dn)}intersectsSprite(t){return dn.center.set(0,0,0),dn.radius=.7071067811865476,dn.applyMatrix4(t.matrixWorld),this.intersectsSphere(dn)}intersectsSphere(t){const e=this.planes,i=t.center,n=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(i)0?t.max.x:t.min.x,pn.y=n.normal.y>0?t.max.y:t.min.y,pn.z=n.normal.z>0?t.max.z:t.min.z,n.distanceToPoint(pn)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let i=0;i<6;i++)if(e[i].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function fn(){let t=null,e=!1,i=null,n=null;function r(e,s){i(e,s),n=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==i&&(n=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(n),e=!1},setAnimationLoop:function(t){i=t},setContext:function(e){t=e}}}function gn(t,e){const i=e.isWebGL2,n=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),n.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const i=n.get(e);i&&(t.deleteBuffer(i.buffer),n.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=n.get(e);return void((!t||t.version 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif",iridescence_fragment:"#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660,\t0.0556434,\n\t\t-1.5371385,\t1.8760108, -0.2040259,\n\t\t-0.4985314,\t0.0415560,\t1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\t return vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat R21 = R12;\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = dFdx( surf_pos.xyz );\n\t\tvec3 vSigmaY = dFdy( surf_pos.xyz );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif",clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#if defined( USE_ENVMAP )\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#else\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULARINTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;\n\t\t#endif\n\t\t#ifdef USE_SPECULARCOLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vUv ).rgb;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vUv ).a;\n\t#endif\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3(\t\t0, 1,\t\t0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX_Iridescence( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness );\n\t#else\n\t\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = vec4( mix( pow( sampledDiffuseColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), sampledDiffuseColor.rgb * 0.0773993808, vec3( lessThanEqual( sampledDiffuseColor.rgb, vec3( 0.04045 ) ) ) ), sampledDiffuseColor.w );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * faceDirection;\n\t\t\tbitangent = bitangent * faceDirection;\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( - vViewPosition, normal, mapN, faceDirection );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN, float faceDirection ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n\t\treturn normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN, faceDirection );\n\t#endif\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",output_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha + 0.1;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t\tf.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if defined( USE_SHADOWMAP ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_COORDS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3(\t1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108,\t1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605,\t1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmission = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmission.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmission.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\t#ifdef texture2DLodEXT\n\t\t\treturn texture2DLodEXT( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#else\n\t\t\treturn texture2D( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#endif\n\t}\n\tvec3 applyVolumeAttenuation( const in vec3 radiance, const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn radiance;\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance * radiance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );\n\t}\n#endif",uv_pars_fragment:"#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif",uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULARINTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n\t#ifdef USE_SPECULARCOLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},_n={common:{diffuse:{value:new qt(16777215)},opacity:{value:1},map:{value:null},uvTransform:{value:new Rt},uv2Transform:{value:new Rt},alphaMap:{value:null},alphaTest:{value:0}},specularmap:{specularMap:{value:null}},envmap:{envMap:{value:null},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1}},emissivemap:{emissiveMap:{value:null}},bumpmap:{bumpMap:{value:null},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalScale:{value:new Lt(1,1)}},displacementmap:{displacementMap:{value:null},displacementScale:{value:1},displacementBias:{value:0}},roughnessmap:{roughnessMap:{value:null}},metalnessmap:{metalnessMap:{value:null}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new qt(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new qt(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new Rt}},sprite:{diffuse:{value:new qt(16777215)},opacity:{value:1},center:{value:new Lt(.5,.5)},rotation:{value:0},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new Rt}}},yn={basic:{uniforms:Ki([_n.common,_n.specularmap,_n.envmap,_n.aomap,_n.lightmap,_n.fog]),vertexShader:xn.meshbasic_vert,fragmentShader:xn.meshbasic_frag},lambert:{uniforms:Ki([_n.common,_n.specularmap,_n.envmap,_n.aomap,_n.lightmap,_n.emissivemap,_n.bumpmap,_n.normalmap,_n.displacementmap,_n.fog,_n.lights,{emissive:{value:new qt(0)}}]),vertexShader:xn.meshlambert_vert,fragmentShader:xn.meshlambert_frag},phong:{uniforms:Ki([_n.common,_n.specularmap,_n.envmap,_n.aomap,_n.lightmap,_n.emissivemap,_n.bumpmap,_n.normalmap,_n.displacementmap,_n.fog,_n.lights,{emissive:{value:new qt(0)},specular:{value:new qt(1118481)},shininess:{value:30}}]),vertexShader:xn.meshphong_vert,fragmentShader:xn.meshphong_frag},standard:{uniforms:Ki([_n.common,_n.envmap,_n.aomap,_n.lightmap,_n.emissivemap,_n.bumpmap,_n.normalmap,_n.displacementmap,_n.roughnessmap,_n.metalnessmap,_n.fog,_n.lights,{emissive:{value:new qt(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:xn.meshphysical_vert,fragmentShader:xn.meshphysical_frag},toon:{uniforms:Ki([_n.common,_n.aomap,_n.lightmap,_n.emissivemap,_n.bumpmap,_n.normalmap,_n.displacementmap,_n.gradientmap,_n.fog,_n.lights,{emissive:{value:new qt(0)}}]),vertexShader:xn.meshtoon_vert,fragmentShader:xn.meshtoon_frag},matcap:{uniforms:Ki([_n.common,_n.bumpmap,_n.normalmap,_n.displacementmap,_n.fog,{matcap:{value:null}}]),vertexShader:xn.meshmatcap_vert,fragmentShader:xn.meshmatcap_frag},points:{uniforms:Ki([_n.points,_n.fog]),vertexShader:xn.points_vert,fragmentShader:xn.points_frag},dashed:{uniforms:Ki([_n.common,_n.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:xn.linedashed_vert,fragmentShader:xn.linedashed_frag},depth:{uniforms:Ki([_n.common,_n.displacementmap]),vertexShader:xn.depth_vert,fragmentShader:xn.depth_frag},normal:{uniforms:Ki([_n.common,_n.bumpmap,_n.normalmap,_n.displacementmap,{opacity:{value:1}}]),vertexShader:xn.meshnormal_vert,fragmentShader:xn.meshnormal_frag},sprite:{uniforms:Ki([_n.sprite,_n.fog]),vertexShader:xn.sprite_vert,fragmentShader:xn.sprite_frag},background:{uniforms:{uvTransform:{value:new Rt},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:xn.background_vert,fragmentShader:xn.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1}},vertexShader:xn.backgroundCube_vert,fragmentShader:xn.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:xn.cube_vert,fragmentShader:xn.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:xn.equirect_vert,fragmentShader:xn.equirect_frag},distanceRGBA:{uniforms:Ki([_n.common,_n.displacementmap,{referencePosition:{value:new re},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:xn.distanceRGBA_vert,fragmentShader:xn.distanceRGBA_frag},shadow:{uniforms:Ki([_n.lights,_n.fog,{color:{value:new qt(0)},opacity:{value:1}}]),vertexShader:xn.shadow_vert,fragmentShader:xn.shadow_frag}};yn.physical={uniforms:Ki([yn.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatNormalScale:{value:new Lt(1,1)},clearcoatNormalMap:{value:null},iridescence:{value:0},iridescenceMap:{value:null},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},sheen:{value:0},sheenColor:{value:new qt(0)},sheenColorMap:{value:null},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},transmission:{value:0},transmissionMap:{value:null},transmissionSamplerSize:{value:new Lt},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},attenuationDistance:{value:0},attenuationColor:{value:new qt(0)},specularIntensity:{value:1},specularIntensityMap:{value:null},specularColor:{value:new qt(1,1,1)},specularColorMap:{value:null}}]),vertexShader:xn.meshphysical_vert,fragmentShader:xn.meshphysical_frag};const Mn={r:0,b:0,g:0};function bn(t,e,i,n,r,s,a){const o=new qt(0);let c,h,u=!0===s?0:1,d=null,p=0,m=null;function f(e,i){e.getRGB(Mn,$i(t)),n.buffers.color.setClear(Mn.r,Mn.g,Mn.b,i,a)}return{getClearColor:function(){return o},setClearColor:function(t,e=1){o.set(t),u=e,f(o,u)},getClearAlpha:function(){return u},setClearAlpha:function(t){u=t,f(o,u)},render:function(n,s){let a=!1,g=!0===s.isScene?s.background:null;if(g&&g.isTexture){g=(s.backgroundBlurriness>0?i:e).get(g)}const v=t.xr,x=v.getSession&&v.getSession();x&&"additive"===x.environmentBlendMode&&(g=null),null===g?f(o,u):g&&g.isColor&&(f(g,1),a=!0),(t.autoClear||a)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),g&&(g.isCubeTexture||g.mapping===l)?(void 0===h&&(h=new Xi(new Zi(1,1,1),new tn({name:"BackgroundCubeMaterial",uniforms:Ji(yn.backgroundCube.uniforms),vertexShader:yn.backgroundCube.vertexShader,fragmentShader:yn.backgroundCube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1})),h.geometry.deleteAttribute("normal"),h.geometry.deleteAttribute("uv"),h.onBeforeRender=function(t,e,i){this.matrixWorld.copyPosition(i.matrixWorld)},Object.defineProperty(h.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(h)),h.material.uniforms.envMap.value=g,h.material.uniforms.flipEnvMap.value=g.isCubeTexture&&!1===g.isRenderTargetTexture?-1:1,h.material.uniforms.backgroundBlurriness.value=s.backgroundBlurriness,h.material.uniforms.backgroundIntensity.value=s.backgroundIntensity,h.material.toneMapped=g.encoding!==ot,d===g&&p===g.version&&m===t.toneMapping||(h.material.needsUpdate=!0,d=g,p=g.version,m=t.toneMapping),h.layers.enableAll(),n.unshift(h,h.geometry,h.material,0,0,null)):g&&g.isTexture&&(void 0===c&&(c=new Xi(new vn(2,2),new tn({name:"BackgroundMaterial",uniforms:Ji(yn.background.uniforms),vertexShader:yn.background.vertexShader,fragmentShader:yn.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1})),c.geometry.deleteAttribute("normal"),Object.defineProperty(c.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(c)),c.material.uniforms.t2D.value=g,c.material.uniforms.backgroundIntensity.value=s.backgroundIntensity,c.material.toneMapped=g.encoding!==ot,!0===g.matrixAutoUpdate&&g.updateMatrix(),c.material.uniforms.uvTransform.value.copy(g.matrix),d===g&&p===g.version&&m===t.toneMapping||(c.material.needsUpdate=!0,d=g,p=g.version,m=t.toneMapping),c.layers.enableAll(),n.unshift(c,c.geometry,c.material,0,0,null))}}}function Sn(t,e,i,n){const r=t.getParameter(34921),s=n.isWebGL2?null:e.get("OES_vertex_array_object"),a=n.isWebGL2||null!==s,o={},l=p(null);let c=l,h=!1;function u(e){return n.isWebGL2?t.bindVertexArray(e):s.bindVertexArrayOES(e)}function d(e){return n.isWebGL2?t.deleteVertexArray(e):s.deleteVertexArrayOES(e)}function p(t){const e=[],i=[],n=[];for(let t=0;t=0){const i=r[e];let n=s[e];if(void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor)),void 0===i)return!0;if(i.attribute!==n)return!0;if(n&&i.data!==n.data)return!0;a++}}return c.attributesNum!==a||c.index!==n}(r,_,d,y),M&&function(t,e,i,n){const r={},s=e.attributes;let a=0;const o=i.getAttributes();for(const e in o){if(o[e].location>=0){let i=s[e];void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor));const n={};n.attribute=i,i&&i.data&&(n.data=i.data),r[e]=n,a++}}c.attributes=r,c.attributesNum=a,c.index=n}(r,_,d,y)}else{const t=!0===l.wireframe;c.geometry===_.id&&c.program===d.id&&c.wireframe===t||(c.geometry=_.id,c.program=d.id,c.wireframe=t,M=!0)}null!==y&&i.update(y,34963),(M||h)&&(h=!1,function(r,s,a,o){if(!1===n.isWebGL2&&(r.isInstancedMesh||o.isInstancedBufferGeometry)&&null===e.get("ANGLE_instanced_arrays"))return;m();const l=o.attributes,c=a.getAttributes(),h=s.defaultAttributeValues;for(const e in c){const n=c[e];if(n.location>=0){let s=l[e];if(void 0===s&&("instanceMatrix"===e&&r.instanceMatrix&&(s=r.instanceMatrix),"instanceColor"===e&&r.instanceColor&&(s=r.instanceColor)),void 0!==s){const e=s.normalized,a=s.itemSize,l=i.get(s);if(void 0===l)continue;const c=l.buffer,h=l.type,u=l.bytesPerElement;if(s.isInterleavedBufferAttribute){const i=s.data,l=i.stride,d=s.offset;if(i.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(35632,36338).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(35633,36337).precision>0&&t.getShaderPrecisionFormat(35632,36337).precision>0?"mediump":"lowp"}const s="undefined"!=typeof WebGL2RenderingContext&&t instanceof WebGL2RenderingContext||"undefined"!=typeof WebGL2ComputeRenderingContext&&t instanceof WebGL2ComputeRenderingContext;let a=void 0!==i.precision?i.precision:"highp";const o=r(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=s||e.has("WEBGL_draw_buffers"),c=!0===i.logarithmicDepthBuffer,h=t.getParameter(34930),u=t.getParameter(35660),d=t.getParameter(3379),p=t.getParameter(34076),m=t.getParameter(34921),f=t.getParameter(36347),g=t.getParameter(36348),v=t.getParameter(36349),x=u>0,_=s||e.has("OES_texture_float");return{isWebGL2:s,drawBuffers:l,getMaxAnisotropy:function(){if(void 0!==n)return n;if(!0===e.has("EXT_texture_filter_anisotropic")){const i=e.get("EXT_texture_filter_anisotropic");n=t.getParameter(i.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else n=0;return n},getMaxPrecision:r,precision:a,logarithmicDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:d,maxCubemapSize:p,maxAttributes:m,maxVertexUniforms:f,maxVaryings:g,maxFragmentUniforms:v,vertexTextures:x,floatFragmentTextures:_,floatVertexTextures:x&&_,maxSamples:s?t.getParameter(36183):0}}function An(t){const e=this;let i=null,n=0,r=!1,s=!1;const a=new un,o=new Rt,l={value:null,needsUpdate:!1};function c(){l.value!==i&&(l.value=i,l.needsUpdate=n>0),e.numPlanes=n,e.numIntersection=0}function h(t,i,n,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=n+4*s,r=i.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0){const a=new on(s.height/2);return a.fromEquirectangularTexture(t,r),e.set(r,a),r.addEventListener("dispose",n),i(a.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class Cn extends en{constructor(t=-1,e=1,i=1,n=-1,r=.1,s=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=i,this.bottom=n,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,i,n,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=i,this.view.offsetY=n,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),i=(this.right+this.left)/2,n=(this.top+this.bottom)/2;let r=i-t,s=i+t,a=n+e,o=n-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const Ln=[.125,.215,.35,.446,.526,.582],Rn=20,Pn=new Cn,In=new qt;let Dn=null;const Nn=(1+Math.sqrt(5))/2,On=1/Nn,zn=[new re(1,1,1),new re(-1,1,1),new re(1,1,-1),new re(-1,1,-1),new re(0,Nn,On),new re(0,Nn,-On),new re(On,0,Nn),new re(-On,0,Nn),new re(Nn,On,0),new re(-Nn,On,0)];class Un{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,i=.1,n=100){Dn=this._renderer.getRenderTarget(),this._setSize(256);const r=this._allocateTargets();return r.depthBuffer=!0,this._sceneToCubeUV(t,i,n,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=Gn(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=kn(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?o=Ln[a-t+4-1]:0===a&&(o=0),n.push(o);const l=1/(s-2),c=-l,h=1+l,u=[c,c,h,c,h,h,c,c,h,h,c,h],d=6,p=6,m=3,f=2,g=1,v=new Float32Array(m*p*d),x=new Float32Array(f*p*d),_=new Float32Array(g*p*d);for(let t=0;t2?0:-1,n=[e,i,0,e+2/3,i,0,e+2/3,i+1,0,e,i,0,e+2/3,i+1,0,e,i+1,0];v.set(n,m*p*t),x.set(u,f*p*t);const r=[t,t,t,t,t,t];_.set(r,g*p*t)}const y=new Di;y.setAttribute("position",new bi(v,m)),y.setAttribute("uv",new bi(x,f)),y.setAttribute("faceIndex",new bi(_,g)),e.push(y),r>4&&r--}return{lodPlanes:e,sizeLods:i,sigmas:n}}(n)),this._blurMaterial=function(t,e,i){const n=new Float32Array(Rn),r=new re(0,1,0),s=new tn({name:"SphericalGaussianBlur",defines:{n:Rn,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/i,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:n},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:Vn(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return s}(n,t,e)}return n}_compileMaterial(t){const e=new Xi(this._lodPlanes[0],t);this._renderer.compile(e,Pn)}_sceneToCubeUV(t,e,i,n){const r=new nn(90,1,e,i),s=[1,-1,1,1,1,1],a=[1,1,1,-1,-1,-1],o=this._renderer,l=o.autoClear,c=o.toneMapping;o.getClearColor(In),o.toneMapping=0,o.autoClear=!1;const h=new _i({name:"PMREM.Background",side:1,depthWrite:!1,depthTest:!1}),u=new Xi(new Zi,h);let d=!1;const p=t.background;p?p.isColor&&(h.color.copy(p),t.background=null,d=!0):(h.color.copy(In),d=!0);for(let e=0;e<6;e++){const i=e%3;0===i?(r.up.set(0,s[e],0),r.lookAt(a[e],0,0)):1===i?(r.up.set(0,0,s[e]),r.lookAt(0,a[e],0)):(r.up.set(0,s[e],0),r.lookAt(0,0,a[e]));const l=this._cubeSize;Fn(n,i*l,e>2?l:0,l,l),o.setRenderTarget(n),d&&o.render(u,r),o.render(t,r)}u.geometry.dispose(),u.material.dispose(),o.toneMapping=c,o.autoClear=l,t.background=p}_textureToCubeUV(t,e){const i=this._renderer,n=t.mapping===r||t.mapping===s;n?(null===this._cubemapMaterial&&(this._cubemapMaterial=Gn()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=kn());const a=n?this._cubemapMaterial:this._equirectMaterial,o=new Xi(this._lodPlanes[0],a);a.uniforms.envMap.value=t;const l=this._cubeSize;Fn(e,0,0,3*l,2*l),i.setRenderTarget(e),i.render(o,Pn)}_applyPMREM(t){const e=this._renderer,i=e.autoClear;e.autoClear=!1;for(let e=1;eRn&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;tv-4?n-v+4:0),4*(this._cubeSize-x),3*x,2*x),o.setRenderTarget(e),o.render(c,Pn)}}function Bn(t,e,i){const n=new te(t,e,i);return n.texture.mapping=l,n.texture.name="PMREM.cubeUv",n.scissorTest=!0,n}function Fn(t,e,i,n,r){t.viewport.set(e,i,n,r),t.scissor.set(e,i,n,r)}function kn(){return new tn({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:Vn(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function Gn(){return new tn({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:Vn(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function Vn(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function Hn(t){let e=new WeakMap,i=null;function n(t){const i=t.target;i.removeEventListener("dispose",n);const r=e.get(i);void 0!==r&&(e.delete(i),r.dispose())}return{get:function(l){if(l&&l.isTexture){const c=l.mapping,h=c===a||c===o,u=c===r||c===s;if(h||u){if(l.isRenderTargetTexture&&!0===l.needsPMREMUpdate){l.needsPMREMUpdate=!1;let n=e.get(l);return null===i&&(i=new Un(t)),n=h?i.fromEquirectangular(l,n):i.fromCubemap(l,n),e.set(l,n),n.texture}if(e.has(l))return e.get(l).texture;{const r=l.image;if(h&&r&&r.height>0||u&&r&&function(t){let e=0;const i=6;for(let n=0;ne.maxTextureSize&&(E=Math.ceil(A/e.maxTextureSize),A=e.maxTextureSize);const C=new Float32Array(A*E*4*m),L=new ee(C,A,E,m);L.type=M,L.needsUpdate=!0;const R=4*T;for(let I=0;I0)return t;const r=e*i;let s=ir[r];if(void 0===s&&(s=new Float32Array(r),ir[r]=s),0!==e){n.toArray(s,0);for(let n=1,r=0;n!==e;++n)r+=i,t[n].toArray(s,r)}return s}function lr(t,e){if(t.length!==e.length)return!1;for(let i=0,n=t.length;i":" "} ${r}: ${i[t]}`)}return n.join("\n")}(t.getShaderSource(e),n)}return r}function ss(t,e){const i=function(t){switch(t){case at:return["Linear","( value )"];case ot:return["sRGB","( value )"];default:return console.warn("THREE.WebGLProgram: Unsupported encoding:",t),["Linear","( value )"]}}(e);return"vec4 "+t+"( vec4 value ) { return LinearTo"+i[0]+i[1]+"; }"}function as(t,e){let i;switch(e){case 1:i="Linear";break;case 2:i="Reinhard";break;case 3:i="OptimizedCineon";break;case 4:i="ACESFilmic";break;case 5:i="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),i="Linear"}return"vec3 "+t+"( vec3 color ) { return "+i+"ToneMapping( color ); }"}function os(t){return""!==t}function ls(t,e){const i=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,i).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function cs(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const hs=/^[ \t]*#include +<([\w\d./]+)>/gm;function us(t){return t.replace(hs,ds)}function ds(t,e){const i=xn[e];if(void 0===i)throw new Error("Can not resolve #include <"+e+">");return us(i)}const ps=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function ms(t){return t.replace(ps,fs)}function fs(t,e,i,n){let r="";for(let t=parseInt(e);t0&&(_+="\n"),y=[g,v].filter(os).join("\n"),y.length>0&&(y+="\n")):(_=[gs(i),"#define SHADER_NAME "+i.shaderName,v,i.instancing?"#define USE_INSTANCING":"",i.instancingColor?"#define USE_INSTANCING_COLOR":"",i.supportsVertexTextures?"#define VERTEX_TEXTURES":"",i.useFog&&i.fog?"#define USE_FOG":"",i.useFog&&i.fogExp2?"#define FOG_EXP2":"",i.map?"#define USE_MAP":"",i.envMap?"#define USE_ENVMAP":"",i.envMap?"#define "+p:"",i.lightMap?"#define USE_LIGHTMAP":"",i.aoMap?"#define USE_AOMAP":"",i.emissiveMap?"#define USE_EMISSIVEMAP":"",i.bumpMap?"#define USE_BUMPMAP":"",i.normalMap?"#define USE_NORMALMAP":"",i.normalMap&&i.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",i.normalMap&&i.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",i.clearcoatMap?"#define USE_CLEARCOATMAP":"",i.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",i.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",i.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",i.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",i.displacementMap&&i.supportsVertexTextures?"#define USE_DISPLACEMENTMAP":"",i.specularMap?"#define USE_SPECULARMAP":"",i.specularIntensityMap?"#define USE_SPECULARINTENSITYMAP":"",i.specularColorMap?"#define USE_SPECULARCOLORMAP":"",i.roughnessMap?"#define USE_ROUGHNESSMAP":"",i.metalnessMap?"#define USE_METALNESSMAP":"",i.alphaMap?"#define USE_ALPHAMAP":"",i.transmission?"#define USE_TRANSMISSION":"",i.transmissionMap?"#define USE_TRANSMISSIONMAP":"",i.thicknessMap?"#define USE_THICKNESSMAP":"",i.sheenColorMap?"#define USE_SHEENCOLORMAP":"",i.sheenRoughnessMap?"#define USE_SHEENROUGHNESSMAP":"",i.vertexTangents?"#define USE_TANGENT":"",i.vertexColors?"#define USE_COLOR":"",i.vertexAlphas?"#define USE_COLOR_ALPHA":"",i.vertexUvs?"#define USE_UV":"",i.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",i.flatShading?"#define FLAT_SHADED":"",i.skinning?"#define USE_SKINNING":"",i.morphTargets?"#define USE_MORPHTARGETS":"",i.morphNormals&&!1===i.flatShading?"#define USE_MORPHNORMALS":"",i.morphColors&&i.isWebGL2?"#define USE_MORPHCOLORS":"",i.morphTargetsCount>0&&i.isWebGL2?"#define MORPHTARGETS_TEXTURE":"",i.morphTargetsCount>0&&i.isWebGL2?"#define MORPHTARGETS_TEXTURE_STRIDE "+i.morphTextureStride:"",i.morphTargetsCount>0&&i.isWebGL2?"#define MORPHTARGETS_COUNT "+i.morphTargetsCount:"",i.doubleSided?"#define DOUBLE_SIDED":"",i.flipSided?"#define FLIP_SIDED":"",i.shadowMapEnabled?"#define USE_SHADOWMAP":"",i.shadowMapEnabled?"#define "+u:"",i.sizeAttenuation?"#define USE_SIZEATTENUATION":"",i.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",i.logarithmicDepthBuffer&&i.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(os).join("\n"),y=[g,gs(i),"#define SHADER_NAME "+i.shaderName,v,i.useFog&&i.fog?"#define USE_FOG":"",i.useFog&&i.fogExp2?"#define FOG_EXP2":"",i.map?"#define USE_MAP":"",i.matcap?"#define USE_MATCAP":"",i.envMap?"#define USE_ENVMAP":"",i.envMap?"#define "+d:"",i.envMap?"#define "+p:"",i.envMap?"#define "+m:"",f?"#define CUBEUV_TEXEL_WIDTH "+f.texelWidth:"",f?"#define CUBEUV_TEXEL_HEIGHT "+f.texelHeight:"",f?"#define CUBEUV_MAX_MIP "+f.maxMip+".0":"",i.lightMap?"#define USE_LIGHTMAP":"",i.aoMap?"#define USE_AOMAP":"",i.emissiveMap?"#define USE_EMISSIVEMAP":"",i.bumpMap?"#define USE_BUMPMAP":"",i.normalMap?"#define USE_NORMALMAP":"",i.normalMap&&i.objectSpaceNormalMap?"#define OBJECTSPACE_NORMALMAP":"",i.normalMap&&i.tangentSpaceNormalMap?"#define TANGENTSPACE_NORMALMAP":"",i.clearcoat?"#define USE_CLEARCOAT":"",i.clearcoatMap?"#define USE_CLEARCOATMAP":"",i.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",i.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",i.iridescence?"#define USE_IRIDESCENCE":"",i.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",i.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",i.specularMap?"#define USE_SPECULARMAP":"",i.specularIntensityMap?"#define USE_SPECULARINTENSITYMAP":"",i.specularColorMap?"#define USE_SPECULARCOLORMAP":"",i.roughnessMap?"#define USE_ROUGHNESSMAP":"",i.metalnessMap?"#define USE_METALNESSMAP":"",i.alphaMap?"#define USE_ALPHAMAP":"",i.alphaTest?"#define USE_ALPHATEST":"",i.sheen?"#define USE_SHEEN":"",i.sheenColorMap?"#define USE_SHEENCOLORMAP":"",i.sheenRoughnessMap?"#define USE_SHEENROUGHNESSMAP":"",i.transmission?"#define USE_TRANSMISSION":"",i.transmissionMap?"#define USE_TRANSMISSIONMAP":"",i.thicknessMap?"#define USE_THICKNESSMAP":"",i.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",i.vertexTangents?"#define USE_TANGENT":"",i.vertexColors||i.instancingColor?"#define USE_COLOR":"",i.vertexAlphas?"#define USE_COLOR_ALPHA":"",i.vertexUvs?"#define USE_UV":"",i.uvsVertexOnly?"#define UVS_VERTEX_ONLY":"",i.gradientMap?"#define USE_GRADIENTMAP":"",i.flatShading?"#define FLAT_SHADED":"",i.doubleSided?"#define DOUBLE_SIDED":"",i.flipSided?"#define FLIP_SIDED":"",i.shadowMapEnabled?"#define USE_SHADOWMAP":"",i.shadowMapEnabled?"#define "+u:"",i.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",i.physicallyCorrectLights?"#define PHYSICALLY_CORRECT_LIGHTS":"",i.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",i.logarithmicDepthBuffer&&i.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==i.toneMapping?"#define TONE_MAPPING":"",0!==i.toneMapping?xn.tonemapping_pars_fragment:"",0!==i.toneMapping?as("toneMapping",i.toneMapping):"",i.dithering?"#define DITHERING":"",i.opaque?"#define OPAQUE":"",xn.encodings_pars_fragment,ss("linearToOutputTexel",i.outputEncoding),i.useDepthPacking?"#define DEPTH_PACKING "+i.depthPacking:"","\n"].filter(os).join("\n")),c=us(c),c=ls(c,i),c=cs(c,i),h=us(h),h=ls(h,i),h=cs(h,i),c=ms(c),h=ms(h),i.isWebGL2&&!0!==i.isRawShaderMaterial&&(M="#version 300 es\n",_=["precision mediump sampler2DArray;","#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+_,y=["#define varying in",i.glslVersion===dt?"":"layout(location = 0) out highp vec4 pc_fragColor;",i.glslVersion===dt?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+y);const b=M+y+h,S=is(a,35633,M+_+c),w=is(a,35632,b);if(a.attachShader(x,S),a.attachShader(x,w),void 0!==i.index0AttributeName?a.bindAttribLocation(x,0,i.index0AttributeName):!0===i.morphTargets&&a.bindAttribLocation(x,0,"position"),a.linkProgram(x),t.debug.checkShaderErrors){const t=a.getProgramInfoLog(x).trim(),e=a.getShaderInfoLog(S).trim(),i=a.getShaderInfoLog(w).trim();let n=!0,r=!0;if(!1===a.getProgramParameter(x,35714)){n=!1;const e=rs(a,S,"vertex"),i=rs(a,w,"fragment");console.error("THREE.WebGLProgram: Shader Error "+a.getError()+" - VALIDATE_STATUS "+a.getProgramParameter(x,35715)+"\n\nProgram Info Log: "+t+"\n"+e+"\n"+i)}else""!==t?console.warn("THREE.WebGLProgram: Program Info Log:",t):""!==e&&""!==i||(r=!1);r&&(this.diagnostics={runnable:n,programLog:t,vertexShader:{log:e,prefix:_},fragmentShader:{log:i,prefix:y}})}let T,A;return a.deleteShader(S),a.deleteShader(w),this.getUniforms=function(){return void 0===T&&(T=new es(a,x)),T},this.getAttributes=function(){return void 0===A&&(A=function(t,e){const i={},n=t.getProgramParameter(e,35721);for(let r=0;r0,D=s.clearcoat>0,N=s.iridescence>0;return{isWebGL2:u,shaderID:S,shaderName:s.type,vertexShader:A,fragmentShader:E,defines:s.defines,customVertexShaderID:C,customFragmentShaderID:L,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:m,instancing:!0===v.isInstancedMesh,instancingColor:!0===v.isInstancedMesh&&null!==v.instanceColor,supportsVertexTextures:p,outputEncoding:null===P?t.outputEncoding:!0===P.isXRRenderTarget?P.texture.encoding:at,map:!!s.map,matcap:!!s.matcap,envMap:!!M,envMapMode:M&&M.mapping,envMapCubeUVHeight:b,lightMap:!!s.lightMap,aoMap:!!s.aoMap,emissiveMap:!!s.emissiveMap,bumpMap:!!s.bumpMap,normalMap:!!s.normalMap,objectSpaceNormalMap:1===s.normalMapType,tangentSpaceNormalMap:0===s.normalMapType,decodeVideoTexture:!!s.map&&!0===s.map.isVideoTexture&&s.map.encoding===ot,clearcoat:D,clearcoatMap:D&&!!s.clearcoatMap,clearcoatRoughnessMap:D&&!!s.clearcoatRoughnessMap,clearcoatNormalMap:D&&!!s.clearcoatNormalMap,iridescence:N,iridescenceMap:N&&!!s.iridescenceMap,iridescenceThicknessMap:N&&!!s.iridescenceThicknessMap,displacementMap:!!s.displacementMap,roughnessMap:!!s.roughnessMap,metalnessMap:!!s.metalnessMap,specularMap:!!s.specularMap,specularIntensityMap:!!s.specularIntensityMap,specularColorMap:!!s.specularColorMap,opaque:!1===s.transparent&&1===s.blending,alphaMap:!!s.alphaMap,alphaTest:I,gradientMap:!!s.gradientMap,sheen:s.sheen>0,sheenColorMap:!!s.sheenColorMap,sheenRoughnessMap:!!s.sheenRoughnessMap,transmission:s.transmission>0,transmissionMap:!!s.transmissionMap,thicknessMap:!!s.thicknessMap,combine:s.combine,vertexTangents:!!s.normalMap&&!!_.attributes.tangent,vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!_.attributes.color&&4===_.attributes.color.itemSize,vertexUvs:!!(s.map||s.bumpMap||s.normalMap||s.specularMap||s.alphaMap||s.emissiveMap||s.roughnessMap||s.metalnessMap||s.clearcoatMap||s.clearcoatRoughnessMap||s.clearcoatNormalMap||s.iridescenceMap||s.iridescenceThicknessMap||s.displacementMap||s.transmissionMap||s.thicknessMap||s.specularIntensityMap||s.specularColorMap||s.sheenColorMap||s.sheenRoughnessMap),uvsVertexOnly:!(s.map||s.bumpMap||s.normalMap||s.specularMap||s.alphaMap||s.emissiveMap||s.roughnessMap||s.metalnessMap||s.clearcoatNormalMap||s.iridescenceMap||s.iridescenceThicknessMap||s.transmission>0||s.transmissionMap||s.thicknessMap||s.specularIntensityMap||s.specularColorMap||s.sheen>0||s.sheenColorMap||s.sheenRoughnessMap||!s.displacementMap),fog:!!x,useFog:!0===s.fog,fogExp2:x&&x.isFogExp2,flatShading:!!s.flatShading,sizeAttenuation:s.sizeAttenuation,logarithmicDepthBuffer:d,skinning:!0===v.isSkinnedMesh,morphTargets:void 0!==_.morphAttributes.position,morphNormals:void 0!==_.morphAttributes.normal,morphColors:void 0!==_.morphAttributes.color,morphTargetsCount:T,morphTextureStride:R,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numSpotLightMaps:o.spotLightMap.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numSpotLightShadowsWithMaps:o.numSpotLightShadowsWithMaps,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&h.length>0,shadowMapType:t.shadowMap.type,toneMapping:s.toneMapped?t.toneMapping:0,physicallyCorrectLights:t.physicallyCorrectLights,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:1===s.side,useDepthPacking:!!s.depthPacking,depthPacking:s.depthPacking||0,index0AttributeName:s.index0AttributeName,extensionDerivatives:s.extensions&&s.extensions.derivatives,extensionFragDepth:s.extensions&&s.extensions.fragDepth,extensionDrawBuffers:s.extensions&&s.extensions.drawBuffers,extensionShaderTextureLOD:s.extensions&&s.extensions.shaderTextureLOD,rendererExtensionFragDepth:u||n.has("EXT_frag_depth"),rendererExtensionDrawBuffers:u||n.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:u||n.has("EXT_shader_texture_lod"),customProgramCacheKey:s.customProgramCacheKey()}},getProgramCacheKey:function(e){const i=[];if(e.shaderID?i.push(e.shaderID):(i.push(e.customVertexShaderID),i.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)i.push(t),i.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputEncoding),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.combine),t.push(e.vertexUvs),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(i,e),function(t,e){o.disableAll(),e.isWebGL2&&o.enable(0);e.supportsVertexTextures&&o.enable(1);e.instancing&&o.enable(2);e.instancingColor&&o.enable(3);e.map&&o.enable(4);e.matcap&&o.enable(5);e.envMap&&o.enable(6);e.lightMap&&o.enable(7);e.aoMap&&o.enable(8);e.emissiveMap&&o.enable(9);e.bumpMap&&o.enable(10);e.normalMap&&o.enable(11);e.objectSpaceNormalMap&&o.enable(12);e.tangentSpaceNormalMap&&o.enable(13);e.clearcoat&&o.enable(14);e.clearcoatMap&&o.enable(15);e.clearcoatRoughnessMap&&o.enable(16);e.clearcoatNormalMap&&o.enable(17);e.iridescence&&o.enable(18);e.iridescenceMap&&o.enable(19);e.iridescenceThicknessMap&&o.enable(20);e.displacementMap&&o.enable(21);e.specularMap&&o.enable(22);e.roughnessMap&&o.enable(23);e.metalnessMap&&o.enable(24);e.gradientMap&&o.enable(25);e.alphaMap&&o.enable(26);e.alphaTest&&o.enable(27);e.vertexColors&&o.enable(28);e.vertexAlphas&&o.enable(29);e.vertexUvs&&o.enable(30);e.vertexTangents&&o.enable(31);e.uvsVertexOnly&&o.enable(32);t.push(o.mask),o.disableAll(),e.fog&&o.enable(0);e.useFog&&o.enable(1);e.flatShading&&o.enable(2);e.logarithmicDepthBuffer&&o.enable(3);e.skinning&&o.enable(4);e.morphTargets&&o.enable(5);e.morphNormals&&o.enable(6);e.morphColors&&o.enable(7);e.premultipliedAlpha&&o.enable(8);e.shadowMapEnabled&&o.enable(9);e.physicallyCorrectLights&&o.enable(10);e.doubleSided&&o.enable(11);e.flipSided&&o.enable(12);e.useDepthPacking&&o.enable(13);e.dithering&&o.enable(14);e.specularIntensityMap&&o.enable(15);e.specularColorMap&&o.enable(16);e.transmission&&o.enable(17);e.transmissionMap&&o.enable(18);e.thicknessMap&&o.enable(19);e.sheen&&o.enable(20);e.sheenColorMap&&o.enable(21);e.sheenRoughnessMap&&o.enable(22);e.decodeVideoTexture&&o.enable(23);e.opaque&&o.enable(24);t.push(o.mask)}(i,e),i.push(t.outputEncoding)),i.push(e.customProgramCacheKey),i.join()},getUniforms:function(t){const e=f[t.type];let i;if(e){const t=yn[e];i=Qi.clone(t.uniforms)}else i=t.uniforms;return i},acquireProgram:function(e,i){let n;for(let t=0,e=h.length;t0?n.push(h):!0===a.transparent?r.push(h):i.push(h)},unshift:function(t,e,a,o,l,c){const h=s(t,e,a,o,l,c);a.transmission>0?n.unshift(h):!0===a.transparent?r.unshift(h):i.unshift(h)},finish:function(){for(let i=e,n=t.length;i1&&i.sort(t||Ss),n.length>1&&n.sort(e||ws),r.length>1&&r.sort(e||ws)}}}function As(){let t=new WeakMap;return{get:function(e,i){const n=t.get(e);let r;return void 0===n?(r=new Ts,t.set(e,[r])):i>=n.length?(r=new Ts,n.push(r)):r=n[i],r},dispose:function(){t=new WeakMap}}}function Es(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let i;switch(e.type){case"DirectionalLight":i={direction:new re,color:new qt};break;case"SpotLight":i={position:new re,direction:new re,color:new qt,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":i={position:new re,color:new qt,distance:0,decay:0};break;case"HemisphereLight":i={direction:new re,skyColor:new qt,groundColor:new qt};break;case"RectAreaLight":i={color:new qt,position:new re,halfWidth:new re,halfHeight:new re}}return t[e.id]=i,i}}}let Cs=0;function Ls(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function Rs(t,e){const i=new Es,n=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let i;switch(e.type){case"DirectionalLight":case"SpotLight":i={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new Lt};break;case"PointLight":i={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new Lt,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=i,i}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0};for(let t=0;t<9;t++)r.probe.push(new re);const s=new re,a=new Ne,o=new Ne;return{setup:function(s,a){let o=0,l=0,c=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let h=0,u=0,d=0,p=0,m=0,f=0,g=0,v=0,x=0,_=0;s.sort(Ls);const y=!0!==a?Math.PI:1;for(let t=0,e=s.length;t0&&(e.isWebGL2||!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=_n.LTC_FLOAT_1,r.rectAreaLTC2=_n.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=_n.LTC_HALF_1,r.rectAreaLTC2=_n.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=o,r.ambient[1]=l,r.ambient[2]=c;const M=r.hash;M.directionalLength===h&&M.pointLength===u&&M.spotLength===d&&M.rectAreaLength===p&&M.hemiLength===m&&M.numDirectionalShadows===f&&M.numPointShadows===g&&M.numSpotShadows===v&&M.numSpotMaps===x||(r.directional.length=h,r.spot.length=d,r.rectArea.length=p,r.point.length=u,r.hemi.length=m,r.directionalShadow.length=f,r.directionalShadowMap.length=f,r.pointShadow.length=g,r.pointShadowMap.length=g,r.spotShadow.length=v,r.spotShadowMap.length=v,r.directionalShadowMatrix.length=f,r.pointShadowMatrix.length=g,r.spotLightMatrix.length=v+x-_,r.spotLightMap.length=x,r.numSpotLightShadowsWithMaps=_,M.directionalLength=h,M.pointLength=u,M.spotLength=d,M.rectAreaLength=p,M.hemiLength=m,M.numDirectionalShadows=f,M.numPointShadows=g,M.numSpotShadows=v,M.numSpotMaps=x,r.version=Cs++)},setupView:function(t,e){let i=0,n=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=s.length?(a=new Ps(t,e),s.push(a)):a=s[r],a},dispose:function(){i=new WeakMap}}}class Ds extends xi{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class Ns extends xi{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.referencePosition=new re,this.nearDistance=1,this.farDistance=1e3,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.referencePosition.copy(t.referencePosition),this.nearDistance=t.nearDistance,this.farDistance=t.farDistance,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function Os(t,e,i){let n=new mn;const r=new Lt,s=new Lt,a=new Qt,o=new Ds({depthPacking:3201}),l=new Ns,c={},h=i.maxTextureSize,u={0:1,1:0,2:2},p=new tn({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new Lt},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),m=p.clone();m.defines.HORIZONTAL_PASS=1;const f=new Di;f.setAttribute("position",new bi(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const g=new Xi(f,p),v=this;function x(i,n){const s=e.update(g);p.defines.VSM_SAMPLES!==i.blurSamples&&(p.defines.VSM_SAMPLES=i.blurSamples,m.defines.VSM_SAMPLES=i.blurSamples,p.needsUpdate=!0,m.needsUpdate=!0),null===i.mapPass&&(i.mapPass=new te(r.x,r.y)),p.uniforms.shadow_pass.value=i.map.texture,p.uniforms.resolution.value=i.mapSize,p.uniforms.radius.value=i.radius,t.setRenderTarget(i.mapPass),t.clear(),t.renderBufferDirect(n,null,s,p,g,null),m.uniforms.shadow_pass.value=i.mapPass.texture,m.uniforms.resolution.value=i.mapSize,m.uniforms.radius.value=i.radius,t.setRenderTarget(i.map),t.clear(),t.renderBufferDirect(n,null,s,m,g,null)}function _(e,i,n,r,s,a){let h=null;const d=!0===n.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==d)h=d;else if(h=!0===n.isPointLight?l:o,t.localClippingEnabled&&!0===i.clipShadows&&Array.isArray(i.clippingPlanes)&&0!==i.clippingPlanes.length||i.displacementMap&&0!==i.displacementScale||i.alphaMap&&i.alphaTest>0||i.map&&i.alphaTest>0){const t=h.uuid,e=i.uuid;let n=c[t];void 0===n&&(n={},c[t]=n);let r=n[e];void 0===r&&(r=h.clone(),n[e]=r),h=r}return h.visible=i.visible,h.wireframe=i.wireframe,h.side=3===a?null!==i.shadowSide?i.shadowSide:i.side:null!==i.shadowSide?i.shadowSide:u[i.side],h.alphaMap=i.alphaMap,h.alphaTest=i.alphaTest,h.map=i.map,h.clipShadows=i.clipShadows,h.clippingPlanes=i.clippingPlanes,h.clipIntersection=i.clipIntersection,h.displacementMap=i.displacementMap,h.displacementScale=i.displacementScale,h.displacementBias=i.displacementBias,h.wireframeLinewidth=i.wireframeLinewidth,h.linewidth=i.linewidth,!0===n.isPointLight&&!0===h.isMeshDistanceMaterial&&(h.referencePosition.setFromMatrixPosition(n.matrixWorld),h.nearDistance=r,h.farDistance=s),h}function y(i,r,s,a,o){if(!1===i.visible)return;if(i.layers.test(r.layers)&&(i.isMesh||i.isLine||i.isPoints)&&(i.castShadow||i.receiveShadow&&3===o)&&(!i.frustumCulled||n.intersectsObject(i))){i.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,i.matrixWorld);const n=e.update(i),r=i.material;if(Array.isArray(r)){const e=n.groups;for(let l=0,c=e.length;lh||r.y>h)&&(r.x>h&&(s.x=Math.floor(h/m.x),r.x=s.x*m.x,u.mapSize.x=s.x),r.y>h&&(s.y=Math.floor(h/m.y),r.y=s.y*m.y,u.mapSize.y=s.y)),null===u.map){const t=3!==this.type?{minFilter:d,magFilter:d}:{};u.map=new te(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const f=u.getViewportCount();for(let t=0;t=1):-1!==I.indexOf("OpenGL ES")&&(P=parseFloat(/^OpenGL ES (\d)/.exec(I)[1]),R=P>=2);let D=null,N={};const O=t.getParameter(3088),z=t.getParameter(2978),U=(new Qt).fromArray(O),B=(new Qt).fromArray(z);function F(e,i,n){const r=new Uint8Array(4),s=t.createTexture();t.bindTexture(e,s),t.texParameteri(e,10241,9728),t.texParameteri(e,10240,9728);for(let e=0;en||t.height>n)&&(r=n/Math.max(t.width,t.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const n=e?Tt:Math.floor,s=n(r*t.width),a=n(r*t.height);void 0===D&&(D=z(s,a));const o=i?z(s,a):D;o.width=s,o.height=a;return o.getContext("2d").drawImage(t,0,0,s,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+t.width+"x"+t.height+") to ("+s+"x"+a+")."),o}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+t.width+"x"+t.height+")."),t}return t}function B(t){return St(t.width)&&St(t.height)}function F(t,e){return t.generateMipmaps&&e&&t.minFilter!==d&&t.minFilter!==f}function k(e){t.generateMipmap(e)}function G(i,n,r,s,a=!1){if(!1===o)return n;if(null!==i){if(void 0!==t[i])return t[i];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+i+"'")}let l=n;return 6403===n&&(5126===r&&(l=33326),5131===r&&(l=33325),5121===r&&(l=33321)),33319===n&&(5126===r&&(l=33328),5131===r&&(l=33327),5121===r&&(l=33323)),6408===n&&(5126===r&&(l=34836),5131===r&&(l=34842),5121===r&&(l=s===ot&&!1===a?35907:32856),32819===r&&(l=32854),32820===r&&(l=32855)),33325!==l&&33326!==l&&33327!==l&&33328!==l&&34842!==l&&34836!==l||e.get("EXT_color_buffer_float"),l}function V(t,e,i){return!0===F(t,i)||t.isFramebufferTexture&&t.minFilter!==d&&t.minFilter!==f?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function H(t){return t===d||t===p||t===m?9728:9729}function W(t){const e=t.target;e.removeEventListener("dispose",W),function(t){const e=n.get(t);if(void 0===e.__webglInit)return;const i=t.source,r=N.get(i);if(r){const n=r[e.__cacheKey];n.usedTimes--,0===n.usedTimes&&q(t),0===Object.keys(r).length&&N.delete(i)}n.remove(t)}(e),e.isVideoTexture&&I.delete(e)}function j(e){const i=e.target;i.removeEventListener("dispose",j),function(e){const i=e.texture,r=n.get(e),s=n.get(i);void 0!==s.__webglTexture&&(t.deleteTexture(s.__webglTexture),a.memory.textures--);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++)t.deleteFramebuffer(r.__webglFramebuffer[e]),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer[e]);else{if(t.deleteFramebuffer(r.__webglFramebuffer),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer),r.__webglMultisampledFramebuffer&&t.deleteFramebuffer(r.__webglMultisampledFramebuffer),r.__webglColorRenderbuffer)for(let e=0;e0&&r.__version!==t.version){const i=t.image;if(null===i)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==i.complete)return void Q(r,t,e);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}i.bindTexture(3553,r.__webglTexture,33984+e)}const Z={[c]:10497,[h]:33071,[u]:33648},J={[d]:9728,[p]:9984,[m]:9986,[f]:9729,[g]:9985,[v]:9987};function K(i,s,a){if(a?(t.texParameteri(i,10242,Z[s.wrapS]),t.texParameteri(i,10243,Z[s.wrapT]),32879!==i&&35866!==i||t.texParameteri(i,32882,Z[s.wrapR]),t.texParameteri(i,10240,J[s.magFilter]),t.texParameteri(i,10241,J[s.minFilter])):(t.texParameteri(i,10242,33071),t.texParameteri(i,10243,33071),32879!==i&&35866!==i||t.texParameteri(i,32882,33071),s.wrapS===h&&s.wrapT===h||console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),t.texParameteri(i,10240,H(s.magFilter)),t.texParameteri(i,10241,H(s.minFilter)),s.minFilter!==d&&s.minFilter!==f&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),!0===e.has("EXT_texture_filter_anisotropic")){const a=e.get("EXT_texture_filter_anisotropic");if(s.magFilter===d)return;if(s.minFilter!==m&&s.minFilter!==v)return;if(s.type===M&&!1===e.has("OES_texture_float_linear"))return;if(!1===o&&s.type===b&&!1===e.has("OES_texture_half_float_linear"))return;(s.anisotropy>1||n.get(s).__currentAnisotropy)&&(t.texParameterf(i,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),n.get(s).__currentAnisotropy=s.anisotropy)}}function $(e,i){let n=!1;void 0===e.__webglInit&&(e.__webglInit=!0,i.addEventListener("dispose",W));const r=i.source;let s=N.get(r);void 0===s&&(s={},N.set(r,s));const o=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.encoding),e.join()}(i);if(o!==e.__cacheKey){void 0===s[o]&&(s[o]={texture:t.createTexture(),usedTimes:0},a.memory.textures++,n=!0),s[o].usedTimes++;const r=s[e.__cacheKey];void 0!==r&&(s[e.__cacheKey].usedTimes--,0===r.usedTimes&&q(i)),e.__cacheKey=o,e.__webglTexture=s[o].texture}return n}function Q(e,r,a){let l=3553;(r.isDataArrayTexture||r.isCompressedArrayTexture)&&(l=35866),r.isData3DTexture&&(l=32879);const c=$(e,r),u=r.source;i.bindTexture(l,e.__webglTexture,33984+a);const p=n.get(u);if(u.version!==p.__version||!0===c){i.activeTexture(33984+a),t.pixelStorei(37440,r.flipY),t.pixelStorei(37441,r.premultiplyAlpha),t.pixelStorei(3317,r.unpackAlignment),t.pixelStorei(37443,0);const e=function(t){return!o&&(t.wrapS!==h||t.wrapT!==h||t.minFilter!==d&&t.minFilter!==f)}(r)&&!1===B(r.image);let n=U(r.image,e,!1,C);n=st(r,n);const m=B(n)||o,g=s.convert(r.format,r.encoding);let v,x=s.convert(r.type),b=G(r.internalFormat,g,x,r.encoding,r.isVideoTexture);K(l,r,m);const E=r.mipmaps,L=o&&!0!==r.isVideoTexture,R=void 0===p.__version||!0===c,P=V(r,n,m);if(r.isDepthTexture)b=6402,o?b=r.type===M?36012:r.type===y?33190:r.type===S?35056:33189:r.type===M&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),r.format===T&&6402===b&&r.type!==_&&r.type!==y&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),r.type=y,x=s.convert(r.type)),r.format===A&&6402===b&&(b=34041,r.type!==S&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),r.type=S,x=s.convert(r.type))),R&&(L?i.texStorage2D(3553,1,b,n.width,n.height):i.texImage2D(3553,0,b,n.width,n.height,0,g,x,null));else if(r.isDataTexture)if(E.length>0&&m){L&&R&&i.texStorage2D(3553,P,b,E[0].width,E[0].height);for(let t=0,e=E.length;t>=1,e>>=1}}else if(E.length>0&&m){L&&R&&i.texStorage2D(3553,P,b,E[0].width,E[0].height);for(let t=0,e=E.length;t=34069&&l<=34074)&&t.framebufferTexture2D(36160,o,l,n.get(a).__webglTexture,0),i.bindFramebuffer(36160,null)}function et(e,i,n){if(t.bindRenderbuffer(36161,e),i.depthBuffer&&!i.stencilBuffer){let r=33189;if(n||rt(i)){const e=i.depthTexture;e&&e.isDepthTexture&&(e.type===M?r=36012:e.type===y&&(r=33190));const n=nt(i);rt(i)?R.renderbufferStorageMultisampleEXT(36161,n,r,i.width,i.height):t.renderbufferStorageMultisample(36161,n,r,i.width,i.height)}else t.renderbufferStorage(36161,r,i.width,i.height);t.framebufferRenderbuffer(36160,36096,36161,e)}else if(i.depthBuffer&&i.stencilBuffer){const r=nt(i);n&&!1===rt(i)?t.renderbufferStorageMultisample(36161,r,35056,i.width,i.height):rt(i)?R.renderbufferStorageMultisampleEXT(36161,r,35056,i.width,i.height):t.renderbufferStorage(36161,34041,i.width,i.height),t.framebufferRenderbuffer(36160,33306,36161,e)}else{const e=!0===i.isWebGLMultipleRenderTargets?i.texture:[i.texture];for(let r=0;r0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==i.__useRenderToTexture}function st(t,i){const n=t.encoding,r=t.format,s=t.type;return!0===t.isCompressedTexture||!0===t.isVideoTexture||t.format===pt||n!==at&&(n===ot?!1===o?!0===e.has("EXT_sRGB")&&r===w?(t.format=pt,t.minFilter=f,t.generateMipmaps=!1):i=Yt.sRGBToLinear(i):r===w&&s===x||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture encoding:",n)),i}this.allocateTextureUnit=function(){const t=X;return t>=l&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+l),X+=1,t},this.resetTextureUnits=function(){X=0},this.setTexture2D=Y,this.setTexture2DArray=function(t,e){const r=n.get(t);t.version>0&&r.__version!==t.version?Q(r,t,e):i.bindTexture(35866,r.__webglTexture,33984+e)},this.setTexture3D=function(t,e){const r=n.get(t);t.version>0&&r.__version!==t.version?Q(r,t,e):i.bindTexture(32879,r.__webglTexture,33984+e)},this.setTextureCube=function(e,r){const a=n.get(e);e.version>0&&a.__version!==e.version?function(e,r,a){if(6!==r.image.length)return;const l=$(e,r),c=r.source;i.bindTexture(34067,e.__webglTexture,33984+a);const h=n.get(c);if(c.version!==h.__version||!0===l){i.activeTexture(33984+a),t.pixelStorei(37440,r.flipY),t.pixelStorei(37441,r.premultiplyAlpha),t.pixelStorei(3317,r.unpackAlignment),t.pixelStorei(37443,0);const e=r.isCompressedTexture||r.image[0].isCompressedTexture,n=r.image[0]&&r.image[0].isDataTexture,u=[];for(let t=0;t<6;t++)u[t]=e||n?n?r.image[t].image:r.image[t]:U(r.image[t],!1,!0,E),u[t]=st(r,u[t]);const d=u[0],p=B(d)||o,m=s.convert(r.format,r.encoding),f=s.convert(r.type),g=G(r.internalFormat,m,f,r.encoding),v=o&&!0!==r.isVideoTexture,x=void 0===h.__version||!0===l;let _,y=V(r,d,p);if(K(34067,r,p),e){v&&x&&i.texStorage2D(34067,y,g,d.width,d.height);for(let t=0;t<6;t++){_=u[t].mipmaps;for(let e=0;e<_.length;e++){const n=_[e];r.format!==w?null!==m?v?i.compressedTexSubImage2D(34069+t,e,0,0,n.width,n.height,m,n.data):i.compressedTexImage2D(34069+t,e,g,n.width,n.height,0,n.data):console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()"):v?i.texSubImage2D(34069+t,e,0,0,n.width,n.height,m,f,n.data):i.texImage2D(34069+t,e,g,n.width,n.height,0,m,f,n.data)}}}else{_=r.mipmaps,v&&x&&(_.length>0&&y++,i.texStorage2D(34067,y,g,u[0].width,u[0].height));for(let t=0;t<6;t++)if(n){v?i.texSubImage2D(34069+t,0,0,0,u[t].width,u[t].height,m,f,u[t].data):i.texImage2D(34069+t,0,g,u[t].width,u[t].height,0,m,f,u[t].data);for(let e=0;e<_.length;e++){const n=_[e].image[t].image;v?i.texSubImage2D(34069+t,e+1,0,0,n.width,n.height,m,f,n.data):i.texImage2D(34069+t,e+1,g,n.width,n.height,0,m,f,n.data)}}else{v?i.texSubImage2D(34069+t,0,0,0,m,f,u[t]):i.texImage2D(34069+t,0,g,m,f,u[t]);for(let e=0;e<_.length;e++){const n=_[e];v?i.texSubImage2D(34069+t,e+1,0,0,m,f,n.image[t]):i.texImage2D(34069+t,e+1,g,m,f,n.image[t])}}}F(r,p)&&k(34067),h.__version=c.version,r.onUpdate&&r.onUpdate(r)}e.__version=r.version}(a,e,r):i.bindTexture(34067,a.__webglTexture,33984+r)},this.rebindTextures=function(t,e,i){const r=n.get(t);void 0!==e&&tt(r.__webglFramebuffer,t,t.texture,36064,3553),void 0!==i&&it(t)},this.setupRenderTarget=function(e){const l=e.texture,c=n.get(e),h=n.get(l);e.addEventListener("dispose",j),!0!==e.isWebGLMultipleRenderTargets&&(void 0===h.__webglTexture&&(h.__webglTexture=t.createTexture()),h.__version=l.version,a.memory.textures++);const u=!0===e.isWebGLCubeRenderTarget,d=!0===e.isWebGLMultipleRenderTargets,p=B(e)||o;if(u){c.__webglFramebuffer=[];for(let e=0;e<6;e++)c.__webglFramebuffer[e]=t.createFramebuffer()}else{if(c.__webglFramebuffer=t.createFramebuffer(),d)if(r.drawBuffers){const i=e.texture;for(let e=0,r=i.length;e0&&!1===rt(e)){const n=d?l:[l];c.__webglMultisampledFramebuffer=t.createFramebuffer(),c.__webglColorRenderbuffer=[],i.bindFramebuffer(36160,c.__webglMultisampledFramebuffer);for(let i=0;i0&&!1===rt(e)){const r=e.isWebGLMultipleRenderTargets?e.texture:[e.texture],s=e.width,a=e.height;let o=16384;const l=[],c=e.stencilBuffer?33306:36096,h=n.get(e),u=!0===e.isWebGLMultipleRenderTargets;if(u)for(let e=0;eo+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,i),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(n=e.getPose(t.targetRaySpace,i),null===n&&null!==r&&(n=r),null!==n&&(a.matrix.fromArray(n.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),n.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(n.linearVelocity)):a.hasLinearVelocity=!1,n.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(n.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(Gs)))}return null!==a&&(a.visible=null!==n),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const i=new ks;i.matrixAutoUpdate=!1,i.visible=!1,t.joints[e.jointName]=i,t.add(i)}return t.joints[e.jointName]}}class Hs extends $t{constructor(t,e,i,n,r,s,a,o,l,c){if((c=void 0!==c?c:T)!==T&&c!==A)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===i&&c===T&&(i=y),void 0===i&&c===A&&(i=S),super(null,n,r,s,a,o,c,i,l),this.isDepthTexture=!0,this.image={width:t,height:e},this.magFilter=void 0!==a?a:d,this.minFilter=void 0!==o?o:d,this.flipY=!1,this.generateMipmaps=!1}}class Ws extends mt{constructor(t,e){super();const i=this;let n=null,r=1,s=null,a="local-floor",o=null,l=null,c=null,h=null,u=null,d=null;const p=e.getContextAttributes();let m=null,f=null;const g=[],v=[],_=new Set,M=new Map,b=new nn;b.layers.enable(1),b.viewport=new Qt;const E=new nn;E.layers.enable(2),E.viewport=new Qt;const C=[b,E],L=new Fs;L.layers.enable(1),L.layers.enable(2);let R=null,P=null;function I(t){const e=v.indexOf(t.inputSource);if(-1===e)return;const i=g[e];void 0!==i&&i.dispatchEvent({type:t.type,data:t.inputSource})}function D(){n.removeEventListener("select",I),n.removeEventListener("selectstart",I),n.removeEventListener("selectend",I),n.removeEventListener("squeeze",I),n.removeEventListener("squeezestart",I),n.removeEventListener("squeezeend",I),n.removeEventListener("end",D),n.removeEventListener("inputsourceschange",N);for(let t=0;t=0&&(v[n]=null,g[n].disconnect(i))}for(let e=0;e=v.length){v.push(i),n=t;break}if(null===v[t]){v[t]=i,n=t;break}}if(-1===n)break}const r=g[n];r&&r.connect(i)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(t){let e=g[t];return void 0===e&&(e=new Vs,g[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=g[t];return void 0===e&&(e=new Vs,g[t]=e),e.getGripSpace()},this.getHand=function(t){let e=g[t];return void 0===e&&(e=new Vs,g[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===i.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){a=t,!0===i.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return o||s},this.setReferenceSpace=function(t){o=t},this.getBaseLayer=function(){return null!==h?h:u},this.getBinding=function(){return c},this.getFrame=function(){return d},this.getSession=function(){return n},this.setSession=async function(l){if(n=l,null!==n){if(m=t.getRenderTarget(),n.addEventListener("select",I),n.addEventListener("selectstart",I),n.addEventListener("selectend",I),n.addEventListener("squeeze",I),n.addEventListener("squeezestart",I),n.addEventListener("squeezeend",I),n.addEventListener("end",D),n.addEventListener("inputsourceschange",N),!0!==p.xrCompatible&&await e.makeXRCompatible(),void 0===n.renderState.layers||!1===t.capabilities.isWebGL2){const i={antialias:void 0!==n.renderState.layers||p.antialias,alpha:p.alpha,depth:p.depth,stencil:p.stencil,framebufferScaleFactor:r};u=new XRWebGLLayer(n,e,i),n.updateRenderState({baseLayer:u}),f=new te(u.framebufferWidth,u.framebufferHeight,{format:w,type:x,encoding:t.outputEncoding,stencilBuffer:p.stencil})}else{let i=null,s=null,a=null;p.depth&&(a=p.stencil?35056:33190,i=p.stencil?A:T,s=p.stencil?S:y);const o={colorFormat:32856,depthFormat:a,scaleFactor:r};c=new XRWebGLBinding(n,e),h=c.createProjectionLayer(o),n.updateRenderState({layers:[h]}),f=new te(h.textureWidth,h.textureHeight,{format:w,type:x,depthTexture:new Hs(h.textureWidth,h.textureHeight,s,void 0,void 0,void 0,void 0,void 0,void 0,i),stencilBuffer:p.stencil,encoding:t.outputEncoding,samples:p.antialias?4:0});t.properties.get(f).__ignoreDepthValues=h.ignoreDepthValues}f.isXRRenderTarget=!0,this.setFoveation(1),o=null,s=await n.requestReferenceSpace(a),F.setContext(n),F.start(),i.isPresenting=!0,i.dispatchEvent({type:"sessionstart"})}};const O=new re,z=new re;function U(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCamera=function(t){if(null===n)return;L.near=E.near=b.near=t.near,L.far=E.far=b.far=t.far,R===L.near&&P===L.far||(n.updateRenderState({depthNear:L.near,depthFar:L.far}),R=L.near,P=L.far);const e=t.parent,i=L.cameras;U(L,e);for(let t=0;te&&(M.set(t,t.lastChangedTime),i.dispatchEvent({type:"planechanged",data:t}))}else _.add(t),M.set(t,n.lastChangedTime),i.dispatchEvent({type:"planeadded",data:t})}d=null})),this.setAnimationLoop=function(t){B=t},this.dispose=function(){}}}function js(t,e){function i(i,n){i.opacity.value=n.opacity,n.color&&i.diffuse.value.copy(n.color),n.emissive&&i.emissive.value.copy(n.emissive).multiplyScalar(n.emissiveIntensity),n.map&&(i.map.value=n.map),n.alphaMap&&(i.alphaMap.value=n.alphaMap),n.bumpMap&&(i.bumpMap.value=n.bumpMap,i.bumpScale.value=n.bumpScale,1===n.side&&(i.bumpScale.value*=-1)),n.displacementMap&&(i.displacementMap.value=n.displacementMap,i.displacementScale.value=n.displacementScale,i.displacementBias.value=n.displacementBias),n.emissiveMap&&(i.emissiveMap.value=n.emissiveMap),n.normalMap&&(i.normalMap.value=n.normalMap,i.normalScale.value.copy(n.normalScale),1===n.side&&i.normalScale.value.negate()),n.specularMap&&(i.specularMap.value=n.specularMap),n.alphaTest>0&&(i.alphaTest.value=n.alphaTest);const r=e.get(n).envMap;if(r&&(i.envMap.value=r,i.flipEnvMap.value=r.isCubeTexture&&!1===r.isRenderTargetTexture?-1:1,i.reflectivity.value=n.reflectivity,i.ior.value=n.ior,i.refractionRatio.value=n.refractionRatio),n.lightMap){i.lightMap.value=n.lightMap;const e=!0!==t.physicallyCorrectLights?Math.PI:1;i.lightMapIntensity.value=n.lightMapIntensity*e}let s,a;n.aoMap&&(i.aoMap.value=n.aoMap,i.aoMapIntensity.value=n.aoMapIntensity),n.map?s=n.map:n.specularMap?s=n.specularMap:n.displacementMap?s=n.displacementMap:n.normalMap?s=n.normalMap:n.bumpMap?s=n.bumpMap:n.roughnessMap?s=n.roughnessMap:n.metalnessMap?s=n.metalnessMap:n.alphaMap?s=n.alphaMap:n.emissiveMap?s=n.emissiveMap:n.clearcoatMap?s=n.clearcoatMap:n.clearcoatNormalMap?s=n.clearcoatNormalMap:n.clearcoatRoughnessMap?s=n.clearcoatRoughnessMap:n.iridescenceMap?s=n.iridescenceMap:n.iridescenceThicknessMap?s=n.iridescenceThicknessMap:n.specularIntensityMap?s=n.specularIntensityMap:n.specularColorMap?s=n.specularColorMap:n.transmissionMap?s=n.transmissionMap:n.thicknessMap?s=n.thicknessMap:n.sheenColorMap?s=n.sheenColorMap:n.sheenRoughnessMap&&(s=n.sheenRoughnessMap),void 0!==s&&(s.isWebGLRenderTarget&&(s=s.texture),!0===s.matrixAutoUpdate&&s.updateMatrix(),i.uvTransform.value.copy(s.matrix)),n.aoMap?a=n.aoMap:n.lightMap&&(a=n.lightMap),void 0!==a&&(a.isWebGLRenderTarget&&(a=a.texture),!0===a.matrixAutoUpdate&&a.updateMatrix(),i.uv2Transform.value.copy(a.matrix))}return{refreshFogUniforms:function(e,i){i.color.getRGB(e.fogColor.value,$i(t)),i.isFog?(e.fogNear.value=i.near,e.fogFar.value=i.far):i.isFogExp2&&(e.fogDensity.value=i.density)},refreshMaterialUniforms:function(t,n,r,s,a){n.isMeshBasicMaterial||n.isMeshLambertMaterial?i(t,n):n.isMeshToonMaterial?(i(t,n),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,n)):n.isMeshPhongMaterial?(i(t,n),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,n)):n.isMeshStandardMaterial?(i(t,n),function(t,i){t.roughness.value=i.roughness,t.metalness.value=i.metalness,i.roughnessMap&&(t.roughnessMap.value=i.roughnessMap);i.metalnessMap&&(t.metalnessMap.value=i.metalnessMap);const n=e.get(i).envMap;n&&(t.envMapIntensity.value=i.envMapIntensity)}(t,n),n.isMeshPhysicalMaterial&&function(t,e,i){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap),e.clearcoatNormalMap&&(t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),t.clearcoatNormalMap.value=e.clearcoatNormalMap,1===e.side&&t.clearcoatNormalScale.value.negate()));e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap);e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap)}(t,n,a)):n.isMeshMatcapMaterial?(i(t,n),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,n)):n.isMeshDepthMaterial?i(t,n):n.isMeshDistanceMaterial?(i(t,n),function(t,e){t.referencePosition.value.copy(e.referencePosition),t.nearDistance.value=e.nearDistance,t.farDistance.value=e.farDistance}(t,n)):n.isMeshNormalMaterial?i(t,n):n.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity}(t,n),n.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,n)):n.isPointsMaterial?function(t,e,i,n){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*i,t.scale.value=.5*n,e.map&&(t.map.value=e.map);e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest);let r;e.map?r=e.map:e.alphaMap&&(r=e.alphaMap);void 0!==r&&(!0===r.matrixAutoUpdate&&r.updateMatrix(),t.uvTransform.value.copy(r.matrix))}(t,n,r,s):n.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map);e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest);let i;e.map?i=e.map:e.alphaMap&&(i=e.alphaMap);void 0!==i&&(!0===i.matrixAutoUpdate&&i.updateMatrix(),t.uvTransform.value.copy(i.matrix))}(t,n):n.isShadowMaterial?(t.color.value.copy(n.color),t.opacity.value=n.opacity):n.isShaderMaterial&&(n.uniformsNeedUpdate=!1)}}}function qs(t,e,i,n){let r={},s={},a=[];const o=i.isWebGL2?t.getParameter(35375):0;function l(t,e,i){const n=t.value;if(void 0===i[e]){if("number"==typeof n)i[e]=n;else{const t=Array.isArray(n)?n:[n],r=[];for(let e=0;e0){r=i%n;0!==r&&n-r-a.boundary<0&&(i+=n-r,s.__offset=i)}i+=a.storage}r=i%n,r>0&&(i+=n-r);t.__size=i,t.__cache={}}(i),d=function(e){const i=function(){for(let t=0;t0&&function(t,e,i){const n=Z.isWebGL2;null===V&&(V=new te(1,1,{generateMipmaps:!0,type:Y.has("EXT_color_buffer_half_float")?b:x,minFilter:v,samples:n&&!0===a?4:0}));g.getDrawingBufferSize(W),n?V.setSize(W.x,W.y):V.setSize(Tt(W.x),Tt(W.y));const r=g.getRenderTarget();g.setRenderTarget(V),g.clear();const s=g.toneMapping;g.toneMapping=0,zt(t,e,i),g.toneMapping=s,Q.updateMultisampleRenderTarget(V),Q.updateRenderTargetMipmap(V),g.setRenderTarget(r)}(r,e,i),n&&J.viewport(C.copy(n)),r.length>0&&zt(r,e,i),s.length>0&&zt(s,e,i),o.length>0&&zt(o,e,i),J.buffers.depth.setTest(!0),J.buffers.depth.setMask(!0),J.buffers.color.setMask(!0),J.setPolygonOffset(!1)}function zt(t,e,i){const n=!0===e.isScene?e.overrideMaterial:null;for(let r=0,s=t.length;r0?f[f.length-1]:null,m.pop(),d=m.length>0?m[m.length-1]:null},this.getActiveCubeFace=function(){return y},this.getActiveMipmapLevel=function(){return S},this.getRenderTarget=function(){return T},this.setRenderTargetTextures=function(t,e,i){$.get(t.texture).__webglTexture=e,$.get(t.depthTexture).__webglTexture=i;const n=$.get(t);n.__hasExternalTextures=!0,n.__hasExternalTextures&&(n.__autoAllocateDepthBuffer=void 0===i,n.__autoAllocateDepthBuffer||!0===Y.has("WEBGL_multisampled_render_to_texture")&&(console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided"),n.__useRenderToTexture=!1))},this.setRenderTargetFramebuffer=function(t,e){const i=$.get(t);i.__webglFramebuffer=e,i.__useDefaultFramebuffer=void 0===e},this.setRenderTarget=function(t,e=0,i=0){T=t,y=e,S=i;let n=!0,r=null,s=!1,a=!1;if(t){const i=$.get(t);void 0!==i.__useDefaultFramebuffer?(J.bindFramebuffer(36160,null),n=!1):void 0===i.__webglFramebuffer?Q.setupRenderTarget(t):i.__hasExternalTextures&&Q.rebindTextures(t,$.get(t.texture).__webglTexture,$.get(t.depthTexture).__webglTexture);const o=t.texture;(o.isData3DTexture||o.isDataArrayTexture||o.isCompressedArrayTexture)&&(a=!0);const l=$.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=l[e],s=!0):r=Z.isWebGL2&&t.samples>0&&!1===Q.useMultisampledRTT(t)?$.get(t).__webglMultisampledFramebuffer:l,C.copy(t.viewport),L.copy(t.scissor),R=t.scissorTest}else C.copy(z).multiplyScalar(D).floor(),L.copy(U).multiplyScalar(D).floor(),R=B;if(J.bindFramebuffer(36160,r)&&Z.drawBuffers&&n&&J.drawBuffers(t,r),J.viewport(C),J.scissor(L),J.setScissorTest(R),s){const n=$.get(t.texture);_t.framebufferTexture2D(36160,36064,34069+e,n.__webglTexture,i)}else if(a){const n=$.get(t.texture),r=e||0;_t.framebufferTextureLayer(36160,36064,n.__webglTexture,i||0,r)}A=-1},this.readRenderTargetPixels=function(t,e,i,n,r,s,a){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=$.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){J.bindFramebuffer(36160,o);try{const a=t.texture,o=a.format,l=a.type;if(o!==w&>.convert(o)!==_t.getParameter(35739))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=l===b&&(Y.has("EXT_color_buffer_half_float")||Z.isWebGL2&&Y.has("EXT_color_buffer_float"));if(!(l===x||gt.convert(l)===_t.getParameter(35738)||l===M&&(Z.isWebGL2||Y.has("OES_texture_float")||Y.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");e>=0&&e<=t.width-n&&i>=0&&i<=t.height-r&&_t.readPixels(e,i,n,r,gt.convert(o),gt.convert(l),s)}finally{const t=null!==T?$.get(T).__webglFramebuffer:null;J.bindFramebuffer(36160,t)}}},this.copyFramebufferToTexture=function(t,e,i=0){const n=Math.pow(2,-i),r=Math.floor(e.image.width*n),s=Math.floor(e.image.height*n);Q.setTexture2D(e,0),_t.copyTexSubImage2D(3553,i,0,0,t.x,t.y,r,s),J.unbindTexture()},this.copyTextureToTexture=function(t,e,i,n=0){const r=e.image.width,s=e.image.height,a=gt.convert(i.format),o=gt.convert(i.type);Q.setTexture2D(i,0),_t.pixelStorei(37440,i.flipY),_t.pixelStorei(37441,i.premultiplyAlpha),_t.pixelStorei(3317,i.unpackAlignment),e.isDataTexture?_t.texSubImage2D(3553,n,t.x,t.y,r,s,a,o,e.image.data):e.isCompressedTexture?_t.compressedTexSubImage2D(3553,n,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,a,e.mipmaps[0].data):_t.texSubImage2D(3553,n,t.x,t.y,a,o,e.image),0===n&&i.generateMipmaps&&_t.generateMipmap(3553),J.unbindTexture()},this.copyTextureToTexture3D=function(t,e,i,n,r=0){if(g.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const s=t.max.x-t.min.x+1,a=t.max.y-t.min.y+1,o=t.max.z-t.min.z+1,l=gt.convert(n.format),c=gt.convert(n.type);let h;if(n.isData3DTexture)Q.setTexture3D(n,0),h=32879;else{if(!n.isDataArrayTexture)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");Q.setTexture2DArray(n,0),h=35866}_t.pixelStorei(37440,n.flipY),_t.pixelStorei(37441,n.premultiplyAlpha),_t.pixelStorei(3317,n.unpackAlignment);const u=_t.getParameter(3314),d=_t.getParameter(32878),p=_t.getParameter(3316),m=_t.getParameter(3315),f=_t.getParameter(32877),v=i.isCompressedTexture?i.mipmaps[0]:i.image;_t.pixelStorei(3314,v.width),_t.pixelStorei(32878,v.height),_t.pixelStorei(3316,t.min.x),_t.pixelStorei(3315,t.min.y),_t.pixelStorei(32877,t.min.z),i.isDataTexture||i.isData3DTexture?_t.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,v.data):i.isCompressedArrayTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),_t.compressedTexSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,v.data)):_t.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,v),_t.pixelStorei(3314,u),_t.pixelStorei(32878,d),_t.pixelStorei(3316,p),_t.pixelStorei(3315,m),_t.pixelStorei(32877,f),0===r&&n.generateMipmaps&&_t.generateMipmap(h),J.unbindTexture()},this.initTexture=function(t){t.isCubeTexture?Q.setTextureCube(t,0):t.isData3DTexture?Q.setTexture3D(t,0):t.isDataArrayTexture||t.isCompressedArrayTexture?Q.setTexture2DArray(t,0):Q.setTexture2D(t,0),J.unbindTexture()},this.resetState=function(){y=0,S=0,T=null,J.reset(),vt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}class Ys extends Xs{}Ys.prototype.isWebGL1Renderer=!0;class Zs{constructor(t,e=25e-5){this.isFogExp2=!0,this.name="",this.color=new qt(t),this.density=e}clone(){return new Zs(this.color,this.density)}toJSON(){return{type:"FogExp2",color:this.color.getHex(),density:this.density}}}class Js{constructor(t,e=1,i=1e3){this.isFog=!0,this.name="",this.color=new qt(t),this.near=e,this.far=i}clone(){return new Js(this.color,this.near,this.far)}toJSON(){return{type:"Fog",color:this.color.getHex(),near:this.near,far:this.far}}}class Ks extends si{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.backgroundIntensity=this.backgroundIntensity),e}get autoUpdate(){return console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate}set autoUpdate(t){console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate=t}}class $s{constructor(t,e){this.isInterleavedBuffer=!0,this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=ut,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=_t()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,i){t*=this.stride,i*=e.stride;for(let n=0,r=this.stride;nt.far||e.push({distance:o,point:na.clone(),uv:gi.getUV(na,ca,ha,ua,da,pa,ma,new Lt),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function ga(t,e,i,n,r,s){aa.subVectors(t,i).addScalar(.5).multiply(n),void 0!==r?(oa.x=s*aa.x-r*aa.y,oa.y=r*aa.x+s*aa.y):oa.copy(aa),t.copy(e),t.x+=oa.x,t.y+=oa.y,t.applyMatrix4(la)}const va=new re,xa=new re;class _a extends si{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,i=e.length;t0){let i,n;for(i=1,n=e.length;i0){va.setFromMatrixPosition(this.matrixWorld);const i=t.ray.origin.distanceTo(va);this.getObjectForDistance(i).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){va.setFromMatrixPosition(t.matrixWorld),xa.setFromMatrixPosition(this.matrixWorld);const i=va.distanceTo(xa)/t.zoom;let n,r;for(e[0].object.visible=!0,n=1,r=e.length;n=t))break;e[n-1].object.visible=!1,e[n].object.visible=!0}for(this._currentLevel=n-1;no)continue;u.applyMatrix4(this.matrixWorld);const s=t.ray.origin.distanceTo(u);st.far||e.push({distance:s,point:h.clone().applyMatrix4(this.matrixWorld),index:i,face:null,faceIndex:null,object:this})}}else{for(let i=Math.max(0,s.start),n=Math.min(m.count,s.start+s.count)-1;io)continue;u.applyMatrix4(this.matrixWorld);const n=t.ray.origin.distanceTo(u);nt.far||e.push({distance:n,point:h.clone().applyMatrix4(this.matrixWorld),index:i,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;t0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;tr.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:i,index:e,face:null,object:a})}}class io extends $t{constructor(t,e,i,n,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,n,r,h,u),this.isCompressedTexture=!0,this.image={width:e,height:i},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class no{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const i=this.getUtoTmapping(t);return this.getPoint(i,e)}getPoints(t=5){const e=[];for(let i=0;i<=t;i++)e.push(this.getPoint(i/t));return e}getSpacedPoints(t=5){const e=[];for(let i=0;i<=t;i++)e.push(this.getPointAt(i/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let i,n=this.getPoint(0),r=0;e.push(0);for(let s=1;s<=t;s++)i=this.getPoint(s/t),r+=i.distanceTo(n),e.push(r),n=i;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const i=this.getLengths();let n=0;const r=i.length;let s;s=e||t*i[r-1];let a,o=0,l=r-1;for(;o<=l;)if(n=Math.floor(o+(l-o)/2),a=i[n]-s,a<0)o=n+1;else{if(!(a>0)){l=n;break}l=n-1}if(n=l,i[n]===s)return n/(r-1);const c=i[n];return(n+(s-c)/(i[n+1]-c))/(r-1)}getTangent(t,e){const i=1e-4;let n=t-i,r=t+i;n<0&&(n=0),r>1&&(r=1);const s=this.getPoint(n),a=this.getPoint(r),o=e||(s.isVector2?new Lt:new re);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const i=this.getUtoTmapping(t);return this.getTangent(i,e)}computeFrenetFrames(t,e){const i=new re,n=[],r=[],s=[],a=new re,o=new Ne;for(let e=0;e<=t;e++){const i=e/t;n[e]=this.getTangentAt(i,new re)}r[0]=new re,s[0]=new re;let l=Number.MAX_VALUE;const c=Math.abs(n[0].x),h=Math.abs(n[0].y),u=Math.abs(n[0].z);c<=l&&(l=c,i.set(1,0,0)),h<=l&&(l=h,i.set(0,1,0)),u<=l&&i.set(0,0,1),a.crossVectors(n[0],i).normalize(),r[0].crossVectors(n[0],a),s[0].crossVectors(n[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(n[e-1],n[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(yt(n[e-1].dot(n[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(n[e],r[e])}if(!0===e){let e=Math.acos(yt(r[0].dot(r[t]),-1,1));e/=t,n[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let i=1;i<=t;i++)r[i].applyMatrix4(o.makeRotationAxis(n[i],e*i)),s[i].crossVectors(n[i],r[i])}return{tangents:n,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.5,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class ro extends no{constructor(t=0,e=0,i=1,n=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=i,this.yRadius=n,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e){const i=e||new Lt,n=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)n;)r-=n;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=n[(l-1)%r]:(oo.subVectors(n[0],n[1]).add(n[0]),a=oo);const h=n[l%r],u=n[(l+1)%r];if(this.closed||l+2n.length-2?n.length-1:s+1],h=n[s>n.length-3?n.length-1:s+2];return i.set(po(a,o.x,l.x,c.x,h.x),po(a,o.y,l.y,c.y,h.y)),i}copy(t){super.copy(t),this.points=[];for(let e=0,i=t.points.length;e=i){const t=n[r]-i,s=this.curves[r],a=s.getLength(),o=0===a?0:1-t/a;return s.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let i=0,n=this.curves.length;i1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,i=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class Ao extends Di{constructor(t=[new Lt(0,-.5),new Lt(.5,0),new Lt(0,.5)],e=12,i=0,n=2*Math.PI){super(),this.type="LatheGeometry",this.parameters={points:t,segments:e,phiStart:i,phiLength:n},e=Math.floor(e),n=yt(n,0,2*Math.PI);const r=[],s=[],a=[],o=[],l=[],c=1/e,h=new re,u=new Lt,d=new re,p=new re,m=new re;let f=0,g=0;for(let e=0;e<=t.length-1;e++)switch(e){case 0:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,m.copy(d),d.normalize(),o.push(d.x,d.y,d.z);break;case t.length-1:o.push(m.x,m.y,m.z);break;default:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,p.copy(d),d.x+=m.x,d.y+=m.y,d.z+=m.z,d.normalize(),o.push(d.x,d.y,d.z),m.copy(p)}for(let r=0;r<=e;r++){const d=i+r*c*n,p=Math.sin(d),m=Math.cos(d);for(let i=0;i<=t.length-1;i++){h.x=t[i].x*p,h.y=t[i].y,h.z=t[i].x*m,s.push(h.x,h.y,h.z),u.x=r/e,u.y=i/(t.length-1),a.push(u.x,u.y);const n=o[3*i+0]*p,c=o[3*i+1],d=o[3*i+0]*m;l.push(n,c,d)}}for(let i=0;i0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new Ti(h,3)),this.setAttribute("normal",new Ti(u,3)),this.setAttribute("uv",new Ti(d,2))}static fromJSON(t){return new Lo(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class Ro extends Lo{constructor(t=1,e=1,i=32,n=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,i,n,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:i,heightSegments:n,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new Ro(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class Po extends Di{constructor(t=[],e=[],i=1,n=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:i,detail:n};const r=[],s=[];function a(t,e,i,n){const r=n+1,s=[];for(let n=0;n<=r;n++){s[n]=[];const a=t.clone().lerp(i,n/r),o=e.clone().lerp(i,n/r),l=r-n;for(let t=0;t<=l;t++)s[n][t]=0===t&&n===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),i<.2&&(s[t+2]+=1),n<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new Ti(r,3)),this.setAttribute("normal",new Ti(r.slice(),3)),this.setAttribute("uv",new Ti(s,2)),0===n?this.computeVertexNormals():this.normalizeNormals()}static fromJSON(t){return new Po(t.vertices,t.indices,t.radius,t.details)}}class Io extends Po{constructor(t=1,e=0){const i=(1+Math.sqrt(5))/2,n=1/i;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-n,-i,0,-n,i,0,n,-i,0,n,i,-n,-i,0,-n,i,0,n,-i,0,n,i,0,-i,0,-n,i,0,-n,-i,0,n,i,0,n],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new Io(t.radius,t.detail)}}const Do=new re,No=new re,Oo=new re,zo=new gi;class Uo extends Di{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const i=4,n=Math.pow(10,i),r=Math.cos(vt*e),s=t.getIndex(),a=t.getAttribute("position"),o=s?s.count:a.count,l=[0,0,0],c=["a","b","c"],h=new Array(3),u={},d=[];for(let t=0;t80*i){o=c=t[0],l=h=t[1];for(let e=i;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?32767/p:0}return Vo(s,a,i,o,l,p,0),a};function ko(t,e,i,n,r){let s,a;if(r===function(t,e,i,n){let r=0;for(let s=e,a=i-n;s0)for(s=e;s=e;s-=n)a=ol(s,t[s],t[s+1],a);return a&&el(a,a.next)&&(ll(a),a=a.next),a}function Go(t,e){if(!t)return t;e||(e=t);let i,n=t;do{if(i=!1,n.steiner||!el(n,n.next)&&0!==tl(n.prev,n,n.next))n=n.next;else{if(ll(n),n=e=n.prev,n===n.next)break;i=!0}}while(i||n!==e);return e}function Vo(t,e,i,n,r,s,a){if(!t)return;!a&&s&&function(t,e,i,n){let r=t;do{0===r.z&&(r.z=Jo(r.x,r.y,e,i,n)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,i,n,r,s,a,o,l,c=1;do{for(i=t,t=null,s=null,a=0;i;){for(a++,n=i,o=0,e=0;e0||l>0&&n;)0!==o&&(0===l||!n||i.z<=n.z)?(r=i,i=i.nextZ,o--):(r=n,n=n.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;i=n}s.nextZ=null,c*=2}while(a>1)}(r)}(t,n,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?Wo(t,n,r,s):Ho(t))e.push(o.i/i|0),e.push(t.i/i|0),e.push(l.i/i|0),ll(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Vo(t=jo(Go(t),e,i),e,i,n,r,s,2):2===a&&qo(t,e,i,n,r,s):Vo(Go(t),e,i,n,r,s,1);break}}function Ho(t){const e=t.prev,i=t,n=t.next;if(tl(e,i,n)>=0)return!1;const r=e.x,s=i.x,a=n.x,o=e.y,l=i.y,c=n.y,h=rs?r>a?r:a:s>a?s:a,p=o>l?o>c?o:c:l>c?l:c;let m=n.next;for(;m!==e;){if(m.x>=h&&m.x<=d&&m.y>=u&&m.y<=p&&$o(r,o,s,l,a,c,m.x,m.y)&&tl(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function Wo(t,e,i,n){const r=t.prev,s=t,a=t.next;if(tl(r,s,a)>=0)return!1;const o=r.x,l=s.x,c=a.x,h=r.y,u=s.y,d=a.y,p=ol?o>c?o:c:l>c?l:c,g=h>u?h>d?h:d:u>d?u:d,v=Jo(p,m,e,i,n),x=Jo(f,g,e,i,n);let _=t.prevZ,y=t.nextZ;for(;_&&_.z>=v&&y&&y.z<=x;){if(_.x>=p&&_.x<=f&&_.y>=m&&_.y<=g&&_!==r&&_!==a&&$o(o,h,l,u,c,d,_.x,_.y)&&tl(_.prev,_,_.next)>=0)return!1;if(_=_.prevZ,y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&$o(o,h,l,u,c,d,y.x,y.y)&&tl(y.prev,y,y.next)>=0)return!1;y=y.nextZ}for(;_&&_.z>=v;){if(_.x>=p&&_.x<=f&&_.y>=m&&_.y<=g&&_!==r&&_!==a&&$o(o,h,l,u,c,d,_.x,_.y)&&tl(_.prev,_,_.next)>=0)return!1;_=_.prevZ}for(;y&&y.z<=x;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&$o(o,h,l,u,c,d,y.x,y.y)&&tl(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function jo(t,e,i){let n=t;do{const r=n.prev,s=n.next.next;!el(r,s)&&il(r,n,n.next,s)&&sl(r,s)&&sl(s,r)&&(e.push(r.i/i|0),e.push(n.i/i|0),e.push(s.i/i|0),ll(n),ll(n.next),n=t=s),n=n.next}while(n!==t);return Go(n)}function qo(t,e,i,n,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&Qo(a,t)){let o=al(a,t);return a=Go(a,a.next),o=Go(o,o.next),Vo(a,e,i,n,r,s,0),void Vo(o,e,i,n,r,s,0)}t=t.next}a=a.next}while(a!==t)}function Xo(t,e){return t.x-e.x}function Yo(t,e){const i=function(t,e){let i,n=e,r=-1/0;const s=t.x,a=t.y;do{if(a<=n.y&&a>=n.next.y&&n.next.y!==n.y){const t=n.x+(a-n.y)*(n.next.x-n.x)/(n.next.y-n.y);if(t<=s&&t>r&&(r=t,i=n.x=n.x&&n.x>=l&&s!==n.x&&$o(ai.x||n.x===i.x&&Zo(i,n)))&&(i=n,u=h)),n=n.next}while(n!==o);return i}(t,e);if(!i)return e;const n=al(i,t);return Go(n,n.next),Go(i,i.next)}function Zo(t,e){return tl(t.prev,t,e.prev)<0&&tl(e.next,t,t.next)<0}function Jo(t,e,i,n,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-i)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-n)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function Ko(t){let e=t,i=t;do{(e.x=(t-a)*(s-o)&&(t-a)*(n-o)>=(i-a)*(e-o)&&(i-a)*(s-o)>=(r-a)*(n-o)}function Qo(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let i=t;do{if(i.i!==t.i&&i.next.i!==t.i&&i.i!==e.i&&i.next.i!==e.i&&il(i,i.next,t,e))return!0;i=i.next}while(i!==t);return!1}(t,e)&&(sl(t,e)&&sl(e,t)&&function(t,e){let i=t,n=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{i.y>s!=i.next.y>s&&i.next.y!==i.y&&r<(i.next.x-i.x)*(s-i.y)/(i.next.y-i.y)+i.x&&(n=!n),i=i.next}while(i!==t);return n}(t,e)&&(tl(t.prev,t,e.prev)||tl(t,e.prev,e))||el(t,e)&&tl(t.prev,t,t.next)>0&&tl(e.prev,e,e.next)>0)}function tl(t,e,i){return(e.y-t.y)*(i.x-e.x)-(e.x-t.x)*(i.y-e.y)}function el(t,e){return t.x===e.x&&t.y===e.y}function il(t,e,i,n){const r=rl(tl(t,e,i)),s=rl(tl(t,e,n)),a=rl(tl(i,n,t)),o=rl(tl(i,n,e));return r!==s&&a!==o||(!(0!==r||!nl(t,i,e))||(!(0!==s||!nl(t,n,e))||(!(0!==a||!nl(i,t,n))||!(0!==o||!nl(i,e,n)))))}function nl(t,e,i){return e.x<=Math.max(t.x,i.x)&&e.x>=Math.min(t.x,i.x)&&e.y<=Math.max(t.y,i.y)&&e.y>=Math.min(t.y,i.y)}function rl(t){return t>0?1:t<0?-1:0}function sl(t,e){return tl(t.prev,t,t.next)<0?tl(t,e,t.next)>=0&&tl(t,t.prev,e)>=0:tl(t,e,t.prev)<0||tl(t,t.next,e)<0}function al(t,e){const i=new cl(t.i,t.x,t.y),n=new cl(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,i.next=r,r.prev=i,n.next=i,i.prev=n,s.next=n,n.prev=s,n}function ol(t,e,i,n){const r=new cl(t,e,i);return n?(r.next=n.next,r.prev=n,n.next.prev=r,n.next=r):(r.prev=r,r.next=r),r}function ll(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function cl(t,e,i){this.i=t,this.x=e,this.y=i,this.prev=null,this.next=null,this.z=0,this.prevZ=null,this.nextZ=null,this.steiner=!1}class hl{static area(t){const e=t.length;let i=0;for(let n=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function dl(t,e){for(let i=0;iNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((i.x-c/d-p)*c-(i.y+l/d-m)*l)/(a*c-o*l);n=p+a*f-t.x,r=m+o*f-t.y;const g=n*n+r*r;if(g<=2)return new Lt(n,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(n=-o,r=a,s=Math.sqrt(h)):(n=a,r=o,s=Math.sqrt(h/2))}return new Lt(n/s,r/s)}const P=[];for(let t=0,e=A.length,i=e-1,n=t+1;t=0;t--){const e=t/p,i=h*Math.cos(e*Math.PI/2),n=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=A.length;t=0;){const n=i;let r=i-1;r<0&&(r=t.length-1);for(let t=0,i=o+2*p;t0)&&d.push(e,r,l),(t!==i-1||o0!=t>0&&this.version++,this._sheen=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class Pl extends xi{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new qt(16777215),this.specular=new qt(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new qt(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Lt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Il extends xi{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new qt(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new qt(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Lt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class Dl extends xi{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Lt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Nl extends xi{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new qt(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new qt(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Lt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Ol extends xi{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new qt(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Lt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class zl extends Ba{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}function Ul(t,e,i){return Fl(t)?new t.constructor(t.subarray(e,void 0!==i?i:t.length)):t.slice(e,i)}function Bl(t,e,i){return!t||!i&&t.constructor===e?t:"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t)}function Fl(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}function kl(t){const e=t.length,i=new Array(e);for(let t=0;t!==e;++t)i[t]=t;return i.sort((function(e,i){return t[e]-t[i]})),i}function Gl(t,e,i){const n=t.length,r=new t.constructor(n);for(let s=0,a=0;a!==n;++s){const n=i[s]*e;for(let i=0;i!==e;++i)r[a++]=t[n+i]}return r}function Vl(t,e,i,n){let r=1,s=t[0];for(;void 0!==s&&void 0===s[n];)s=t[r++];if(void 0===s)return;let a=s[n];if(void 0!==a)if(Array.isArray(a))do{a=s[n],void 0!==a&&(e.push(s.time),i.push.apply(i,a)),s=t[r++]}while(void 0!==s);else if(void 0!==a.toArray)do{a=s[n],void 0!==a&&(e.push(s.time),a.toArray(i,i.length)),s=t[r++]}while(void 0!==s);else do{a=s[n],void 0!==a&&(e.push(s.time),i.push(a)),s=t[r++]}while(void 0!==s)}var Hl=Object.freeze({__proto__:null,arraySlice:Ul,convertArray:Bl,isTypedArray:Fl,getKeyframeOrder:kl,sortedArray:Gl,flattenJSON:Vl,subclip:function(t,e,i,n,r=30){const s=t.clone();s.name=e;const a=[];for(let t=0;t=n)){l.push(e.times[t]);for(let i=0;is.tracks[t].times[0]&&(o=s.tracks[t].times[0]);for(let t=0;t=n.times[u]){const t=u*l+o,e=t+l-o;d=Ul(n.values,t,e)}else{const t=n.createInterpolant(),e=o,i=l-o;t.evaluate(s),d=Ul(t.resultBuffer,e,i)}if("quaternion"===r){(new ne).fromArray(d).normalize().conjugate().toArray(d)}const p=a.times.length;for(let t=0;t=r)break t;{const a=e[1];t=r)break e}s=i,i=0}}for(;i>>1;te;)--s;if(++s,0!==r||s!==n){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=Ul(i,r,s),this.values=Ul(this.values,r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const i=this.times,n=this.values,r=i.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const n=i[e];if("number"==typeof n&&isNaN(n)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,n),t=!1;break}if(null!==s&&s>n){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,n,s),t=!1;break}s=n}if(void 0!==n&&Fl(n))for(let e=0,i=n.length;e!==i;++e){const i=n[e];if(isNaN(i)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,i),t=!1;break}}return t}optimize(){const t=Ul(this.times),e=Ul(this.values),i=this.getValueSize(),n=this.getInterpolation()===tt,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*i,n=s*i,a=0;a!==i;++a)e[n+a]=e[t+a];++s}return s!==t.length?(this.times=Ul(t,0,s),this.values=Ul(e,0,s*i)):(this.times=t,this.values=e),this}clone(){const t=Ul(this.times,0),e=Ul(this.values,0),i=new(0,this.constructor)(this.name,t,e);return i.createInterpolant=this.createInterpolant,i}}Yl.prototype.TimeBufferType=Float32Array,Yl.prototype.ValueBufferType=Float32Array,Yl.prototype.DefaultInterpolation=Q;class Zl extends Yl{}Zl.prototype.ValueTypeName="bool",Zl.prototype.ValueBufferType=Array,Zl.prototype.DefaultInterpolation=$,Zl.prototype.InterpolantFactoryMethodLinear=void 0,Zl.prototype.InterpolantFactoryMethodSmooth=void 0;class Jl extends Yl{}Jl.prototype.ValueTypeName="color";class Kl extends Yl{}Kl.prototype.ValueTypeName="number";class $l extends Wl{constructor(t,e,i,n){super(t,e,i,n)}interpolate_(t,e,i,n){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(i-e)/(n-e);let l=t*a;for(let t=l+a;l!==t;l+=4)ne.slerpFlat(r,0,s,l-a,s,l,o);return r}}class Ql extends Yl{InterpolantFactoryMethodLinear(t){return new $l(this.times,this.values,this.getValueSize(),t)}}Ql.prototype.ValueTypeName="quaternion",Ql.prototype.DefaultInterpolation=Q,Ql.prototype.InterpolantFactoryMethodSmooth=void 0;class tc extends Yl{}tc.prototype.ValueTypeName="string",tc.prototype.ValueBufferType=Array,tc.prototype.DefaultInterpolation=$,tc.prototype.InterpolantFactoryMethodLinear=void 0,tc.prototype.InterpolantFactoryMethodSmooth=void 0;class ec extends Yl{}ec.prototype.ValueTypeName="vector";class ic{constructor(t,e=-1,i,n=2500){this.name=t,this.tracks=i,this.duration=e,this.blendMode=n,this.uuid=_t(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],i=t.tracks,n=1/(t.fps||1);for(let t=0,r=i.length;t!==r;++t)e.push(nc(i[t]).scale(n));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],i=t.tracks,n={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,n=i.length;t!==n;++t)e.push(Yl.toJSON(i[t]));return n}static CreateFromMorphTargetSequence(t,e,i,n){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=n[t];e||(n[t]=e=[]),e.push(i)}}const s=[];for(const t in n)s.push(this.CreateFromMorphTargetSequence(t,n[t],e,i));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const i=function(t,e,i,n,r){if(0!==i.length){const s=[],a=[];Vl(i,s,a,n),0!==s.length&&r.push(new t(e,s,a))}},n=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t{e&&e(r),this.manager.itemEnd(t)}),0),r;if(void 0!==lc[t])return void lc[t].push({onLoad:e,onProgress:i,onError:n});lc[t]=[],lc[t].push({onLoad:e,onProgress:i,onError:n});const s=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin"}),a=this.mimeType,o=this.responseType;fetch(s).then((e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const i=lc[t],n=e.body.getReader(),r=e.headers.get("Content-Length")||e.headers.get("X-File-Size"),s=r?parseInt(r):0,a=0!==s;let o=0;const l=new ReadableStream({start(t){!function e(){n.read().then((({done:n,value:r})=>{if(n)t.close();else{o+=r.byteLength;const n=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:s});for(let t=0,e=i.length;t{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then((t=>(new DOMParser).parseFromString(t,a)));case"json":return t.json();default:if(void 0===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),i=e&&e[1]?e[1].toLowerCase():void 0,n=new TextDecoder(i);return t.arrayBuffer().then((t=>n.decode(t)))}}})).then((e=>{rc.add(t,e);const i=lc[t];delete lc[t];for(let t=0,n=i.length;t{const i=lc[t];if(void 0===i)throw this.manager.itemError(t),e;delete lc[t];for(let t=0,n=i.length;t{this.manager.itemEnd(t)})),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}}class uc extends oc{constructor(t){super(t)}load(t,e,i,n){void 0!==this.path&&(t=this.path+t),t=this.manager.resolveURL(t);const r=this,s=rc.get(t);if(void 0!==s)return r.manager.itemStart(t),setTimeout((function(){e&&e(s),r.manager.itemEnd(t)}),0),s;const a=Ot("img");function o(){c(),rc.add(t,this),e&&e(this),r.manager.itemEnd(t)}function l(e){c(),n&&n(e),r.manager.itemError(t),r.manager.itemEnd(t)}function c(){a.removeEventListener("load",o,!1),a.removeEventListener("error",l,!1)}return a.addEventListener("load",o,!1),a.addEventListener("error",l,!1),"data:"!==t.slice(0,5)&&void 0!==this.crossOrigin&&(a.crossOrigin=this.crossOrigin),r.manager.itemStart(t),a.src=t,a}}class dc extends si{constructor(t,e=1){super(),this.isLight=!0,this.type="Light",this.color=new qt(t),this.intensity=e}dispose(){}copy(t,e){return super.copy(t,e),this.color.copy(t.color),this.intensity=t.intensity,this}toJSON(t){const e=super.toJSON(t);return e.object.color=this.color.getHex(),e.object.intensity=this.intensity,void 0!==this.groundColor&&(e.object.groundColor=this.groundColor.getHex()),void 0!==this.distance&&(e.object.distance=this.distance),void 0!==this.angle&&(e.object.angle=this.angle),void 0!==this.decay&&(e.object.decay=this.decay),void 0!==this.penumbra&&(e.object.penumbra=this.penumbra),void 0!==this.shadow&&(e.object.shadow=this.shadow.toJSON()),e}}class pc extends dc{constructor(t,e,i){super(t,i),this.isHemisphereLight=!0,this.type="HemisphereLight",this.position.copy(si.DefaultUp),this.updateMatrix(),this.groundColor=new qt(e)}copy(t,e){return super.copy(t,e),this.groundColor.copy(t.groundColor),this}}const mc=new Ne,fc=new re,gc=new re;class vc{constructor(t){this.camera=t,this.bias=0,this.normalBias=0,this.radius=1,this.blurSamples=8,this.mapSize=new Lt(512,512),this.map=null,this.mapPass=null,this.matrix=new Ne,this.autoUpdate=!0,this.needsUpdate=!1,this._frustum=new mn,this._frameExtents=new Lt(1,1),this._viewportCount=1,this._viewports=[new Qt(0,0,1,1)]}getViewportCount(){return this._viewportCount}getFrustum(){return this._frustum}updateMatrices(t){const e=this.camera,i=this.matrix;fc.setFromMatrixPosition(t.matrixWorld),e.position.copy(fc),gc.setFromMatrixPosition(t.target.matrixWorld),e.lookAt(gc),e.updateMatrixWorld(),mc.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),this._frustum.setFromProjectionMatrix(mc),i.set(.5,0,0,.5,0,.5,0,.5,0,0,.5,.5,0,0,0,1),i.multiply(mc)}getViewport(t){return this._viewports[t]}getFrameExtents(){return this._frameExtents}dispose(){this.map&&this.map.dispose(),this.mapPass&&this.mapPass.dispose()}copy(t){return this.camera=t.camera.clone(),this.bias=t.bias,this.radius=t.radius,this.mapSize.copy(t.mapSize),this}clone(){return(new this.constructor).copy(this)}toJSON(){const t={};return 0!==this.bias&&(t.bias=this.bias),0!==this.normalBias&&(t.normalBias=this.normalBias),1!==this.radius&&(t.radius=this.radius),512===this.mapSize.x&&512===this.mapSize.y||(t.mapSize=this.mapSize.toArray()),t.camera=this.camera.toJSON(!1).object,delete t.camera.matrix,t}}class xc extends vc{constructor(){super(new nn(50,1,.5,500)),this.isSpotLightShadow=!0,this.focus=1}updateMatrices(t){const e=this.camera,i=2*xt*t.angle*this.focus,n=this.mapSize.width/this.mapSize.height,r=t.distance||e.far;i===e.fov&&n===e.aspect&&r===e.far||(e.fov=i,e.aspect=n,e.far=r,e.updateProjectionMatrix()),super.updateMatrices(t)}copy(t){return super.copy(t),this.focus=t.focus,this}}class _c extends dc{constructor(t,e,i=0,n=Math.PI/3,r=0,s=2){super(t,e),this.isSpotLight=!0,this.type="SpotLight",this.position.copy(si.DefaultUp),this.updateMatrix(),this.target=new si,this.distance=i,this.angle=n,this.penumbra=r,this.decay=s,this.map=null,this.shadow=new xc}get power(){return this.intensity*Math.PI}set power(t){this.intensity=t/Math.PI}dispose(){this.shadow.dispose()}copy(t,e){return super.copy(t,e),this.distance=t.distance,this.angle=t.angle,this.penumbra=t.penumbra,this.decay=t.decay,this.target=t.target.clone(),this.shadow=t.shadow.clone(),this}}const yc=new Ne,Mc=new re,bc=new re;class Sc extends vc{constructor(){super(new nn(90,1,.5,500)),this.isPointLightShadow=!0,this._frameExtents=new Lt(4,2),this._viewportCount=6,this._viewports=[new Qt(2,1,1,1),new Qt(0,1,1,1),new Qt(3,1,1,1),new Qt(1,1,1,1),new Qt(3,0,1,1),new Qt(1,0,1,1)],this._cubeDirections=[new re(1,0,0),new re(-1,0,0),new re(0,0,1),new re(0,0,-1),new re(0,1,0),new re(0,-1,0)],this._cubeUps=[new re(0,1,0),new re(0,1,0),new re(0,1,0),new re(0,1,0),new re(0,0,1),new re(0,0,-1)]}updateMatrices(t,e=0){const i=this.camera,n=this.matrix,r=t.distance||i.far;r!==i.far&&(i.far=r,i.updateProjectionMatrix()),Mc.setFromMatrixPosition(t.matrixWorld),i.position.copy(Mc),bc.copy(i.position),bc.add(this._cubeDirections[e]),i.up.copy(this._cubeUps[e]),i.lookAt(bc),i.updateMatrixWorld(),n.makeTranslation(-Mc.x,-Mc.y,-Mc.z),yc.multiplyMatrices(i.projectionMatrix,i.matrixWorldInverse),this._frustum.setFromProjectionMatrix(yc)}}class wc extends dc{constructor(t,e,i=0,n=2){super(t,e),this.isPointLight=!0,this.type="PointLight",this.distance=i,this.decay=n,this.shadow=new Sc}get power(){return 4*this.intensity*Math.PI}set power(t){this.intensity=t/(4*Math.PI)}dispose(){this.shadow.dispose()}copy(t,e){return super.copy(t,e),this.distance=t.distance,this.decay=t.decay,this.shadow=t.shadow.clone(),this}}class Tc extends vc{constructor(){super(new Cn(-5,5,5,-5,.5,500)),this.isDirectionalLightShadow=!0}}class Ac extends dc{constructor(t,e){super(t,e),this.isDirectionalLight=!0,this.type="DirectionalLight",this.position.copy(si.DefaultUp),this.updateMatrix(),this.target=new si,this.shadow=new Tc}dispose(){this.shadow.dispose()}copy(t){return super.copy(t),this.target=t.target.clone(),this.shadow=t.shadow.clone(),this}}class Ec extends dc{constructor(t,e){super(t,e),this.isAmbientLight=!0,this.type="AmbientLight"}}class Cc extends dc{constructor(t,e,i=10,n=10){super(t,e),this.isRectAreaLight=!0,this.type="RectAreaLight",this.width=i,this.height=n}get power(){return this.intensity*this.width*this.height*Math.PI}set power(t){this.intensity=t/(this.width*this.height*Math.PI)}copy(t){return super.copy(t),this.width=t.width,this.height=t.height,this}toJSON(t){const e=super.toJSON(t);return e.object.width=this.width,e.object.height=this.height,e}}class Lc{constructor(){this.isSphericalHarmonics3=!0,this.coefficients=[];for(let t=0;t<9;t++)this.coefficients.push(new re)}set(t){for(let e=0;e<9;e++)this.coefficients[e].copy(t[e]);return this}zero(){for(let t=0;t<9;t++)this.coefficients[t].set(0,0,0);return this}getAt(t,e){const i=t.x,n=t.y,r=t.z,s=this.coefficients;return e.copy(s[0]).multiplyScalar(.282095),e.addScaledVector(s[1],.488603*n),e.addScaledVector(s[2],.488603*r),e.addScaledVector(s[3],.488603*i),e.addScaledVector(s[4],i*n*1.092548),e.addScaledVector(s[5],n*r*1.092548),e.addScaledVector(s[6],.315392*(3*r*r-1)),e.addScaledVector(s[7],i*r*1.092548),e.addScaledVector(s[8],.546274*(i*i-n*n)),e}getIrradianceAt(t,e){const i=t.x,n=t.y,r=t.z,s=this.coefficients;return e.copy(s[0]).multiplyScalar(.886227),e.addScaledVector(s[1],1.023328*n),e.addScaledVector(s[2],1.023328*r),e.addScaledVector(s[3],1.023328*i),e.addScaledVector(s[4],.858086*i*n),e.addScaledVector(s[5],.858086*n*r),e.addScaledVector(s[6],.743125*r*r-.247708),e.addScaledVector(s[7],.858086*i*r),e.addScaledVector(s[8],.429043*(i*i-n*n)),e}add(t){for(let e=0;e<9;e++)this.coefficients[e].add(t.coefficients[e]);return this}addScaledSH(t,e){for(let i=0;i<9;i++)this.coefficients[i].addScaledVector(t.coefficients[i],e);return this}scale(t){for(let e=0;e<9;e++)this.coefficients[e].multiplyScalar(t);return this}lerp(t,e){for(let i=0;i<9;i++)this.coefficients[i].lerp(t.coefficients[i],e);return this}equals(t){for(let e=0;e<9;e++)if(!this.coefficients[e].equals(t.coefficients[e]))return!1;return!0}copy(t){return this.set(t.coefficients)}clone(){return(new this.constructor).copy(this)}fromArray(t,e=0){const i=this.coefficients;for(let n=0;n<9;n++)i[n].fromArray(t,e+3*n);return this}toArray(t=[],e=0){const i=this.coefficients;for(let n=0;n<9;n++)i[n].toArray(t,e+3*n);return t}static getBasisAt(t,e){const i=t.x,n=t.y,r=t.z;e[0]=.282095,e[1]=.488603*n,e[2]=.488603*r,e[3]=.488603*i,e[4]=1.092548*i*n,e[5]=1.092548*n*r,e[6]=.315392*(3*r*r-1),e[7]=1.092548*i*r,e[8]=.546274*(i*i-n*n)}}class Rc extends dc{constructor(t=new Lc,e=1){super(void 0,e),this.isLightProbe=!0,this.sh=t}copy(t){return super.copy(t),this.sh.copy(t.sh),this}fromJSON(t){return this.intensity=t.intensity,this.sh.fromArray(t.sh),this}toJSON(t){const e=super.toJSON(t);return e.object.sh=this.sh.toArray(),e}}class Pc extends oc{constructor(t){super(t),this.textures={}}load(t,e,i,n){const r=this,s=new hc(r.manager);s.setPath(r.path),s.setRequestHeader(r.requestHeader),s.setWithCredentials(r.withCredentials),s.load(t,(function(i){try{e(r.parse(JSON.parse(i)))}catch(e){n?n(e):console.error(e),r.manager.itemError(t)}}),i,n)}parse(t){const e=this.textures;function i(t){return void 0===e[t]&&console.warn("THREE.MaterialLoader: Undefined texture",t),e[t]}const n=Pc.createMaterialFromType(t.type);if(void 0!==t.uuid&&(n.uuid=t.uuid),void 0!==t.name&&(n.name=t.name),void 0!==t.color&&void 0!==n.color&&n.color.setHex(t.color),void 0!==t.roughness&&(n.roughness=t.roughness),void 0!==t.metalness&&(n.metalness=t.metalness),void 0!==t.sheen&&(n.sheen=t.sheen),void 0!==t.sheenColor&&(n.sheenColor=(new qt).setHex(t.sheenColor)),void 0!==t.sheenRoughness&&(n.sheenRoughness=t.sheenRoughness),void 0!==t.emissive&&void 0!==n.emissive&&n.emissive.setHex(t.emissive),void 0!==t.specular&&void 0!==n.specular&&n.specular.setHex(t.specular),void 0!==t.specularIntensity&&(n.specularIntensity=t.specularIntensity),void 0!==t.specularColor&&void 0!==n.specularColor&&n.specularColor.setHex(t.specularColor),void 0!==t.shininess&&(n.shininess=t.shininess),void 0!==t.clearcoat&&(n.clearcoat=t.clearcoat),void 0!==t.clearcoatRoughness&&(n.clearcoatRoughness=t.clearcoatRoughness),void 0!==t.iridescence&&(n.iridescence=t.iridescence),void 0!==t.iridescenceIOR&&(n.iridescenceIOR=t.iridescenceIOR),void 0!==t.iridescenceThicknessRange&&(n.iridescenceThicknessRange=t.iridescenceThicknessRange),void 0!==t.transmission&&(n.transmission=t.transmission),void 0!==t.thickness&&(n.thickness=t.thickness),void 0!==t.attenuationDistance&&(n.attenuationDistance=t.attenuationDistance),void 0!==t.attenuationColor&&void 0!==n.attenuationColor&&n.attenuationColor.setHex(t.attenuationColor),void 0!==t.fog&&(n.fog=t.fog),void 0!==t.flatShading&&(n.flatShading=t.flatShading),void 0!==t.blending&&(n.blending=t.blending),void 0!==t.combine&&(n.combine=t.combine),void 0!==t.side&&(n.side=t.side),void 0!==t.shadowSide&&(n.shadowSide=t.shadowSide),void 0!==t.opacity&&(n.opacity=t.opacity),void 0!==t.transparent&&(n.transparent=t.transparent),void 0!==t.alphaTest&&(n.alphaTest=t.alphaTest),void 0!==t.depthTest&&(n.depthTest=t.depthTest),void 0!==t.depthWrite&&(n.depthWrite=t.depthWrite),void 0!==t.colorWrite&&(n.colorWrite=t.colorWrite),void 0!==t.stencilWrite&&(n.stencilWrite=t.stencilWrite),void 0!==t.stencilWriteMask&&(n.stencilWriteMask=t.stencilWriteMask),void 0!==t.stencilFunc&&(n.stencilFunc=t.stencilFunc),void 0!==t.stencilRef&&(n.stencilRef=t.stencilRef),void 0!==t.stencilFuncMask&&(n.stencilFuncMask=t.stencilFuncMask),void 0!==t.stencilFail&&(n.stencilFail=t.stencilFail),void 0!==t.stencilZFail&&(n.stencilZFail=t.stencilZFail),void 0!==t.stencilZPass&&(n.stencilZPass=t.stencilZPass),void 0!==t.wireframe&&(n.wireframe=t.wireframe),void 0!==t.wireframeLinewidth&&(n.wireframeLinewidth=t.wireframeLinewidth),void 0!==t.wireframeLinecap&&(n.wireframeLinecap=t.wireframeLinecap),void 0!==t.wireframeLinejoin&&(n.wireframeLinejoin=t.wireframeLinejoin),void 0!==t.rotation&&(n.rotation=t.rotation),1!==t.linewidth&&(n.linewidth=t.linewidth),void 0!==t.dashSize&&(n.dashSize=t.dashSize),void 0!==t.gapSize&&(n.gapSize=t.gapSize),void 0!==t.scale&&(n.scale=t.scale),void 0!==t.polygonOffset&&(n.polygonOffset=t.polygonOffset),void 0!==t.polygonOffsetFactor&&(n.polygonOffsetFactor=t.polygonOffsetFactor),void 0!==t.polygonOffsetUnits&&(n.polygonOffsetUnits=t.polygonOffsetUnits),void 0!==t.dithering&&(n.dithering=t.dithering),void 0!==t.alphaToCoverage&&(n.alphaToCoverage=t.alphaToCoverage),void 0!==t.premultipliedAlpha&&(n.premultipliedAlpha=t.premultipliedAlpha),void 0!==t.visible&&(n.visible=t.visible),void 0!==t.toneMapped&&(n.toneMapped=t.toneMapped),void 0!==t.userData&&(n.userData=t.userData),void 0!==t.vertexColors&&("number"==typeof t.vertexColors?n.vertexColors=t.vertexColors>0:n.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(n.uniforms[e]={},r.type){case"t":n.uniforms[e].value=i(r.value);break;case"c":n.uniforms[e].value=(new qt).setHex(r.value);break;case"v2":n.uniforms[e].value=(new Lt).fromArray(r.value);break;case"v3":n.uniforms[e].value=(new re).fromArray(r.value);break;case"v4":n.uniforms[e].value=(new Qt).fromArray(r.value);break;case"m3":n.uniforms[e].value=(new Rt).fromArray(r.value);break;case"m4":n.uniforms[e].value=(new Ne).fromArray(r.value);break;default:n.uniforms[e].value=r.value}}if(void 0!==t.defines&&(n.defines=t.defines),void 0!==t.vertexShader&&(n.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(n.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(n.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)n.extensions[e]=t.extensions[e];if(void 0!==t.size&&(n.size=t.size),void 0!==t.sizeAttenuation&&(n.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(n.map=i(t.map)),void 0!==t.matcap&&(n.matcap=i(t.matcap)),void 0!==t.alphaMap&&(n.alphaMap=i(t.alphaMap)),void 0!==t.bumpMap&&(n.bumpMap=i(t.bumpMap)),void 0!==t.bumpScale&&(n.bumpScale=t.bumpScale),void 0!==t.normalMap&&(n.normalMap=i(t.normalMap)),void 0!==t.normalMapType&&(n.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),n.normalScale=(new Lt).fromArray(e)}return void 0!==t.displacementMap&&(n.displacementMap=i(t.displacementMap)),void 0!==t.displacementScale&&(n.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(n.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(n.roughnessMap=i(t.roughnessMap)),void 0!==t.metalnessMap&&(n.metalnessMap=i(t.metalnessMap)),void 0!==t.emissiveMap&&(n.emissiveMap=i(t.emissiveMap)),void 0!==t.emissiveIntensity&&(n.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(n.specularMap=i(t.specularMap)),void 0!==t.specularIntensityMap&&(n.specularIntensityMap=i(t.specularIntensityMap)),void 0!==t.specularColorMap&&(n.specularColorMap=i(t.specularColorMap)),void 0!==t.envMap&&(n.envMap=i(t.envMap)),void 0!==t.envMapIntensity&&(n.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(n.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(n.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(n.lightMap=i(t.lightMap)),void 0!==t.lightMapIntensity&&(n.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(n.aoMap=i(t.aoMap)),void 0!==t.aoMapIntensity&&(n.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(n.gradientMap=i(t.gradientMap)),void 0!==t.clearcoatMap&&(n.clearcoatMap=i(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(n.clearcoatRoughnessMap=i(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(n.clearcoatNormalMap=i(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(n.clearcoatNormalScale=(new Lt).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(n.iridescenceMap=i(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(n.iridescenceThicknessMap=i(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(n.transmissionMap=i(t.transmissionMap)),void 0!==t.thicknessMap&&(n.thicknessMap=i(t.thicknessMap)),void 0!==t.sheenColorMap&&(n.sheenColorMap=i(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(n.sheenRoughnessMap=i(t.sheenRoughnessMap)),n}setTextures(t){return this.textures=t,this}static createMaterialFromType(t){return new{ShadowMaterial:El,SpriteMaterial:ea,RawShaderMaterial:Cl,ShaderMaterial:tn,PointsMaterial:Za,MeshPhysicalMaterial:Rl,MeshStandardMaterial:Ll,MeshPhongMaterial:Pl,MeshToonMaterial:Il,MeshNormalMaterial:Dl,MeshLambertMaterial:Nl,MeshDepthMaterial:Ds,MeshDistanceMaterial:Ns,MeshBasicMaterial:_i,MeshMatcapMaterial:Ol,LineDashedMaterial:zl,LineBasicMaterial:Ba,Material:xi}[t]}}class Ic{static decodeText(t){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let i=0,n=t.length;i0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(i,n,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(i[t]!==i[t+e]){a.setValue(i,n);break}}saveOriginalState(){const t=this.binding,e=this.buffer,i=this.valueSize,n=i*this._origIndex;t.getValue(e,n);for(let t=i,r=n;t!==r;++t)e[t]=e[n+t%i];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let i=t;i=.5)for(let n=0;n!==r;++n)t[e+n]=t[i+n]}_slerp(t,e,i,n){ne.slerpFlat(t,e,t,e,t,i,n)}_slerpAdditive(t,e,i,n,r){const s=this._workIndex*r;ne.multiplyQuaternionsFlat(t,s,t,e,t,i),ne.slerpFlat(t,e,t,e,t,s,n)}_lerp(t,e,i,n,r){const s=1-n;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*s+t[i+a]*n}}_lerpAdditive(t,e,i,n,r){for(let s=0;s!==r;++s){const r=e+s;t[r]=t[r]+t[i+s]*n}}}const eh="\\[\\]\\.:\\/",ih=new RegExp("["+eh+"]","g"),nh="[^"+eh+"]",rh="[^"+eh.replace("\\.","")+"]",sh=new RegExp("^"+/((?:WC+[\/:])*)/.source.replace("WC",nh)+/(WCOD+)?/.source.replace("WCOD",rh)+/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",nh)+/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",nh)+"$"),ah=["material","materials","bones","map"];class oh{constructor(t,e,i){this.path=e,this.parsedPath=i||oh.parseTrackName(e),this.node=oh.findNode(t,this.parsedPath.nodeName)||t,this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,i){return t&&t.isAnimationObjectGroup?new oh.Composite(t,e,i):new oh(t,e,i)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(ih,"")}static parseTrackName(t){const e=sh.exec(t);if(null===e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const i={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},n=i.nodeName&&i.nodeName.lastIndexOf(".");if(void 0!==n&&-1!==n){const t=i.nodeName.substring(n+1);-1!==ah.indexOf(t)&&(i.nodeName=i.nodeName.substring(0,n),i.objectName=t)}if(null===i.propertyName||0===i.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return i}static findNode(t,e){if(void 0===e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const i=t.skeleton.getBoneByName(e);if(void 0!==i)return i}if(t.children){const i=function(t){for(let n=0;n0){const t=this._interpolants,e=this._propertyBindings;if(this.blendMode===st)for(let i=0,n=t.length;i!==n;++i)t[i].evaluate(s),e[i].accumulateAdditive(a);else for(let i=0,r=t.length;i!==r;++i)t[i].evaluate(s),e[i].accumulate(n,a)}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const i=this._weightInterpolant;if(null!==i){const n=i.evaluate(t)[0];e*=n,t>i.parameterPositions[1]&&(this.stopFading(),0===n&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const i=this._timeScaleInterpolant;if(null!==i){e*=i.evaluate(t)[0],t>i.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,i=this.loop;let n=this.time+t,r=this._loopCount;const s=2202===i;if(0===t)return-1===r?n:s&&1==(1&r)?e-n:n;if(2200===i){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(n>=e)n=e;else{if(!(n<0)){this.time=n;break t}n=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=n,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,s)):this._setEndings(0===this.repetitions,!0,s)),n>=e||n<0){const i=Math.floor(n/e);n-=e*i,r+=Math.abs(i);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,n=t>0?e:0,this.time=n,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,s)}else this._setEndings(!1,!1,s);this._loopCount=r,this.time=n,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:i})}}else this.time=n;if(s&&1==(1&r))return e-n}return n}_setEndings(t,e,i){const n=this._interpolantSettings;i?(n.endingStart=it,n.endingEnd=it):(n.endingStart=t?this.zeroSlopeAtStart?it:et:nt,n.endingEnd=e?this.zeroSlopeAtEnd?it:et:nt)}_scheduleFading(t,e,i){const n=this._mixer,r=n.time;let s=this._weightInterpolant;null===s&&(s=n._lendControlInterpolant(),this._weightInterpolant=s);const a=s.parameterPositions,o=s.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=i,this}}const ch=new Float32Array(1);class hh{constructor(t){this.value=t}clone(){return new hh(void 0===this.value.clone?this.value:this.value.clone())}}let uh=0;function dh(t,e){return t.distance-e.distance}function ph(t,e,i,n){if(t.layers.test(e.layers)&&t.raycast(e,i),!0===n){const n=t.children;for(let t=0,r=n.length;t>-e-14,n[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(n[t]=e+15<<10,n[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(n[t]=31744,n[256|t]=64512,r[t]=24,r[256|t]=24):(n[t]=31744,n[256|t]=64512,r[t]=13,r[256|t]=13)}const s=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,i=0;for(;0==(8388608&e);)e<<=1,i-=8388608;e&=-8388609,i+=947912704,s[t]=e|i}for(let t=1024;t<2048;++t)s[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:i,baseTable:n,shiftTable:r,mantissaTable:s,exponentTable:a,offsetTable:o}}var Uh=Object.freeze({__proto__:null,toHalfFloat:function(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=yt(t,-65504,65504),Oh.floatView[0]=t;const e=Oh.uint32View[0],i=e>>23&511;return Oh.baseTable[i]+((8388607&e)>>Oh.shiftTable[i])},fromHalfFloat:function(t){const e=t>>10;return Oh.uint32View[0]=Oh.mantissaTable[Oh.offsetTable[e]+(1023&t)]+Oh.exponentTable[e],Oh.floatView[0]}});"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("register",{detail:{revision:e}})),"undefined"!=typeof window&&(window.__THREE__?console.warn("WARNING: Multiple instances of Three.js being imported."):window.__THREE__=e),t.ACESFilmicToneMapping=4,t.AddEquation=i,t.AddOperation=2,t.AdditiveAnimationBlendMode=st,t.AdditiveBlending=2,t.AlphaFormat=1021,t.AlwaysDepth=1,t.AlwaysStencilFunc=519,t.AmbientLight=Ec,t.AmbientLightProbe=class extends Rc{constructor(t,e=1){super(void 0,e),this.isAmbientLightProbe=!0;const i=(new qt).set(t);this.sh.coefficients[0].set(i.r,i.g,i.b).multiplyScalar(2*Math.sqrt(Math.PI))}},t.AnimationClip=ic,t.AnimationLoader=class extends oc{constructor(t){super(t)}load(t,e,i,n){const r=this,s=new hc(this.manager);s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,(function(i){try{e(r.parse(JSON.parse(i)))}catch(e){n?n(e):console.error(e),r.manager.itemError(t)}}),i,n)}parse(t){const e=[];for(let i=0;i=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,i=this._nActiveActions,n=this.time+=t,r=Math.sign(t),s=this._accuIndex^=1;for(let a=0;a!==i;++a){e[a]._update(n,t,r,s)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(s);return this}setTime(t){this.time=0;for(let t=0;t=r){const s=r++,c=t[s];e[c.uuid]=l,t[l]=c,e[o]=s,t[s]=a;for(let t=0,e=n;t!==e;++t){const e=i[t],n=e[s],r=e[l];e[l]=n,e[s]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,i=this._bindings,n=i.length;let r=this.nCachedObjects_,s=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,l=e[o];if(void 0!==l)if(delete e[o],l0&&(e[a.uuid]=l),t[l]=a,t.pop();for(let t=0,e=n;t!==e;++t){const e=i[t];e[l]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const i=this._bindingsIndicesByPath;let n=i[t];const r=this._bindings;if(void 0!==n)return r[n];const s=this._paths,a=this._parsedPaths,o=this._objects,l=o.length,c=this.nCachedObjects_,h=new Array(l);n=r.length,i[t]=n,s.push(t),a.push(e),r.push(h);for(let i=c,n=o.length;i!==n;++i){const n=o[i];h[i]=new oh(n,t,e)}return h}unsubscribe_(t){const e=this._bindingsIndicesByPath,i=e[t];if(void 0!==i){const n=this._paths,r=this._parsedPaths,s=this._bindings,a=s.length-1,o=s[a];e[t[a]]=i,s[i]=o,s.pop(),r[i]=r[a],r.pop(),n[i]=n[a],n.pop()}}},t.AnimationUtils=Hl,t.ArcCurve=so,t.ArrayCamera=Fs,t.ArrowHelper=class extends si{constructor(t=new re(0,0,1),e=new re(0,0,0),i=1,n=16776960,r=.2*i,s=.2*r){super(),this.type="ArrowHelper",void 0===Dh&&(Dh=new Di,Dh.setAttribute("position",new Ti([0,0,0,0,1,0],3)),Nh=new Lo(0,.5,1,5,1),Nh.translate(0,-.5,0)),this.position.copy(e),this.line=new Wa(Dh,new Ba({color:n,toneMapped:!1})),this.line.matrixAutoUpdate=!1,this.add(this.line),this.cone=new Xi(Nh,new _i({color:n,toneMapped:!1})),this.cone.matrixAutoUpdate=!1,this.add(this.cone),this.setDirection(t),this.setLength(i,r,s)}setDirection(t){if(t.y>.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{Ih.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(Ih,e)}}setLength(t,e=.2*t,i=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(i,e,i),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}dispose(){this.line.geometry.dispose(),this.line.material.dispose(),this.cone.geometry.dispose(),this.cone.material.dispose()}},t.Audio=Zc,t.AudioAnalyser=class{constructor(t,e=2048){this.analyser=t.context.createAnalyser(),this.analyser.fftSize=e,this.data=new Uint8Array(this.analyser.frequencyBinCount),t.getOutput().connect(this.analyser)}getFrequencyData(){return this.analyser.getByteFrequencyData(this.data),this.data}getAverageFrequency(){let t=0;const e=this.getFrequencyData();for(let i=0;ithis.max.x||t.ythis.max.y)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y)}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return mh.copy(t).clamp(this.min,this.max).sub(t).length()}intersect(t){return this.min.max(t.min),this.max.min(t.max),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}},t.Box3=oe,t.Box3Helper=class extends Xa{constructor(t,e=16776960){const i=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),n=new Di;n.setIndex(new bi(i,1)),n.setAttribute("position",new Ti([1,1,1,-1,1,1,-1,-1,1,1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1],3)),super(n,new Ba({color:e,toneMapped:!1})),this.box=t,this.type="Box3Helper",this.geometry.computeBoundingSphere()}updateMatrixWorld(t){const e=this.box;e.isEmpty()||(e.getCenter(this.position),e.getSize(this.scale),this.scale.multiplyScalar(.5),super.updateMatrixWorld(t))}dispose(){this.geometry.dispose(),this.material.dispose()}},t.BoxBufferGeometry=class extends Zi{constructor(t,e,i,n,r,s){console.warn("THREE.BoxBufferGeometry has been renamed to THREE.BoxGeometry."),super(t,e,i,n,r,s)}},t.BoxGeometry=Zi,t.BoxHelper=class extends Xa{constructor(t,e=16776960){const i=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),n=new Float32Array(24),r=new Di;r.setIndex(new bi(i,1)),r.setAttribute("position",new bi(n,3)),super(r,new Ba({color:e,toneMapped:!1})),this.object=t,this.type="BoxHelper",this.matrixAutoUpdate=!1,this.update()}update(t){if(void 0!==t&&console.warn("THREE.BoxHelper: .update() has no longer arguments."),void 0!==this.object&&Ph.setFromObject(this.object),Ph.isEmpty())return;const e=Ph.min,i=Ph.max,n=this.geometry.attributes.position,r=n.array;r[0]=i.x,r[1]=i.y,r[2]=i.z,r[3]=e.x,r[4]=i.y,r[5]=i.z,r[6]=e.x,r[7]=e.y,r[8]=i.z,r[9]=i.x,r[10]=e.y,r[11]=i.z,r[12]=i.x,r[13]=i.y,r[14]=e.z,r[15]=e.x,r[16]=i.y,r[17]=e.z,r[18]=e.x,r[19]=e.y,r[20]=e.z,r[21]=i.x,r[22]=e.y,r[23]=e.z,n.needsUpdate=!0,this.geometry.computeBoundingSphere()}setFromObject(t){return this.object=t,this.update(),this}copy(t,e){return super.copy(t,e),this.object=t.object,this}dispose(){this.geometry.dispose(),this.material.dispose()}},t.BufferAttribute=bi,t.BufferGeometry=Di,t.BufferGeometryLoader=Nc,t.ByteType=1010,t.Cache=rc,t.Camera=en,t.CameraHelper=class extends Xa{constructor(t){const e=new Di,i=new Ba({color:16777215,vertexColors:!0,toneMapped:!1}),n=[],r=[],s={};function a(t,e){o(t),o(e)}function o(t){n.push(0,0,0),r.push(0,0,0),void 0===s[t]&&(s[t]=[]),s[t].push(n.length/3-1)}a("n1","n2"),a("n2","n4"),a("n4","n3"),a("n3","n1"),a("f1","f2"),a("f2","f4"),a("f4","f3"),a("f3","f1"),a("n1","f1"),a("n2","f2"),a("n3","f3"),a("n4","f4"),a("p","n1"),a("p","n2"),a("p","n3"),a("p","n4"),a("u1","u2"),a("u2","u3"),a("u3","u1"),a("c","t"),a("p","c"),a("cn1","cn2"),a("cn3","cn4"),a("cf1","cf2"),a("cf3","cf4"),e.setAttribute("position",new Ti(n,3)),e.setAttribute("color",new Ti(r,3)),super(e,i),this.type="CameraHelper",this.camera=t,this.camera.updateProjectionMatrix&&this.camera.updateProjectionMatrix(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.pointMap=s,this.update();const l=new qt(16755200),c=new qt(16711680),h=new qt(43775),u=new qt(16777215),d=new qt(3355443);this.setColors(l,c,h,u,d)}setColors(t,e,i,n,r){const s=this.geometry.getAttribute("color");s.setXYZ(0,t.r,t.g,t.b),s.setXYZ(1,t.r,t.g,t.b),s.setXYZ(2,t.r,t.g,t.b),s.setXYZ(3,t.r,t.g,t.b),s.setXYZ(4,t.r,t.g,t.b),s.setXYZ(5,t.r,t.g,t.b),s.setXYZ(6,t.r,t.g,t.b),s.setXYZ(7,t.r,t.g,t.b),s.setXYZ(8,t.r,t.g,t.b),s.setXYZ(9,t.r,t.g,t.b),s.setXYZ(10,t.r,t.g,t.b),s.setXYZ(11,t.r,t.g,t.b),s.setXYZ(12,t.r,t.g,t.b),s.setXYZ(13,t.r,t.g,t.b),s.setXYZ(14,t.r,t.g,t.b),s.setXYZ(15,t.r,t.g,t.b),s.setXYZ(16,t.r,t.g,t.b),s.setXYZ(17,t.r,t.g,t.b),s.setXYZ(18,t.r,t.g,t.b),s.setXYZ(19,t.r,t.g,t.b),s.setXYZ(20,t.r,t.g,t.b),s.setXYZ(21,t.r,t.g,t.b),s.setXYZ(22,t.r,t.g,t.b),s.setXYZ(23,t.r,t.g,t.b),s.setXYZ(24,e.r,e.g,e.b),s.setXYZ(25,e.r,e.g,e.b),s.setXYZ(26,e.r,e.g,e.b),s.setXYZ(27,e.r,e.g,e.b),s.setXYZ(28,e.r,e.g,e.b),s.setXYZ(29,e.r,e.g,e.b),s.setXYZ(30,e.r,e.g,e.b),s.setXYZ(31,e.r,e.g,e.b),s.setXYZ(32,i.r,i.g,i.b),s.setXYZ(33,i.r,i.g,i.b),s.setXYZ(34,i.r,i.g,i.b),s.setXYZ(35,i.r,i.g,i.b),s.setXYZ(36,i.r,i.g,i.b),s.setXYZ(37,i.r,i.g,i.b),s.setXYZ(38,n.r,n.g,n.b),s.setXYZ(39,n.r,n.g,n.b),s.setXYZ(40,r.r,r.g,r.b),s.setXYZ(41,r.r,r.g,r.b),s.setXYZ(42,r.r,r.g,r.b),s.setXYZ(43,r.r,r.g,r.b),s.setXYZ(44,r.r,r.g,r.b),s.setXYZ(45,r.r,r.g,r.b),s.setXYZ(46,r.r,r.g,r.b),s.setXYZ(47,r.r,r.g,r.b),s.setXYZ(48,r.r,r.g,r.b),s.setXYZ(49,r.r,r.g,r.b),s.needsUpdate=!0}update(){const t=this.geometry,e=this.pointMap;Lh.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse),Rh("c",e,t,Lh,0,0,-1),Rh("t",e,t,Lh,0,0,1),Rh("n1",e,t,Lh,-1,-1,-1),Rh("n2",e,t,Lh,1,-1,-1),Rh("n3",e,t,Lh,-1,1,-1),Rh("n4",e,t,Lh,1,1,-1),Rh("f1",e,t,Lh,-1,-1,1),Rh("f2",e,t,Lh,1,-1,1),Rh("f3",e,t,Lh,-1,1,1),Rh("f4",e,t,Lh,1,1,1),Rh("u1",e,t,Lh,.7,1.1,-1),Rh("u2",e,t,Lh,-.7,1.1,-1),Rh("u3",e,t,Lh,0,2,-1),Rh("cf1",e,t,Lh,-1,0,1),Rh("cf2",e,t,Lh,1,0,1),Rh("cf3",e,t,Lh,0,-1,1),Rh("cf4",e,t,Lh,0,1,1),Rh("cn1",e,t,Lh,-1,0,-1),Rh("cn2",e,t,Lh,1,0,-1),Rh("cn3",e,t,Lh,0,-1,-1),Rh("cn4",e,t,Lh,0,1,-1),t.getAttribute("position").needsUpdate=!0}dispose(){this.geometry.dispose(),this.material.dispose()}},t.CanvasTexture=class extends $t{constructor(t,e,i,n,r,s,a,o,l){super(t,e,i,n,r,s,a,o,l),this.isCanvasTexture=!0,this.needsUpdate=!0}},t.CapsuleBufferGeometry=class extends Eo{constructor(t,e,i,n){console.warn("THREE.CapsuleBufferGeometry has been renamed to THREE.CapsuleGeometry."),super(t,e,i,n)}},t.CapsuleGeometry=Eo,t.CatmullRomCurve3=uo,t.CineonToneMapping=3,t.CircleBufferGeometry=class extends Co{constructor(t,e,i,n){console.warn("THREE.CircleBufferGeometry has been renamed to THREE.CircleGeometry."),super(t,e,i,n)}},t.CircleGeometry=Co,t.ClampToEdgeWrapping=h,t.Clock=Hc,t.Color=qt,t.ColorKeyframeTrack=Jl,t.ColorManagement=Ft,t.CompressedArrayTexture=class extends io{constructor(t,e,i,n,r,s){super(t,e,i,r,s),this.isCompressedArrayTexture=!0,this.image.depth=n,this.wrapR=h}},t.CompressedTexture=io,t.CompressedTextureLoader=class extends oc{constructor(t){super(t)}load(t,e,i,n){const r=this,s=[],a=new io,o=new hc(this.manager);o.setPath(this.path),o.setResponseType("arraybuffer"),o.setRequestHeader(this.requestHeader),o.setWithCredentials(r.withCredentials);let l=0;function c(c){o.load(t[c],(function(t){const i=r.parse(t,!0);s[c]={width:i.width,height:i.height,format:i.format,mipmaps:i.mipmaps},l+=1,6===l&&(1===i.mipmapCount&&(a.minFilter=f),a.image=s,a.format=i.format,a.needsUpdate=!0,e&&e(a))}),i,n)}if(Array.isArray(t))for(let e=0,i=t.length;e0){const i=new sc(e);r=new uc(i),r.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e0){n=new uc(this.manager),n.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e1)for(let i=0;iNumber.EPSILON){if(l<0&&(i=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===i.y){if(t.x===i.x)return!0}else{const e=l*(t.x-i.x)-o*(t.y-i.y);if(0===e)return!0;if(e<0)continue;n=!n}}else{if(t.y!==i.y)continue;if(a.x<=t.x&&t.x<=i.x||i.x<=t.x&&t.x<=a.x)return!0}}return n}const i=hl.isClockWise,n=this.subPaths;if(0===n.length)return[];let r,s,a;const o=[];if(1===n.length)return s=n[0],a=new Bo,a.curves=s.curves,o.push(a),o;let l=!i(n[0].getPoints());l=t?!l:l;const c=[],h=[];let u,d,p=[],m=0;h[m]=void 0,p[m]=[];for(let e=0,a=n.length;e1){let t=!1,i=0;for(let t=0,e=h.length;t0&&!1===t&&(p=c)}for(let t=0,e=h.length;t=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}},t.WebGL1Renderer=Ys,t.WebGL3DRenderTarget=class extends te{constructor(t=1,e=1,i=1){super(t,e),this.isWebGL3DRenderTarget=!0,this.depth=i,this.texture=new ie(null,t,e,i),this.texture.isRenderTargetTexture=!0}},t.WebGLArrayRenderTarget=class extends te{constructor(t=1,e=1,i=1){super(t,e),this.isWebGLArrayRenderTarget=!0,this.depth=i,this.texture=new ee(null,t,e,i),this.texture.isRenderTargetTexture=!0}},t.WebGLCubeRenderTarget=on,t.WebGLMultipleRenderTargets=class extends te{constructor(t=1,e=1,i=1,n={}){super(t,e,n),this.isWebGLMultipleRenderTargets=!0;const r=this.texture;this.texture=[];for(let t=0;tReset on {{ object.get_display_name() }}

    +

    Self score: {{ object.galaxy_user.mass }}

    + + + + + + + + + + + {% for lane in lanes %} + + + + + + + + + + {% endfor %} +
    CitizenScoreDistanceFamilyPicturesClubs
    Locate{{ lane.other_star_name }}{{ lane.other_star_mass }}{{ lane.distance }}{{ lane.family }}{{ lane.pictures }}{{ lane.clubs }}
    + +
    +{% else %} +

    This citizen has not yet joined the galaxy

    +{% endif %} + +{% endblock %} + +{% block script %} + {{ super() }} + + + + + + + +{% endblock %} + + diff --git a/galaxy/tests.py b/galaxy/tests.py new file mode 100644 index 00000000..06995640 --- /dev/null +++ b/galaxy/tests.py @@ -0,0 +1,129 @@ +# -*- coding:utf-8 -* +# +# Copyright 2023 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.test import TestCase +from django.core.management import call_command + +from core.models import User +from galaxy.models import Galaxy + + +class GalaxyTest(TestCase): + def setUp(self): + call_command("populate") + self.root = User.objects.get(username="root") + self.skia = User.objects.get(username="skia") + self.sli = User.objects.get(username="sli") + self.krophil = User.objects.get(username="krophil") + self.richard = User.objects.get(username="rbatsbak") + self.subscriber = User.objects.get(username="subscriber") + self.public = User.objects.get(username="public") + self.com = User.objects.get(username="comunity") + + def test_user_self_score(self): + with self.assertNumQueries(8): + self.assertEqual(Galaxy.compute_user_score(self.root), 9) + self.assertEqual(Galaxy.compute_user_score(self.skia), 10) + self.assertEqual(Galaxy.compute_user_score(self.sli), 8) + self.assertEqual(Galaxy.compute_user_score(self.krophil), 2) + self.assertEqual(Galaxy.compute_user_score(self.richard), 10) + self.assertEqual(Galaxy.compute_user_score(self.subscriber), 8) + self.assertEqual(Galaxy.compute_user_score(self.public), 8) + self.assertEqual(Galaxy.compute_user_score(self.com), 1) + + def test_users_score(self): + expected_scores = { + "krophil": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "rbatsbak": {"clubs": 100, "family": 0, "pictures": 0, "score": 100}, + "subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + }, + "public": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0} + }, + "rbatsbak": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "public": {"clubs": 0, "family": 366, "pictures": 0, "score": 366}, + "subscriber": {"clubs": 0, "family": 366, "pictures": 0, "score": 366}, + }, + "root": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "krophil": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "rbatsbak": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "skia": {"clubs": 0, "family": 732, "pictures": 0, "score": 732}, + "sli": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + }, + "skia": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "krophil": {"clubs": 114, "family": 0, "pictures": 2, "score": 116}, + "public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "rbatsbak": {"clubs": 100, "family": 0, "pictures": 0, "score": 100}, + "sli": {"clubs": 0, "family": 366, "pictures": 4, "score": 370}, + "subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + }, + "sli": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "krophil": {"clubs": 17, "family": 0, "pictures": 2, "score": 19}, + "public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "rbatsbak": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "subscriber": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + }, + "subscriber": { + "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + "public": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, + }, + } + computed_scores = {} + users = [ + self.root, + self.skia, + self.sli, + self.krophil, + self.richard, + self.subscriber, + self.public, + self.com, + ] + + with self.assertNumQueries(100): + while len(users) > 0: + user1 = users.pop(0) + for user2 in users: + score, family, pictures, clubs = Galaxy.compute_users_score( + user1, user2 + ) + u1 = computed_scores.get(user1.username, {}) + u1[user2.username] = { + "score": score, + "family": family, + "pictures": pictures, + "clubs": clubs, + } + computed_scores[user1.username] = u1 + + self.maxDiff = None # Yes, we want to see the diff if any + self.assertDictEqual(expected_scores, computed_scores) diff --git a/galaxy/urls.py b/galaxy/urls.py new file mode 100644 index 00000000..fcab45bc --- /dev/null +++ b/galaxy/urls.py @@ -0,0 +1,40 @@ +# -*- coding:utf-8 -* +# +# Copyright 2023 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.urls import path + +from galaxy.views import * + +urlpatterns = [ + path( + "/", + GalaxyUserView.as_view(), + name="user", + ), + path( + "data.json", + GalaxyDataView.as_view(), + name="data", + ), +] diff --git a/galaxy/views.py b/galaxy/views.py new file mode 100644 index 00000000..1aa65bff --- /dev/null +++ b/galaxy/views.py @@ -0,0 +1,97 @@ +# -*- coding:utf-8 -* +# +# Copyright 2023 +# - Skia +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.views.generic import DetailView, View +from django.http import JsonResponse +from django.db.models import Q, Case, F, When, Value +from django.db.models.functions import Concat + +from core.views import ( + CanViewMixin, + FormerSubscriberMixin, +) +from core.models import User +from core.views import UserTabsMixin +from galaxy.models import Galaxy, GalaxyLane + + +class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView): + model = User + pk_url_kwarg = "user_id" + template_name = "galaxy/user.jinja" + current_tab = "galaxy" + + def get_queryset(self): + return super(GalaxyUserView, self).get_queryset().select_related("galaxy_user") + + def get_context_data(self, **kwargs): + kwargs = super(GalaxyUserView, self).get_context_data(**kwargs) + kwargs["lanes"] = ( + GalaxyLane.objects.filter( + Q(star1=self.object.galaxy_user) | Q(star2=self.object.galaxy_user) + ) + .order_by("distance") + .annotate( + other_star_id=Case( + When(star1=self.object.galaxy_user, then=F("star2__owner__id")), + default=F("star1__owner__id"), + ), + other_star_mass=Case( + When(star1=self.object.galaxy_user, then=F("star2__mass")), + default=F("star1__mass"), + ), + other_star_name=Case( + When( + star1=self.object.galaxy_user, + then=Case( + When( + star2__owner__nick_name=None, + then=Concat( + F("star2__owner__first_name"), + Value(" "), + F("star2__owner__last_name"), + ), + ) + ), + ), + default=Case( + When( + star1__owner__nick_name=None, + then=Concat( + F("star1__owner__first_name"), + Value(" "), + F("star1__owner__last_name"), + ), + ) + ), + ), + ) + .select_related("star1", "star2") + ) + return kwargs + + +class GalaxyDataView(FormerSubscriberMixin, View): + def get(self, request, *args, **kwargs): + return JsonResponse(Galaxy.objects.first().state) diff --git a/sith/settings.py b/sith/settings.py index 67dbac38..cd85a670 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -97,6 +97,7 @@ INSTALLED_APPS = ( "trombi", "matmat", "pedagogy", + "galaxy", ) MIDDLEWARE = ( @@ -210,6 +211,29 @@ DATABASES = { } } +# Logging +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": {"format": "%(levelname)s %(message)s"}, + }, + "handlers": { + "log_to_stdout": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + "loggers": { + "main": { + "handlers": ["log_to_stdout"], + "level": "INFO", + "propagate": True, + } + }, +} + # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ @@ -690,4 +714,8 @@ SITH_FRONT_DEP_VERSIONS = { "https://github.com/getsentry/sentry-javascript/": "4.0.6", "https://github.com/jhuckaby/webcamjs/": "1.0.0", "https://github.com/alpinejs/alpine": "3.10.5", + "https://github.com/mrdoob/three.js/": "r148", + "https://github.com/vasturiano/three-spritetext": "1.6.5", + "https://github.com/vasturiano/3d-force-graph/": "1.70.19", + "https://github.com/vasturiano/d3-force-3d": "3.0.3", } diff --git a/sith/urls.py b/sith/urls.py index d8086d05..86f59649 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -76,6 +76,7 @@ urlpatterns = [ path("api/v1/", include(("api.urls", "api"), namespace="api")), path("election/", include(("election.urls", "election"), namespace="election")), path("forum/", include(("forum.urls", "forum"), namespace="forum")), + path("galaxy/", include(("galaxy.urls", "galaxy"), namespace="galaxy")), path("trombi/", include(("trombi.urls", "trombi"), namespace="trombi")), path("matmatronch/", include(("matmat.urls", "matmat"), namespace="matmat")), path("pedagogy/", include(("pedagogy.urls", "pedagogy"), namespace="pedagogy")), From a73fe598ef8c96bb2eb53c5e181626c9442dd760 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Sat, 4 Mar 2023 15:01:08 +0100 Subject: [PATCH 24/95] repair user merging tool (#498) --- counter/models.py | 26 +++--- rootplace/tests.py | 207 ++++++++++++++++++++++++++++++++++++++++- rootplace/views.py | 159 +++++++++++++++++++------------ subscription/models.py | 5 +- 4 files changed, 321 insertions(+), 76 deletions(-) diff --git a/counter/models.py b/counter/models.py index cdc28896..ceec55ab 100644 --- a/counter/models.py +++ b/counter/models.py @@ -22,6 +22,7 @@ # # from __future__ import annotations +from django.db.models import Sum, F from typing import Tuple @@ -90,12 +91,9 @@ class Customer(models.Model): about the relation between a User (not a Customer, don't mix them) and a Product. """ - return self.user.subscriptions.last() and ( - date.today() - - self.user.subscriptions.order_by("subscription_end") - .last() - .subscription_end - ) < timedelta(days=90) + subscription = self.user.subscriptions.order_by("subscription_end").last() + time_diff = date.today() - subscription.subscription_end + return subscription is not None and time_diff < timedelta(days=90) @classmethod def get_or_create(cls, user: User) -> Tuple[Customer, bool]: @@ -151,12 +149,16 @@ class Customer(models.Model): super(Customer, self).save(*args, **kwargs) def recompute_amount(self): - self.amount = 0 - for r in self.refillings.all(): - self.amount += r.amount - for s in self.buyings.filter(payment_method="SITH_ACCOUNT"): - self.amount -= s.quantity * s.unit_price - self.save() + refillings = self.refillings.aggregate(sum=Sum(F("amount")))["sum"] + self.amount = refillings if refillings is not None else 0 + purchases = ( + self.buyings.filter(payment_method="SITH_ACCOUNT") + .annotate(amount=F("quantity") * F("unit_price")) + .aggregate(sum=Sum(F("amount"))) + )["sum"] + if purchases is not None: + self.amount -= purchases + self.save() def get_absolute_url(self): return reverse("core:user_account", kwargs={"user_id": self.user.pk}) diff --git a/rootplace/tests.py b/rootplace/tests.py index b8fbbe1e..19ee86e4 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -21,7 +21,212 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +from datetime import date, timedelta +from django.core.management import call_command from django.test import TestCase +from django.urls import reverse -# Create your tests here. +from club.models import Club +from core.models import User, RealGroup +from counter.models import Customer, Product, Selling, Counter, Refilling +from subscription.models import Subscription + + +class MergeUserTest(TestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + call_command("populate") + cls.ae = Club.objects.get(unix_name="ae") + cls.eboutic = Counter.objects.get(name="Eboutic") + cls.barbar = Product.objects.get(code="BARB") + cls.barbar.selling_price = 2 + cls.barbar.save() + cls.root = User.objects.get(username="root") + + def setUp(self) -> None: + super().setUp() + self.to_keep = User(username="to_keep", password="plop", email="u.1@utbm.fr") + self.to_delete = User(username="to_del", password="plop", email="u.2@utbm.fr") + self.to_keep.save() + self.to_delete.save() + self.client.login(username="root", password="plop") + + def test_simple(self): + self.to_delete.first_name = "Biggus" + self.to_keep.last_name = "Dickus" + self.to_keep.nick_name = "B'ian" + self.to_keep.address = "Jerusalem" + self.to_delete.parent_address = "Rome" + self.to_delete.address = "Rome" + subscribers = RealGroup.objects.get(name="Subscribers") + mde_admin = RealGroup.objects.get(name="MDE admin") + sas_admin = RealGroup.objects.get(name="SAS admin") + self.to_keep.groups.add(subscribers.id) + self.to_delete.groups.add(mde_admin.id) + self.to_keep.groups.add(sas_admin.id) + self.to_delete.groups.add(sas_admin.id) + self.to_delete.save() + self.to_keep.save() + data = {"user1": self.to_keep.id, "user2": self.to_delete.id} + res = self.client.post(reverse("rootplace:merge"), data) + self.assertRedirects(res, self.to_keep.get_absolute_url()) + self.assertFalse(User.objects.filter(pk=self.to_delete.pk).exists()) + self.to_keep = User.objects.get(pk=self.to_keep.pk) + # fields of to_delete should be assigned to to_keep + # if they were not set beforehand + self.assertEqual("Biggus", self.to_keep.first_name) + self.assertEqual("Dickus", self.to_keep.last_name) + self.assertEqual("B'ian", self.to_keep.nick_name) + self.assertEqual("Jerusalem", self.to_keep.address) + self.assertEqual("Rome", self.to_keep.parent_address) + self.assertEqual(3, self.to_keep.groups.count()) + groups = list(self.to_keep.groups.all()) + expected = [subscribers, mde_admin, sas_admin] + self.assertCountEqual(groups, expected) + + def test_both_subscribers_and_with_account(self): + Customer(user=self.to_keep, account_id="11000l", amount=0).save() + Customer(user=self.to_delete, account_id="12000m", amount=0).save() + Refilling( + amount=10, + operator=self.root, + customer=self.to_keep.customer, + counter=self.eboutic, + ).save() + Refilling( + amount=20, + operator=self.root, + customer=self.to_delete.customer, + counter=self.eboutic, + ).save() + Selling( + label="barbar", + counter=self.eboutic, + club=self.ae, + product=self.barbar, + customer=self.to_keep.customer, + seller=self.root, + unit_price=2, + quantity=2, + payment_method="SITH_ACCOUNT", + ).save() + Selling( + label="barbar", + counter=self.eboutic, + club=self.ae, + product=self.barbar, + customer=self.to_delete.customer, + seller=self.root, + unit_price=2, + quantity=4, + payment_method="SITH_ACCOUNT", + ).save() + today = date.today() + # both subscriptions began last month and shall end in 5 months + Subscription( + member=self.to_keep, + subscription_type="un-semestre", + payment_method="EBOUTIC", + subscription_start=today - timedelta(30), + subscription_end=today + timedelta(5 * 30), + ).save() + Subscription( + member=self.to_delete, + subscription_type="un-semestre", + payment_method="EBOUTIC", + subscription_start=today - timedelta(30), + subscription_end=today + timedelta(5 * 30), + ).save() + data = {"user1": self.to_keep.id, "user2": self.to_delete.id} + res = self.client.post(reverse("rootplace:merge"), data) + self.to_keep = User.objects.get(pk=self.to_keep.id) + self.assertRedirects(res, self.to_keep.get_absolute_url()) + # to_keep had 10€ at first and bought 2 barbar worth 2€ each + # to_delete had 20€ and bought 4 barbar + # total should be 10 - 4 + 20 - 8 = 18 + self.assertAlmostEqual(18, self.to_keep.customer.amount, delta=0.0001) + self.assertEqual(2, self.to_keep.customer.buyings.count()) + self.assertEqual(2, self.to_keep.customer.refillings.count()) + self.assertTrue(self.to_keep.is_subscribed) + # to_keep had 5 months of subscription remaining and received + # 5 more months from to_delete, so he should be subscribed for 10 months + self.assertEqual( + today + timedelta(10 * 30), + self.to_keep.subscriptions.order_by("subscription_end") + .last() + .subscription_end, + ) + + def test_godfathers(self): + users = list(User.objects.all()[:4]) + self.to_keep.godfathers.add(users[0]) + self.to_keep.godchildren.add(users[1]) + self.to_delete.godfathers.add(users[2]) + self.to_delete.godfathers.add(self.to_keep) + self.to_delete.godchildren.add(users[3]) + data = {"user1": self.to_keep.id, "user2": self.to_delete.id} + res = self.client.post(reverse("rootplace:merge"), data) + self.assertRedirects(res, self.to_keep.get_absolute_url()) + self.to_keep = User.objects.get(pk=self.to_keep.id) + self.assertCountEqual(list(self.to_keep.godfathers.all()), [users[0], users[2]]) + self.assertCountEqual( + list(self.to_keep.godchildren.all()), [users[1], users[3]] + ) + + def test_keep_has_no_account(self): + Customer(user=self.to_delete, account_id="12000m", amount=0).save() + Refilling( + amount=20, + operator=self.root, + customer=self.to_delete.customer, + counter=self.eboutic, + ).save() + Selling( + label="barbar", + counter=self.eboutic, + club=self.ae, + product=self.barbar, + customer=self.to_delete.customer, + seller=self.root, + unit_price=2, + quantity=4, + payment_method="SITH_ACCOUNT", + ).save() + data = {"user1": self.to_keep.id, "user2": self.to_delete.id} + res = self.client.post(reverse("rootplace:merge"), data) + self.to_keep = User.objects.get(pk=self.to_keep.id) + self.assertRedirects(res, self.to_keep.get_absolute_url()) + # to_delete had 20€ and bought 4 barbar worth 2€ each + # total should be 20 - 8 = 12 + self.assertTrue(hasattr(self.to_keep, "customer")) + self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001) + + def test_delete_has_no_account(self): + Customer(user=self.to_keep, account_id="12000m", amount=0).save() + Refilling( + amount=20, + operator=self.root, + customer=self.to_keep.customer, + counter=self.eboutic, + ).save() + Selling( + label="barbar", + counter=self.eboutic, + club=self.ae, + product=self.barbar, + customer=self.to_keep.customer, + seller=self.root, + unit_price=2, + quantity=4, + payment_method="SITH_ACCOUNT", + ).save() + data = {"user1": self.to_keep.id, "user2": self.to_delete.id} + res = self.client.post(reverse("rootplace:merge"), data) + self.to_keep = User.objects.get(pk=self.to_keep.id) + self.assertRedirects(res, self.to_keep.get_absolute_url()) + # to_keep had 20€ and bought 4 barbar worth 2€ each + # total should be 20 - 8 = 12 + self.assertTrue(hasattr(self.to_keep, "customer")) + self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001) diff --git a/rootplace/views.py b/rootplace/views.py index 802262fc..fbb04e79 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -23,72 +23,114 @@ # # -from django.utils.translation import gettext as _ -from django.views.generic.edit import FormView -from django.views.generic import ListView -from django.urls import reverse +from ajax_select.fields import AutoCompleteSelectField from django import forms from django.core.exceptions import PermissionDenied +from django.urls import reverse +from django.utils import timezone +from django.utils.translation import gettext as _ +from django.views.generic import ListView +from django.views.generic.edit import FormView -from ajax_select.fields import AutoCompleteSelectField - +from core.models import User, OperationLog, SithFile from core.views import CanEditPropMixin -from core.models import User, OperationLog from counter.models import Customer - from forum.models import ForumMessageMeta -def merge_users(u1, u2): - u1.nick_name = u1.nick_name or u2.nick_name - u1.date_of_birth = u1.date_of_birth or u2.date_of_birth - u1.home = u1.home or u2.home - u1.sex = u1.sex or u2.sex - u1.pronouns = u1.pronouns or u2.pronouns - u1.tshirt_size = u1.tshirt_size or u2.tshirt_size - u1.role = u1.role or u2.role - u1.department = u1.department or u2.department - u1.dpt_option = u1.dpt_option or u2.dpt_option - u1.semester = u1.semester or u2.semester - u1.quote = u1.quote or u2.quote - u1.school = u1.school or u2.school - u1.promo = u1.promo or u2.promo - u1.forum_signature = u1.forum_signature or u2.forum_signature - u1.second_email = u1.second_email or u2.second_email - u1.phone = u1.phone or u2.phone - u1.parent_phone = u1.parent_phone or u2.parent_phone - u1.address = u1.address or u2.address - u1.parent_address = u1.parent_address or u2.parent_address +def __merge_subscriptions(u1: User, u2: User): + """ + Give all the subscriptions of the second user to first one + If some subscriptions are still active, update their end date + to increase the overall subscription time of the first user. + + Some examples : + - if u1 is not subscribed, his subscription end date become the one of u2 + - if u1 is subscribed but not u2, nothing happen + - if u1 is subscribed for, let's say, 2 remaining months and u2 is subscribed for 3 remaining months, + he shall then be subscribed for 5 months + """ + last_subscription = ( + u1.subscriptions.filter( + subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now() + ) + .order_by("subscription_end") + .last() + ) + if last_subscription is not None: + subscription_end = last_subscription.subscription_end + for subscription in u2.subscriptions.filter( + subscription_end__gte=timezone.now() + ): + subscription.subscription_start = subscription_end + if subscription.subscription_start > timezone.now().date(): + remaining = subscription.subscription_end - timezone.now().date() + else: + remaining = ( + subscription.subscription_end - subscription.subscription_start + ) + subscription_end += remaining + subscription.subscription_end = subscription_end + subscription.save() + u2.subscriptions.all().update(member=u1) + + +def __merge_pictures(u1: User, u2: User) -> None: + SithFile.objects.filter(owner=u2).update(owner=u1) + if u1.profile_pict is None and u2.profile_pict is not None: + u1.profile_pict, u2.profile_pict = u2.profile_pict, None + if u1.scrub_pict is None and u2.scrub_pict is not None: + u1.scrub_pict, u2.scrub_pict = u2.scrub_pict, None + if u1.avatar_pict is None and u2.avatar_pict is not None: + u1.avatar_pict, u2.avatar_pict = u2.avatar_pict, None + u2.save() u1.save() - for u in u2.godfathers.all(): - u1.godfathers.add(u) + + +def merge_users(u1: User, u2: User) -> User: + """ + Merge u2 into u1 + This means that u1 shall receive everything that belonged to u2 : + + - pictures + - refills of the sith account + - purchases of any item bought on the eboutic or the counters + - subscriptions + - godfathers + - godchildren + + If u1 had no account id, he shall receive the one of u2. + If u1 and u2 were both in the middle of a subscription, the remaining + durations stack + If u1 had no profile picture, he shall receive the one of u2 + """ + for field in u1._meta.fields: + if not field.is_relation and not u1.__dict__[field.name]: + u1.__dict__[field.name] = u2.__dict__[field.name] + for group in u2.groups.all(): + u1.groups.add(group.id) + for godfather in u2.godfathers.exclude(id=u1.id): + u1.godfathers.add(godfather) + for godchild in u2.godchildren.exclude(id=u1.id): + u1.godchildren.add(godchild) + __merge_subscriptions(u1, u2) + __merge_pictures(u1, u2) + u2.invoices.all().update(user=u1) + c_src = Customer.objects.filter(user=u2).first() + if c_src is not None: + c_dest, created = Customer.get_or_create(u1) + c_src.refillings.update(customer=c_dest) + c_src.buyings.update(customer=c_dest) + c_dest.recompute_amount() + if created: + # swap the account numbers, so that the user keep + # the id he is accustomed to + tmp_id = c_src.account_id + # delete beforehand in order not to have a unique constraint violation + c_src.delete() + c_dest.account_id = tmp_id u1.save() - for i in u2.invoices.all(): - for f in i._meta.local_fields: # I have sadly not found anything better :/ - if f.name == "date": - f.auto_now = False - u1.invoices.add(i) - u1.save() - s1 = User.objects.filter(id=u1.id).first() - s2 = User.objects.filter(id=u2.id).first() - for s in s2.subscriptions.all(): - s1.subscriptions.add(s) - s1.save() - c1 = Customer.objects.filter(user__id=u1.id).first() - c2 = Customer.objects.filter(user__id=u2.id).first() - if c1 and c2: - for r in c2.refillings.all(): - c1.refillings.add(r) - c1.save() - for s in c2.buyings.all(): - c1.buyings.add(s) - c1.save() - elif c2 and not c1: - c2.user = u1 - c1 = c2 - c1.save() - c1.recompute_amount() - u2.delete() + u2.delete() # everything remaining in u2 gets deleted thanks to on_delete=CASCADE return u1 @@ -128,9 +170,8 @@ class MergeUsersView(FormView): form_class = MergeForm def dispatch(self, request, *arg, **kwargs): - res = super(MergeUsersView, self).dispatch(request, *arg, **kwargs) if request.user.is_root: - return res + return super().dispatch(request, *arg, **kwargs) raise PermissionDenied def form_valid(self, form): @@ -140,7 +181,7 @@ class MergeUsersView(FormView): return super(MergeUsersView, self).form_valid(form) def get_success_url(self): - return reverse("core:user_profile", kwargs={"user_id": self.final_user.id}) + return self.final_user.get_absolute_url() class DeleteAllForumUserMessagesView(FormView): diff --git a/subscription/models.py b/subscription/models.py index 31f6a2de..8461ac6e 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -165,7 +165,4 @@ class Subscription(models.Model): return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root def is_valid_now(self): - return ( - self.subscription_start <= date.today() - and date.today() <= self.subscription_end - ) + return self.subscription_start <= date.today() <= self.subscription_end From c1e59a067659fc2289812a0dada9312e006319b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20DURR?= Date: Tue, 7 Mar 2023 21:32:37 +0100 Subject: [PATCH 25/95] Disabled galaxy feature (only visually) --- core/views/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/views/user.py b/core/views/user.py index d4ccb135..f9e616fa 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -207,7 +207,7 @@ class UserTabsMixin(TabedViewMixin): "name": _("Pictures"), }, ] - if self.request.user.was_subscribed: + if False and self.request.user.was_subscribed: tab_list.append( { "url": reverse("galaxy:user", kwargs={"user_id": user.id}), From 773808fa59447e98a7c2290261936fd10489c036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20DURR?= Date: Wed, 8 Mar 2023 12:50:52 +0100 Subject: [PATCH 26/95] Disabled Galaxy button & Removed 404 exception display --- core/templates/core/404.jinja | 4 ++-- core/views/user.py | 2 +- sith/settings.py | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/templates/core/404.jinja b/core/templates/core/404.jinja index 3846ed70..cc46a70e 100644 --- a/core/templates/core/404.jinja +++ b/core/templates/core/404.jinja @@ -4,9 +4,9 @@

    {% trans %}404, Not Found{% endtrans %}

    -

    +{% comment %}

    {{ exception }} -

    +

    {% endcomment %}
    {% endblock %} diff --git a/core/views/user.py b/core/views/user.py index f9e616fa..bcff5504 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -207,7 +207,7 @@ class UserTabsMixin(TabedViewMixin): "name": _("Pictures"), }, ] - if False and self.request.user.was_subscribed: + if settings.SITH_ENABLE_GALAXY and self.request.user.was_subscribed: tab_list.append( { "url": reverse("galaxy:user", kwargs={"user_id": user.id}), diff --git a/sith/settings.py b/sith/settings.py index 8cce1144..69ecddc4 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -291,6 +291,10 @@ SITH_URL = "my.url.git.an" SITH_NAME = "Sith website" SITH_TWITTER = "@ae_utbm" +# Enable experimental features +# Enable/Disable the galaxy button on user profile (urls stay activated) +SITH_ENABLE_GALAXY = False + # AE configuration # TODO: keep only that first setting, with the ID, and do the same for the other clubs SITH_MAIN_CLUB_ID = 1 From 9a376887accef37737198c6267a2a6f0a91987d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20DURR?= Date: Wed, 8 Mar 2023 13:08:23 +0100 Subject: [PATCH 27/95] Update 404.jinja --- core/templates/core/404.jinja | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/templates/core/404.jinja b/core/templates/core/404.jinja index cc46a70e..a777cea6 100644 --- a/core/templates/core/404.jinja +++ b/core/templates/core/404.jinja @@ -4,9 +4,6 @@

    {% trans %}404, Not Found{% endtrans %}

    -{% comment %}

    - {{ exception }} -

    {% endcomment %}
    {% endblock %} From 16150905a010181aa85e43c40ecbf135ce98fbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20DURR?= Date: Wed, 8 Mar 2023 14:11:10 +0100 Subject: [PATCH 28/95] Fixed broken test --- core/templates/core/404.jinja | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/templates/core/404.jinja b/core/templates/core/404.jinja index a777cea6..3846ed70 100644 --- a/core/templates/core/404.jinja +++ b/core/templates/core/404.jinja @@ -4,6 +4,9 @@

    {% trans %}404, Not Found{% endtrans %}

    +

    + {{ exception }} +

    {% endblock %} From db8a1ed0ab263ba8c20d476aefd0bc2f610f9c80 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Wed, 8 Mar 2023 20:10:54 +0100 Subject: [PATCH 29/95] Added eurocks links to eboutic --- eboutic/templates/eboutic/eboutic_main.jinja | 13 +++++++++++++ locale/fr/LC_MESSAGES/django.po | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index b2ff6681..74065e51 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -123,6 +123,19 @@ {% else %}

    {% trans %}There are no items available for sale{% endtrans %}

    {% endfor %} + +

    {% trans %}Partnership Eurockéenes 2023{% endtrans %}

    + {% if user.is_subscribed %} + Billetterie Weezevent + + {% else %} +
    {% trans %}You must be a contributor to access the Eurockéenes ticketing service.{% endtrans %}
    + {% endif %}

    5(o5&UaLw<2hJf+Y{`+T%`K-j)|H2{y{{rko&3E*pe~s$xqD>t? zw66qyz3`C|l~FP*TNX)*0zl}(9YoCt@{#)P#O zTtW@p3G~ox5k6YrEtP1sgxy40W25{Mjv6V*0=dZ&ke_7b=eg ztA6O*xRWTe-abwv_00QAPLjV^wNV9|FUK>FGzx)-_|fmQVO^LGWj>rq;%SZ}MF^3V z+#+gBp3Qd;BhqW@Sa!H1IR74KbFE9?-4)II3OdL-`P@)c_&&`R25*-{tasCOYVYoc z>KNrdh|WlwrM4%u=w)fV`>^5DWi`PhcqPT0diG7{Gcc^yEH$9Ef%7M?^YBm0W_LKW zx)PX2C)fzV1BPiObkVKDZC3K^ZN-fFSjdqSp67`KNdYHOjLE{D6;?1tk`@emC)eE~ zXJXg7e^;}(zW&OBePpi|?dDIH*Qu2@#G>Bo?j?Vhb37RQ^Dm&Jd%CEzd-=Q@h=`?Bid6|9e`OTR(4zNmN-?rmN$+$6cveSV~5oh2yf56#e^f zbBl9t`a*9yoeDe4V#md$dMZ!KPeTMD^KG(K!kGM2&a`T*)|PMeDJhMW-o9BecXmCm zZj`l-Y3a_hMdbcOJ}S>M7}U-@@fNzi5{G)@o9*pNEiBzW`35o+?@LG#w`V9n>Q+a; z=va;M%uX0QU)Bej3LFJWbc7-JEYHsCg6rwCGJnudcr1?$Q64KbN(cJ59<*k>(__yo znM)MGu&^3-F|na`{h(CQQJptx5@l(f5bbIWZM?slZ-YUK+^typ^}M$))umVI$R*2f z8R}>oY9nng0@&AX-GYaa9p?6kZRsdTu6faI-OQDKZM=W-$>V&vGBh{O{x^5r(dZ$a zM8Rp~aCPIy#$rW@KZKR(l~$a_&aO%rw&GL-_61YVgHyCNOyqC6$g7;#4eNbVs~j-9 zj2$p>sst3yJ}1PbW|TUmuzuX6URM$}kyLK%uHnDgc!SKKnLHXnlI)r5PwIuDSIUkm zbuV9Z^SI}}C_MDJc79=o*4$M8d^nbh{5jm~Sh{T8x`d=-FiwkYxt8)C?&OXVE7Kfb zZreFtfpAqkAm-a61af_QXU#&Pft3pH-|5I zAT%1(1g!sJ-L=fJnvnmHFD$q`L--|h?9tyF)FI<#qTJ+b_&VT4egAwSNLzhQ?WC|Y zq|e@YXBL@(!*s%_YVRwNnd~pi3w#d@AWWI^nUI0CI+SjUv3AV*DI*P~O zCHq{R_t7{C3eYVivt&kVwx{-5S(!MPdMsk@ySKwSHv97pj@SGku|h7Ubxj^A;bkW7 zmnlaK`3{E@L7(G}P@2{0q(9xWPyZSz_j_E$i!}h-z5NSNd<6DR3@Ochyz|j?NYk97 zJ^GEypj^n4GBD#!VPZU1o(J*SpKZYP`P!l~gvnPtL;sNdCNtlE=ZGe^JOjhtN+2Nu z`xX!OZgl!g!KhM$YNgT~XC)fAEdDzfdn5`t-yWM@YJtio+{;|6VXI%Ese7vPR2hc# z@MXiBb18Qk`v(Sfz9?~(Bm)^&j z<4CLRVg$T}z81RV{^M8(na0VZo=P$1tvA>dLP~+=VNBk*4^)(S){Ucfm zL1xRL9d41XUkHS-Z>++G zHun8MWi({!l}X`eyGNF@Yrr7)(GWJ_6@4pWzt@VXu5A+mIqyb||CNGnVMNo?`Z{oP zfd77hW?iXYujJbUDFp%hp@C;f*pAG;8rU`es1hCqr%k!Ht~^Scevxuh+zTzeDXiI7 z$v^qRKih^@R=;Pws5Y%i4rR4NavycOvW%N*heAHV2y25jLD=Q3U#wQ9t7ny1n4~1d z2pSJ(k@RK|&PPSZWXB<61{|?$b=utn!?L3h;Qd_X&xVUK<}}yyp0-9vsl!^llQ1Hs z#y>OdP@MhNuQcJQ-aDkOj3ICo@527JQ0*$4!QqkcrJiImNWSnQZTe4I_Nh6`;_Pkq z>-Zp=lADgk9%`1Y&us5Z5ufKljYFN*K0}1{;R`{X^ltrB$H0sjm{g#!5k9RKcN}MR z`|z=A0{`OP7)h$wnu^g6I5Q!aD*nf{q2JCtqUmNq({;xm&U)AX)zmeQh*Bp4g&}S< zAb&e7Evu1sbVjX_;;~CPXg5gbaPa{yIwNge(Iqv(m74YVYW3z+9(rQ_-Vhnp>vIIO zH@>C^7w*AE>G(Q1;ZKrc8~mfkhea&wO1eh@FC^!TeE8=^x9&LKwOR{Pdskl{9Y6GN znoqJkxDYD&JwExG=bSM#&m~zWq~r^f{qf!&=-8YLQeln#Ce_{qW`0E=TO)LCu}K*h z?6mD5upNH(p=B&|p&I_|E~6|mg;dXctJl4b*5@g`!@J=cKiw<)pTRz^QFpe!G`Y6- z*1OyC?Wy})P2)+d)UV>tFLW}zdMvh$9mA5Q+C^zf{1t0c6|v`!T)yR@K)1#%1c4=D zu-(fPtB=*^c_@~AVg{?+1sI369r$7p_DfXLuvvlbywU;Hqe*&=FWgD!d|$BD^}P}& zB9p*{NzS__@!{R`&tD7DEahL{2=}wB&zGm z-`J@UUh}9Wbnw&+%_zrps2?FWWn*bFFSL*9`n=n|e5lo=YS4JrZ@WCdrt_#=K0a%E zy6Cg_`{(d5*fd~;B9|Fe)WznYC(Z(e9C4C;`umX8Y{na#w=>=wQ`e!$g;|l!i{x$aOB4Ec{9!2`lp;4dCr}vvL0;Z%Bth;DiQoI zHPi?zUCl@Dx)#y!MMVn}r>nlrLO`0dstJ(pF@&i;^?w2EzeNgs2aszIi~qSYlm@=2 zcc9R|kYF}0f<%6-tqiNT-Hh>(y<5e&z7sUCd9QZqb?+MZCrX|0t2Z(uiH)Y4f;Zpp zQ5`y+0vQgZtd_Oaw-9;)niUrLUis)IDOx+M{r;a6Rf;nr0%mq6kO-g}7(#4{eZdS>(G7w9% zwRK~6%QI_d%QIHTHIb$zte>|L0&WVf}Nfd z+n!XyS6K22on$Ug8$Mb*MLEHWTqj>$yaRob@>}B^?*@@0^cA(|J=s{?&p?*TRWD9| zkaLnkflHJ7SARFXqk)#Hdji*LpkGJO^yrqr(d_u#)8@O~3B?L!bAgfhb{K5F_1uNa zAC}&&Ap0_^WM~a)3G;5?^|>x+SCX-bkWSeWS#SgOm zKDo5TcD|2e&!UR<-=~imSkKdXx_OY|kHGnqR{PGdX$s}8ZyV`t#*f%gBz;p65|DcO(a>lzsx1kj-niRy_!f&i*8Doe%6Wj4A=`TV?21 zFvy%I3Z-9hXh^s`7F3A`Ql`{rcFv6bhf##fw>!HqAQfjHp6>aCy;VA4hpvrl3#wr| za30mWsowXo^YD8$L)jCT3vwY#3yYJnWD?nzAFS7gcHeeG3Wz?$`?8Xpt0&&s zV9hCfEw@3-TB8dJ@2c5|BU>6yfu`}>>_er5!V-nO+`B|(^;Y<%rf=@fk0jOI+4uee z%J)!@RR&Y*q0E*}c+Wj<12f|&^$ncHP<^ar_NM-sgQLa0Y)y9>{ncHl)A-Eck;3e> zenM)zDEtg1d*^J-b%YPt&B@Uzn$k-r4b1hJ9l~Q@#Ar&YZU|XQs4jARyQnexFwl;Wf#jK4Shw?wdC33|q3RwAk$K^w0gU7i`tFF}Tc$!9N4Rq9rQ z8)(e>Vpc}qldz7SBifo?FtjV=eC3k9%}>}U(B}9<+}o#?W85rA?>D9y1o5CV!bM)c zwT0W8A5{bF8fBwiwa8i?)*R2@Y%ExC+G}_(6*A9KXu>K#J9aFVIv*ICZ@M?<6V!O5 z%-ZX&YWNhRIab0hUs*%dT^ZrVKcEQ%GbB-3sfgMNNi)q`cf@V0^!)kFQ&^#lnLHZe zV&FoknKvM!(SgL~5Y8o6_2_X43dX`DA5=1Buzut*4g0kHn=<)(Z?o@~XvbebkoS?j z-3^tCZTz~Yl;nJX^8iBbP;SvkwN{kaYoMt@StwgJ}~zdu^w@H zDEtU3SK6NaG%xmxd39<`ONrE%>MMf%-4wIqt#2S5tD-5XOP2+xUdxu|&G2sq!S6NK z&1vA2p0wuQ&g|e2opCv+PDJ-@kd;}1+H6{VadBemPq$X#P=p&}+xV3Ollf=E<N=M8b~;s}I9`f)bK z>@*bwAKtOh*;Nxmi<$3L{>=h)fj|qyjv;TLuMfQm6@7Puizsqgs8W-pR)Ta7^uv42 z64b&qj_ZuviLo5*4sY?o>ndfQ_52H9%wmZllB&2Cz59kml*VTw}Evb#=Z$yYU;}`pWnoGvUKaV_}*7)hv zJvlJ=iu|#m-~{Qq_9&g53jB7@J@O~;t22svTA0Lz3EF`Cvevh1xyVCBxfXDFFdv^}KkW*mQAvcAMW#>M)z;6JRVp zS_j@)K|+vkxG|O`6?bj@I|YYU{4S0&s3tyvNHMncK2Edz9+qM(N2kxm;2_*>Zn>o=>&gV zh;>(frD$fFTzk%JXjF6nu6K_0rdsQ=VG%bJRb~B8zah02$-LCXk#&#}t$)VA)uUV( zEN~b^dwC7}(t-eXOpzi-T}<5Sbld{-lg=^{pIR(p-(+5E&DY~LDuyGH0V~#67HIff zgP)o?to%^qlrpLhmi3Dl0%<4*G{>9BmYMDJ6|;eQV|~=1{bSClYEWHOh0@2&rthUH zBa9X$sjw}bgDycySi^+NXNfkR=yP=535ihe1D1{n`!_4nm#;%fmQ9NnU*FD3N4tGr zpipiU!%>Ia!++#c#jgZogdZhnVzr)s1(L9(){p8nO@Vy=;|yf8<;w(S2%Tc&Lo&)# z6Bgr|aM#JpJ#GC(yw$CwDDU@_(l`6a2S33yKOf;5MpDZ+)*dh_|FGP;mC;1CwSYeV z1^C?Pvlhy^)vQBsZ$gq;0FZNRHrgg{*9;X12S@49duTTCLLl0RR8 zujfE>WnRbAm9vBO*8ynEthrnZuWVfzN3g_o^}ao|v#N%y6~_+ixM6e#u2*^a{KQFF zD^$Je*Z`rQG}sghEP2G06Okw3h>gfA zgrJ5b!9$sW_lH4Gf}Qv7MlU1>2O-tJ6wNxsG2Le$PlF{d2TO>@Qypt!b?79QOqGQXxViwjo+WE_D)nRtb!hRX-+ zRU>>Cr(2haQt7Xg4H7T3L1-p4J0p`&efp5NgTDaneQ3P1VDS{ zerafrpW8pNqN9K189#6K4N(TKpFBJ$^gB^8D_lSIX?R`tz`oj1GzEXA)~?S&BlIe7h)TkVlXyZop;L#b}Sw8^sf4gbOe z8KR5a4Y_}#`5z6`(CD{7+4l@FLbf-<789}N)DhQC7n)<9HoIV=S|!9_$&aHOj?4o4 z5yH1eAR9wpZP%C*TWXc)lF7t5#dS+A$7S5^CzYLHT<_ryv)Ye1_cMBvme^as!v;mH zB6jGzO5-upzJi=vbt7CK*-jo73(Yg2`uyIL=(N!p=kbqM4Yv`i9MA_z=*KtsGn;x2 zg?jHYQbdDD`J;<>_>Y-aGsR~4SqR(jD^RUR41}(Ig!**xUAivU zWVn-e(D@BR>viw+4MbcL*j|m%8WZjEFkKl~+CjZf1W)Q<~`p<6s za@+-om;4k+i0W-w=PW{FaL0`~Wzh#3#8w{-j&<3o2(+{U2pi`cB;MaA1|7|K)>W$D zk4R?~2*^mK95!cFvH5)O*u#CVG)OR_%qJ*kiTPDS1C3TNAoR`%lDorD{In=a$ z#0Bbvf&wMf`hZG`4&5uSs~2b3eu2BfnCw&QJaO&zq@%CQ4p|+ym--L+ z+M5eQLG=p=GLK&S<`+g6#J~*V$@lyVkZ%gs&PA3lJqoS=*UohJ6I^b;=*_Dt9kx8> z9SG8%{rdEkJ~_YnXT+>x{5{yAli!l|#{2V6TfrS-4SdCGeVMVh5?nC*#mmlBi-6yKDkor_%GrNp{*Rj>Z%3-{BX}Rt> zq12E2^N$cZ^~7I`;5y}h+=-a*1C9OkX<_m9&I!zBE=;}HBc^P9#fG%e2o-g{t z;Hf}e5{D6bG$?wPk_Io?3qW&V)JJo)AlBy5iPH)><4ooxqfOARSI%ADK%nE?s?z@QBN{5Sj*N-GFLiTzZch7r*+iZ z!3aZ}Te9`wGeq~#`5%q1rxj&8fg{&Rx3Q6n+esG`4ANc9omKj_5Pyk6 zXIX_N;I=hbu1T#wIf;&uYDr0JhlPi%8KCF(uLQQ9TVcnItwt82w|SZxtcvQ1T#uhwcjJGR zs%sLtV%EmpBw(tfTKia&MV?$I6~%FE4p}j^Q`~g+@ru6$Xt=_rg~1?!Czg%pOF0Vh zpBtayU15%1oPkyBy}HQftxxyeD=YtbX;<>53iIXZjXj+Ue;x&<1IH$re_pcVW`p?^ zBf57+KL`vQn&K=#{H%=kd@84%$V(VFJ8t1=&Q>G+0Gpgc*?Us)u2;lpUhHw87jb$& zPd0e3YjNB3TnbCt{HPI7DV&zj!=BpJR;pnd>2$4+Ha|@ML^0W${Wcz2xbXa?&TQ-S zPpB<_a>_KzYl6|HaT|@=h?25g;qWeXdtRfC_}-+$+7b)-xCAsEnrbmD>WLZwre2%N z+n`qQR#aAfvc}$}wS3vB*B15;D_D3=5E^ZF*L}VU_{Y|2)WFq+hwo)5)(ozul~mg0 z6Tpn|vxzf0nf&>MK)_64KBx7S%=a6af8;DP5difcI{SY?vM8tkG;|{3*BB&>qha}vR&A1 zgT2#jkPG#jQyyJ0Z0I?G%lrD#=Jg6vDQ1R#aZ^k+BlR`r|2Q@_)Wmn%X6kVgi9LVe zbOR-}q}>ze8zia|YEi3M;;SH5!-{1iD-kf3j?3jcW-L^&up%4h^=Yf_@bapE_(@$| z@B3}erAd5p@`QkjvR$ZV;BG;9I95u;XliH+n;FcUDGXHf8Vd2N(sDG`+PtY^sWF=1 zvg%_}9RU@lk{=_Y>uS^J=tFFMw3?Ue2Ys{jIw*vk^7;r3cDC*p*~W{rM7p+)n(APz z|3$t>0fS6dg*Seoc%V%sIvF$K&aF&YJ$AV1rbvs@4{}B7YtK4wTkkUuGtiMutm3WO zNq?8+e|=J0tP6)94Zia`2>!F>YIXcYiE3=Z)=H1F>@@(TlD^Z$izyp3TUl@qidIOp zy>aP!*y%xus&V(c8seUa#2a zfjO5Px|PE<*4(J;B=U+WSu2+Aayxsbtp@xRIfJxa0rVtX?IU1PXOE&Sc2VQL9q+XE(`F{|m11pV-slU~!D7nk$r z<_S_C3OD2{HgG-MD}h>1e47>xf@OvI>qsUfuUeO4aqq1|5yGcs!?cg7&yg1{^b7!r<-6Q&7A*Urs3}g)gv5uA$ynrNY zngQ@ES;|POAAG0Rcj8~fv8@)4BPiqu&^B(tkV|{9J>EhNnbCKhg^UMzZcQ7`p!uMt zb7ivRv5vJ_x!PT|Ya5cmC~}+>?r!|VrdDRuUXMi|Rv}X0?X=hw)#g)F$X@^&lu&J| zNB+a23VnC5CtRE8ZTIVPxk%P=yb=oiXrxMb8GdWA1-L*1=Lb4*Wswgd;yDH_%7htw zcv#b+yGF5_PdcI=Gw{UWlHF?A+$+_dh+!~E-g(L_<%I@tdDg5Cyq&s*{{R4#Xrn^n zlu%Iq0_a_incI9{a_xreE%t5XrJXY4SK2K1($iG(6`4wtW5$(|tXsuL!H2AGBD*88 zu~)WOL;XXRgE#zpR%es2$HZ8mW7aBpXL^P=2HNB`d>T+)Y7|-W4*>IWiC~zFud6>2eE<2aKC3X5t$(I;5%t zYz$-3848deWay4&Uhe&S{y{T>;ik?#UWt`pTJjGHly{R#t)NbLLerY$v|8>Mizk5_ z_OfKSo$ZR!ch=y|(@28XufA*(AsVeU9%)*_WFh%WpDK4ai*SE5L#fvmN(m%c1t{Mr zCU8ZvR9i6nsh&QJR$VR!uV6oWhaAcbr7B1Ak^C@8cB#dx(HZk<-ar}cqCQ=N1sg^? z>>oHcISA{#ss2GW&t_zzR^yrCLet5Mnaofm{_4p)r6R&VQ;@%2ZaDl=Rr2|>@3IwT z3o{*^*|2A(o4)w#qQb-vwA5pjluH(ZdldiLz4OGe(tBK$){0enrH!Yo0@7F#d2uhkd&vgC*Vpj`KsmA!n*$|Y+E-KbtY01; z@1AR(L4k@(Ha4M|mCwpHO)xP}Y8z`>)jEHe4su0kK5N;V;|2~g``RG)b+X?ds-<_n z6J8@*%~>uD?8g_J+gr|mIpLpc`U`j^@+q5!c_>vNRp(}j;}_=m5@f1XF99f4wEKl_ zivij8D)r%ye0lZPSzA0^dLt-DahN>U@G@?WRkBU>mpyR{+iwFtwx`}-s6a{NuzDn+ zTPwhsEN_$TcK;~Eekeq*sQ&$jg%A%uSt@s`r3hGT;OJ<*p3MHGnNTM=`P;UZ)G#Z| z`OY7%_i@@PT=)d;j=!PqO=paA4}lykK6;V$IJ467jKf3@2%Q9W&A$LaxuD@)l>v(O zHCiI7nK|-ITIY{It$|j)P3-)kEaXvhg{*`A&rU z3hZduD=n={zEU}%%E)gg$b?P?GV~!|wpyc8#%>PzW~CZ~JA)#vauCnBhMloU1EUeHh8-?`bRB}xE zBk!MM6;Nb3%SpmTvh=OqAkM=2xh9R)y_z|_^f=?J=3noT%i0+aIgq3T_Q2hkn|)7LkoNVc2=fjlD0wWSwC?k zp5AY|qFCPq#$D+Y=P$ncWJNa~@Kup#J~pnT`0hKSPFv__&7nW! z1J`Qxn?ivg>rP=?R>hKau3eW7ugxx!dsd0zNS_~^5UkhDZasB(loPfww&@nqm8gyT z0Uf&K{(7f|?00#8U{EGaVswx3ocE~och+qlnOdevBq-JvLyaa<(;*AwGJzymHpmgB{Gmg-h?y^+ zQeBMUV};@@2fGld;yK`~{IH0W#DtSq)LF!O^SrG>7<9-zycw205pKvPua=3_!!evM zihAa5OKQVrv?TuERWxwlT}$la@aofC!<$sGYtu=kWI+-A*F3ekT1QvJY(;DkL8j0k zq)EF{)6X!8SNp5h!{JZFr1Aj12^>r>bDT@x1uJL4Iy%p+QFMpNm2X8A0{e9lQ>2SY zQY5EaUb4{%!v#HXFr7hL{6OfyWCmSTBt{+PcgrkMwK|g~#xwac5aJ1A6}}oM*ElFx zqQH*dv)PnjhDA=b#F%5}Vuj9KX8c)?JFA`?&}B(k5ZF%wCd)A0}Z<-VT+?g?QDjBKU60kjDy(fQ`-+oB6eCK#6DR zC?8rqpy696JB7Zr!LlHrG(|8nhXdz00b<8x&}d!1g`#qp0TtGbd%GvKxEGq{FeZjl z;59c_)#%*V)&Pj)=RF((9AWP}|M4vx`*9Y*C_bHNv_L0VKF*(Cwp^lWvWPP`RMZL3 z2Oky$Vozwe8sdNKi?s{DVs#6V)N5d(#~zm;uHqSa4z6QI|Al$0P$30Ds=dvU&dC10e2KzsYqoHhMi2goS zzR&SpsWwEb&rC;Uh5XW90VP|QlSPxBa%9>0KQu=gr!1;?hs@HJPnj+#S?Y5DIWbU)rC0|Sjg@*kGDB#&O6{i(GJ%Naz0 zk&?)0=Lu`DGsanrf2S zIND#8=^K?>Hsd;0^4J3a7Bd=fh1=ah0xFO(MqONM3W>5=7e4>6)<~6so-zJ8v1O!C z^S{3=Z1kpTCCBGk zWM7*zEAuTxm-C&Fr>3VS#DFEu$Z{9kTAn}lY@Ri_>DnI+ySZc2W1!@XUQGlUzJn|v zaI91UMV7lwihGYx=;LiQX}9*q+q&e|4{qVe4|M4sO%|~R8cg=u`-vlZxg_|z5~!P< zufz@u^srwwm{p1)yqe`KLv^YPIUA5Le<$slnpAtgU> z-WIAU|K~Ei3TnzaQbnCkNUq=6e6oiM*X5-|8?;;`TYW>DdK zA)m&hw9%ps>bniJvGNjmeB#*C_n?px(3tVknisTn z{THX7j}o8kBZ&-yC?@u>H8$fv&aNPE`bYe%k3C~buUfD%n{dveM!hW>Sorx{AfQ4~ zpP4X@Vlgz3J(CjKcn#ridJ$U(mDy=+ilEnMdV^Y?t>E1FVd2}9^oW;lf4H1(O+{3O zEpJk}@kujafV2hUil;au{I{__OS1#~+=J*(+hHW2G1uZzCKXnuNlhv8VJ5)mOIQzG zO^gFAgq@;Z=TnORrYjfMKbcFB2!Miyf{uZMg^GcW`cLutH;|4>%!KxuUzX^Nrlltd zqkvpQdU4YgIdnj{~wc8 z9G>!DG|k=YQqt7**;8#)_@s(dm*15_4u)TmBK5)S_ZOkzPZ0Qf?zn*6M8b{& zyYBXisYz_v6!|QxpA;1{GLwQKQz}m)p5?b9VPT7YcCpKC~}oOq#M?A4i<{6qi!py10|Hs|lQ0F3B#16!#;;j$1lR zU6+tsN-y(VXZ>i#J9q8lm|Y4&XWmB6u;zRp4S%-yA~~MJ|Kf`#1K#}P{4d|t!R6pA zow~>61mgd-^&iBr`vZP_EB70ypO;rZi(JuGmp7j1p|RN6MZ>0q2iTr=wve<*Y`^vA ziZx`W$g_ZIJl29g>G{cBvn)5`!dIM_8TU_Wj;p16p?W!?dPH?Jp2M-+gf*dGE&KcI z%rjj-_GxXx&J(&w88pk54x}Ac{{pBgRQAndX80IFPijh)#M#$N{}%v(Kz_dsKA{`| z^9UDo0eHd0mppIyEqY&Ycr5)5Nt$$ge@;&d1Wyk zd)6XR=+kn9=80`GJU1eB#x1Fj1m`$jIHK2VpQ5~`>$?f^DjZfSLCQk0KFi5kJ6(b+ zopnkKA4J=0S3k4zh7wfap|@^O5r*(k39R5zb)kQU+WS>TZ**4eK3%*HVKvR5i1pl?Xj(}k?n5t{=c{>7p&m4`t4em zFv_aJ^P6iaqQ{eVx52w&W9*$HTy#@d+GmM8z!M^5UcwNzI@l{F2HX%uKX>Occq-|~ zN1luk(Nt$_Wtt!dS-vD}p;ni!KaAxYkU8L+o#Juag`CdSB)fVn5oitF=yh;cU4oKm zP{j;$Qm;rku1?V5vKGAM5;*3iIkj#Z5G{fxR2!wW7|!G+LEY*UpEPZ)+h*Rjt#aCX z6_tQ%Y{s-}J2S_5(K;?0;}E1&MJhJ$oRDzUIARcVmbtMQb(TYleVe7GBdx#x0FT5I z?XSJJL*nk^`%~Lzb94~lAnlFl`&*n#?pd-Xl_z6ztKtw1>`gni&S|*8?9CRL>Wn^AFGr8Xy%rN)Q`P$MR({i!*F_2q ze%`8SWHr}`@J))2{Cm_00*eIs#w(6G1C4U=K$&a^D6>-S>Bu1F#m7(0lCSA+L8Q)c zteEktWQds2F;pJgrv+wU-LI@vJcy*BXv&;aw^YM{_1>JruE!3<$2MCKrwle|t#lJn zu|q-Gie;N`ywX6JC#8i4iZ>*FVFW-HormaDEFnXs5A`i^+I))FJM>3xjFceOxfaDW zozZU9X?vnWjsiS_b6c;f?VAqKmT4;1XpCPw4GZsmEs6H%6d0-uEfh+~?(>!%M@O|uhc1){fiqVq9#ZR}Lx zP>9p4=KP_JNX6vKXHYW!XWRr~6Rp)2+PGIQVz1CM?c&duPLJs`X zn(9VS$78H6=HYfC>pHQb`o@aqZf zMzxCQzwGyZ2Ne!0Zi~R|U&0RR(7VYumoB}l(e=4rxoE?B$=xf14dd#KgEx65+c#Y6 zCSEpegIX>l|aC=-r*Jhvj(9!zUF7LQoz zqM``RDdvd=@l!GtXEEM}Iq+D)EjzPJwDx{T{{R!>CV*sPcWUHr-F4{oMSjk3rU;n> zr7`wJ=IMFETY8BB!nBRCy*5j;k5HU1+87PU6JOD$f=mQkZ3rhw7Bp5xGil4F1MuWa2czn zF$DJ`iY*vCc1T*zv&-PlbARpI@T;P7Z9?|1YZju#y0${|p3@F_$P*0<#F2GJbCzG5{geF{RY)GMvoBsevil-NgLPRyqE)B6|HLhHoCMlDah@}UxetSo+uV5W=0yFujjp!6t!T@%^@?+<<0S1`+IdH8Xfed0 z%n>UEip62f#Rr{;jB?nT*NT+pJUNH7E5+QSx=p%2>We7u$n#C5cqwQTiId!o;yPPC z>Gy77wcfE;TwAcmGahqbd{?mgrp$osB3tzPqT&P_-Vwigzk9eJuX=Trplw}=A2Kw$ z+us(}{t=3)cEKI{mc_}q9G+{q{gI7a6S^RF@A@dDi`y1_!IN5e{ZRzpxCFp)J_B!J zHZGy0;G1t}s*AN9Lflt!_ih`JzT=dFw|JuI+=IQ6#E*o2d{A?sW3x132GnfCU1=H7 zHPNec3IjRTn5#G<{d;lyD0#yaDZMqOdpML|8wMH#rVA^s@e2738TbWTHVS9mq$;W` z5d8@r1wj?2^&%D54VV?mSlKS1>es(Kbhq`bP4iF?wa9ZLb?b9jOwO&Ck ztxO-nv=uO88vdJ@>(*{_OhjYnl5?KtJhtZIz{y`lkCon-Sktt{!^Crr#uq5#yo+iX$~A=!JXL zr?gs&HbFSo2K>qFYnWTmI`01f*)TAfH?j*EK{?RY0!yY4v2XtX*m_R&;GJ!H&^MxP zfuCSi-wL<|T(nX&3{VSj@B1yDyMGDagfMeUPi%MKnwWDyg?x6ar4!nl$!lV+xU~2D zNbmzc!eJq^ws;|pRGeKK=bc_)L02T}DmBrZM32FQ*nR1|={mbgGCmXQ!4}X~2YNZG zDG9RXPO!-}{VAVWCfUm*#xm}YU`?PN2wwGygfrNIGl8%Jl+dC!l`u4}hlp8~pR z(e*?xe5t0lwGPF1Ia6eJDmQK&sE0YPrmT-R;;&mYR0sE_wCiE&iW{khe}oW_-qbL1 z)u1&oyN4A20B0@sty`{b%fD3XnxZe0KZQ!@#cP+(%l;9$ta@p-b|7DPqypZN+u7Qx zrB!THL83~=1PtXDVtf$o(AMuLpl~pIg#8uLH6wjUC>l|oUxen*uFYU;( zt3VnIbY}B#;k#sQs7Gm9r7{c69a#0nKJH3S=!7)Rfq$o@_E1(^|zKN!j2#HN5 z@yBjx5H_Ik2OO%cY)#<%%$kFXcqxYpf)wbi_IIoHPGsHQt*pyf=GUuxpnYVow6f8S zZPzYH)M1(tUW=93Gpe0wVC$~0;G)_)5rPl2;>E9K?T;EMj*VhSrV+6l*!QO!fulSP zB7UDl8D`cG=Xb9J$61P0W{V)6$kBM+9lMZZ7Bp(+xlebh=R4G@Tp^*sw7=1JY6Rd0 z;D(4)lR=i%c;ifh_4ixk{{U-O`ONzN01ILKeo)!=LMOf04gpgNcS<-DO@^zJ;JCTF z{)cK7y4GdBRoH$L{i)1x)z%7U;WZ8Qy#T}Ou@XKR_1wF?eNc8CsE@xoR@5;=NYe&I z4&@CR)pmOZ^>n~FW1f&{x~feuQK5jFp#K0UFr7UZZv0PXj`4yHFzPw0lx!=Ju zi<$)Vf_;!{U4&KNS1ZvS>KWY|zskIBwyrrVry7(%+}iQtiFvdXGerH^W++wTFb3ct zX}o=RebajN`Bi;t?&IX0(H*dt{IwoqBBBxM9%}YS=w4P){`h%0_PD3xzaEgEB!3Gskctw zE_HNoR8-~!N;@=WvBgp(V~pUUqYbJ_%muMs#+*}~MvYwE81Nre7%tDc$;yLf)E`1U zaZYV7lE|nza45O4n{Y>>_T`GcIMNZJSExStHz>9E=UdlAK#pNcGx)_pdtL zU)%R`vS2SQW#rS*ZQI!>LldUx4a};;HL~OQKxZ%bzKs6>hbz+%b5muE*$-bM52y^xfFIS+8cOMhu3m!P2@1`u zy;eSTpwNk=k2ExLjt$G#nmnUO=(7)mu24nHFfzGE)&OXVDk#wv*o)kKK>YbHhLn!P z)``X51Ao~Fbei=yx!orn>a?Zr2L04sV8=Bc4IFlT&};&6H9L0wf+tzj*fwq({wE+Y z>>2iK9wlw#A9W_^?fZS#ucEpuiSXQ;XgBRov2}3C$VhOD-`f8GvHcUyD4e`>4F_ti zNzgW;E4fcU)(N7bg+j!f{178qT6;aD4PaY4in!xzYSHg~cdI8qqkixFxA0ZfiP^LA zh1NRKc|KD1sf}#)RhL8q$}!EosA9R^%W+d;!?C(yO(+gW1p9@r29Pg&v8s5fEv8iR zSKZTa83v~|Yma;>$z24acJa*0!%Q>Q0a8^bGet!uL7o71epO`UU#hH}g+Ft?%KPWx zp!%NAHTG5YMU#8}-+t{KcXkaS;uEZ2aNvDU(4lD1bHU%ZXWRGYPJv3U?ITYF7aav1 z`YBPMDGM-)&z;z$bgotDB>if&h{{ZaW>42BoJEkkey#v{jy<#Z?VrrtDs0pc@F#4g$xrVTI_W*+q zJyF~1HE3*`?JpXVLw^4NWaAVdI|^co<(N4tICXaZP_nWW*3}V~_*7CfQ`fB24;5Y> zdN2xHc&`#X*0>wwqk!6c@j?$KMCSJlKX2a^1}609JuUm2;#KP9=dcg9Sg3+~zWm$m zM7)jP+&QB)TIREz5{8}%iv)L~iYVv`H=~k;P8gtY{SBHpaN3%}(Pw0A-FTNe8xP7O zte>rEdtF)kJ&pTAssm*0cigY_LTpKM+FO;_Wqdc3%R3Kv>xcfz%J0k3A2NbMg@qN% z0~C^P9gWZMztmc`O(#49ePXQ}W-M;C%)TAjyK?26HDRq^bno>VnR=?V={oEUnSIv| z)lC(Ri^)K)YGc^1Y(fmW{UdvMiWotTn>A-w4=%F0;NYoa3rvRo@|;wkt}PI{3Ya!f?P-xiRDe z>DyMa#Z$34z*#a(nLGDz3AC;fe!)XfaV6q}Q8~P&4jQCpu5zu7Y~8!oO3rQ;mp}Jm zvQc~IA%=+pRbBf}i;AJq0c#adyqvrPypcbIPBa@n`*>iQz^wi-n(m8%?b87d;#;~K zkn1~#!Z>%UCef_A)Ytn>glG0TN{u`8?p^%j`Wuni;v%n-h88D97==S~J{>No(0JX^Xs}2NZE&q>2{A4F5fxf!8ym_Z;4Ebs|~0gi@U!H=mMrT z$u_?%p(mggB`oTD~Ul3xb@?--W_U=G? zB!1NU7vEv784yBstDNVVbFn``xMiX$t!+rU$3|{ALK0&DuH&~skO1}Mn#Zv89iGP~ zyxz1!Ut2jNx? zW4+HQsdcZv)A_zvC)irQlx^RXV2%r_(Np4?7QCpFbZ(Ez{{T&EU2R*}hmy6hb?E78 z?T^Z5von=$wXLiD|T>jA$vpKS4Uy()(=sJwL)@=sG36 zFNCJrHsz+XnlAoRY{`Tg-twqzGj>}Mb~GNaKobUFsh$d`h$vcFhYpqnYcHmXFLck_ z`c-Dn;c)%PSc@DKWn##TH;!3u_d?9wq!&^=kE$nK zFK`iQvn9!b=L(wt04lv$SDPKY5V|Y1JnFPS_`ciKnmBIXMB;oaPW)BU*_a2^BmS1r%yzJ~X~wFgvkEU~ z_JhjZZ@K7RR>9Vu)-G~%aSiI_hu6VL^_q33yKSSQvYN))i%>7#_ir6Y7lWC+n3>rM z8x9_}?pU-50%Yt5E;6HOD-*Q_xv))1l+x(s9p4JfPy@zvMd9iVN7-thl-^M~DpeMv zPpntEv*FK*tn_QNY?pmMSAK$ylc4D+qPJ+>&5=qj{RPYcxZV}52Ix+6u30yfy{+(F zV}5UsZRKZo-Zs?U)inPAqV@}il5M5toUpy~-pLc#gkwK_>C-4O@w3-&!|!TSo@FS>M&uRF7S$ple{8(rAfSa7PRZecfKu znrIECSpCtxmWi(GzhzP~G76yDC8vvj8Y(y}RI{}C0=?X9bBB!me?S?7hB_}f=df^X zJoLtEe}X^n2QrRUcllA%10P#e<~aOE`@YJ+nmXzK0LatPv~S<$T$i+3Sp%xrBWyTG zTQpiR-t9f}eZ&}4yhcFXukvA0~mbg{-``|#a0tR4L{)nt%5TQ z7mg%s9<3$>G!mo1aH)DbND%v-iMrqqt)d*Qx zmuHfTHuFy2X8vja0BJ->-i{a`b&Vl0;yOchPdMr|Zh3T)hnA7mz|ytTGfdyWLh_*VYH z+m@|p59?mVd%EJKrU96aK8kd*^n*TG_9p;as_Jkx%@*;^UIGbe!1sED;y^Wp8Yw(f z*|6=eb}EzE=CuzMHNX{p@O_&=cP^?CcTPOSuS6tKZ zxy|uZMYy1I=ExyKh>hQ6Lc@#|Q`md?ha~N@qjKH;<`n3y-K%;S5C}}Qvbk_O_k9#} zCwoswBGgwc?BV@({{YR0Sxu4su3pz-&W(=C+S;k{$~}$&=5NIesKXUwZ)C%9jJNBx zje(61saoZmJx3ofs@QfWsF0{=V=_H@2sqOUdYshLK@J-ZBkC343r$u(v;IyWMNj_# zU;WpVDy(;_)^BgbWO0ceG8Owh@Cugj((%8F6AlD;`6lG24HuhuVy@b3kbB474Oe_X zd1$7P*EAlvhM$`oy`v2!9(?dXZb0GsN1K|2PX&*z?q%b?I4s2-{jdC@4Mj#s*uPhy zZ0&EA`+u23Zh)KZE_LbT+v5512$r_YH{EU8aHq=E!@V>ywvXvUn;%p3S8Z)A1{Rlp z%4?dRX4c;5&E2VvZtpd6Z7ljANo?RV?9BoJO>oxfDV?6YRodPsAf}sK6d&ba{{XU| zKcLZ^TD@AFq)M&4kT9&aY`Xx@yg_EDP@j%52gwz153JKEv*SJ)1C z9wS9D=gI+8S%EHgUrq|^V?58Shx-}bwO!w{nNv&N^jjb*-N!@IQ5Zdci;0&DTk9h~Vz9{oV<<->%2N?$%5qZwmPE+r432 zB!>F&PPBb`IG?Y2lH;_`;b*j}Pp&A`uSRzo-8I9mD;O^f`eDUa~`3r1X{NUcEG!>Vv~-!Zk=T zYU6lhor1I0WwY+M*7wm}c(k!)qwH?g_j=zezNqZ%`=Hxv7Qq_Dr0zdWCA%?Kq+!86 z%Tm~1XI9g1?)jCB%S>)fZU*zXf7|Gb&OemaHseq3J9iJd1gWj+a73ITp=Pf}?Nh;W zZY_84-TjAX-Q=3)zQx(${KR3qG(*c=>$New{#V6lM*->(4K5ERlLkJ4R5=Tt&syvE zQ5KuQ&S~1y5xi|1m7%4e7KbZL9orvX^Z;magJ>jdo5#4t-2jS-3z2j?_-;C`->~oV zLTOf-{ng*1yNRb_7SCxP!?QCug6yE+Pm<|>PyYZne<%Evi)}4$Ikt9X`kbSHameFR zKBY8?p&M}vf*MIMfTAa8X6*j}3zf8={B~vbf5|}q0Bb0P3_DSHSZ+`$#b9Oz zebfptO>?Ty>BfRewzg*_WvI@#R(?;~izH6B8O<8F8iV*n?G|qRzn)`U*mGoTOqsHE z&ymT^{{YmhNP(!+{chvA7}hn(x%TW%0vaiv)w@B!iXzh^H8gf1U?!bYv+?AlRXWax zSSmyzv?5y|lXEO+SFfU}KM#wYMum3Z_*+=)`ym6Amp9}1e&JH{2zyRr>M?2Jffr5( zC2Q>e0K=DA{{V1Aj3;&lKG^&X^Gw>Uz1fol{Sq(D`a<* z`>{{6!%*jN!2X_(f^=36{)zh)x;qd3Rg?1_htcp%+7&3S>P$J^Xro3IR*x>TYqcBF zfBjqc+JJyCAM8$(f;uHY8}wdjQ-XrgklX7O%K#0bX!(Arp}TQEA8qgV|7Vf7tT%m)=<0d=E6;+pP*TE_gpblwPN z5cq0{HNjHzYCpJr!Wbg$&mJk(eKAjqtH7#ZL_f|boD;|RL|}W9tq!Yu)t!}GX6<;p zZycI33j3$3?2&Ir`cu#FOdQ6~!g=Yo4$<`=P>9|Yo2pnlBzYBP(?*Xo;)7Xw?@JG- zX5W=xwB*`k40s-szjd+y0Q{QJhD&l8Q9Zagrw)(@eh0iK4zV0{zbe`Q4q)2hp6JP1 z`$4^ILt5X28Gpn1$wqVaP(Rw811$v>@F)#XhHCAZtZX%qQM+*3yZ0E@n#Iu(8&o99ym0gMW!-Xa~eEN0AE6)v|kUd@j4JgI$BHj?Sq?)oa3gXnVqb^=H-( z?Sh+h+-@GQPJXicgj{!^&0*8)eUQa8ijLG+Rrf@|5?%7NVt&Y3*q03TEZgDh<-Cna zGUrvtf7oC*b2|~^q_)Y8GcjJ@_b`u1ZBo)SP$oI1`KEa$UN>eY8=3pBXu9XPT=_|_ zYWZ&+yEgo~n9jUtcfm25 zie0RCuC`@NY$7|fc_I$tgNdSU&lEl%60H@dz{^kEt)pJj#=)N@>qmc6kR2O9^mh}v zIftX(eu{dE97HO@Fyi0*dflId%Sf6zxNm95qSFd$GzX2_b|GbETb-p|{{WGlUF92F zO5n)=WDbAQA%nMSymx2LB*3XdRJ$Bhs(-ier`czbTX0ZxGS#}L9|Yp*aWE7Mw@a^j zs&oCPRjsr5e|OBiqB$bLXCkJjyH&7z)25A_DZd2Z;tOHJCEl8B?!-lSovHRNY2^)> z=+nsUPIj{%?|HG;Zrd~e0Ftz{GU8m|?2X=!N}2W}e$E_qo+zsBDol2b0!IWms^FT_ zIc{GIJIZo_tPshnah8vk6pcc7yIC|6U1;vW0pBiCYa6rtIf z>?q+Y5LM}JW6$@@5WTIhFw*}r59Liuiaik%yM%LXyrYj+p=K!pN9l(Uh6}dpxEB1>>T)2Uh$u~YR=#GfdN*E ztK^4bjg_lf;i`Hr?9Y00yM0j$L9fyYq=w+(4jcVoRoHaF!-|@#Yj&GE+Kk)aJz$*y zfK)h#z`<3x!N+3d?Hb$$EbKmPIv9TYfp!Tn8|GH=Vzl>C^MLzpHwt z^=Q{#XbuIjQxL7ap3jnKV0ESA$~JR%{Ex)9rnVsnhU4U^eE9tlI>!^ERJUO-9ugM2 zNW>e;?Wd1ns+OpxnHs(7=&1`{^J+IMqUg^RNg1f8dC+swMR(cm=V<8c7_V~0sQ7W8 z0JV2QzJ6ac8v||wH*np(TxlN&nBUcJYq409(s7b_;-^chdcJ(}p#Wjs9mwL>(BYr1 zD$|HVZGTF5XakvcYpwPCdB9a#&RB0gg1d%UAY0kuw&012H)V$O=eFb9Bo37KPW|Ch zxhV*=L|~n3Xk2Hbhm8>v2Zyc+?#9xaf@5Z7#YvjR#4sO$rydZmGR*G(0E(y^qd-G7 zl4u6;3JHoV7E57LLG`^4${T+DpLAmg39faJt?9twc6`;#5I4C?NRdjOD&!5V!)kl6 z)gHUM6~@|Ibgnm7{;eM~oSgUFJ7xa>zwF+RFqw~1wJ!X>rFH|3r;-sv+OOmJPiM^E zAIgzR>1u5>$*zFf3{gO4iKbq$Q998TqUHbp`WC%Z>- zZUMj;z?h!Y7+*P4A|f`dJ3O=nPG(AMh2hpPqp@EA?8RL>jthFbTv4esUG7~J5uMr` z=mSN-PA6h7w!Csf!Cu2Mh^fU87ykf>cdji9BmUirCq>YS6i|48dGn%H)f`c69B6~E z&;nq^z$-7@ns92XM{1fjr*2wu3VmX-82NEi(#2gA=^Q@k&NB1`6;)iT*cC}l4I`{@ z-SZvWvs)2rf+`im4uf2)0WsnMgR?j)&Eo5cWutyu-lbj6CcF_rM ztcv-#AmD4cVs@%7zF8?!jxF0HcqjUV>A-iX#Y6yk`6vpSNi@Z6S4ps6BntouXV7rM0t~+F7z2#Hbz!DfNfvz#$9a@6(CzC}gEj@Kq{G7Hp;`0rfkF z;Sx<}Zv7ITb%SNII~&r%RjfJTkGm&;9r%3~)k5hNbvNxtBy(1d5*@;ER?~$zc_Y>r zeyiHW7!BUVY)sm;p2S_4XJ!h}9*`*6h$lYD8xw~GIMFrBxGTn8(Qia$#PL%>Mvq;} zyi`|mo+=UAi*Pkhsh;(I1j@k|xG*RIxejqs@!R?`cMTGw4KjmF5iq8zu!97gaA?|d zS6DW?Sq24=_4!#3Teu>5l!?)r)li`w5}F%Kb64IO)NfDb_(uc?BZ`OIH4z|<#|3m= zjZvP?D*dBbwhs~hGEQ@D*@oe=oxBx=hN<_KgBYVmg5elug9Z0@ zW=b~JfzIFt$xX8!O7W%xKewu)R?QvjzeNe|Qgl)`s%e#X*OILj+`3+;u{)G&jCGsq za@L5-XXvlB%(TM4(-9|gt70NW-pWX2ORhVtoYg!Q(p7H8?0!8xeH}ZsgJRC@@pMLFa5x?;d0uhqH(2LIS3N0Chnzwt-qt zQ5P90wrwssA3Ash=baLLQ}Uyj05VZ=rWybn*1Cfm)ihn>re7xj*pJwL!3daLa@N{k5FIzl{^ql+N_026Q5 zf^!`P4)M+Z0P-FCyz~ywEdn%Zs<)Yvb)g}nRAN=wZrpgO^6k-e-zlj3c5{FVYE@(@ zXwn_@D$%r~hmYvNy4-U)4$Zy_YT{PJdQQhJ$~zH{9Wl)=IMYA|xdKk%_a{X{sL@Vk zKInszJ!y$33{Fwlh#I*46S+oxX_7$q@wT)r+(li~) zb3;d0yP6w{2SjrNK_?|b?A&KX=T}8E#*E5^JQhPlVT!ugIGtzz0M)(qIxs4uoD6S9 zHFnp4Mx^cZbl&TIs;us7^ja~&HMW+QLAw-_N56MsqJpu|AmpCP zI0rVc;j`#>D)CZdC2468C@?wpZB1YW*r9-;$wHL}jE?mLY{3@VkT5kx1YM%L5F6I` z3LvY)nRr4i#ORQo%&!gDp|Q&Gpgqb>+?v@uR5UDACqYrBRA`MobAZE-{=1k%gS1dN9DWvuJJ`ztrHTHzzBp9tEr=6mz}x+Ap+ z{Dkd_)PLfhD$>+e18apTxd&?AIO0OFZZhM&Hpps#HX`FbDiwLPYn0QNPKkElutl}+ zQ&0-p3I$IzCJwWE)$8u~pBi-=qKjuX2dXL4+&vGvDlz5(%r{g(cVY&2AO$-UoF+c# zn$Uoq16S7NX6lVou~v3-ed_w46I<0Csq?7T{{Tv~e@U%5eadZ{a9R4UjLs?tK3F*D z$rmWo0(Ps!m3j40AYq~v>fnylMGk_AA+yCo)nd;?HM}(WEHqCKJr|DdY%I~Ne~koJ zn=oo5Q|yaddsxZU;*HSRgJ^nR3Uh4X&AoRA0pgxcKcBjbNStvTa|+`@PIpzKcuvI- zLK=0a_UL)&&lG3kJ^@Bp`mW>{H~A~&WPnxXS4=5mm0&60qKyk`8ZNN#j?$>PGhNA$ zA>Oy94g3}9-{hG9V1`Z|=ozjaX}PAN_js!IE{_ERMyTKr$C?&g*jq|*7p{=ci)zPu zs>YcpHSFdFwm{eNO*R3waX7!NeaAiDMRLw;_r0&PDbVk!U2k1dmRqa($suml$u%|@xB*0C$9`P=9VBkIFi?Cl2Q-+8YSGZ_B<`*XN(m{-2UL&_map%jH zs;Z)@sN>Dgo3lg$xm!xrpvn7bzS$UrL7n<46xTpbxb{2ZLCttCa0EmH(TdVNRR-CH z3J8kpME-mg(J@keN)pnY6!q&QM2x)98#H(68L6{;Y?VzAk1&-xZr$7vm@4}sE|BQ|06USr9nh|~4_j6Q z>{SP6avi+cYlhIG^G;4HImJSO=ciQ!aBK-dx!!kxQ;OIT+oBa?n{aO{SRKjRii1^} zyj5X9l_gU~0Yh|5=+zt+2S%t+RO8Jjf{ht>qibk5(H4PGgeVF$NFdduK?vCsO;27b z>r-CvEgx~Fm$&2)YpQSA+&O1C?oDl@{u|%Z$LP5?+;+2A-CvuEYuyagEhc?=Epp4m zKoH@-BwKZrcJ%NYea*AoQuM|)FG=2ki3XYNSU@@OS;lC}-OM(2&iRz;?^jB`phRdV zf{$4v5YY(e8frp=I!cOn=;)~v*r3kPsGuqJf`I`_s0sqSWm8QhQA<@8Aq;uDROpT< z;k8|D^T2&iGjhHpIB-ChHqoKN>b^^?i(3E&*?|)q_MNEShaY!&x3+)~vJDm{>91VG z(`q57l<5=SzlOy?>Yyb2MyahgqOKt}g!(E(-n+cyu36r{dSfV=@kEKpB93r~;msE8 zx_U#Ro=PIgsZ(DTr8aU+yHpOso9Jlq(HbGz9{K=y zrn99}@Qgc8GA^t}(JENeqE6tTd8()is~!|oD;^psR;m4}3Xq_cb?YLh1I(2eE2Dan zooMM5`XJGCXmJvZ_$Q1(2(ZXaeb)nDg#%w+f@8H z@fiovS=Sd%)W+b*LrxaW7;3wL!1w8>*riH{Q;57WtOR1~bw=;LD{7qqOPW1ZLz~>S zTr^2kQgq;-{mj-iQ2CnjlkpCZ%eqZi+{(+D?M%Hw?i{q+dH;Ssf?W!B$No8j)3;X4SYfzWnD%=5ub{n8CG`5ot#w! zyA0shBirJ+IXS@tM!1(t@eRt?m1A0D8acULO?!1R zq5Go{40`Nd?wu&{YA&<9aTPzHeHBi4&@?gZEN}&`#2ThCoUMe2=`?x5GKtfz#ZGH_ zj%6FhRV{yS)N?D$s>MQ(sHLGnJXEzpygc+>SB}+lUHvS#Xr!+Zqgy3;jR1i`$;dk5r-Se)>f_zfpOPl z{L7MM*HpOWnkX3)t)B0~A?EaJ$bqo|mksKYe<=6!2j~>-$b-xXGCiWqYPy!3a(B^3 z-5nlkBY!qX3Jo$4p^=-+pV~0zPKnbZJwlLpEm+Y-Azd+%P`OtKKvbMPNYG|tiz#1S z1=tBH=S*)+eg`sZlD(A%25~Do)5QYnI_*6Lk=FVvphhRrO~91b3Az5E?bZ!GsGmj_ zuzX)C9KZa&>)zQ0j2=zt)#&&mX4bv#JTlh%Z9qM*G6n>#sugU zyA-?CMePDFdh=B?JEu%*6FVhs5P{E}W-(Kubjk=(9VY7?^Qloy3_7mQJjVsFj@83N zpb>GNez!J?LW@o-4K!6={k^EIR0JH23IfpVdZKe(_{m+>d$oL(n}Foa=S~4$ZnH}_ z9FTaBGt79aLorXU6kPF0_@^}%){#pv{Hig%L-OabSQeKwha0&43OB5*gRa+RJC&;~ zVeP+Xh1T@{0CMG>ZEWUpdg$`b^^zguy;@tc^F|aqFD_0A#|yi6gaVMGiW$_P<0TEs zIfYl45Zb3rH2aVL0CQFw;#KEv8g|oum{ZS{td6KP}#j;GhLKGxK08~t0zS%%v()0B25@Y{{uiI20X$H9Bm4jYew zR+duMS~-$ths8el7q4G1TzW&bPFZ>@8qi_+M9})&RA8Kn+oYn7fueLpG#+NHpYNXq z4JT~|ifP$IcAwI!!8seS zEUg?(M8xeLvRsSH;#%&c(E}3Owd#}`s$Y-9JyBnKwWhfy)Ci%fkZ$y$Ka@Qy_lowt zWhMYN* zV|(E(J}Pt{*D5);PU5o)py`mOK+(lrv}3hgCaotepJicf$%4|ui(3WE*{oV|*)&#c zvlcxZs-4Q+vNxM{^S^$f4rQR+~FJ_@>4aEnvvDgrc31fma+@_-th!=BKpj~WZCUV@2%dFaW0)zE#@_x3i zvrQ4*2<%;@Zb0U@q0HWjHBK2UIg{+RwCa!f4H#(kza>2Lk~!!I9W%|#M1nR~P9sDf zX0VNl)O*18L)rz?h1{fccqTNCQuChz2yU<8Suw6_wVdv(`iA+ZNG%EWj+?heXVz-e zb>DLsPmyRIX96AcR+D>we4yHcdU``<@Sfv(?eB_Ly|bpTUhma5%i7;((z|!0hQh4v zMBwp<*r=fpQ-PMMjE-o8XL4(?5wPu9jh>_;-B$?+I|<>R{hStt{_*KTFs2lmqoerJ1{}d;Yu8Wu@12emG2UBw?~KSDUSe zRct_KC&@OuPH3~*TKdEK+uCsM@X%#n5a18da%6*s)Y2pZ9lH+H627w}Wt49mgR+T+h zQ1GU!g0v#m%;Lt9K35FC3C%i(RgKsE&o`kxz)WFS@LGUlC^T=82xg0nwa~2GT+&+M z5aBW@HuoPsewU;2rbpKN-1BDBZNQC!*K8xurg@z0%fS|HJ?&5C8!J1p@;G0RaF200000 z0096I5Fs%^@KIrLfswHwp~2DN@i70|00;p900BP`D?NW8wPGmnh6XYu1hT9;PUeQ+ zsPS_ssi_P~6hwmKYB=Gt69=rCR7S}(q=}b`Xhr~@>>K|0RHf*-9-#(dKt7&s{N!XN zuD>XYpj%- zC|I>Unm`25NbctQrv{%&BSwQqVf7dYN)Q;C(Z$mrqzTml4hYW?oKM0sKv3!#B*LZ2 zs`g9(1Oah6be#Q=Q3Fu})Pevj1+~dH$)XeiBoN@uXQ^y;hXwxtU^{k)4A7lg%38O8 zfc*r0rtn|J5iuxC?E)lrmkHSl_!xtB1r>lweJX|S!eL!Q9;;321JU`0#JMS6#9HoqG+Zk z6Sg3cayf|v0z6#-P{g)(RcT3TlM)UZxM=`de{TiRaAZg(@Ws`j$!b!2L|CE#k-$4f=bXodWFU^zjt`YUI}#d839L_K z5=4$kJfy#-FjFzEQAIOC)NdCePpLEYtU#cCl7eLlFgh9grwD@)bV-0ogQi^SJFGXn zYtC$V+d_X7PE`9bIzdlSeRj$Z00CTDp?VDBVBNu30_ z1+P+VpwR$_Cv*kXM8-_no#(z$SXvMWD0T#ZTmv59cT-7LboPo}z<};eIYc2@+K4Iw z_-U|+4I8n8h>J4zS;QWNk=a#%aoC1V(jt)wp#Y9csR@%q3yVmJhsuZj17s2~7oroG zl|o86!EP|P5F`-g*_Yx!A>exiFCmXC9G!JsQ}6r6&w{bRfHAsc8(m5aq}j$uM~9@+ zt3QD{$)V^AH`zrF z4BJeBNE2k;ylC*YTv?{GRrq3l%_<7+t~Brc#{)@pzjYsi{S6#l*Hv29UArbGtC~)} z{b~?^W(T483P)-)WX7d5dn#f3gco4V6SR)-xo8kkAh(h5(TElAi0_^&b_iw3rV{xf zoH%2k!{##!K3dYK9gTpng2(%ppn-n4xU4xEO*A`M@QD zo|+m9^h>zw-7ZBaZWdbv<^4%LvnCTw`nOo6j!6tun5;5RRMoRe%(n%Tt<)jx$GQ~g zWE2*93UkgJH7i~M@45Xel29k|ehgso0@qOPV_YHv;v}iT@#GO-_p-E#cU!;K9=IFg z>c%t(r?=cFNnpL5e2zYA)7lY&U(zq4EwY%~E$k(Qw=Co6Af_(5R zZOnHuoIt~XJhk$oXloihQ0J8(7GEIH`^_pm#fg7@+DkYd&QmAyA`fMdn09g&ysUpS z;rfR!%m&=9XGv37Xic+pGW#+m*N{V2Mj$lZUhN~DBpgLavMLTPu6qN8r|g1fsXJDz z`;51mXuiUw^%mY8-GSn`>46QYn`0>t|MFFO@3( zg|zA2)p(uC+tWt9E^H9?l(i-Dh?xrr*_2L4U#h!9EtaAgmG^Ki)mCsxpul>5Jcj4y z{sS5?h-s?B_$F6HFk=ak&f8Z?@+h|li7FENsT;JI#{8YLTKhK=m~KX(pQq}-7PEL| z99~j3C2N^Cl*=qmu+8)MH@cq4QRqfWFqP19d}ZRR#-L2fTe_t+&v9pj+XwB@-*27% z^#&!TFn{JRN_LUxzV`xG4y9C3%usZ2j4X@ypvskt40XsL1vlRQOP5#0bA3zsn@4#BzR|XYZ1_E5~d5&G+&$LVHFON&op8mjwmoQCZqLlTa zGW$idUzEa)Unct8?Vf$<3J2>0>$w#6Y@ioFK8n!o#Mb6HYIX>glho;7yI?PJGH)G9 z;SD=uDY+_%ycMqACP0jJaGtJp#p&mnpAqL4y_L|wEY1U#4 z{w>-^!zNVCqsLP|8DBsPZpd)3MjUq*%^@)09qYBDSh27T7^jnijfhDnWPrYCL2RZ1 z9so+vJ?|8_HnI^r>G|{(azzP4Z$dWVm!9A#yT9%nB>T4v}pJF{uDEqMn=x^)P-?9PP5P(6d zPgJppnUzCnOG(i3S-ltLssxC4GkpABzc%RSr6A(p!7~*1GA8=<%T`Tw;K_tme9-LT zMK7GZr-hed>-VT5X%7(boHJ8lL^5+TFYH>=xnB}2MqJrKj6^M!u&(f>E`muM?5v&c z_hyU;d0`3GotXiW1+UmA0Q{aT1*L4#vTLF;e=O$fT0LHU`PT#matsXAPxF zeDjZqctS#Tqr={430dgOH14oKRmRZwc}3VQPc%8IV>9bBjQfz{1FVo!!6QFv$fcViLH2u>Lf|QY%b=_odYv#(z>o&ZbBT zXWJn#%*wVZvh@Q`-EDX}vLQfG&9R|XUR zKB+pz{OG}u#sWg*M3#hwnR^JeulSXUbIMa6YE}Kh7TvPwl+gHK3`eyd!u~b-T&V#Y zRps*uk`%yP0;oh$Hh70LO#WYtn{&x6yPYv{p=yRVChGL;evO(O{yj)CCd*8-oAb8J z9PT@+a{KZ7P!eA48gZ9-v94m(LH*dEuXu*SuU9{?hJO8N^Avlh&F0x@9)^S2MbP9K zKYe#Isml@1S1mkI)qO4NWhR7tk5!xK1YZLBehw9-pY1wc6uy_5m}sMSb0^yIL)?rc zQ*ME%BrUJ#+;48vfDqs%hRY}PO?qA3Y{x!LK!8BNV04h>{`X!)j&P1~k;!XY;twT^ zqfG`b{!1Jm@Zx`0!v+4RW57iKwwoWl{=8ls5w1zFgtkS zo1Q{H7NBSk`50tFlff42(bH^8eRHZ+4(wFTt3C|(`EYh$i2d1}xE!e^TxGFf^@x(L zNpdpm*wHfg{god7o&2+2Xj9CNH@O@n7Gda@|17mXx8*e&bd~R}+>^%X9?Mi3%td6< z6iKBSV(L3z91y5f{YohdW|C#Fn}dTKyCL745#|j5al7@X2=;_|jK5IhKLDP{sJ`9{ z7$N=u`cYW-eOg9Z|I&^&m-W9M?pvX5*X~icnlZ7U2Gioo}rt~ z4!sIN#Q;Xd%ybC3(B|H4NyNf&C6 z$2Z}#7d6T2c(%)*G}X;<3Il%c^kSK*%G~}RS7phWD%_hQ4Z?LZ1k00EtOC;Zt1h?mP&8GO-_&1e*Mk$j;5i)fshpN` zZ!kN{%Cf-uH6oMX1z)y|R7<{@H-9Gyie#ByO}}zf$~3P?7AamCZ1 ziWt}%?dT%rJBj|WpF4$(`JKLQLfDj9)oS5LYF;lk)HZa+97wF2Nq z4GL=9rh^`dRFS-(NU8xEc1;u8o-7R$pKJ9iB28BU!vLg&z@h}3X>jT2a z8Zv_lYY;fM4VE7Ld-5m1*&A%Y&==z{2zzAW$j-l!^S@lTe2t<+oHdqOJ(xD`(zXz5 zaOGc7ijV)wPrkFd4OgrapY*)|?~4cfJ@}Bp^a_#9!^Enkbw={kH>aWtoZO%^RXB7s zkAQpsJJLpCS?%9I4-|^7@&jT3C^7bp-z^fc{#U64zp=D{vbrM&fs3%E(b42JnZLSf zQ;`aO8OiZn3crVn~aiNhHvJfz_FqXQO=4q2^s|Fvb5 zxK7v0&f8tj6n{9vjxm0oB%AZwW{fJ2s*vn^zEzNdDlo{HvM2=bsXZsMy!w)LGe`$@ zbygF#v|R8BY)QprqS(etpuc!2HiXDnF>wwegbuvLXTbX}fpm4&3DGeHN!|9GqYOtS z-BTk?FPS(GV*v}+zZkba(wmQM*g#N$gb1jqqFPM6&5Q9&|ET&e*T?9~o*QW@1zeU4 z5O;DULGoVkjgy^U@Uc4vOXtMyYF}*z7sCPbo@@clXkj`pN|OnSp|I0UQW|guPzI)Z z{NQaDyvyB|E$iG~E$pJ-0zukw8$8#h{@nAZ;1&WlI{&Vr-cJv%3FpDp2ncEj59& z7nOsC?k88Vb#X#}DWbGVDCs&gH2rC3lQehbA(>Wr)5~LUuVDeyBi))N2i5vVb0`U} zV;8Zyrt{#WgMHU0w4Pa5FQ4qn4^FYEy+~C|d>Qf%$E7sq#%1ea03`97PFU>|E+h|1 zNdM*gjLfT}aGga)@tHe=LKoiQXM)}2Fub6BY62x0!%RLCjk1Q3wHl#vQ_TWrUxU@D z#5_r}Eig#U>$milu{w(doP9mv7_RF*W~ln`$Kv}Pj@@jHarsI6@@qG?^5JrX*`ETT z_3hypF9OXH8aj}hFMylR*_inO!n4E6N8~lUq$PN6k<4WwGcUH0^3;?WfeAMAb!ncz z^O!UQv>1izReAaDw%-7IDvrz0lO$pO{9jY`G|rRBzEzQLH<5%s=SXMfJ#L)A4_~Hm z>6;Jk%ezlS`+*&*(ixO}uC+f0SJ-C~Sd77IOa^p_nQ{z2e@x}Ki~SQ9he9kp6s#?o z9v$t~kHS2`=?dAXd1b;XATzf{g3Wd(FJ|9zKFFQxm1u2k+!Z7{lD^uGfbfi^PCr>Y;v4)37kqk;F)G z$Pu-}XAxc51mxOi>QUTQE3aN*84qi3_Tf<1M+g>ZZ@Tzhpi-Xba8S^m1=cQqq;R z*!zu_>XcB-g^~142NQio{PJvJat#K}Z{ERUY_i}WsQ7G0reX=I(#x%Wd#RFY-}*k} zXYozd!LitcWTxAS(M;kBW*KM5&n+G_Wp8B^E1fG#GBKa{x-erQo--kxN8M25^MLXz z!LIO-vs!x)ya1{-E;evZ8F%S#yEIz7sKZh{0OKDlt01-R$5{Y{E?*dkIKA|Rbp+yju^Dg^qp zVU|SS@ftb0kzB(=7JDbR%RkgME?(kpes06y510547<59rCo5kw6=W4g(V(=MV(TH; z`c_Td2$4In<{^91?OxedQ9!efQVh`3+4%^$>`Aq%g!{l3=_(q$NHctUrk8;OqonDG zoa{X7s8P_ZRU^46dVY?pD_D8(=paOxh)i~omNv?%?()~wyQB8$NPnAuA7oQ7+2|pV zMP(N|GXO7=g4>c9H)4ZY&{da&Z96o17t`Y{o#v5i)gRk-A5y}!bRUW zhq~TdC#3!BrB1Gt2!fQQZKVx**W=sVGv8&SEMRe%3g(duirP1&A~?2LQdqAez#75i&MSX1$3X% zq;)`H6{nB;`1SmoEAV7gpBM1WWEafjbvl>`7vfx4)u*gpFczK-0f~-0H-ct_dFBkm z%`dJ72sPv0_0S+>$pW4}7)YHN7ExwJpfhRnK!%g31sqMcQhNv)$nO> z1<;G3J1UDK$8QHU@#puJ8mrZ00<$TTn0xKi--QYSg$#zD4Y{{V-8&BcK0I)9CX&{r zHV}PMDQm>$W79z{?C4oHnIjC)b~m8iLUp@AkQrR<#i^_W>Ta*eJe$Fg?ffml?`_2Syka_%{1 zHvww(YmhAeg{K%1DMh**@}>S)n0^}FOS>J+^@h+!9?`BBCh!EmIOw1IOS*&0U;~;gLTi^2C21XGfxqFQ^0HIgIQBX zvE09_h-m1mrSOY_cTb!B2Z;NJv{HYAwih{tme|Da7*_9P_>|ewRwsq_uHnG;LDG+v z<=46d54*T_w-ks6R>213Og$Sv`B*j{@phAcNbCFUw=!hcMG!Y~9heK8PC|w)#X00( z@Um@L?Gq>b`48G9e!%(wgmo!@sqv(*{w4sWW;f1h@@o!!5J_A{hsW{ju|8;gZi*w4 z)K-oq92MKp>{ZvNmwA~t=!fUXsF-Nz#$p~3>=N}y=pBH#dS69+`M#D-M6M=i^YfMY zc16=n`7U4m!anOSF_Gk;g7;s!+N>71Y6{~{x23*TB(C_UR%e>D2WuX|{WVJtYyNSj zs3da>icKrG3c1PIp1N)YcT?@Vv#kD*20kA$B2MKqa}VFYOO1y0ltIw;Ir9HZpEFOx z%*kA-=UdxvN#q(*PjkCaA2fhSKpLeX!2*}?#XX}udF0l)rpHG4f)tiz8$C<7rRKe# zStk^o#nrXgy#%s{u1gi!L|Ij8fRGIN`Jnf^f)qyba@uQov?M(MzowqnCqdafLiAA< zmny>H(E!Nz%F}A{8NYh>ZRW#VUCrLq3ry4xAKkmqf|eJ1*nsbQmt!UEtW6YcneC>7 z61>v;PXDTMTYxoMgoAezU_m1&h~N8f|3;#+GOYn2&>oH?!b18(>DA;~$?HlP^!wJ+^aWoFSexbtKC`uUGJkuk%Od zt8ZA31dvz(77^9IJ#Vr(ZLCAzR87qkTxej6HfzE7SL}4zy zRihYx5$D+jxu|vHI#}f`4^_~S{ZVXPSXk-;mn6Ts{3{vWRzzl?493bOX}Zp!WP>ug zmm#@~87yqox&ncIt7(-`4Og%<3H1v<{QU+@qs%rO5K^hLf0MflkSG~H)p7{DV81Fy7^<@O3#n@y$Z9X@cgqC4@2zn zKbz-|TewC5xSD3Yn^|_1*C+#-jRj8$fGZ%|e9OouoS%uFz#F6v^V6aE8K14|?Gqze z+A>Tg8jDKU^AC3}`$PnZj*>@b`{bBk8LQlMx*9>?`ns16GoFoy)w_ols~T{uum(aA z8R_`c1+mR9o9sW5y_LA3=ugz(KO~5;_n;>rqG8Gy&m67}UsJ`?&qBquF!=~YU^IMn zMUiT&_lNCYcB~3HxS}bZ9(KQN>eU;GDQXhI^Q1;n_mV3u%b==;+-MVAHOYQ^8^r`g zF+kExqx~`DF5<$Mbxq7-a z5Cw*n94I&g!-YvOPbYwpyGvJ@&G*@O?8ubzKfsA#B^1a&m_3PRX^MAmT;{~gc>Zf- z9nXEis@IPGVtf7_@nG&D$vzR0r}lNq{ocBgCRKJ`ljDhek5%FpAZ;9ygvyd#Ghl!M z%!iK8^PR4Vps>sI|9cYyz{Ng4-YSJIEAzoUuZc!B;j<%~_Zqcj9Q0o>+`N?8O?QRi zB230^VC-8>lvFUA46cD_P%7h$p(RuX6ER~0YS|0*$!mDWBej1y@|;`PpHSuJ7q(%* zr_;pi4K*{YpGnXzX?I?E!sw01q;apEO$3yL1WlloNf~Dlb|Oext)W zRyI3t#=RMpXw)e1lM0sPYRvO}Y$Tq0e}M&5TK$}w@zO1_I_}uoMemvnz4IN7YV|C@ z%Izod{@oW)<`U*DJ(b_)K^$-Z__jPLX5CaQz{zdimq8^j6$y|H_fb4fphrVPaN}w1 zX6PlBQrZ2pvDdKH)DhXWPc2jtcP`6|opO?-Q#5mKoO}BiYKv62d#QNYSpCONx_-*$ zRP(Io#mfN+=)cgc1jad%jrR$DS%tirsGYNv=!RnycercU+MI5!VCQqx3s%c!HMY^6 z&GMxfTk?P2FN#-~CPE&^=SvS4_!Vn}Ueq&n6S#!Z2}mgX8F%K(c>+YMDNmVsdyNlm z@eOoyE@1s7opi=Jt|T0B*)^KmHkU1xfyn_;SIl^9o00r4>Qd8^Ehw(&r6UXz7U^~q zD=DW3?w8D*DDvq0qpyMH7D;6)NlI)xmS7GsY~7-~d2Ph{-Z%unY-9G59j@ABrFFh8 zD^~+=;)709Z+VvpnGSu297pZ3{8Lr=PbMSNSU?JY1^F%)psvJL(@*)yM{f!woG!$A{3_ z=VRiO_aVSzX|DCcpQUR;CXcc!0L0lB+rn<`A_mzqeXCunQ@8^G3}sHO{TY?gfXTUVo2yyQ0wB<{f(YKYxg1`kMX zKLLa=2&~vry=itx=phkB*R9WIpED%Zu=Pbv2fr`%t6|oqntnC}m74vYUq;!3xm$fB z^26$1{uCBRS+t8?l|D0-Lpm3TEnXpmY|3!x{(`c}v|JO9!J};}NR_mWz1RpjbfY5E z641H>HK}i2gv(WL^#lObNhuo%Vp#+Xf_j-31hfc9>?ZU6bwyfOBit>7rAx1K;rh9) zsL}jzqzN|i&Lxx|2ux+)?S1viW{rE)OxJv;VS^#BSxK2>Xyl?n@~~)S0fGXt+%O}` zm4kP%`+p@+IA*AWOgvb?%qI60HIh_G06I@`@=2V%7H>PFD*x7NuCInKVVA1v?geU< zsfSpndS%F`4UD0}-RBTX4(=@u9R9zX-)Wu+IF(Vd#ZjJ370N&L2%U_$tDK}i1z-|F z0TQfw{!^36DOzGW&cx_UIT)>2J0n+Nw#&IxWc$_qJM&{808`K#4rJ>8(P}9Ss3Xpy zDYp*dcGCn!!!LZCwD=LtP$5J!afuSBN58_^l)|70u0@S5O6@MAr!Ntk){>O%@MaVPRU4!o+v`X`sd&X_c>_ z>mCW^?UENPpB5Nt?PRDYiQ8%sxZ&&$TN~Zs@Zvh(!81N$zO!~M;WkN>SJRl(2pI57 z;g~g3vb-!oFNPa~5$*|&m{Wv>ULP$%$I+Phelk_%pOcneP!}l(gNh{s0yX-#o^Um9 zlHsR9jX~HK#;bwdmX=#6pIF9Q8geo1nr-7ZsbMIY~YIzrTYrUq-mY3q67f(nXG7?6!iZoDr*CU8L)wocgp$Ek!c z$LyZ(5A*+gqe7adMI}&a%bbKsnV$(CtGqE~X%ANXFfYd^)qxe;AcFs<{qH2>N~)RR z8p_MEn6LDscfg0Iei*hbuoz%^Gt|cD^UnqYNj}C`0p&?(Gy4`-`a(AwpqjnA2I(bO z34dUolCyH@%|tRDVc0sR{|OGy>H8R5A8%%r;f>yH?vy zP00Y;$^PhjIZ??%MEr2OUAxem|Mfqh=0i5~QxN4`BDA1F0x63elD?4W$A9< z-~#zuOeXAhHcXgR;5OalHA{RdX2blgm^!gyF`R~SJvUOViaCXDgzFi{Hkc>w!8gW- zqlgLl4-JrynY_&68{965(X(>^OFx*0vR^l6@sV6@lr1h;q6l>^@kwrAnZAUiKE^>0 zV9};ALzl>DQX6ea_Oq6#g z5DV@isLaekeu3_frA4jviteIfpVKR)z8#2ji-7wIh^R7UZQOx~{A*04 zCpCy(LpC%#6d>CNz`!!wppk!!DUB%_oT= zUX1tgjHH9*43oxw#47sVf=I!{zD-sUf)`8(49c9o+7^=|vb--BZNxVQh1?>W=Pbyc zE)w43+Md%{trURoIURTTmmlLrbSot!uG{?Jo(^`zLj#yA?fRe~p}cK4a{9a>t)vVG zPWd5__t*S#DhmOLaQ0bgT!jjZ1>Q(@1Wz>-9&TmfP~#wd2%8Qjd~u{dc=SLeTu!~u z^Prt$6?>;&#mATrtKv4NOoemu&;9J*RRn_uVvC+|;xgeE@{CbKT~jQw+`UD;*_Agz z022f9F~Sg#{&0PS@}iSgt9scA#q*2BzPEwAVjJOgupHFM5|fbpmsqatz@Gbt`j&+_p( zn!$JX;5<+Hn*}Atp@XFu7A6V{y-r%aqbfY@J0Ams1haNNig#<_{|^9RE2TsLFl(!r z4?SymI6Iwd-PaAvF>g#pwulyArg{O|5AnA#1yi6!RQ8k0^SS+=DdvlyFo3 z(FCyv_p(wWQbCBQ=0s!qE^FB`|4|Bv^~9`y-SV!fM=nX^X1-aQ1hJZZWlkLWD~2CV zGAWfB{LWEHr|rA;3X|%?`pxa&6V{Gl^{hc{-YL_d@LRS)CKhBFrkqIv5MoO??*j#S zoJ!;h_AHyINV4ln6h2X{&srFmv&fz1Kn!_dY_5RD({7FyZD7i|6p@<>{5YrzQcF?= zdGQ25pIL>_@T-i>b4A14s8P&00rX>(IEiA%A)aZgolwwqMI%_|33Qf{;1w*a-*0;# z3MUu~Od}EE;b7w|%1KVxZpUMJKY4I^JO6+{TFrkzMd7u(Db;EOiOH28tU*`e?r)+J zi_(&dW+i*zU>~AdJM;J((J=r(YBTc1TfU@6*p+M)q@#T6bCXRbm& zD-dYh7-tSLmxwW8ntDLB2m8dOyi3jEhBZP2qQx`HdcQ6us8ZRP@dyl%>X3*j!IY^a zZFhL`RJmU|G(U5C6%q(Xuf1sGA=?zgpYIHk#NfwZSS*iCN*+oUkI8%`ldGcnWL*9q zj=>#?Rx>ZF5Oa5;29%>zFd=}94L0%m5Hwhf$aXXQ3(+Ee*4X9=8xL%yL8Bo6#W|QN zcB<_sA$Zhg%KDPcW8LjW?zNHE?tbR*OQU~qC`~NnWJZPW5>^|>IcdQY(z-VVNcrgN zKWlU=9|B7=Uoe&g9MS<|pvohi$r2a-#7&aB3(Hvdjpd0s!NxwXzUDCNP-zu~JP7Q$UqIm!h)FDTFf-r<@z)3noz=%PYnV zvXiX&jA}!*_1{*Pn*#a*+y(=-kSH=*vho+#8Mq`thHsAR()FOMg-XE3mX8bg=5Rnn zt6*7v=*!qrB+(6UY+f*pN!`{a7u-**=gYUr+3zZX@1)oK{ebh@c*A~H=#sHJF*Dk2 z^fOPQNcThfMl9^!BhAzLATE~VZEL16zAKajD8h|J;>L{e7i%c-&LBr{D*kFP4OOm< zrQcx4A75(`+(~<5G603+FnF6meEX272M_@xu=CIIb)>+mPND6xs2Ael`dx}#u}>_|j-x<ss7(Lq|V5wI^O<)=uQ5;ri_Kk z>{(8m@q%@8iq!byAp3AY*69K0Q+sLEN1Je@8m;8vG}u)c6f84f6=~?h0*s{yzT`(# z(6&qDXsM^;Z7OwOi5cPM4Z~a980pDI1MzbxE7{E%pC)raQbR;HzrQ(U(pjN*J>f9) z!TG#(rpNw9296)yhURL_Zn}+6k}R<++{zb8F9FSv(gD_FRS!PLG-cIQ?QbRLM2#io zTk+D07teVqH|#N2zGlCT6Q7Q$H-7vqhw{fF@Y!EhS!g6x0F%YGEKZk1*`k{Ehld-y&sB&|i$!e2XJh4fffX%B^ zaj3e4>FHOm6O%A*Ks*KVJ7F&gHQM*7|UN6y}LV8375^W zVhE^#8xOPFK>z2gQ&yz`;t}$6pT5=nQ6qnlc>MYOvi>#1qsd`a!fnDTpLu8QH@EER zeZ)vZdvD=xn%A`(;TXPuvU#^g#X_NpyCXX?KzD1FUk*e&liX)mJ1Cw83KrUGjH<9N zttMp~*|Jm?HpNZSl+(qIVg&Svj>`P4A@jHQ?OZJK5YGXsk~c2=)DTN{$G`D(J~$H6 z&)k|g{x>0ky&MoMeuQ%E+_YZ7f*Ff=+!3pvUxXLvP=mnYooIM}4z~8%IM3mrMmF2v zjMqD9BgBDoAQ3%Yp2n>454NKq{ zQ*4mr2dS_PZ(`OU4f~mj)e)ehtQ$hVrKCwxAg@(dpp7DOV4JsZ6=%r`xv1BG{b{9p zmPfzI_}A`Mv*q%PU|v=^wubL&Rx7-!7gdt~Po7SC+jA<}Ii*c@;7r3g=GE5{Sh-ul z#Ft4VINeb3UNf!(UsIF!bUBXa2Hy){ST1rgECtY>f&{6D1w9MNq#HR{rZj=(O|{u%F-ig>BpKXMVo_=vO1l=IpFzV zN2YtbWu2efW9vzoy;sf?)RmBst6i5G)xDyHh_v9TeCjqvs49T7|1!nNV7P+I7zCyn zUtO+Kt>myIcLV;P7z`)VF<}5(aS?sF9GXIhaF!=;*g!L86VmQ&(1uEpP14t8b z<1I=0$UCN+BbAC&mfsbg%#v0Co*i`|}*@oFyK2JfQd^YT{9SEGWS+-6e<$YB>r50l8S ze(F*<)IC<>dA{IgB=707n`Wo)zqKoT@wxro?`bB7e802_{>Z)=yby9|umfe#(-kI9 zd7^TsXFD@%VWLP?{;%zT2;k@2R&gQwMp}x!J?q_*d>@5xi$MoocmD&DbyQ93cbfb9 zlyh!hmbdZl_A?`m$AreZu4sRRCp!wsZrG%S3Fb~^XlL@ie$Ue8ES&LdhVyy;CXU}d zed5$`VPn5j2o0GLIW#!@t%CHsIaRK`sKB1oR8@UWF8{@j-9xF2cRvlh#s>TP2lOOG z%6+ZkSSFeljuWw=A>ZY%eXVFH-1Kq;+nJx%Vqwq(VvWZN+U7=@Z@TvM*e~7L&7ZsE zZ^z@Hv}N*3?ph`K4eg?GL(e_mHs1z=zTrE}+<(|aI5Nt}Uw^vyC?pDQ(B~jdb#?(B zo7Va6wX(KDV&^r9)$(0WlP+Ql(!9y96a;kSWQz{85DRx+INjiV<&|}5WW?ur_<*Sr zyYiGdUe8b@R3ZPJ+3O7-yvfMT->k} z{|Cfs*)^6mp`bQr?8=Jmq7YB-3V%H+eCQ+`@F#SQE2O2>P55Ke>-^WWUf0WnhKbY{ z+HpnZ=1;V~=7>C_2q`Q4?e@LnZ`D_5=yYko>+(E%8J06CcxSNFT~)>hJRg{Ax?5xf zb?mFfqrQ~xH{=#AsgPC?K|1DkMqF_43WJbYd>Gn?pyC&}ohu8At*n>K{R;O{`I zSJ;SgaBs0A3p!jrHq|!^a;5jH>f7Hh>{JmI`_nD><=QeFM)mq;w~!ELdPhy>oX6^h z=O=h~#CdsBRwHB~tDUx6D!ltyz&F_k@;$F6YOU14O$i!1P;q$X&daMY7wT&H(9c4k zisk?_ASeitFe6sfJP*3y7`id1o$s_e-0$*d?whXOnZwqA4{g~+Vz;M1eQuQN)oqRo zy7R}l+L#eyA61jk%kp~IB|#um?)j-ngEB~&rLIP=ym@^7!1=t`^UqHhcQiUC;k;eXP4n-y-g!kM3n>-5++e1xnZuue*%0pz8HXLC zx_Slw0bAehJbQjs*jb>YS+nC_qw=ZRp}~jXy!W4r6TCZk;gDg2GVqkW>bd3DAR*wS z8?+TKIz5rM(bxy5fr+j+G$z0pfk%t#6GcCq&G4GDJs0k1r)(T3tlYo*e#_r$sA{tj zKmFa3Gj%4`o|lacuasKOS@L{RwR<3T)lviacu%8Osx6dQTy(?U{Im|gu1;R=arupN z&g*vv0vqB*(N|eR_Edh%2o3QpN9!~##onEVqKHsGh(521)*t)JV764C?4!o$s|#Ue zA6hE6zU4>1X>C02+AWKGK|c68`!AsA2hf=|GWPSLj|g%L_g?lY2F>0n7AWyR}LxFLt1~VA^Pc?0yP=#9omY$ zfmAe<-`M}`8>ca0PGiXmX8RfSPx!$=TdiZqs3?zR;rXSqB;5x(l{1;HV|mqfV+S6? zFhp3?qVvIIqW#2SRWQn^k0vmEZDo(xa^u}%gk8-8okx2momvhHvkDQ0ViAG|PLDhT zMjE(<)G%}OUGpKk3o*YETEcD*+#YUfv!d#3&PW!HH2nyo4JznQ&A+Lq+sGz^^IC@! z)=ST>T=Dabfz#k;Uly-q1(to(eNybxnqF+D( zDmr^>cs}k$REFrvadE>Ho%cGaM2jhXZ zHO3>1KIGMRKOrj~-XRZE=cr+Z;OkelR;oLmN#uD)wy1h6#Z13Ge|M_>#!JtFh~p)@ z(bNllN-hSny{T?ss(t;gb(sNn~&D5Ec6m@RX9-vKzvIK4oi1#NQYn zYJ06k=6|qq0pN6#b)R|R{!bcvgMru8L~wO~iLcZ!DZd3Yv&!7edH;F-IJe^t?2^H6 z12(0GcDI)N^A8HoRjx!Vq*Fe0*AwU6j!)i$_!49GV^gA7)s9DS{hf|#dD-1NEv+9U z#s4a`-ECi}u(M@kRh?N?!-+wh)0&%-Lq5=~B5!a=7Z}V7_lzfaz zVzZvGTy9iqiCH-%&q;Q`^$GH~_SYIu<~OL(kJxsFTt&ZyRa`mM9Q(1cz;oZTBN@05FTNJSQ?zz6h^`g6Q-9hTX>5Hy`5$#oUc=%=y-^L4)Vv4?7Du%k9W%p`h8yj)q#cBi>-^j<$iZg+$O+d~o=V z^{o^;#puMm?mh2?f-_$~!jDH==yUup1w%Yte-~#eejDd0{uHThXZ_Mz76Z*h?Yv+a zk_?q7S1OniDpnX8Rp!3?J&;@szq?;6 zcJ^V6P|tWxn2Ya&R~N4YeB;jMllYpQ8w1OX&xiaL2`kyfRwpB^Jx)tAfxL<%>?bXp zX7S8D?_T&meE96CoAm}vQDG>c{e>m{unsb(qhxgO;gLHvtc)JfS>5S)!mke<_R@gg z-{bF|WxG%2w zRnPa%H$$td8@|~uD!u!2Q#7N+%zwG4{Z*Fqd$~Y5+|-~L`QTczYc4ieWZ__|5Wekw zUV-{I&zs@xtu=2wpQ#T%<&f!m#&z-0!;*w*Ui){;kTvgqkwm!rM6Uxp=xtT#X)F62s{Zl)cK#}tzMZxB*23GZO9Uhw zliHRvnI3T0jx>??);ZBoMDzw{VX53*?_0w34sJ)U`+GfIJC$~Xw*`Dus_Mm${2J;< z5tu`^N#aLMG5z~sX*VJ?W*?c$4ac?WD8i2Pgv)Sq#DBN#S}>Lpg)VdN=PZR$@RMLk z*t3}nHtkM%vuTs@7V`&4{g#BgWW@oEPdl4qvZ(+NmdQS@sVNSoVDD^jo)-|f+cDim z0EnwzP+s4>GVQtm!oM&O}{p+1D*`JCJitNgHcS0q_d+_(7*y(+Qm~t zce%Fhb9xq*Ib@UIDHPLROsp@9+Iz#r&b)$)mBAma zGad@=unqu|fd2!NvedyH#tXx9-kvT*uzU-8aLY97krCVz3MpFvIXED99@E2CC+w_~ z0D(^<#==xK5xn9AsOsIAWB1O**Gt+?MWOO_c*QN#)uTwMGFr^{`B@BSvZ-j2AcPiT zO9#xu%Klen`2{8dvlzPFR*f2;>CNsmD?z*_x*eNTGQzAe!4_7Ya8mNc>~1Mh{uC?q zrPhTd2k*v;zz28x>I$=h)ANL#Z_?l@RW?X0ojBwEfFDES$E0&B0rHzk5~Bh6HuPsKiK`K(aGu7Y=PSqPw$v%y(6mg%~QhyEB7}eE_o!FS|9w9Fig0) zgRjIK;W%As+?84%>X6~mO zzCGgq;IM2>*1snv`|ur&MKviOYHFJR8`c_FDk#ZIoJ&TtqQ)n4O=$c%9s|IY z#D`wI4=L*!N*)!<7Yt;JUgK5&FCr%0*~L`@L>MB%As&Eo7zZ|XOeKwM*7dX_kvq~v zFu@vDL|qf~bi~KdjHt>jCoL?<&sY7tWEnWjDl6zTK@7FSIAGs7^6(Ql#Lf*VIwlw( zhAx^RaE4Z4_&aDNrRS*C*;#jC`3th~oghsbdaMFdE1n-80X>emehe@} zJHjBHgW|)mpb}srJOIET(uRj(2qKg8%*GjQdDU7aXw z=x){Z_2dIW2y|eC!4?q02qQ%`pMe;;JMWfl@r`zVxDDiJWUVk?0`5aO%@}?;ragdz z0nrQy3*L=!VbO~iK?E_uiK8om4}w{k=*){Qi(3zFI1Hm5vg`6x(DcT7mFRO_l6LHN#qzJ6GqLb975%+~`*fa?V@`lRmI!Bl8Xb4#;zA(Vh6r_0oF~|^ zZ`m!y8CcEZ^6-u;ClT*UDncSo8f>K2gRp22L;H8zfP8aGL@+@I!CykW_&PJ2A|yJm zUZp=UnFt9_8<1t<%$pe|Os->6xMfGhnArmt6@ZJOzRq)+8ssr{KZylM=DD&oVwVC5 z(eYyCH|K#>liJI}+UDbthn%*~0tr3!W#CxH+IO80!8j)f0FH1NKc5Qv^PB*zVGe=r zht2e^BO+wmkWyrxn6`N5 zh`Dd}QDLZ)J>nz!4bHMQ%Y)VswHq8!)}wVpXm5X{J?vU`&qs9S@{VL|*|CGPTaEpm z7DAPE2){}mlWa4`zPM_agvhY-{{V34*~KB(GcRL*-v;VI(X@=Q?jdT}%YmrPDoI($ z)mc-jl@W1Qm-Q9{}>Mat1?t|_erHl5g84ExyqFDc%Db*qmLMivhY(98EwXJX?u=h{bt zR(vIfGQwt3?Jfe%!Mq)HR$(3PfvB)KU}gURzo0Wc?^ndSM18lLj>;DPW0$Q%&AFv& zh|MxI5lX!3~P{ zmNNx>+R)3*4ELjb+Kyes%I4`@>xZKbe!NwyZDu#_X=4W2Dz(X{H3pJ5ct z{JUU=dc48cY|%$uphSw{J>)m+`8HaP=x6Q*Mr0nOye#f<6EdIOig)hi2OCAO^l_I4 z!+z*HXyvAn_LdwBy9Qm1T(pOy?Drb7iKhcH!}Gak><&0;xHsU)wQko@4GSRKtc}g} z+_i298{hCj6}K*XsMrgA6{EZlUv|?+^VM$DQTw5k8>!TaKAUc%I3X>zhQlK+;w=TE znL^Ml;Zhr_Qc=FQ!dDQD^0?*TcbA&%l>fr+*C`lV9$m+8BlT`4nNlC znchp`c_XAN@2cml9cQ2%q~-;OHZ?Cr+pgaI()+GbI=kLKhN>wRk#ngGYfs3bQO(wR zeK#*=ix&fKPBy~WGoDxi1y4W zTT52%?gUaEqgc6=H$rI3srTFzT2&$C(_B?=*ZM}z!dYc1MRzMzGBp)damI7TL~{P& z+SbZVq8nISgtZz;3_4Yk+`29i$jpfP30X+YiddAm4VI1ZT@O&(ao2nH@?rk~K%>%G z_=3sqz80)Q;|=h%8d6;;l6+=m+H&Lg@zJz}pFDSEd-P+@A2CFvJyDN+)qcG|rq@y) z@;>eqfcH}Mn75?ZiyXhW7Q(WI*$1Nbg(B`*+EgeggqO3;Z$q@MaxMn;l64LouAe1#p#J(aJ0b&4&Yqit@5se7NZ z2gV4aztUAyeemMf@Zn(A(G2|-t@dc_{dl)~nJ5vIt`s~W*`$X9y$%z?D@%j5pa8s?G%Krc{a*<Y--j84o$?ZgQkXSOT9o zR}{i}pZTH6KzCw;^oMqFWGx}T3P@+{kyDCh%b^6cyGKQD;f0`c+z0vp05gZ5DYH6+ zRSRmB>3)>Jqucy3MUU+P#$BiSpPz~L-D(t@SSUFJhO32)ziLtgkLA63nl_3(u7g6P z9H={??*?dZyY{7^vU<(L2AFcCf*fhYeyRrjKa+E#XBadwdRRR(Q-(VETAN`~D@{{RaXn2u0%Jf&uGezE-u`Nzwd zckN7rN@(eIQf7Y7esT+={)B-nv$m zA!JHQibhA;ULURoAEb8yN?v|gqBgDdW(qqr8vSEo`bzDaZpEM1`r}~5-InL0#ok8? z@;mr*R@+#zOOHH`o?0u0mYR+rTQOsP$>8-MQlp<7_*%tY!nIv~OD|F0;`Og9)?cXH z6T4X!D)CpXJZhRKg{U8`n~fYB~9TVz}bZ zfO4R+y_*&tXfsK_DIb)E9NKGCf>GL2@DnpL#LVwiicgTQa=57m!Wvz(YXr5Kr-$JL zC|KMgDRLc`tu)N>Grxq$R8Wyii60Nw8G}4X&rH{r&k652WU{D?s@xFd zSshC@0hXHxq_j?;ltgBv$wWkCY2(1mkBMSeW<}-9U?ih2JBZPl5|x1;VR1jNhvo>1 zNhuE)%~O{QE>9jyiAYvn_(hk*gSJeI78`E%7jfnc_ya9@f-MX*6I*)aPf$H*9mheBF~Cb-$n`5ilqoGzGaJ8-jQP_uCAC#x+?(AdcvK(fCv;(8K}7ctSU7c-i8etukzJf3*XgMMunmCeY^r=jTMG$kb>NuSnSe>qQqaMPZa7v*-h%H;V; zoQpuq0WfjSxX?LF%~L)GM})fw<-_OW*$H~nF2adu`D+@ceYePVLjjLFMU9KM2-0Q1 zr7|Pw7oW>a{B`Atio&j}xg{v@;n|Pt8`TjXM+;@F(Uomy21D<NjI!Td z1{T|vErQ2~j4?CqJ|)?u+bv$g5-1-#IN3XB6tQNZP;ELDIu>E4DDHwwTb$hCiCvlN z;iiWQN+U8Z5;G}z;A9lf^21S8qEMc$6&1KQ&^N-^jjHU?(;nf^M&u7hXE>yYr7jqs zY~d_SPW~K>udb!#!_qE0M0i>wkjD)fl%wJZu46wx_U9|oEE4O9!-IeMc@6%za3Zuf zFEa#J%nogW!k1;Q+ovL!!%q%g#*poFR}Np?hFIHR>p$hgF41y7bqjO72wf{>Y$aS2 zlRqwB1NjUt&KPq^Z8eqA33sVjaiL_NiF1DSy{ioc?9b6ffV6eUHsf{Mc#VdOkV)G1w(C?7iH7T9hX9~3`%nlMEm zQkVR(WbHv}6s2w>bC#Bd5SBce@38&tqprNm!I1SBSy(R5IpJoTW=7KAD>Ku>TLL@( z06)Uzh;GbV+;TKK%kKjZeXZh0u$8GYJu!)+)Z0`@tJ6E_ zzeuWYqB?bJjF&Ek`7=3bjFshMQ6@_C=Oqv)sC2|kqZ1k4l#7qeiz4?g`6)cOV;*sIdl zuJ)(6@?2aeBN{2tg;#f%<$NI+s)X&!qE<@FJ`;o0PcgZgEh1YeLL8wuL=986(lL@# zla)MbBaJ1>Mm&{dQ+7k+pCcRk?@nn_ZqZK|>(svEYMEIj#!t}c_6C@Qn3Sm|Af}2` zC5Yt2uN3BJp~{6mdE?8{Nc2$OuC_0l>}p+8VFoaz$6lescY87zS! zPh6r)e%@bmUG(W!8bp?fCTUc^^qqOqrA03&td7N~RJhV<<8+>wgyo`&f6=utlGl0Z zSHFJUc23Zo)7PhM&CR>2{{S8fUYL?h>wccO@h{d$a}iSm8Yv+P@MNj2En^iYR8#kQ z@OtWOXH!g}3_?dgoBseVGDM<$xhR=h#$3*%b;@+RPEPYr502w?lu8N^-MO2t(L#Ru z_vmZasbbV5CYvdz@WvpRxl>us_pxTRJ@*)!pom#MlYG0sV-)oFY|$j0&bcMp=& zX;Y`B`r6V-Hg~r?nyW;+s;;389d4WW?xvIe-oLIg)mO4lR?!1ZtH_e4B&sPgQeV<8 z?BYD$l6r4RAtak(+{=Nkdh5~=-t{)Lx5l}{8BvC4sk6PNj-$;rRJ^qsMGdD7<&=aa zBQB(Rw@7P9rgNN?64p#&RD_1kx1`%RH|bYfIWL?$)G{9+n#j_mh9QW0L>Tedqt{T| z+Et{hOH6GdR7RT19c^l#d(j>0eOl+0DtX5;)lKBLebObWjNYWFiKRPJsZwOi6{gx6 zVjh_G+DUOt$HmD`TYVVL^yq}iXF1J!^$#ImNhdSkb5wQdZ61=Cc+6_aI!Qupu_v@C zm9{qSl_a&%h~Tx<*8O@#yak=Qc*4ncvn2s{{SVXQO>AjSsCr z{kp(8Yq*ozQK}tV8pt9knF%>(6Qw0U%tO~adCt4m$EWe#t*6%d_tU>}B{R3FujiT! z4^TDh)s%6W7Zaf($k(6dqnz%uDjwO>oaf5dIsV*cIHAueNk$;Jp2WgmNva&ph4RwtWL5M=rNhHy9#y#^-@0@2}`tcE|$69eFX4sR{ zc*JB_mu6Yy^3D{QE=Xyu^GwTTME1w0uANW6>ED#Gxh45-pY(Oor_T;?zFF#<6YAY- z7>W~3G|sd1v`Ko zCsC4qU-+f`+W!D-Z__%2#*(E+o2-LzD;YN2-9);dIb+V?guXL;#bB?~h5R%%&!|qD z{{R&JQ@Te-azbjW($%Cow<4;?IXmSlGBL9ohn7`U?5df~g7Fbkj=wElZ8haXM-*f! z{{WJG-#8|hQk4lxOh>Ncc^*?0aAVagX?f>1tG(P~$B8uT%Y~DY@?lwOQchgCYuEH; zKfZk}cWB~pOOnWGd4lR^BE@>6xoM%Jhd6V|5Bp)hQ0lUEmHE_UQj$2=DS0XX0K&J? zG}4`M7|Wxoa+ob&^!|?c#F*B-dhDJ#uf%b$(52SBW4aK8h9aeYS}Fek$cBk>OF|y3 zx|jVJtNL}mQIBmx^q%M5XafxoAv@%~+@N%AENbOl40whCNZq7t1l^{TQ=ibm}61H~xI& z%hntRr&QWvF%6Dt|T=T^JbqqzA)SYGN689wK{;s`rW7Qb` zB9@r7)YBO1ve8Z_)2^?E+twjzDH|xGf=4(ZX*&{>w96jTI+5P@y=PrFjlhmMLT&sZ zJGw5exS7;CY;Cb?7=-%YN-46p(usZQGLj)_lryQ4xZh6q*RM{y<_s3JwQo(P#8ZPc zRULQfwA<>hT3UxMyX{d!TppPxdW^)9E!r6}pl42q=9)bc;?p~7S6V-!yo8xp-%-?e ztU}V$B2cu4^XHT^)il=g2u(7ZN+%e!#vzPOs#Q9D?lnYQ*RM*1bskQ)V;k>Q`q~%5 z+>N#(iK*1ixoSncf)I|aEpTjaz37U4+$Ld^nMx=5olh+ZQpjCt3*l*RiA<%&jwSBp z9ark#w?aBliTPuF_16A-@hZecyx-EYPES>lW~|ayvOJEy7MO&&q={Ii5~xB&3K~zD z9!hVGt#yd*kcQs7rT#u}swFw+Iwq1$V^yq=BYz0!mo$?Sf(A)=+17IrkyXXyeLBP; z2(nIzJ@@0Ok7+5V1rm~@o?1zXa&*fwC$uqXEpaJak_wq-SviEIQ;`%%nA&0x^*il7 zwAU2;DMD6ol2s|1nU+eKNMbUpFI`(nYmqkPElW;GbXsSrwRvRhjr8hzq#-{|^h^9D zF%YePGBnmTNs3ZriBSf0%$i!_658jpBeId! zBGj=VCNUHt85Sp%BQg+@5<=9JN?PFGxvmtn%2|twYUTH$5!7)ekpBR}UPT!H0GBwG zxpqL@p=yXILMVqtEUBcDUo-JEdGqBEJA1P2N9<71--OY{NvEgw%x7XLDCM~ro+QLE z7E7laM7*8j z{9`DZn{F$u<($#cKBwKS^$3-wb)0%mUtMr|=(L@X*vcfv(U(c)Eir8pk`k24N?P=W z(?%4l1<_SU5hpp$Ehdg@z3+OC{6bOWe;kv8C5bqGRwmi`*&H&iWlPD^bx*oJY4mSZv!%T|&Wl4%a4hjrgqky@tdX4MVK z>sc{z{Uwc9d)Y!Ea@%ZbsAu)()HZn_1H7v0RwMS5;3r)FGDa6gE5>xdnGLq9y zrBlc>KcvocjcJJPmbjm$CWev3_LUNGuFIs;5lYvX!x;U)(f)-t=M+hyD=Iq-bJrWOBxE>TRK!lRA@y3Opy0qLxZfG*W_H zBPmO%`X9sBo^ziK9Mt~+ifbh0>TlzS>DEg!2xd{F)qD3As!Nn1EJj9~D(mzrdga8_ zE2PKw)cfAH_MC110E!kxeeYFwNuh|PjkNxx#~!m~l`3)h^W`hU63s>aee(F^5^ zP3^s7p{M?d_r2?-O~o;7r}vJ(uX!sS4f;f%rg|o;jXC4f6?HKrHlF#;X&p_k_iv)9 zrC8gmUaG3Ll0#{wD2I?^sjV%|D&G@5MVAN%k}wUhe0Z%HvW z^p9~uEfO}X|HJ@45dZ-J1qK2K1qBBM00000009vI0uUe}5+X1$KtWMq10zszfsybN zGD4xTQewf;;WI;1V{(%5U~__#vj5ru2mt~C13v(r?#DCQ{{TXgD8DJMPhj9@%DPUI zMoG|p#&ZhI0;!@5&mjQtsLmG~tCa~0LOx@Jfe6ID&cOzS%Q-A8>2F|HbneC2Yp6gS z*y=PqvOGpue_U$U0Ihb!7>60x8>?$Dy>XwK=#%PuJa3E_A`h?qYdSd}hw|9lTO}#8 z5?%?uq*oM>i{beC@JMmjIU*$9Vry~a23ER~w{!9{$0 zphD5!IR<`Z-53nsuQ8+MaqL3;DMtI6tIv4IJI^1$YtUtf)>_ivjn`$q_WCQ>HNyMN zK+062B|paUTK9FA;O@JZlU61oPldkxFUfhXvVe3q8tYmTU)1k`%LAPDndcp|(Y2?E zc&Y?ks?PhY0ijYeJbyIA2B*GX?QX7RvfCwim{+k!0pQ$+d@Gn|lJipv?ls1*y^U6+ z+nfS1#0WX4jo4Zx(?S^!gB6<0EmulaKIjDaYB|g|Vcgr+VXV7_O=@->6*i^y@i$X?qk?M?TKHFGjdj{{WO+a8`Gv zx<(jb+7yN93itm2nX47RJ(YFr8z~YK<=ocY$GO`58$9#A>@LM)a~0kmd3G1G&{~i3 z+wI_8Ym26$@Iyj|Iu6m1|r|jo@0_K2rXIR#bp8Uo(`(6DXzz_8w!f_sYr4J@! zZFSk)>{9^?-Gh6=drn)QmuGt@*uVXrxMEv+aKClwFX7niAG4veH%8ZbKlbFi9t5Rx4km~~y06bK(27PjdhE+M)_Hb>7e-8O&>gopJl(R+;MHRebaI-R2Rm%2}e4?nR+vH%nS zBnmN&w{H8*_h57uV@O(WYTYC&HI}tr8BvaZEOL#<$)1n2>6*p-9;yIPWj#^v1|22m zKWX)12@eD#FmqP3P4^$?48Bz5EvTcej!?32>7?r+b3(E9p6s0`!_l`&&rh2Y$T{G@ z(cu>JjdT}ct&#H#S1+76{*0snlUD&xA?QnOpZ1N>^mEz5t5|oRs^Y%@4t>}jS}WKo z0!ouXw>ce?;Ejz(7=fN+b_LYQjiKtJ$*McxY_@E4F7$CQTor3SkLlQpF3t3B zgyu5mhL{BRNQ|;&0!s%n&wM^IqMkZo6?7g_KJE={yO_+Jhld}NRY@nGucK@{;*Ox= zunYZCV?XPuD%4O4|yth=ZZr+z&h zg)mc6!Me-8p`}?#fif)^JI(K;h!aSHvfhP9 za{?6$%$6YuE2>dNQ52M zsgX^!74^(PY0a#RFcMm59Shj1tcE}%QrDV^&8)j6T7yZv_i%lip8GsNc+tml3!3dr zfz$5KJvCybHVl$hYQnN6q3d4r8fU@*6A0u=;_g{b(gPFd+34?L-)q{hTo?$}L;zwI zy9?NBw6(yp8}0tnL7oawx0D_vs^>wnGBVJ4vIyRSwHl&jlQ7jdnag_t-jUfWmv?Z* zrEpS0bIFiKgVybP29Tpy9=25YV)bp+>Xf3RDXs+~rv~d)uHqxu-DECXVh%^g7#tdo z_Ne*GRE0+<)+#2uhqO}~{kbn__ku@0XheyhglZ;7n|4^E(^Xos&`t5{%Y9|e8$4^x zw-pV<7WKi1D%-MR zREftgJXi5g2o6#VWSEc5fzF)bu~t3olA%MpBUNC7lU<11{FS+xdQ4Yv#-#Cx)uUSH}*k|y%++e?)KTg6u97!2j9JMfDZ{ literal 0 HcmV?d00001 diff --git a/core/fixtures/images/sas/Pepper/2022-12-06_Rain-City_by-David-Revoy.jpg b/core/fixtures/images/sas/Pepper/2022-12-06_Rain-City_by-David-Revoy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..65c3fe4f182691adb1f5ef1c515dcee1f7d5af79 GIT binary patch literal 27752 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 CjrMi` literal 0 HcmV?d00001 diff --git a/core/fixtures/images/sas/Shichimi/2022-06-30_Asleep_by-David-Revoy.jpg b/core/fixtures/images/sas/Shichimi/2022-06-30_Asleep_by-David-Revoy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..96ecbec60200c0d857fcb8487672e6a8d4aabc8c GIT binary patch literal 31281 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

  • {% endblock %} diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 2ae9a68b..4feb039d 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6325,3 +6325,11 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." #, python-format msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" + +#: eboutic/templates/eboutic/eboutic_main.jinja:127 +msgid "Partnership Eurockéenes 2023" +msgstr "Partenariat Eurockéenes 2023" + +#: eboutic/templates/eboutic/eboutic_main.jinja:137 +msgid "You must be a contributor to access the Eurockéenes ticketing service." +msgstr "Vous devez être cotisant pour pouvoir accéder à la billetterie des Eurockéenes." From 559bfcac606ee054de544e38f18cd2004eba8096 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Wed, 8 Mar 2023 20:13:12 +0100 Subject: [PATCH 30/95] fix typo --- eboutic/templates/eboutic/eboutic_main.jinja | 4 ++-- locale/fr/LC_MESSAGES/django.po | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 74065e51..45f934bb 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -124,7 +124,7 @@

    {% trans %}There are no items available for sale{% endtrans %}

    {% endfor %} -

    {% trans %}Partnership Eurockéenes 2023{% endtrans %}

    +

    {% trans %}Partnership Eurockéennes 2023{% endtrans %}

    {% if user.is_subscribed %} Billetterie Weezevent {% else %} -
    {% trans %}You must be a contributor to access the Eurockéenes ticketing service.{% endtrans %}
    +
    {% trans %}You must be a contributor to access the Eurockéennes ticketing service.{% endtrans %}
    {% endif %}
    diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 4feb039d..75118b2d 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6327,9 +6327,9 @@ msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" #: eboutic/templates/eboutic/eboutic_main.jinja:127 -msgid "Partnership Eurockéenes 2023" -msgstr "Partenariat Eurockéenes 2023" +msgid "Partnership Eurockéennes 2023" +msgstr "Partenariat Eurockéennes 2023" #: eboutic/templates/eboutic/eboutic_main.jinja:137 -msgid "You must be a contributor to access the Eurockéenes ticketing service." -msgstr "Vous devez être cotisant pour pouvoir accéder à la billetterie des Eurockéenes." +msgid "You must be a contributor to access the Eurockéennes ticketing service." +msgstr "Vous devez être cotisant pour pouvoir accéder à la billetterie des Eurockéennes." From 0cf203669f908aff1869cbb9f65f4a61a15a4e2b Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Wed, 8 Mar 2023 20:35:24 +0100 Subject: [PATCH 31/95] fix wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théo DURR --- locale/fr/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 75118b2d..72321c61 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6331,5 +6331,5 @@ msgid "Partnership Eurockéennes 2023" msgstr "Partenariat Eurockéennes 2023" #: eboutic/templates/eboutic/eboutic_main.jinja:137 -msgid "You must be a contributor to access the Eurockéennes ticketing service." +msgid "You must be a subscriber to access the Eurockéennes ticketing service." msgstr "Vous devez être cotisant pour pouvoir accéder à la billetterie des Eurockéennes." From 5ea181829e7e83cd24d13fe1699f343d08d1ef04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=CC=81o=20DURR?= Date: Wed, 8 Mar 2023 20:47:59 +0100 Subject: [PATCH 32/95] Edited unit tests This test caused a breach in security due to the alert block displaying sensitive data. --- core/templates/core/404.jinja | 3 --- galaxy/tests.py | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/core/templates/core/404.jinja b/core/templates/core/404.jinja index 3846ed70..a777cea6 100644 --- a/core/templates/core/404.jinja +++ b/core/templates/core/404.jinja @@ -4,9 +4,6 @@

    {% trans %}404, Not Found{% endtrans %}

    -

    - {{ exception }} -

    {% endblock %} diff --git a/galaxy/tests.py b/galaxy/tests.py index 595844b2..d5957a16 100644 --- a/galaxy/tests.py +++ b/galaxy/tests.py @@ -142,8 +142,4 @@ class GalaxyTest(TestCase): Galaxy.rule() self.client.login(username="root", password="plop") response = self.client.get("/galaxy/2/") - self.assertContains( - response, - "Ce citoyen n'a pas encore rejoint la galaxie", - status_code=404, - ) + self.assertEquals(response.status_code, 404) From 25c5a3297c688122021dab307843f957beeef447 Mon Sep 17 00:00:00 2001 From: Thomas Girod Date: Thu, 9 Mar 2023 14:21:12 +0100 Subject: [PATCH 33/95] Repair NaN bug for autocomplete on counter click --- counter/static/counter/js/counter_click.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/counter/static/counter/js/counter_click.js b/counter/static/counter/js/counter_click.js index 46f22e93..7f412f85 100644 --- a/counter/static/counter/js/counter_click.js +++ b/counter/static/counter/js/counter_click.js @@ -45,7 +45,7 @@ $(function () { const code_field = $("#code_field"); let quantity = ""; - let search = ""; + // let search = ""; code_field.autocomplete({ select: function (event, ui) { event.preventDefault(); @@ -56,13 +56,13 @@ $(function () { code_field.val(quantity + ui.item.value); }, source: function (request, response) { - // by the dark magic of JS, parseInt("123abc") === 123 - quantity = parseInt(request.term); - search = request.term.slice(quantity.toString().length) - let matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i"); - response($.grep(products_autocomplete, function (value) { + 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($.grep(products_autocomplete, function(value) { value = value.tags; - return matcher.test(value); + return matcher.test( value ); })); }, }); From 6c1fa6de0bd44a21646ecccf4ae703ddacdf0f43 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Fri, 24 Mar 2023 15:32:05 +0100 Subject: [PATCH 34/95] remove-useless-queries-counter-stats (#519) --- core/static/core/style.scss | 43 +- core/templates/core/user_detail.jinja | 86 ++-- core/templatetags/renderer.py | 55 +-- core/utils.py | 15 +- counter/models.py | 87 +++- counter/templates/counter/stats.jinja | 97 ++-- counter/tests.py | 208 +++++++- counter/views.py | 93 +--- locale/fr/LC_MESSAGES/django.po | 662 +++++++++++++------------- sith/settings.py | 2 + 10 files changed, 790 insertions(+), 558 deletions(-) diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 3279d0d7..f4840082 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -1198,18 +1198,38 @@ blockquote h5:first-child { } table { - width: 100%; - font-size: 0.9em; + width: 90%; + margin: 15px auto; + border-collapse: collapse; + border-spacing: 0; + border-radius: 5px; + -moz-border-radius: 5px; + overflow: hidden; + box-shadow: rgba(60, 64, 67, .3) 0 1px 3px 0, rgba(60, 64, 67, .15) 0 4px 8px 3px; +} + +@media screen and (max-width: 500px){ + table { + width: 100%; + } } th { padding: 4px; } +td, th { + vertical-align: middle; + text-align: center; + padding: 5px 10px; + > ul { + margin-top: 0; + } +} + td { padding: 4px; margin: 5px; - border: solid 1px $primary-neutral-color; border-collapse: collapse; vertical-align: top; overflow: hidden; @@ -1219,18 +1239,29 @@ td { } } +th, thead td { + text-align: center; + border-top: none; +} + thead { - font-weight: bold; + background-color: #354a5f; + color: white; } tbody > tr { &:nth-child(even) { background: $primary-neutral-light-color; } - &:hover { + &.clickable:hover { + cursor: pointer; background: $secondary-neutral-light-color; width: 100%; } + &.highlight { + color: $primary-dark-color; + font-style: italic; + } } sup { @@ -2350,4 +2381,4 @@ $pedagogy-white-text: #f0f0f0; } } } -} +} \ No newline at end of file diff --git a/core/templates/core/user_detail.jinja b/core/templates/core/user_detail.jinja index b2e834e9..a8978229 100644 --- a/core/templates/core/user_detail.jinja +++ b/core/templates/core/user_detail.jinja @@ -5,8 +5,12 @@ {% trans user_name=profile.get_display_name() %}{{ user_name }}'s profile{% endtrans %} {% endblock %} +{% block additional_js %} + +{% endblock %} + {% block content %} -
    +
    @@ -15,7 +19,7 @@
    « {{ profile.nick_name }} »
    {% endif %} - + {% if profile.quote %}
    @@ -146,24 +150,35 @@
    {% endif %} {% if profile.was_subscribed and (user == profile or user.can_read_subscription_history)%} -
    -
    {% trans %}Subscription history{% endtrans %}
    - - - - - - - - {% for sub in profile.subscriptions.all() %} - - - - - - - {% endfor %} -
    {% trans %}Subscription start{% endtrans %}{% trans %}Subscription end{% endtrans %}{% trans %}Subscription type{% endtrans %}{% trans %}Payment method{% endtrans %}
    {{ sub.subscription_start }}{{ sub.subscription_end }}{{ sub.subscription_type }}{{ sub.get_payment_method_display() }}
    +
    +
    + + {% trans %}Subscription history{% endtrans %} + + + + +
    +
    + + + + + + + + + + {% for sub in profile.subscriptions.all() %} + + + + + + + {% endfor %} +
    {% trans %}Subscription start{% endtrans %}{% trans %}Subscription end{% endtrans %}{% trans %}Subscription type{% endtrans %}{% trans %}Payment method{% endtrans %}
    {{ sub.subscription_start }}{{ sub.subscription_end }}{{ sub.subscription_type }}{{ sub.get_payment_method_display() }}
    +
    {% endif %} @@ -177,13 +192,25 @@ {% if profile.gifts.exists() %} -
    -
    -
    {% trans %}Last given gift :{% endtrans %} {{ profile.gifts.order_by('-date').first() }}
    -
    + {% set gifts = profile.gifts.order_by("-date")|list %} +
    +
    +
    + + {% trans %}Last given gift :{% endtrans %} {{ gifts[0] }} + + + + +
    +
    @@ -229,12 +256,5 @@ $(function(){ active: false }); }); -$(function(){ - $("#drop_subscriptions").accordion({ - heightStyle: "content", - collapsible: true, - active: false - }); -}); {% endblock %} diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 656efca3..2ee19a45 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -23,11 +23,13 @@ # # +import datetime import phonenumbers from django import template from django.template.defaultfilters import stringfilter from django.utils.safestring import mark_safe +from django.utils.translation import ngettext from core.scss.processor import ScssProcessor from core.markdown import markdown as md @@ -54,41 +56,26 @@ def phonenumber(value, country="FR", format=phonenumbers.PhoneNumberFormat.NATIO return value -@register.filter() -@stringfilter -def datetime_format_python_to_PHP(python_format_string): - """ - Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible. - """ - python2PHP = { - "%a": "D", - "%a": "D", - "%A": "l", - "%b": "M", - "%B": "F", - "%c": "", - "%d": "d", - "%H": "H", - "%I": "h", - "%j": "z", - "%m": "m", - "%M": "i", - "%p": "A", - "%S": "s", - "%U": "", - "%w": "w", - "%W": "W", - "%x": "", - "%X": "", - "%y": "y", - "%Y": "Y", - "%Z": "e", - } +@register.filter(name="truncate_time") +def truncate_time(value, time_unit): + value = str(value) + return { + "millis": lambda: value.split(".")[0], + "seconds": lambda: value.rsplit(":", maxsplit=1)[0], + "minutes": lambda: value.split(":", maxsplit=1)[0], + "hours": lambda: value.rsplit(" ")[0], + }[time_unit]() - php_format_string = python_format_string - for py, php in python2PHP.items(): - php_format_string = php_format_string.replace(py, php) - return php_format_string + +@register.filter(name="format_timedelta") +def format_timedelta(value: datetime.timedelta) -> str: + days = value.days + if days == 0: + return str(value) + remainder = value - datetime.timedelta(days=days) + return ngettext( + "%(nb_days)d day, %(remainder)s", "%(nb_days)d days, %(remainder)s", days + ) % {"nb_days": days, "remainder": str(remainder)} @register.simple_tag() diff --git a/core/utils.py b/core/utils.py index bb45d726..0c5fa10c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -31,7 +31,6 @@ from datetime import date from PIL import ExifTags -# from exceptions import IOError import PIL from django.conf import settings @@ -52,14 +51,12 @@ def get_start_of_semester(d=date.today()): year = today.year start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1]) start2 = start.replace(month=(start.month + 6) % 12) - if start > start2: - start, start2 = start2, start - if today < start: - return start2.replace(year=year - 1) - elif today < start2: - return start - else: - return start2 + spring, autumn = min(start, start2), max(start, start2) + if today > autumn: # autumn semester + return autumn + if today > spring: # spring semester + return spring + return autumn.replace(year=year - 1) # autumn semester of last year def get_semester(d=date.today()): diff --git a/counter/models.py b/counter/models.py index ceec55ab..2d19cc00 100644 --- a/counter/models.py +++ b/counter/models.py @@ -22,13 +22,12 @@ # # from __future__ import annotations -from django.db.models import Sum, F from typing import Tuple from django.db import models -from django.db.models import OuterRef, Exists -from django.db.models.functions import Length +from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists +from django.db.models.functions import Concat, Length from django.utils.translation import gettext_lazy as _ from django.utils import timezone from django.conf import settings @@ -37,14 +36,14 @@ from django.core.validators import MinLengthValidator from django.forms import ValidationError from django.utils.functional import cached_property -from datetime import timedelta, date +from datetime import timedelta, date, datetime import random import string import os import base64 -import datetime from dict2xml import dict2xml +from core.utils import get_start_of_semester from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from club.models import Club, Membership from accounting.models import CurrencyField @@ -92,8 +91,9 @@ class Customer(models.Model): don't mix them) and a Product. """ subscription = self.user.subscriptions.order_by("subscription_end").last() - time_diff = date.today() - subscription.subscription_end - return subscription is not None and time_diff < timedelta(days=90) + if subscription is None: + return False + return (date.today() - subscription.subscription_end) < timedelta(days=90) @classmethod def get_or_create(cls, user: User) -> Tuple[Customer, bool]: @@ -491,7 +491,7 @@ class Counter(models.Model): """ return self.is_open() and ( (timezone.now() - self.permanencies.order_by("-activity").first().activity) - > datetime.timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE) + > timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE) ) def barman_list(self): @@ -517,6 +517,77 @@ class Counter(models.Model): is_ae_member = True return is_ae_member + def get_top_barmen(self) -> QuerySet: + """ + Return a QuerySet querying the office hours stats of all the barmen of all time + of this counter, ordered by descending number of hours. + + Each element of the QuerySet corresponds to a barman and has the following data : + - the full name (first name + last name) of the barman + - the nickname of the barman + - the promo of the barman + - the total number of office hours the barman did attend + """ + return ( + self.permanencies.exclude(end=None) + .annotate( + name=Concat(F("user__first_name"), Value(" "), F("user__last_name")) + ) + .annotate(nickname=F("user__nick_name")) + .annotate(promo=F("user__promo")) + .values("user", "name", "nickname", "promo") + .annotate(perm_sum=Sum(F("end") - F("start"))) + .exclude(perm_sum=None) + .order_by("-perm_sum") + ) + + def get_top_customers(self, since=get_start_of_semester()) -> QuerySet: + """ + Return a QuerySet querying the money spent by customers of this counter + since the specified date, ordered by descending amount of money spent. + + Each element of the QuerySet corresponds to a customer and has the following data : + - the full name (first name + last name) of the customer + - the nickname of the customer + - the amount of money spent by the customer + """ + return ( + self.sellings.filter(date__gte=since) + .annotate( + name=Concat( + F("customer__user__first_name"), + Value(" "), + F("customer__user__last_name"), + ) + ) + .annotate(nickname=F("customer__user__nick_name")) + .annotate(promo=F("customer__user__promo")) + .values("customer__user", "name", "nickname") + .annotate( + selling_sum=Sum( + F("unit_price") * F("quantity"), output_field=CurrencyField() + ) + ) + .filter(selling_sum__gt=0) + .order_by("-selling_sum") + ) + + def get_total_sales(self, since=get_start_of_semester()) -> CurrencyField: + """ + Compute and return the total turnover of this counter + since the date specified in parameter (by default, since the start of the current + semester) + :param since: timestamp from which to perform the calculation + :type since: datetime | date + :return: Total revenue earned at this counter + """ + if isinstance(since, date): + since = datetime.combine(since, datetime.min.time()) + total = self.sellings.filter(date__gte=since).aggregate( + total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField()) + )["total"] + return total if total is not None else CurrencyField(0) + class Refilling(models.Model): """ diff --git a/counter/templates/counter/stats.jinja b/counter/templates/counter/stats.jinja index 011b8987..03b7f4e0 100644 --- a/counter/templates/counter/stats.jinja +++ b/counter/templates/counter/stats.jinja @@ -2,43 +2,34 @@ {% from 'core/macros.jinja' import user_profile_link %} {% block title %} -{% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %} + {% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %} +{% endblock %} + +{% block jquery_css %} + {# Remove jquery_css #} {% endblock %} {% block content %} -

    {% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}

    +

    {% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}

    {% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %}

    - - - - - - - - + + + + + + + - {% for r in top %} - {% set customer=Customer.objects.filter(user__id=r.customer__user).first() %} - {% if customer.user == user %} - - {% else %} - - {% endif %} + {% for customer in top_customers %} + - - - - - + + + + {% endfor %} @@ -47,23 +38,20 @@

    {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %}

    {% trans %}Nb{% endtrans %}{% trans %}User{% endtrans %}{% trans %}Promo{% endtrans %}{% trans %}Clubs{% endtrans %}{% trans %}Total{% endtrans %}{% trans %}Percentage{% endtrans %}
    {% trans %}User{% endtrans %}{% trans %}Promo{% endtrans %}{% trans %}Total{% endtrans %}{% trans %}Percentage{% endtrans %}
    {{ loop.index }}{{ customer.user.get_display_name() }}{{ customer.user.promo or '' }} - {% for m in customer.user.memberships.filter(club__parent=None, end_date=None, - role__gt=settings.SITH_MAXIMUM_FREE_ROLE).all() -%} - {%- if loop.index>1 -%}, {% endif -%} - {{ m.club.name }} - {%- endfor %} - {{ r.selling_sum }} €{{ '%.2f'|format(100 * r.selling_sum / total_sellings) }}{{ customer.name }} {% if customer.nickname %} ({{ customer.nickname }}) {% endif %}{{ customer.promo or '' }}{{ "%.2f"|format(customer.selling_sum) }} €{{ '%.2f'|format(100 * customer.selling_sum / total_sellings) }}%
    - - - - - + + + + + + - {% for r in top_barman_semester %} - {% set u=User.objects.filter(id=r.user).first() %} - {% if u == user %} - - {% else %} - - {% endif %} + {% for barman in top_barman_semester %} + - - + + + {% endfor %} @@ -72,23 +60,20 @@

    {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }} (all semesters){% endtrans %}

    {% trans %}Nb{% endtrans %}{% trans %}User{% endtrans %}{% trans %}Time{% endtrans %}
    {% trans %}User{% endtrans %}{% trans %}Promo{% endtrans %}{% trans %}Time{% endtrans %}
    {{ loop.index }}{{ u.get_display_name() }}{{ r.perm_sum }}{{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %}{{ barman.promo or '' }}{{ barman.perm_sum|format_timedelta|truncate_time("millis") }}
    - - - - - + + + + + + - {% for r in top_barman %} - {% set u=User.objects.filter(id=r.user).first() %} - {% if u == user %} - - {% else %} - - {% endif %} + {% for barman in top_barman %} + - - + + + {% endfor %} diff --git a/counter/tests.py b/counter/tests.py index d8772d9e..521127ed 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -28,9 +28,13 @@ import string from django.test import TestCase from django.urls import reverse from django.core.management import call_command +from django.utils import timezone +from django.utils.timezone import timedelta +from club.models import Club from core.models import User -from counter.models import Counter, Customer, BillingInfo +from counter.models import Counter, Customer, BillingInfo, Permanency, Selling, Product +from sith.settings import SITH_MAIN_CLUB class CounterTest(TestCase): @@ -164,15 +168,211 @@ class CounterTest(TestCase): class CounterStatsTest(TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): + super().setUpClass() call_command("populate") - self.counter = Counter.objects.filter(id=2).first() + cls.counter = Counter.objects.filter(id=2).first() + cls.krophil = User.objects.get(username="krophil") + cls.skia = User.objects.get(username="skia") + cls.sli = User.objects.get(username="sli") + cls.root = User.objects.get(username="root") + cls.subscriber = User.objects.get(username="subscriber") + cls.old_subscriber = User.objects.get(username="old_subscriber") + cls.counter.sellers.add(cls.sli) + cls.counter.sellers.add(cls.root) + cls.counter.sellers.add(cls.skia) + cls.counter.sellers.add(cls.krophil) - def test_unauthorised_user_fail(self): + barbar = Product.objects.get(code="BARB") + + # remove everything to make sure the fixtures bring no side effect + Permanency.objects.all().delete() + Selling.objects.all().delete() + + now = timezone.now() + # total of sli : 5 hours + Permanency.objects.create( + user=cls.sli, start=now, end=now + timedelta(hours=1), counter=cls.counter + ) + Permanency.objects.create( + user=cls.sli, + start=now + timedelta(hours=4), + end=now + timedelta(hours=6), + counter=cls.counter, + ) + Permanency.objects.create( + user=cls.sli, + start=now + timedelta(hours=7), + end=now + timedelta(hours=9), + counter=cls.counter, + ) + + # total of skia : 16 days, 2 hours, 35 minutes and 54 seconds + Permanency.objects.create( + user=cls.skia, start=now, end=now + timedelta(hours=1), counter=cls.counter + ) + Permanency.objects.create( + user=cls.skia, + start=now + timedelta(days=4, hours=1), + end=now + timedelta(days=20, hours=2, minutes=35, seconds=54), + counter=cls.counter, + ) + + # total of root : 1 hour + 20 hours (but the 20 hours were on last year) + Permanency.objects.create( + user=cls.root, + start=now + timedelta(days=5), + end=now + timedelta(days=5, hours=1), + counter=cls.counter, + ) + Permanency.objects.create( + user=cls.root, + start=now - timedelta(days=300, hours=20), + end=now - timedelta(days=300), + counter=cls.counter, + ) + + # total of krophil : 0 hour + s = Selling( + label=barbar.name, + product=barbar, + club=Club.objects.get(name=SITH_MAIN_CLUB["name"]), + counter=cls.counter, + unit_price=2, + seller=cls.skia, + ) + + krophil_customer = Customer.get_or_create(cls.krophil)[0] + sli_customer = Customer.get_or_create(cls.sli)[0] + skia_customer = Customer.get_or_create(cls.skia)[0] + root_customer = Customer.get_or_create(cls.root)[0] + + # moderate drinker. Total : 100 € + s.quantity = 50 + s.customer = krophil_customer + s.save(allow_negative=True) + + # Sli is a drunkard. Total : 2000 € + s.quantity = 100 + s.customer = sli_customer + for _ in range(10): + # little trick to make sure the instance is duplicated in db + s.pk = None + s.save(allow_negative=True) # save ten different sales + + # Skia is a heavy drinker too. Total : 1000 € + s.customer = skia_customer + for _ in range(5): + s.pk = None + s.save(allow_negative=True) + + # Root is quite an abstemious one. Total : 2 € + s.pk = None + s.quantity = 1 + s.customer = root_customer + s.save(allow_negative=True) + + def test_not_authenticated_user_fail(self): # Test with not login user response = self.client.get(reverse("counter:stats", args=[self.counter.id])) self.assertTrue(response.status_code == 403) + def test_unauthorized_user_fails(self): + user = User.objects.get(username="public") + self.client.login(username=user.username, password="plop") + response = self.client.get(reverse("counter:stats", args=[self.counter.id])) + self.assertTrue(response.status_code == 403) + + def test_get_total_sales(self): + """ + Test the result of the Counter.get_total_sales() method + """ + total = self.counter.get_total_sales() + self.assertEqual(total, 3102) + + def test_top_barmen(self): + """ + Test the result of Counter.get_top_barmen() is correct + """ + top = iter(self.counter.get_top_barmen()) + self.assertEqual( + next(top), + { + "user": self.skia.id, + "name": f"{self.skia.first_name} {self.skia.last_name}", + "promo": self.skia.promo, + "nickname": self.skia.nick_name, + "perm_sum": timedelta(days=16, hours=2, minutes=35, seconds=54), + }, + ) + self.assertEqual( + next(top), + { + "user": self.root.id, + "name": f"{self.root.first_name} {self.root.last_name}", + "promo": self.root.promo, + "nickname": self.root.nick_name, + "perm_sum": timedelta(hours=21), + }, + ) + self.assertEqual( + next(top), + { + "user": self.sli.id, + "name": f"{self.sli.first_name} {self.sli.last_name}", + "promo": self.sli.promo, + "nickname": self.sli.nick_name, + "perm_sum": timedelta(hours=5), + }, + ) + self.assertIsNone( + next(top, None), msg="barmen with no office hours should not be in the top" + ) + + def test_top_customer(self): + """ + Test the result of Counter.get_top_customers() is correct + """ + top = iter(self.counter.get_top_customers()) + self.assertEqual( + next(top), + { + "customer__user": self.sli.id, + "name": f"{self.sli.first_name} {self.sli.last_name}", + "nickname": self.sli.nick_name, + "selling_sum": 2000, + }, + ) + self.assertEqual( + next(top), + { + "customer__user": self.skia.id, + "name": f"{self.skia.first_name} {self.skia.last_name}", + "nickname": self.skia.nick_name, + "selling_sum": 1000, + }, + ) + self.assertEqual( + next(top), + { + "customer__user": self.krophil.id, + "name": f"{self.krophil.first_name} {self.krophil.last_name}", + "nickname": self.krophil.nick_name, + "selling_sum": 100, + }, + ) + self.assertEqual( + next(top), + { + "customer__user": self.root.id, + "name": f"{self.root.first_name} {self.root.last_name}", + "nickname": self.root.nick_name, + "selling_sum": 2, + }, + ) + self.assertIsNone(next(top, None)) + class BillingInfoTest(TestCase): @classmethod diff --git a/counter/views.py b/counter/views.py index 2b9bf392..e4cbd120 100644 --- a/counter/views.py +++ b/counter/views.py @@ -48,13 +48,15 @@ from django.utils import timezone from django import forms from django.utils.translation import gettext_lazy as _ from django.conf import settings -from django.db import DataError, transaction, models +from django.db import DataError, transaction +import json import re import pytz -from datetime import date, timedelta, datetime +from datetime import timedelta, datetime from http import HTTPStatus +from core.utils import get_start_of_semester from core.views import CanViewMixin, TabedViewMixin, CanEditMixin from core.views.forms import LoginForm from core.models import User @@ -68,7 +70,6 @@ from counter.forms import ( CashSummaryFormBase, EticketForm, ) -from subscription.models import Subscription from counter.models import ( Counter, Customer, @@ -80,7 +81,6 @@ from counter.models import ( CashRegisterSummary, CashRegisterSummaryItem, Eticket, - Permanency, BillingInfo, ) from accounting.models import CurrencyField @@ -1365,80 +1365,21 @@ class CounterStatView(DetailView, CounterAdminMixin): def get_context_data(self, **kwargs): """Add stats to the context""" - from django.db.models import Sum, Case, When, F - + counter = self.object + semester_start = get_start_of_semester() + office_hours = counter.get_top_barmen() kwargs = super(CounterStatView, self).get_context_data(**kwargs) - kwargs["Customer"] = Customer - kwargs["User"] = User - semester_start = Subscription.compute_start(d=date.today(), duration=3) - kwargs["total_sellings"] = Selling.objects.filter( - date__gte=semester_start, counter=self.object - ).aggregate( - total_sellings=Sum( - F("quantity") * F("unit_price"), output_field=CurrencyField() - ) - )[ - "total_sellings" - ] - kwargs["top"] = ( - Selling.objects.values("customer__user") - .annotate( - selling_sum=Sum( - Case( - When( - counter=self.object, - date__gte=semester_start, - unit_price__gt=0, - then=F("unit_price") * F("quantity"), - ), - output_field=CurrencyField(), - ) - ) - ) - .exclude(selling_sum=None) - .order_by("-selling_sum") - .all()[:100] + kwargs.update( + { + "counter": counter, + "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] + ), + } ) - kwargs["top_barman"] = ( - Permanency.objects.values("user") - .annotate( - perm_sum=Sum( - Case( - When( - counter=self.object, - end__gt=datetime(year=1999, month=1, day=1), - then=F("end") - F("start"), - ), - output_field=models.DateTimeField(), - ) - ) - ) - .exclude(perm_sum=None) - .order_by("-perm_sum") - .all()[:100] - ) - kwargs["top_barman_semester"] = ( - Permanency.objects.values("user") - .annotate( - perm_sum=Sum( - Case( - When( - counter=self.object, - start__gt=semester_start, - end__gt=datetime(year=1999, month=1, day=1), - then=F("end") - F("start"), - ), - output_field=models.DateTimeField(), - ) - ) - ) - .exclude(perm_sum=None) - .order_by("-perm_sum") - .all()[:100] - ) - - kwargs["sith_date"] = settings.SITH_START_DATE[0] - kwargs["semester_start"] = semester_start return kwargs def dispatch(self, request, *args, **kwargs): diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index bc9dccc4..977437bc 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: 2023-03-02 11:02+0100\n" +"POT-Creation-Date: 2023-03-07 23:04+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -18,8 +18,8 @@ msgstr "" #: accounting/models.py:61 accounting/models.py:110 accounting/models.py:143 #: accounting/models.py:216 club/models.py:48 com/models.py:279 -#: com/models.py:296 counter/models.py:196 counter/models.py:229 -#: counter/models.py:317 forum/models.py:58 launderette/models.py:38 +#: com/models.py:296 counter/models.py:222 counter/models.py:255 +#: counter/models.py:369 forum/models.py:58 launderette/models.py:38 #: launderette/models.py:93 launderette/models.py:131 stock/models.py:40 #: stock/models.py:63 stock/models.py:105 stock/models.py:133 msgid "name" @@ -66,8 +66,8 @@ msgid "account number" msgstr "numero de compte" #: accounting/models.py:116 accounting/models.py:147 club/models.py:275 -#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:247 -#: counter/models.py:319 trombi/models.py:217 +#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:273 +#: counter/models.py:371 trombi/models.py:217 msgid "club" msgstr "club" @@ -88,12 +88,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:214 club/models.py:281 counter/models.py:753 +#: accounting/models.py:214 club/models.py:281 counter/models.py:878 #: election/models.py:18 launderette/models.py:194 msgid "start date" msgstr "date de début" -#: accounting/models.py:215 club/models.py:282 counter/models.py:754 +#: accounting/models.py:215 club/models.py:282 counter/models.py:879 #: election/models.py:19 msgid "end date" msgstr "date de fin" @@ -106,8 +106,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:225 accounting/models.py:289 counter/models.py:60 -#: counter/models.py:475 +#: accounting/models.py:225 accounting/models.py:289 counter/models.py:64 +#: counter/models.py:600 msgid "amount" msgstr "montant" @@ -129,19 +129,19 @@ msgstr "classeur" #: accounting/models.py:290 core/models.py:862 core/models.py:1400 #: core/models.py:1448 core/models.py:1477 core/models.py:1501 -#: counter/models.py:485 counter/models.py:578 counter/models.py:789 +#: counter/models.py:610 counter/models.py:703 counter/models.py:914 #: eboutic/models.py:67 eboutic/models.py:236 forum/models.py:311 #: forum/models.py:408 stock/models.py:104 msgid "date" msgstr "date" -#: accounting/models.py:291 counter/models.py:198 counter/models.py:790 +#: accounting/models.py:291 counter/models.py:224 counter/models.py:915 #: pedagogy/models.py:219 stock/models.py:107 msgid "comment" msgstr "commentaire" -#: accounting/models.py:293 counter/models.py:487 counter/models.py:580 -#: subscription/models.py:66 +#: accounting/models.py:293 counter/models.py:612 counter/models.py:705 +#: subscription/models.py:65 msgid "payment method" msgstr "méthode de paiement" @@ -149,7 +149,7 @@ msgstr "méthode de paiement" msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:303 eboutic/models.py:328 +#: accounting/models.py:303 eboutic/models.py:329 msgid "invoice" msgstr "facture" @@ -167,7 +167,7 @@ msgstr "type comptable" #: accounting/models.py:328 accounting/models.py:475 accounting/models.py:510 #: accounting/models.py:545 core/models.py:1476 core/models.py:1502 -#: counter/models.py:544 +#: counter/models.py:669 msgid "label" msgstr "étiquette" @@ -180,9 +180,9 @@ msgstr "type de cible" #: club/templates/club/club_old_members.jinja:8 #: club/templates/club/mailing.jinja:41 #: counter/templates/counter/cash_summary_list.jinja:32 -#: counter/templates/counter/stats.jinja:15 -#: counter/templates/counter/stats.jinja:52 -#: counter/templates/counter/stats.jinja:77 +#: counter/templates/counter/stats.jinja:19 +#: counter/templates/counter/stats.jinja:43 +#: counter/templates/counter/stats.jinja:65 #: launderette/templates/launderette/launderette_admin.jinja:44 msgid "User" msgstr "Utilisateur" @@ -211,7 +211,7 @@ msgstr "Utilisateur" msgid "Club" msgstr "Club" -#: accounting/models.py:339 core/views/user.py:279 +#: accounting/models.py:339 core/views/user.py:277 msgid "Account" msgstr "Compte" @@ -219,7 +219,7 @@ msgstr "Compte" msgid "Company" msgstr "Entreprise" -#: accounting/models.py:341 core/models.py:230 sith/settings.py:391 +#: accounting/models.py:341 core/models.py:230 sith/settings.py:393 #: stock/templates/stock/shopping_list_items.jinja:37 msgid "Other" msgstr "Autre" @@ -266,7 +266,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:467 counter/models.py:239 pedagogy/models.py:46 +#: accounting/models.py:467 counter/models.py:265 pedagogy/models.py:46 msgid "code" msgstr "code" @@ -426,7 +426,7 @@ msgstr "Nouveau compte club" #: com/templates/com/weekmail.jinja:61 core/templates/core/file.jinja:38 #: core/templates/core/group_list.jinja:24 core/templates/core/page.jinja:35 #: core/templates/core/poster_list.jinja:40 -#: core/templates/core/user_tools.jinja:43 core/views/user.py:229 +#: core/templates/core/user_tools.jinja:43 core/views/user.py:227 #: counter/templates/counter/cash_summary_list.jinja:53 #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:33 @@ -530,7 +530,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:437 +#: sith/settings.py:439 msgid "Closed" msgstr "Fermé" @@ -599,7 +599,7 @@ msgstr "Classeur : " #: accounting/templates/accounting/journal_statement_accounting.jinja:30 #: core/templates/core/user_account.jinja:38 #: core/templates/core/user_account_detail.jinja:10 -#: counter/templates/counter/counter_click.jinja:26 +#: counter/templates/counter/counter_click.jinja:32 msgid "Amount: " msgstr "Montant : " @@ -617,9 +617,6 @@ msgid "New operation" msgstr "Nouvelle opération" #: accounting/templates/accounting/journal_details.jinja:32 -#: counter/templates/counter/stats.jinja:14 -#: counter/templates/counter/stats.jinja:51 -#: counter/templates/counter/stats.jinja:76 msgid "Nb" msgstr "No" @@ -670,7 +667,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1072 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1084 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:138 #: trombi/templates/trombi/comment.jinja:4 @@ -967,15 +964,15 @@ msgstr "Date de fin" #: club/forms.py:166 club/templates/club/club_sellings.jinja:21 #: core/templates/core/user_account_detail.jinja:18 #: core/templates/core/user_account_detail.jinja:51 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:156 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:158 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:174 counter/views.py:770 +#: club/forms.py:174 counter/views.py:782 msgid "Products" msgstr "Produits" -#: club/forms.py:179 counter/views.py:775 +#: club/forms.py:179 counter/views.py:787 msgid "Archived products" msgstr "Produits archivés" @@ -1045,7 +1042,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:267 counter/models.py:744 counter/models.py:780 +#: club/models.py:267 counter/models.py:869 counter/models.py:905 #: eboutic/models.py:63 eboutic/models.py:232 election/models.py:192 #: launderette/models.py:145 launderette/models.py:213 sas/models.py:244 #: trombi/models.py:213 @@ -1057,8 +1054,8 @@ msgstr "nom d'utilisateur" msgid "role" msgstr "rôle" -#: club/models.py:289 core/models.py:81 counter/models.py:197 -#: counter/models.py:230 election/models.py:15 election/models.py:120 +#: club/models.py:289 core/models.py:81 counter/models.py:223 +#: counter/models.py:256 election/models.py:15 election/models.py:120 #: election/models.py:197 forum/models.py:59 forum/models.py:240 msgid "description" msgstr "description" @@ -1209,7 +1206,7 @@ msgid "Barman" msgstr "Barman" #: club/templates/club/club_sellings.jinja:23 -#: counter/templates/counter/counter_click.jinja:23 +#: counter/templates/counter/counter_click.jinja:29 #: counter/templates/counter/last_ops.jinja:22 #: counter/templates/counter/last_ops.jinja:47 #: counter/templates/counter/refilling_list.jinja:12 @@ -1228,7 +1225,7 @@ msgstr "Quantité" #: core/templates/core/user_account_detail.jinja:22 #: counter/templates/counter/cash_summary_list.jinja:35 #: counter/templates/counter/last_ops.jinja:50 -#: counter/templates/counter/stats.jinja:18 +#: counter/templates/counter/stats.jinja:21 #: subscription/templates/subscription/stats.jinja:40 #: subscription/templates/subscription/stats.jinja:48 msgid "Total" @@ -1362,7 +1359,7 @@ msgstr "Anciens membres" msgid "History" msgstr "Historique" -#: club/views.py:125 core/templates/core/base.jinja:123 core/views/user.py:222 +#: club/views.py:125 core/templates/core/base.jinja:129 core/views/user.py:220 #: sas/templates/sas/picture.jinja:95 trombi/views.py:63 msgid "Tools" msgstr "Outils" @@ -1645,7 +1642,7 @@ msgid "Calls to moderate" msgstr "Appels à modérer" #: com/templates/com/news_admin_list.jinja:242 -#: core/templates/core/base.jinja:177 +#: core/templates/core/base.jinja:183 msgid "Events" msgstr "Événements" @@ -2404,7 +2401,7 @@ msgstr "Connexion" msgid "Register" msgstr "S'enregister" -#: core/templates/core/base.jinja:85 core/templates/core/base.jinja:86 +#: core/templates/core/base.jinja:91 core/templates/core/base.jinja:92 #: forum/templates/forum/macros.jinja:171 #: forum/templates/forum/macros.jinja:175 #: matmat/templates/matmat/search_form.jinja:37 @@ -2414,64 +2411,64 @@ msgstr "S'enregister" msgid "Search" msgstr "Recherche" -#: core/templates/core/base.jinja:112 +#: core/templates/core/base.jinja:118 msgid "View more" msgstr "Voir plus" -#: core/templates/core/base.jinja:116 +#: core/templates/core/base.jinja:122 #: forum/templates/forum/last_unread.jinja:17 msgid "Mark all as read" msgstr "Marquer tout commme lu" -#: core/templates/core/base.jinja:126 +#: core/templates/core/base.jinja:132 msgid "Logout" msgstr "Déconnexion" -#: core/templates/core/base.jinja:161 +#: core/templates/core/base.jinja:167 msgid "Main" msgstr "Accueil" -#: core/templates/core/base.jinja:163 +#: core/templates/core/base.jinja:169 msgid "Associations & Clubs" msgstr "Associations & Clubs" -#: core/templates/core/base.jinja:167 +#: core/templates/core/base.jinja:173 msgid "AE" msgstr "L'AE" -#: core/templates/core/base.jinja:168 +#: core/templates/core/base.jinja:174 msgid "AE's clubs" msgstr "Les clubs de L'AE" -#: core/templates/core/base.jinja:169 +#: core/templates/core/base.jinja:175 msgid "BdF" msgstr "Le BdF" -#: core/templates/core/base.jinja:170 +#: core/templates/core/base.jinja:176 msgid "BDS" msgstr "Le BDS" -#: core/templates/core/base.jinja:171 +#: core/templates/core/base.jinja:177 msgid "CETU" msgstr "Le CETU" -#: core/templates/core/base.jinja:172 +#: core/templates/core/base.jinja:178 msgid "Doceo" msgstr "Doceo" -#: core/templates/core/base.jinja:173 +#: core/templates/core/base.jinja:179 msgid "Positions" msgstr "Postes à pourvoir" -#: core/templates/core/base.jinja:181 core/templates/core/user_tools.jinja:118 +#: core/templates/core/base.jinja:187 core/templates/core/user_tools.jinja:118 msgid "Elections" msgstr "Élections" -#: core/templates/core/base.jinja:182 +#: core/templates/core/base.jinja:188 msgid "Big event" msgstr "Grandes Activités" -#: core/templates/core/base.jinja:185 +#: core/templates/core/base.jinja:191 #: forum/templates/forum/favorite_topics.jinja:14 #: forum/templates/forum/last_unread.jinja:14 #: forum/templates/forum/macros.jinja:90 forum/templates/forum/main.jinja:6 @@ -2480,89 +2477,89 @@ msgstr "Grandes Activités" msgid "Forum" msgstr "Forum" -#: core/templates/core/base.jinja:186 +#: core/templates/core/base.jinja:192 msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:187 counter/models.py:327 +#: core/templates/core/base.jinja:193 counter/models.py:379 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:23 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:390 sith/settings.py:398 +#: sith/settings.py:392 sith/settings.py:400 msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:189 +#: core/templates/core/base.jinja:195 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:193 +#: core/templates/core/base.jinja:199 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:194 launderette/models.py:47 +#: core/templates/core/base.jinja:200 launderette/models.py:47 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:195 core/templates/core/file.jinja:20 +#: core/templates/core/base.jinja:201 core/templates/core/file.jinja:20 #: core/views/files.py:86 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:196 core/templates/core/user_tools.jinja:109 +#: core/templates/core/base.jinja:202 core/templates/core/user_tools.jinja:109 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:200 +#: core/templates/core/base.jinja:206 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:204 +#: core/templates/core/base.jinja:210 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:205 +#: core/templates/core/base.jinja:211 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:209 +#: core/templates/core/base.jinja:215 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:213 +#: core/templates/core/base.jinja:219 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:214 core/templates/core/base.jinja:256 +#: core/templates/core/base.jinja:220 core/templates/core/base.jinja:262 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:215 +#: core/templates/core/base.jinja:221 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:257 +#: core/templates/core/base.jinja:263 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:258 +#: core/templates/core/base.jinja:264 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:259 +#: core/templates/core/base.jinja:265 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:260 +#: core/templates/core/base.jinja:266 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:262 +#: core/templates/core/base.jinja:268 msgid "Site made by good people" msgstr "Site réalisé par des gens bons" @@ -2591,7 +2588,7 @@ msgstr "Confirmation" #: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/file_delete_confirm.jinja:14 -#: counter/templates/counter/counter_click.jinja:107 +#: counter/templates/counter/counter_click.jinja:121 msgid "Cancel" msgstr "Annuler" @@ -3012,8 +3009,7 @@ msgstr "Résultat de la recherche" msgid "Users" msgstr "Utilisateurs" -#: core/templates/core/search.jinja:18 core/views/user.py:244 -#: counter/templates/counter/stats.jinja:17 +#: core/templates/core/search.jinja:18 core/views/user.py:242 msgid "Clubs" msgstr "Clubs" @@ -3070,7 +3066,7 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:57 -#: core/templates/core/user_tools.jinja:37 counter/views.py:787 +#: core/templates/core/user_tools.jinja:37 counter/views.py:807 msgid "Etickets" msgstr "Etickets" @@ -3259,7 +3255,7 @@ msgstr "Voir l'arbre des ancêtres" msgid "No godfathers / godmothers" msgstr "Pas de famille" -#: core/templates/core/user_godfathers.jinja:25 core/views/user.py:464 +#: core/templates/core/user_godfathers.jinja:25 core/views/user.py:462 msgid "Godchildren" msgstr "Fillots / Fillotes" @@ -3328,7 +3324,7 @@ msgid "Error downloading your pictures" msgstr "Erreur de téléchargement de vos photos" #: core/templates/core/user_preferences.jinja:4 -#: core/templates/core/user_preferences.jinja:8 core/views/user.py:236 +#: core/templates/core/user_preferences.jinja:8 core/views/user.py:234 msgid "Preferences" msgstr "Préférences" @@ -3381,7 +3377,7 @@ msgstr "Achats" msgid "Product top 10" msgstr "Top 10 produits" -#: core/templates/core/user_stats.jinja:27 counter/forms.py:168 +#: core/templates/core/user_stats.jinja:27 counter/forms.py:176 msgid "Product" msgstr "Produit" @@ -3398,7 +3394,7 @@ msgstr "Outils utilisateurs" msgid "Sith management" msgstr "Gestion de Sith" -#: core/templates/core/user_tools.jinja:14 core/views/user.py:252 +#: core/templates/core/user_tools.jinja:14 core/views/user.py:250 msgid "Groups" msgstr "Groupes" @@ -3427,7 +3423,7 @@ msgid "Subscription stats" msgstr "Statistiques de cotisation" #: core/templates/core/user_tools.jinja:29 counter/forms.py:139 -#: counter/views.py:765 +#: counter/views.py:777 msgid "Counters" msgstr "Comptoirs" @@ -3444,16 +3440,16 @@ msgid "Product types management" msgstr "Gestion des types de produit" #: core/templates/core/user_tools.jinja:35 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:785 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:797 msgid "Cash register summaries" msgstr "Relevés de caisse" #: core/templates/core/user_tools.jinja:36 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:790 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:802 msgid "Invoices call" msgstr "Appels à facture" -#: core/templates/core/user_tools.jinja:44 core/views/user.py:270 +#: core/templates/core/user_tools.jinja:44 core/views/user.py:268 #: counter/templates/counter/counter_list.jinja:18 #: counter/templates/counter/counter_list.jinja:34 #: counter/templates/counter/counter_list.jinja:56 @@ -3547,6 +3543,13 @@ msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown" msgid "Trombi tools" msgstr "Outils Trombi" +#: core/templatetags/renderer.py:77 +#, python-format +msgid "%(nb_days)d day, %(remainder)s" +msgid_plural "%(nb_days)d days, %(remainder)s" +msgstr[0] "" +msgstr[1] "" + #: core/views/files.py:82 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" @@ -3700,7 +3703,7 @@ msgstr "Utilisateurs à retirer du groupe" msgid "Users to add to group" msgstr "Utilisateurs à ajouter au groupe" -#: core/views/user.py:202 core/views/user.py:466 core/views/user.py:468 +#: core/views/user.py:202 core/views/user.py:464 core/views/user.py:466 msgid "Family" msgstr "Famille" @@ -3709,32 +3712,32 @@ msgstr "Famille" msgid "Pictures" msgstr "Photos" -#: core/views/user.py:217 +#: core/views/user.py:215 msgid "Galaxy" msgstr "Galaxie" -#: core/views/user.py:610 +#: core/views/user.py:608 msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/app.py:31 counter/models.py:340 counter/models.py:749 -#: counter/models.py:779 launderette/models.py:41 stock/models.py:43 +#: counter/app.py:31 counter/models.py:395 counter/models.py:875 +#: counter/models.py:911 launderette/models.py:41 stock/models.py:43 msgid "counter" msgstr "comptoir" -#: counter/forms.py:30 +#: counter/forms.py:38 msgid "This UID is invalid" msgstr "Cet UID est invalide" -#: counter/forms.py:69 +#: counter/forms.py:77 msgid "User not found" msgstr "Utilisateur non trouvé" -#: counter/forms.py:117 +#: counter/forms.py:125 msgid "Parent product" msgstr "Produit parent" -#: counter/forms.py:123 +#: counter/forms.py:131 msgid "Buying groups" msgstr "Groupes d'achat" @@ -3742,165 +3745,165 @@ msgstr "Groupes d'achat" msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:59 +#: counter/models.py:63 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:61 +#: counter/models.py:65 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:64 +#: counter/models.py:68 msgid "customer" msgstr "client" -#: counter/models.py:65 +#: counter/models.py:69 msgid "customers" msgstr "clients" -#: counter/models.py:126 counter/views.py:309 +#: counter/models.py:148 counter/views.py:319 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:157 +#: counter/models.py:183 msgid "First name" msgstr "Prénom" -#: counter/models.py:158 +#: counter/models.py:184 msgid "Last name" msgstr "Nom de famille" -#: counter/models.py:159 +#: counter/models.py:185 msgid "Address 1" msgstr "Adresse 1" -#: counter/models.py:161 +#: counter/models.py:186 msgid "Address 2" msgstr "Adresse 2" -#: counter/models.py:163 +#: counter/models.py:187 msgid "Zip code" msgstr "Code postal" -#: counter/models.py:164 +#: counter/models.py:188 msgid "City" msgstr "Ville" -#: counter/models.py:165 +#: counter/models.py:189 msgid "Country" msgstr "Pays" -#: counter/models.py:209 counter/models.py:237 +#: counter/models.py:232 counter/models.py:260 msgid "product type" msgstr "type du produit" -#: counter/models.py:243 +#: counter/models.py:266 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:244 +#: counter/models.py:267 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:245 +#: counter/models.py:268 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:247 +#: counter/models.py:270 msgid "icon" msgstr "icône" -#: counter/models.py:252 +#: counter/models.py:275 msgid "limit age" msgstr "âge limite" -#: counter/models.py:253 +#: counter/models.py:276 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:257 +#: counter/models.py:280 msgid "parent product" msgstr "produit parent" -#: counter/models.py:263 +#: counter/models.py:286 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:265 election/models.py:52 +#: counter/models.py:288 election/models.py:52 msgid "archived" msgstr "archivé" -#: counter/models.py:268 counter/models.py:874 +#: counter/models.py:291 counter/models.py:1006 msgid "product" msgstr "produit" -#: counter/models.py:321 +#: counter/models.py:374 msgid "products" msgstr "produits" -#: counter/models.py:324 +#: counter/models.py:377 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:326 +#: counter/models.py:379 msgid "Bar" msgstr "Bar" -#: counter/models.py:326 +#: counter/models.py:379 msgid "Office" msgstr "Bureau" -#: counter/models.py:329 +#: counter/models.py:382 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:337 launderette/models.py:207 +#: counter/models.py:390 launderette/models.py:207 msgid "token" msgstr "jeton" -#: counter/models.py:492 +#: counter/models.py:618 msgid "bank" msgstr "banque" -#: counter/models.py:494 counter/models.py:584 +#: counter/models.py:620 counter/models.py:710 msgid "is validated" msgstr "est validé" -#: counter/models.py:497 +#: counter/models.py:623 msgid "refilling" msgstr "rechargement" -#: counter/models.py:561 eboutic/models.py:292 +#: counter/models.py:687 eboutic/models.py:289 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:562 counter/models.py:859 eboutic/models.py:293 +#: counter/models.py:688 counter/models.py:991 eboutic/models.py:290 msgid "quantity" msgstr "quantité" -#: counter/models.py:581 +#: counter/models.py:707 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:581 sith/settings.py:359 sith/settings.py:364 -#: sith/settings.py:384 +#: counter/models.py:707 sith/settings.py:385 sith/settings.py:390 +#: sith/settings.py:410 msgid "Credit card" msgstr "Carte bancaire" -#: counter/models.py:587 +#: counter/models.py:713 msgid "selling" msgstr "vente" -#: counter/models.py:614 +#: counter/models.py:740 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:615 +#: counter/models.py:741 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:617 counter/models.py:640 +#: counter/models.py:743 counter/models.py:766 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3912,59 +3915,59 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:754 +#: counter/models.py:880 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:757 +#: counter/models.py:883 msgid "permanency" msgstr "permanence" -#: counter/models.py:784 +#: counter/models.py:916 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:787 +#: counter/models.py:919 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:855 +#: counter/models.py:987 msgid "cash summary" msgstr "relevé" -#: counter/models.py:858 +#: counter/models.py:990 msgid "value" msgstr "valeur" -#: counter/models.py:860 +#: counter/models.py:992 msgid "check" msgstr "chèque" -#: counter/models.py:863 +#: counter/models.py:995 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:878 +#: counter/models.py:1010 msgid "banner" msgstr "bannière" -#: counter/models.py:880 +#: counter/models.py:1012 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:882 +#: counter/models.py:1014 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:884 +#: counter/models.py:1016 msgid "secret" msgstr "secret" -#: counter/models.py:940 +#: counter/models.py:1072 msgid "uid" msgstr "uid" -#: counter/models.py:945 +#: counter/models.py:1077 msgid "student cards" msgstr "cartes étudiante" @@ -4016,7 +4019,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1065 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1085 msgid "Emptied" msgstr "Coffre vidé" @@ -4028,17 +4031,17 @@ msgstr "oui" 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:30 +#: counter/templates/counter/counter_click.jinja:36 msgid "Add a student card" msgstr "Ajouter une carte étudiante" -#: counter/templates/counter/counter_click.jinja:33 +#: counter/templates/counter/counter_click.jinja:39 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:35 -#: counter/templates/counter/counter_click.jinja:63 -#: counter/templates/counter/counter_click.jinja:117 +#: counter/templates/counter/counter_click.jinja:41 +#: counter/templates/counter/counter_click.jinja:67 +#: counter/templates/counter/counter_click.jinja:132 #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 @@ -4047,41 +4050,33 @@ msgstr "Ce n'est pas un UID de carte étudiante valide" msgid "Go" msgstr "Valider" -#: counter/templates/counter/counter_click.jinja:37 +#: counter/templates/counter/counter_click.jinja:43 msgid "Registered cards" msgstr "Cartes enregistrées" -#: counter/templates/counter/counter_click.jinja:45 +#: counter/templates/counter/counter_click.jinja:51 msgid "No card registered" msgstr "Aucune carte enregistrée" -#: counter/templates/counter/counter_click.jinja:50 +#: counter/templates/counter/counter_click.jinja:56 #: launderette/templates/launderette/launderette_admin.jinja:8 msgid "Selling" msgstr "Vente" -#: counter/templates/counter/counter_click.jinja:65 +#: counter/templates/counter/counter_click.jinja:74 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:20 msgid "Basket: " msgstr "Panier : " -#: counter/templates/counter/counter_click.jinja:102 +#: counter/templates/counter/counter_click.jinja:115 msgid "Finish" msgstr "Terminer" -#: counter/templates/counter/counter_click.jinja:111 +#: counter/templates/counter/counter_click.jinja:125 #: counter/templates/counter/refilling_list.jinja:9 msgid "Refilling" msgstr "Rechargement" -#: counter/templates/counter/counter_click.jinja:193 counter/views.py:578 -msgid "END" -msgstr "FIN" - -#: counter/templates/counter/counter_click.jinja:193 counter/views.py:580 -msgid "CAN" -msgstr "ANN" - #: counter/templates/counter/counter_list.jinja:4 #: counter/templates/counter/counter_list.jinja:10 msgid "Counter admin list" @@ -4211,142 +4206,144 @@ msgid "Seller" msgstr "Vendeur" #: counter/templates/counter/stats.jinja:5 -#: counter/templates/counter/stats.jinja:9 +#: counter/templates/counter/stats.jinja:13 #, python-format msgid "%(counter_name)s stats" msgstr "Stats sur %(counter_name)s" -#: counter/templates/counter/stats.jinja:10 +#: counter/templates/counter/stats.jinja:14 #, python-format msgid "Top 100 %(counter_name)s" msgstr "Top 100 %(counter_name)s" -#: counter/templates/counter/stats.jinja:16 +#: counter/templates/counter/stats.jinja:20 +#: counter/templates/counter/stats.jinja:44 +#: counter/templates/counter/stats.jinja:66 msgid "Promo" msgstr "Promo" -#: counter/templates/counter/stats.jinja:19 +#: counter/templates/counter/stats.jinja:22 msgid "Percentage" msgstr "Pourcentage" -#: counter/templates/counter/stats.jinja:47 +#: counter/templates/counter/stats.jinja:38 #, python-format msgid "Top 100 barman %(counter_name)s" msgstr "Top 100 barman %(counter_name)s" -#: counter/templates/counter/stats.jinja:53 -#: counter/templates/counter/stats.jinja:78 +#: counter/templates/counter/stats.jinja:45 +#: counter/templates/counter/stats.jinja:67 msgid "Time" msgstr "Temps" -#: counter/templates/counter/stats.jinja:72 +#: counter/templates/counter/stats.jinja:60 #, python-format msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:167 +#: counter/views.py:177 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:181 +#: counter/views.py:191 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:196 +#: counter/views.py:206 msgid "Take items from stock" msgstr "Prendre des éléments du stock" -#: counter/views.py:249 +#: counter/views.py:259 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:251 +#: counter/views.py:261 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:256 +#: counter/views.py:266 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:300 +#: counter/views.py:310 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:303 +#: counter/views.py:313 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:306 +#: counter/views.py:316 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:603 +#: counter/views.py:619 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:751 +#: counter/views.py:771 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:753 +#: counter/views.py:773 msgid "Stocks" msgstr "Stocks" -#: counter/views.py:772 +#: counter/views.py:792 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:1022 +#: counter/views.py:1042 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:1023 +#: counter/views.py:1043 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:1024 +#: counter/views.py:1044 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:1025 +#: counter/views.py:1045 msgid "1 euro" msgstr "1 €" -#: counter/views.py:1026 +#: counter/views.py:1046 msgid "2 euros" msgstr "2 €" -#: counter/views.py:1027 +#: counter/views.py:1047 msgid "5 euros" msgstr "5 €" -#: counter/views.py:1028 +#: counter/views.py:1048 msgid "10 euros" msgstr "10 €" -#: counter/views.py:1029 +#: counter/views.py:1049 msgid "20 euros" msgstr "20 €" -#: counter/views.py:1030 +#: counter/views.py:1050 msgid "50 euros" msgstr "50 €" -#: counter/views.py:1032 +#: counter/views.py:1052 msgid "100 euros" msgstr "100 €" -#: counter/views.py:1035 counter/views.py:1041 counter/views.py:1047 -#: counter/views.py:1053 counter/views.py:1059 +#: counter/views.py:1055 counter/views.py:1061 counter/views.py:1067 +#: counter/views.py:1073 counter/views.py:1079 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:1038 counter/views.py:1044 counter/views.py:1050 -#: counter/views.py:1056 counter/views.py:1062 +#: counter/views.py:1058 counter/views.py:1064 counter/views.py:1070 +#: counter/views.py:1076 counter/views.py:1082 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1676 +#: counter/views.py:1637 msgid "people(s)" msgstr "personne(s)" @@ -4371,37 +4368,37 @@ msgstr "Votre panier est vide" msgid "%(name)s : this product does not exist." msgstr "%(name)s : ce produit n'existe pas." -#: eboutic/forms.py:134 +#: eboutic/forms.py:150 #, python-format msgid "%(name)s : this product does not exist or may no longer be available." msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible." -#: eboutic/forms.py:141 +#: eboutic/forms.py:157 #, python-format msgid "You cannot buy %(nbr)d %(name)s." msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s." -#: eboutic/models.py:241 +#: eboutic/models.py:237 msgid "validated" msgstr "validé" -#: eboutic/models.py:251 +#: eboutic/models.py:247 msgid "Invoice already validated" msgstr "Facture déjà validée" -#: eboutic/models.py:289 +#: eboutic/models.py:286 msgid "product id" msgstr "ID du produit" -#: eboutic/models.py:290 +#: eboutic/models.py:287 msgid "product name" msgstr "nom du produit" -#: eboutic/models.py:291 +#: eboutic/models.py:288 msgid "product type id" msgstr "id du type du produit" -#: eboutic/models.py:308 +#: eboutic/models.py:305 msgid "basket" msgstr "panier" @@ -4467,18 +4464,18 @@ msgstr "" "Vous devez renseigner vos coordonnées de facturation si vous voulez payer " "par carte bancaire" -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:112 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:111 msgid "Pay with credit card" msgstr "Payer avec une carte bancaire" -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:116 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:115 msgid "" "AE account payment disabled because your basket contains refilling items." msgstr "" "Paiement par compte AE désactivé parce que votre panier contient des bons de " "rechargement." -#: eboutic/templates/eboutic/eboutic_makecommand.jinja:121 +#: eboutic/templates/eboutic/eboutic_makecommand.jinja:120 msgid "Pay with Sith account" msgstr "Payer avec un compte AE" @@ -4815,43 +4812,44 @@ msgstr "Appliquer les droits et le club propriétaire récursivement" msgid "%(author)s said" msgstr "Citation de %(author)s" -#: galaxy/models.py:52 +#: galaxy/models.py:51 msgid "star owner" msgstr "propriétaire de l'étoile" -#: galaxy/models.py:57 +#: galaxy/models.py:56 msgid "star mass" msgstr "masse de l'étoile" -#: galaxy/models.py:74 +#: galaxy/models.py:73 msgid "galaxy star 1" msgstr "étoile 1" -#: galaxy/models.py:80 +#: galaxy/models.py:79 msgid "galaxy star 2" msgstr "étoile 2" -#: galaxy/models.py:85 +#: galaxy/models.py:84 msgid "distance" msgstr "distance" -#: galaxy/models.py:87 +#: galaxy/models.py:86 msgid "Distance separating star1 and star2" msgstr "Distance séparant étoile 1 et étoile 2" -#: galaxy/models.py:90 +#: galaxy/models.py:89 msgid "family score" msgstr "score de famille" -#: galaxy/models.py:94 +#: galaxy/models.py:93 msgid "pictures score" msgstr "score de photos" -#: galaxy/models.py:98 +#: galaxy/models.py:97 msgid "clubs score" msgstr "score de club" #: galaxy/templates/galaxy/user.jinja:4 +#, python-format msgid "%(user_name)s's Galaxy" msgstr "Galaxie de %(user_name)s" @@ -4912,12 +4910,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:620 +#: sith/settings.py:622 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:620 +#: sith/settings.py:622 msgid "Drying" msgstr "Séchage" @@ -5309,15 +5307,15 @@ msgstr "Fusionner deux utilisateurs" msgid "Merge" msgstr "Fusion" -#: rootplace/views.py:113 +#: rootplace/views.py:155 msgid "User that will be kept" msgstr "Utilisateur qui sera conservé" -#: rootplace/views.py:116 +#: rootplace/views.py:158 msgid "User that will be deleted" msgstr "Utilisateur qui sera supprimé" -#: rootplace/views.py:122 +#: rootplace/views.py:164 msgid "User to be selected" msgstr "Utilisateur à sélectionner" @@ -5401,380 +5399,380 @@ msgstr "Erreur de création de l'album %(album)s : %(msg)s" msgid "Add user" msgstr "Ajouter une personne" -#: sith/settings.py:242 sith/settings.py:445 +#: sith/settings.py:244 sith/settings.py:447 msgid "English" msgstr "Anglais" -#: sith/settings.py:242 sith/settings.py:444 +#: sith/settings.py:244 sith/settings.py:446 msgid "French" msgstr "Français" -#: sith/settings.py:364 +#: sith/settings.py:366 msgid "TC" msgstr "TC" -#: sith/settings.py:365 +#: sith/settings.py:367 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:366 +#: sith/settings.py:368 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:367 +#: sith/settings.py:369 msgid "INFO" msgstr "INFO" -#: sith/settings.py:368 +#: sith/settings.py:370 msgid "GI" msgstr "GI" -#: sith/settings.py:369 sith/settings.py:455 +#: sith/settings.py:371 sith/settings.py:457 msgid "E" msgstr "E" -#: sith/settings.py:370 +#: sith/settings.py:372 msgid "EE" msgstr "EE" -#: sith/settings.py:371 +#: sith/settings.py:373 msgid "GESC" msgstr "GESC" -#: sith/settings.py:372 +#: sith/settings.py:374 msgid "GMC" msgstr "GMC" -#: sith/settings.py:373 +#: sith/settings.py:375 msgid "MC" msgstr "MC" -#: sith/settings.py:374 +#: sith/settings.py:376 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:375 +#: sith/settings.py:377 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:376 +#: sith/settings.py:378 msgid "N/A" msgstr "N/A" -#: sith/settings.py:380 sith/settings.py:387 sith/settings.py:406 +#: sith/settings.py:382 sith/settings.py:389 sith/settings.py:408 msgid "Check" msgstr "Chèque" -#: sith/settings.py:381 sith/settings.py:389 sith/settings.py:407 +#: sith/settings.py:383 sith/settings.py:391 sith/settings.py:409 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:382 +#: sith/settings.py:384 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:395 +#: sith/settings.py:397 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:396 +#: sith/settings.py:398 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:397 +#: sith/settings.py:399 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:425 +#: sith/settings.py:427 msgid "Free" msgstr "Libre" -#: sith/settings.py:426 +#: sith/settings.py:428 msgid "CS" msgstr "CS" -#: sith/settings.py:427 +#: sith/settings.py:429 msgid "TM" msgstr "TM" -#: sith/settings.py:428 +#: sith/settings.py:430 msgid "OM" msgstr "OM" -#: sith/settings.py:429 +#: sith/settings.py:431 msgid "QC" msgstr "QC" -#: sith/settings.py:430 +#: sith/settings.py:432 msgid "EC" msgstr "EC" -#: sith/settings.py:431 +#: sith/settings.py:433 msgid "RN" msgstr "RN" -#: sith/settings.py:432 +#: sith/settings.py:434 msgid "ST" msgstr "ST" -#: sith/settings.py:433 +#: sith/settings.py:435 msgid "EXT" msgstr "EXT" -#: sith/settings.py:438 +#: sith/settings.py:440 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:439 +#: sith/settings.py:441 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:440 +#: sith/settings.py:442 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:446 +#: sith/settings.py:448 msgid "German" msgstr "Allemant" -#: sith/settings.py:447 +#: sith/settings.py:449 msgid "Spanich" msgstr "Espagnol" -#: sith/settings.py:451 +#: sith/settings.py:453 msgid "A" msgstr "A" -#: sith/settings.py:452 +#: sith/settings.py:454 msgid "B" msgstr "B" -#: sith/settings.py:453 +#: sith/settings.py:455 msgid "C" msgstr "C" -#: sith/settings.py:454 +#: sith/settings.py:456 msgid "D" msgstr "D" -#: sith/settings.py:456 +#: sith/settings.py:458 msgid "FX" msgstr "FX" -#: sith/settings.py:457 +#: sith/settings.py:459 msgid "F" msgstr "F" -#: sith/settings.py:458 +#: sith/settings.py:460 msgid "Abs" msgstr "Abs" -#: sith/settings.py:462 +#: sith/settings.py:464 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:463 +#: sith/settings.py:465 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:500 +#: sith/settings.py:502 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:501 +#: sith/settings.py:503 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:503 +#: sith/settings.py:505 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:507 +#: sith/settings.py:509 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:508 +#: sith/settings.py:510 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:509 +#: sith/settings.py:511 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:510 +#: sith/settings.py:512 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:511 +#: sith/settings.py:513 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:512 +#: sith/settings.py:514 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:513 +#: sith/settings.py:515 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:514 +#: sith/settings.py:516 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:516 +#: sith/settings.py:518 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:520 +#: sith/settings.py:522 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:521 +#: sith/settings.py:523 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:522 +#: sith/settings.py:524 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:524 +#: sith/settings.py:526 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:528 +#: sith/settings.py:530 msgid "One day" msgstr "Un jour" -#: sith/settings.py:529 +#: sith/settings.py:531 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:532 +#: sith/settings.py:534 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:537 +#: sith/settings.py:539 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:542 +#: sith/settings.py:544 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:547 +#: sith/settings.py:549 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:552 +#: sith/settings.py:554 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:558 +#: sith/settings.py:560 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:580 +#: sith/settings.py:582 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:581 +#: sith/settings.py:583 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:582 +#: sith/settings.py:584 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:583 +#: sith/settings.py:585 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:584 +#: sith/settings.py:586 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:585 +#: sith/settings.py:587 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:586 +#: sith/settings.py:588 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:587 +#: sith/settings.py:589 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:588 +#: sith/settings.py:590 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:624 +#: sith/settings.py:626 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:625 +#: sith/settings.py:627 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:628 +#: sith/settings.py:630 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:630 +#: sith/settings.py:632 #, 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:631 +#: sith/settings.py:633 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:632 +#: sith/settings.py:634 #, 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:633 +#: sith/settings.py:635 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:634 +#: sith/settings.py:636 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:635 +#: sith/settings.py:637 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:636 +#: sith/settings.py:638 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:648 +#: sith/settings.py:650 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:649 +#: sith/settings.py:651 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:650 +#: sith/settings.py:652 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:651 +#: sith/settings.py:653 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:652 +#: sith/settings.py:654 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:660 +#: sith/settings.py:662 msgid "AE tee-shirt" msgstr "Tee-shirt AE" @@ -5972,35 +5970,35 @@ msgstr " demandé" msgid "%(effective_quantity)s left" msgstr "%(effective_quantity)s restant" -#: subscription/models.py:44 +#: subscription/models.py:43 msgid "Bad subscription type" msgstr "Mauvais type de cotisation" -#: subscription/models.py:49 +#: subscription/models.py:48 msgid "Bad payment method" msgstr "Mauvais type de paiement" -#: subscription/models.py:57 +#: subscription/models.py:56 msgid "subscription type" msgstr "type d'inscription" -#: subscription/models.py:63 +#: subscription/models.py:62 msgid "subscription start" msgstr "début de la cotisation" -#: subscription/models.py:64 +#: subscription/models.py:63 msgid "subscription end" msgstr "fin de la cotisation" -#: subscription/models.py:73 +#: subscription/models.py:72 msgid "location" msgstr "lieu" -#: subscription/models.py:93 +#: subscription/models.py:92 msgid "You can not subscribe many time for the same period" msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" -#: subscription/models.py:98 +#: subscription/models.py:97 msgid "Subscription error" msgstr "Erreur de cotisation" diff --git a/sith/settings.py b/sith/settings.py index 69ecddc4..d650f8ba 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -147,6 +147,8 @@ TEMPLATES = [ "filters": { "markdown": "core.templatetags.renderer.markdown", "phonenumber": "core.templatetags.renderer.phonenumber", + "truncate_time": "core.templatetags.renderer.truncate_time", + "format_timedelta": "core.templatetags.renderer.format_timedelta", }, "globals": { "can_edit_prop": "core.views.can_edit_prop", From 28f397574f7b7ef54914972ed0154ce879128a3b Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:38:40 +0200 Subject: [PATCH 35/95] =?UTF-8?q?Am=C3=A9lioration=20des=20pages=20utilisa?= =?UTF-8?q?teurs=20pour=20les=20petits=20=C3=A9crans=20(#578,=20#520)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refonte de l'organisation des pages utilisateurs (principalement du front) - Page des parrains/fillots - Page d'édition du profil - Page du profil - Page des outils - Page des préférences - Page des stats utilisateurs - Refonte du CSS / organisation de la navbar principale (en haut de l'écran) - Refonte du CSS de la navbar bleu clair (le menu) - Refonte du CSS du SAS : - Page de photo - Page d'albums --- com/models.py | 2 + com/tests.py | 4 +- .../0019_preferences_receive_weekmail.py | 2 +- core/models.py | 4 +- core/static/core/header.scss | 408 +++++++++++++++ core/static/core/img/logo_no_text.png | Bin 0 -> 7918 bytes core/static/core/js/script.js | 2 - core/static/core/navbar.scss | 110 ++++ core/static/core/style.scss | 375 +------------- core/static/counter/activity.scss | 24 + core/static/sas/album.scss | 249 +++++++++ core/static/sas/picture.scss | 309 +++++++++++ core/static/user/login.scss | 108 ++++ core/static/user/user_detail.scss | 200 ++++++++ core/static/user/user_edit.scss | 193 +++++++ core/static/user/user_godfathers.scss | 113 +++++ core/static/user/user_group.scss | 12 + core/static/user/user_preferences.scss | 58 +++ core/static/user/user_stats.scss | 48 ++ core/static/user/user_tools.scss | 41 ++ core/templates/core/base.jinja | 479 +++++++++--------- core/templates/core/login.jinja | 72 ++- core/templates/core/register.jinja | 39 +- core/templates/core/user_detail.jinja | 223 ++++---- core/templates/core/user_edit.jinja | 250 ++++++--- core/templates/core/user_godfathers.jinja | 52 +- core/templates/core/user_group.jinja | 24 +- core/templates/core/user_pictures.jinja | 42 +- core/templates/core/user_preferences.jinja | 95 ++-- core/templates/core/user_stats.jinja | 44 +- core/templates/core/user_tools.jinja | 275 +++++----- core/tests.py | 2 +- core/utils.py | 12 + core/views/forms.py | 1 + counter/templates/counter/activity.jinja | 50 +- locale/fr/LC_MESSAGES/django.po | 51 +- sas/models.py | 14 +- sas/templates/sas/album.jinja | 400 ++++++++------- sas/templates/sas/main.jinja | 148 ++++-- sas/templates/sas/picture.jinja | 284 ++++++----- sith/settings.py | 2 +- 41 files changed, 3415 insertions(+), 1406 deletions(-) create mode 100644 core/static/core/header.scss create mode 100644 core/static/core/img/logo_no_text.png create mode 100644 core/static/core/navbar.scss create mode 100644 core/static/counter/activity.scss create mode 100644 core/static/sas/album.scss create mode 100644 core/static/sas/picture.scss create mode 100644 core/static/user/login.scss create mode 100644 core/static/user/user_detail.scss create mode 100644 core/static/user/user_edit.scss create mode 100644 core/static/user/user_godfathers.scss create mode 100644 core/static/user/user_group.scss create mode 100644 core/static/user/user_preferences.scss create mode 100644 core/static/user/user_stats.scss create mode 100644 core/static/user/user_tools.scss diff --git a/com/models.py b/com/models.py index c3061ed0..c61fe7b4 100644 --- a/com/models.py +++ b/com/models.py @@ -36,6 +36,7 @@ from django.core.exceptions import ValidationError from django.utils import timezone +from core import utils from core.models import User, Preferences, RealGroup, Notification, SithFile from club.models import Club @@ -46,6 +47,7 @@ class Sith(models.Model): alert_msg = models.TextField(_("alert message"), default="", blank=True) info_msg = models.TextField(_("info message"), default="", blank=True) weekmail_destinations = models.TextField(_("weekmail destinations"), default="") + version = utils.get_git_revision_short_hash() def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) diff --git a/com/tests.py b/com/tests.py index e0ebfc69..a18c9819 100644 --- a/com/tests.py +++ b/com/tests.py @@ -82,7 +82,7 @@ class ComTest(TestCase): self.assertContains( r, """
    -

    ALERTE!

    +

    ALERTE!

    Caaaataaaapuuuulte!!!!

    """, ) @@ -100,7 +100,7 @@ class ComTest(TestCase): self.assertContains( r, """
    -

    INFO: Caaaataaaapuuuulte!!!!

    """, +

    INFO: Caaaataaaapuuuulte!!!!

    """, ) def test_birthday_non_subscribed_user(self): diff --git a/core/migrations/0019_preferences_receive_weekmail.py b/core/migrations/0019_preferences_receive_weekmail.py index 951488aa..5bfe0d97 100644 --- a/core/migrations/0019_preferences_receive_weekmail.py +++ b/core/migrations/0019_preferences_receive_weekmail.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): model_name="preferences", name="receive_weekmail", field=models.BooleanField( - default=False, verbose_name="do you want to receive the weekmail" + default=False, verbose_name="receive the weekmail" ), ) ] diff --git a/core/models.py b/core/models.py index e904603f..dff97cf0 100644 --- a/core/models.py +++ b/core/models.py @@ -783,9 +783,7 @@ class Preferences(models.Model): user = models.OneToOneField( User, related_name="_preferences", on_delete=models.CASCADE ) - receive_weekmail = models.BooleanField( - _("do you want to receive the weekmail"), default=False - ) + receive_weekmail = models.BooleanField(_("receive the Weekmail"), default=False) show_my_stats = models.BooleanField(_("show your stats to others"), default=False) notify_on_click = models.BooleanField( _("get a notification for every click"), default=False diff --git a/core/static/core/header.scss b/core/static/core/header.scss new file mode 100644 index 00000000..d1b9581f --- /dev/null +++ b/core/static/core/header.scss @@ -0,0 +1,408 @@ +.header { + box-sizing: border-box; + background-color: #354a5f; + box-shadow: 3px 3px 3px 0 #dfdfdf; + border-radius: 0; + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: 10px; + gap: 10px; + + @media (max-width: 700px) { + height: auto; + } + + @media (max-width: 580px) { + justify-content: space-between; + } + + &-logo { + display: flex; + flex-direction: row; + gap: 10px; + + &:hover > a { + color: #1a78b3; + } + + @media (max-width: 607px) { + width: 100%; + justify-content: center; + } + + &-picture { + height: 100%; + width: 65px; + display: flex; + + background-position: center center; + background-size: contain; + background-repeat: no-repeat; + + @media (max-width: 580px) { + height: auto; + } + } + + &-text { + color: white; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + + > span:first-child { + font-size: 1.43em; + } + + > span:last-child { + font-size: .7em; + } + } + + } + + &-lang { + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 3px; + + @media (max-width: 580px) { + flex-direction: row; + } + + > form { + margin: 0; + box-sizing: border-box; + position: relative; + + > input[type=submit] { + border-radius: 0; + margin: 0; + box-sizing: border-box; + background-color: #354a5f; + width: 45px; + height: 25px; + padding: 0; + color: white; + font-weight: normal; + line-height: 1.3em; + + &:hover { + background-color: #283747; + } + } + } + } + + &-disconnected { + box-sizing: border-box; + flex: 1; + display: flex; + justify-content: flex-end; + align-items: center; + + @media (max-width: 607px) { + justify-content: center; + } + + > .button { + box-sizing: border-box; + height: 35px; + background-color: transparent; + font-weight: normal; + padding: 5px 20px; + display: flex; + justify-content: center; + align-items: center; + text-transform: uppercase; + text-decoration: none; + color: white; + margin: 0; + font-size: .9em; + width: 120px; + + &:hover { + background-color: #283747; + } + } + } + + &-disconnected ~ &-lang { + @media (max-width: 662px) { + flex-direction: row; + width: 100%; + } + } + + &-connected { + box-sizing: border-box; + flex: 1; + display: flex; + flex-direction: row; + + @media (min-width: 400px) and (max-width: 1200px) { + flex-direction: column; + min-width: 100%; + justify-content: center; + align-items: center; + gap: 10px; + } + + @media (max-width: 400px) { + flex-direction: column; + width: 100%; + gap: 10px; + padding: 0 10px; + } + + > .right, + > .left { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + + @media (min-width: 400px) and (max-width: 1200px) { + width: 100%; + justify-content: space-between; + padding: 0 20px; + } + } + + > .right { + flex: 1; + justify-content: flex-end; + + > .user { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 15px; + + @media (max-width: 1200px) { + width: 100%; + flex-direction: row-reverse; + } + + > a > img { + width: 40px; + height: 40px; + border-radius: 50%; + } + + > .options { + width: 100%; + display: flex; + justify-content: flex-end; + flex-direction: column; + gap: 5px; + + @media (max-width: 1200px) { + justify-content: flex-start; + flex-direction: row; + gap: 15px; + } + + > a { + text-align: right; + color: white; + + &:hover { + color: #1a78b3; + } + + &:last-child { + color: #eb2f06; + + &:hover { + color: #cc2804; + } + } + } + } + } + + > .notification { + height: 100%; + width: 55px; + display: flex; + justify-content: center; + align-items: center; + position: relative; + + > a { + color: white; + position: relative; + font-size: 25px; + + &:hover { + color: #1a78b3; + } + + > span { + color: white; + font-size: 14px; + display: flex; + justify-content: center; + align-items: center; + width: 10px; + height: 10px; + padding: 5px; + background-color: #eb2f06; + border-radius: 50%; + position: absolute; + top: -50%; + right: -50%; + } + } + + > #header_notif { + box-sizing: border-box; + display: none; + position: absolute; + margin: 0; + background-color: whitesmoke; + top: calc(100% + 10px); + right: calc(50% - 30px); + width: 400px; + max-width: calc(100vw - 30px); + padding: 10px; + z-index: 100; + border-radius: 10px; + box-shadow: 3px 3px 3px 0 #767676; + + > ul { + list-style-type: none; + margin: 0; + display: flex; + flex-direction: column; + gap: 10px; + max-height: 120px; + overflow-y: auto; + + > li { + > a { + .datetime { + display: flex; + justify-content: flex-start; + gap: 10px; + font-style: italic; + font-size: .8em; + } + } + + &.empty-notification { + text-align: center; + font-style: italic; + } + } + } + + .options { + width: 100%; + display: flex; + justify-content: space-between; + margin-top: 10px; + + > a { + color: black; + padding: 5px; + width: 50%; + display: flex; + justify-content: center; + text-align: center; + align-items: center; + border-radius: 5px; + + &:hover { + background-color: rgba(0, 0, 0, .2); + } + } + } + } + } + } + + > .left { + gap: 10px; + display: flex; + + @media (max-width: 1200px) { + flex-direction: row-reverse; + } + + @media (max-width: 550px) { + flex-direction: column-reverse; + } + + > form { + margin: 0; + width: 200px; + + @media (max-width: 550px) { + width: 100%; + } + + > input[type=text] { + box-sizing: border-box; + max-width: 100%; + width: 100%; + height: 35px; + border-radius: 5px; + font-size: .9em; + margin: 0; + background-color: #283747; + padding: 0 10px; + color: white; + } + } + } + } + + &-connected ~ &-lang { + @media (max-width: 1200px) { + flex-direction: row; + width: 100%; + } + } +} + +.bars { + list-style-type: none; + min-width: 120px; + margin: 0; + padding: 0; + + @media(max-width: 1200px) { + display: flex; + flex-direction: row; + gap: 20px; + } + + > li > a { + display: flex; + color: white; + + &:hover { + color: #1a78b3; + } + + > span { + margin-left: 10px; + } + + > i { + width: 16px; + display: flex; + justify-content: center; + align-items: center; + } + } +} \ No newline at end of file diff --git a/core/static/core/img/logo_no_text.png b/core/static/core/img/logo_no_text.png new file mode 100644 index 0000000000000000000000000000000000000000..73e952577fdaf3ad3c19105db2a6949abb0631ad GIT binary patch literal 7918 zcmcI|XH-*Lw>DS~ASz%)1*C;48bS>uBp?ua7Z8x5frMU@Py!-~f^;N+ia@}ibdV-U z7X+!o2}qM(q&MkB;0t;@-+RY+$GE?4#@IW1&-JWl&iTx_)=qY)zOLH2Gwf$*XlTx9 zs3UJtzxSv|75!h-v%ylaHVqAJpo6lqzJ{_gk3QDT*1;J=LnGW79|KiCsdy!*mOqTl z!e|(B$NBZ`bORQx$Uu4)C8Jf5q2ZUp=)MAu!orKrCogeDnxJg*@`Cbs=(%`GA-BUI z7kUFAN`cIEk5KX>?SAc5kH%2FB}1=x3l^(luZBBF7US*=GxDI01JW@4uY?0x5FQr(kuwp| zsTj}Zkxt~RXF-7s>r_uCwnYgHfrk=`oLiVW;$+|YC>O?CN(L-vSzWOz$`FbdEq*Ni zIf3Um!w^t{h>fJNIF0RTJV*NMl{1`zB1NRpkvOvjzCWX&u>G5vR}REu0k>Y;?Wxch9t( zP48ay5*d|Z1j+m@c6WZsE+y9QB{6@Zub_-+n61Ssb8UIAb>t8}xvjRy0hGlCx9RN> zs6}CLS2x4a(1-$$58CXBVImF9X;lX!Q@p8;Hq6G&MHGd0v&M+}xVTf%G&FDpA9s|E z69&&?jj?lZMF3YSYk@otXavwiT1QOBT^VEVpze#s82aiO+4wryK+!-2`EzIBJ}@eS z3kHwk@o{l>#ld_Kz+c2L>h-Z14CMI*!8;*08-=l9iWT5j?TaAuDE}6peh3PLAis)Ma94_ zF2_;-LgVl%Uew9|#PVO!I3t2P27C*HbMwU7U{t&?u6Vvb)73O|^#7fGoQa)-i~Fx! zsH*+z1a0###@!R^{A(O-1I9RGT&QF?YE0t)V(|{P|6iN`5zq0+ukimkr@Hu;_kSS& z8#3jf6PL_79JSD-MrxwZR1%=2+LS@AM@Z82=anvR{<`NeZm6QVVplx8PR3;R)BOF{%b{Md`s~wQ%Sa=vU zcB+D?liZjK!evZdfBXH)jO!I5eJPrr%>a)U|;|;ZaBw9z*pYCLswElZ1(j z8Hr0!HG@e&uZxMp#QuQY&-vpx(l9{*wb$LQ?9 z^Q+ZiD4X9Y;80!|H1HpzjXlcM4ny4wsBQQg=I~!iEsc?tfTF0SMv0?jsU?zyfKU)w z8;~{18Y6CND=Q%@A^m4Q&dnC@jlyCS?Wn3z#i4fHFL8K;e;4W6AE38A=C}e>eL!N8 zpnvs|g#6J5{2$4{!N&{!KNEz5|C<)SasRPV{Nng+rS3x1JL}n&SX@B`mEZ+NqdX>w?N2g60Lb)1b>qJ=tPqAJT zyOLlRU-*omA2R} z0r=>RpGQTvZOt&La$sbcL`ooy5Pk+2=gf^DhL<|F+-e@^cr%{lQJEgzy~|3nsGQz1 zocST;eiliW#mrnqtLGCZ*EIIza|qK4iGb^>FE=8fcw{YSjD19AoqGjN1vjjX1S!hZHm927QY@D z_^7yx8t-;~IJqjYu771bg9NjY#)_*yRouyE*@(Cd(0+N1TeF2>$oZBwk8kM1hVQ4z zkN>J-tmlL?oH>D{lW?6;(`V*^+0$@h8&8r?>Yt#Z#3H`ht?~&oXOiQYbyoJTu2>h4 zGH7X3J^~IE&z95D+*G{T!^v;ksaC<^c-nBpEU+k^cNOyx*?gSg0PWpVA(6UR?q-4Cq5u*93w*Nk~L-#}h{QE{Hl zk_Ov(&bf7t?9VivO%j>9ICfP|ViDLvS^XMLL+XG~}iqG ziW1eAam{63M$sDCdq#W%0pCyDp5tgs$+GL}&hcrDsFIO83oK#0L|8LSXp;(JvAhjh zF{_HmP@TK;0HD_c5G>cz-;yQoH^ki7VcWL!V$c(Z95ODL?x(m+LnWVV*L3B0PEAY$ z^p>07OT9o#Ps~{16ULrsW4RL+J&-H@V`7kv1us%JU)bTUT1qC@%+X=(h)+*f6IZUS zlD>bh@T{n=nXC1bdrOXXUhNBWif53OPpI-HQ)_>E%?Kg55^rC+C<^O&r6?KH`Q>8&q1yRmwz#A#-=v~4 z$dV=FXp*Mt%LW5}A$8l><~(B3vuem{J(AQ4sS2}_Z3(r7C}4SE3|0f9BGrn(&_zYq zpf5oL#$PO4vIylg-_y(N75Uk;eqBD}#zk0*e?!XdYLFZ?25Pqi<{HOlA($-GDIa$TldY{k-$lPMoiW$#5Ibhrntp&qIBZ zZhX7y22%>jR6NE4DSclMxDO@Hr#om7TrY-geR-CY+866p&%zl#I+L|H#!yq!Kmw*=w zjzhgNO%%s6th#X~w*URqlSOH&usti5&4w-X3z83~zC7*--QIa}6>FL?CMF|)7pNCG zaSIR3-OG<1YTC5$R(FkZ8IUya>|><~>o3Z*^p_8uQXbiy&{-L(_Q&q*c5CQ{#K?=L zbKWmX;fsleeTbcz7mpc|egjL9Zg-n-X_;s-R_K+1H2SL%^MWk?i|w>aK3k_b?%u2Zl-z?is%wPoPds`$ z5Ljn-tPQH|O$;Y-kk0|_7B=g_&y+I}W_?yWR|OB42$A1@V>0l5ool9?9aZ%7oLi$x zpSY0IyGmwGqPY;|25iQ7J9Uab_~>s+b+vd{c*x}whAk@jEK|n3n!3-LCp+fG%ME6C zGl>B^E6KTC+=rV_Hfs`f^9vdH^@5al#4qXcRR}=`d@6LE6PVLK>DOiIk`0Yy6c^*t zG9L_$56a6rc2)LpICw3|VS9Uz5+PXWcom#^W=G^k)fLSfs+&@LFn+xkAB=ta$R_n< z8Qx3tvSfgu%SJSJckJxd28ZBGdO4YMGXhE_=Uc68DLTj>^2(Qb$%Yrywva*aGd0YD zWWAkwy4-_kHM_!uuo1+h2*kgh1D1kof2UQ~kslf;? z2?jrNSYns$C5A+Z_LS+`q=tJU5pcLta^Pokf#A`u-5#L@(A$z!U*^+WIyfKD%HTAe z+@-`s%2SR@zLXI0$=|V;!B~Q>UXoXZHXb;)OIYa#ya`$& z=cG($Z8(&=Ba0m+4dg4kLqsp#V&&|E=!#Hucv$YP=XAmc@vd?U(~J$Xht(cwu`ZuG z*vGPFGx@g|{1BodEunVYl@S?T0*pk!7hi4zQL(I=x;}5bal`BDv6-&EiqB#M>M-r* z>uvK43D7DMXyG7y9`_`2y$f;ZTUv2Ic8q_}`#0<`_tl3PO(DXw5}@DhQ!gk_W?f4R zBi13lrYKO+)G3v%&U5({QdEJr$_Jwm9}OL!pM@JmjtLTH;%{i_N{t_|<@u$2OC;Ot zs)>{77*q>015LKdipI-Sw|UxZ=!ni|NzXBtOq?TeKhiW57!Oy==ap5G7}G9)$x23- zTrFM-A$hhumW^7J>PlUDqHT;%p3-1$dbBQF9<9CgVQB5K+<uK}}U5a4ETVM(FPMYlwHwhf;b1pWbMmZz&G)q+Bc7i9Qoaxyi$SzzxO7z>eXZH zvQPFWC#EXA<;Y-geuB1v9QcOC&Q3@_%j!hL?dU3yf`|@;>Pf! zmoJ!+?Tdd^CYKX!fLXNf9rWw!1huM804z&D=T_vbNV@meQH-tTLX7ftLQ%n+a`Uh@ z#uLxhwtvoEwIkQ=^64DA)6C zIG6n4my?TBa4G!jV;F$bo}6Go+KYqW8DKH zuNIcc@G!*6qQR?z^%2oXoWb&uoY#=seo6&FanhoVI5ye`Ru+UK>7EGtIvQ_}=h@_x z#dR6bxg=$#wG*Glpk++;MRUV-vE6B1=KDSPjg(m4K~7ehdzW*_IcVy-3|+^rjqHS3(f@GuyS7#@;P%cW^7S*!37`RZXj^9=XAHOp2)xuKB9*(8$x`$iRk6?y@f1KAsk*n z;-j3w8_Of$qPb7C)8z)*IkO@2jRHL(q*F+DC<0`8t0hz*Sx@-x$&XFXXS+6sGHJq? zSj*nM;~3@Ubq>vzD7ZJFupZ~Xy!T@PemX3Il``15;4;f)qLFvuL0iD#c|BV0?9x$+ z2e@;NVu|#;f!IGqI-&CU&b~}8=$aD879GWSu;5+D!N)9?=CrE=$3#l*uuL&8kncjin~{pt*aqjUr0(i9HJ)TLS@)oi9hQ0p zuqx5G~C#;s~eN-(Y|7B*!&dj2DO&y>`9%u6|%$g ziCWE+x^xkD!C5pIx8m0iJ?JlZ0Iuw)T;`172z(Kn{+LUG8*l!a|GMPk+wg?)SktSV#q?@zi-+VRbCHh!g*?MeH&FBl=i!D zOYUIfjws^qeOK;5vf(4>o9BjYq&L2I@s#doL-)*P&XFvN33tNNLY(gZptZGs%kUs8 z-NY>Mv~j5_6hzWD8favEd)~lm7*s8|P?q?hHb1*JWZo-=q&@tCZR>RZ1Xw?-5UFa4Cei7Y$j_Kk-~{mFPpz>;-e?4w~OO56qVRvll3*~kHNkI z3cZS_MR)#YDIIM7hy&Et#|FJVictn==>AL(fa$$``bm9XP~imQ*U)8+8@0v!C#>FA zxL$60v6Tb@rEU!Q54uu&=dUB16MA!$C2o?;-g5G-tVQnOS+WH4=-YyDMUuOzpD^4S z`OX}AOC=6`F9B(rlHyD4=y6d}4&`WAnIMuR1e=`3zP{;Jmr-N;9N8Hc{ z^QG!0jX+jJ+I})dkb+lR$#)P=~Mf*g|l;cwL}(%^bUCWBMFM1WCHS&-(|DWY6;A}G3OecM9j zv=op3#9@K3)cqDgp|2(GFMoM_%1+t!;Z>xb-4Lr`L&FJ{^{VqkpKQ#-t z6&QEkpEsl!U!TcFM+2cKk&0W`lyh`!`yW484Bm?}IiFMZ!!|~kNT*@wvjje0GycI1 zxTvfKN-}8H#0U7RnWzl8*CDqG1d70;C1IRBHL)PMAK+a{+l?Xj^Ku_@9+XV3jtIS( zX^l;S&DA%RaH@ILtMErCS=6a??O5pQ#?7C?cYY8t5Q+^uTvo8Av*KDp-e#RBA>g$7 z_8)WR!r=U2x|}W*5TB$kqT2o50|IaXOpJH_?NxmI-}cMJU-k?0tkB+_k)$h3Yk0!~ zfe#@%eK=o>J~lT5C$vwCMU zii5f?STqE$>YkBjDKoJ!k(GOK$mo=KJ0Lbz1LdkPv#BM7SkKek zj*2?*tAaDZ^sOX5ikf=b{K905j=#cMn`Nfr;q7_?o?Qf2lMy zyWV-paCG$9rOhwpDNud$a8ZU8)AoK!p1in!*{WIOv2F2^bts9YMc~HTRHjoDve9@d zO6hBOf+1!5{u^`OdZRXu1Wk@OMAWQ2N{c@lvCaxvo|y6J5Y0BnVqPxW1L6>^giu5O zyOJw$&u^SDz-tG2LnL(tpo)RUd+n~a8nIF4D>K)iq5?v*yW>lH?2*?h+l+a4yd7s|V$Rgr(|=T^R`l4f{o9v1$NNdyavo!s4b6^} zQzoELh^nA|kX~}LmJ4P+{dtYK+jtf_0&$>&L1)Z#KTC2gGuF^;Uha~NAG&0tw+~wI z-4TNKg`8_)FdFX`$eu!`AOF_|8b-@)RxOC-Wvc~c58-R8LL3ECT0rH<)uX4zgO5Wv zae@ZJ_bd){S5{MnJ{(+6p#xU>Pgo%(St*TP?pjT>y&i*|KKU^v{6Y>GA|PEUf zBr{)S=$QQBPuhST?Vj=H7m-_2{MGgq7n21+H*&wTzdm;~(Lw5dT}eO+R%R8*=$N-o z>k(pRCc<^K?1J)QGXMyNY|rg(X0D>9Ce<(M`;116VqAp>k^KRtmj)hJ`o3zdd2I(t z?0H*jDBi(oZIC^!=r9?!%on~GTsHL?=q$uZeG}#^9S~Q`?;PL5U-a}f(NZ8*lM6rz zOHe$8%s)H*+1$v+v+DBx#mQ%0pA&ApO`68GRW11_f=rZ!QWxK& zP#!9Se}C{{X6MVOTr&z$;AZW)|FfpDHaDq)1M;?vZM8nJ6D}pL;pUNnX#jkf%m*?^~}`LuU>6)gdGc3oxp;iC;Rc0p5AWxy80^O zgZK0I0``MmrE@0ab2)+C3x|ARQ~Vz^v25uqe)qn=g~x-sM7|OPzO2U(C=4)KL7_*m z+52}se`dOrV@FDhgKDpR2nig_1)V#X0KnrB+O>ng$%(7#0fgt-%fRRd`+E+fql9w& zI>JE7F~da33r>m55K@%J=}33zkjo<88J!QX316Gg8r5>o1Zi@>P5nourkVO9IY%d; z0=TN|%I?!w+DzlUJ!9HbkWAPw@0naA5l|QI9&pRl9jwwOyQP#Tad2wt_1zzF?0rOl zDA4@2`+-?}fbflM8NOF-^s2O$?_vGW%WA7*XCz|1#J&2>*Sgo&ruB-|`o6kZ-uM)~ zXt2lA5Te^K7qR}j79=VX<3(q2G3?5Cq9hKZyXFVz37vCP++4?si>DWb`M z>aw9$5kK}mzuBrINN*RV6Bjw9g6G!T>GXZt^WH+Vxm^_ybP=Jhzn96079;}{2O4W* zjFx#yMeoTI00{Nqx6xI?PZ-aDE1DZ;@` .menu, + > .link { + box-sizing: border-box; + width: 130px; + height: 52px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + @media (max-width: 500px) { + width: 100%; + height: auto; + justify-content: flex-start; + + &:first-child { + border-radius: .6em .6em 0 0; + } + + &:last-child { + border-radius: 0 0 .6em .6em; + + > .content { + box-shadow: 3px 3px 3px 0 #dfdfdf; + } + } + } + } + + > .menu > .head, + > .link { + color: white; + padding: 10px 20px; + box-sizing: border-box; + + @media (max-width: 500px) { + padding: 10px; + } + } + + .link:hover, + .menu:hover { + background-color: rgba(0, 0, 0, .2); + } + + > .menu:hover > .content, + > .menu > .head:hover + .content, + > .menu > .content:hover { + display: flex; + } + + > .menu { + display: flex; + position: relative; + + > .content { + z-index: 10; + display: none; + position: absolute; + top: 100%; + background-color: white; + margin: 0; + list-style-type: none; + width: 130px; + box-shadow: 3px 3px 3px 0 #dfdfdf; + flex-direction: column; + + @media (max-width: 500px) { + position: absolute; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + box-shadow: inset 3px 3px 3px 0 #dfdfdf; + } + + > li > a { + display: flex; + padding: 15px 20px; + + @media (max-width: 500px) { + padding: 10px; + } + + &:hover { + color: hsl(203, 75%, 40%); + background-color: rgba(0, 0, 0, .05); + } + } + } + } +} \ No newline at end of file diff --git a/core/static/core/style.scss b/core/static/core/style.scss index f4840082..9068e8c5 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -76,6 +76,7 @@ button:not(:disabled), input[type="button"]:not(:disabled), input[type="submit"]:not(:disabled), input[type="reset"]:not(:disabled), +input[type="checkbox"]:not(:disabled), input[type="file"]:not(:disabled) { cursor: pointer; } @@ -99,6 +100,7 @@ textarea { padding: 7px; font-size: 1.2em; border-radius: 5px; + font-family: sans-serif; } select { border: none; @@ -181,166 +183,6 @@ a { /*--------------------------------HEADER-------------------------------*/ -#header_language_chooser { - position: absolute; - top: 2em; - left: 0.5em; - width: 3%; - min-width: 2.2em; - text-align: center; - input { - display: block; - width: 100%; - padding: 4px; - margin: 0px; - } - form { - display: block; - margin: 0.2em 0em; - width: 100%; - } -} - -header { - width: 90%; - margin: 0 auto; - display: flex; - box-shadow: $shadow-color 0 0 15px; - border-top: none; - background-color: $primary-neutral-dark-color; - border-radius: 0px 0px 10px 10px; - - #header_logo { - background-color: $white-color; - padding: 0.2em; - border-radius: 0 0 0 9px; - - a { - display: flex; - align-items: center; - margin: 0px; - width: 100%; - height: 100%; - - img { - max-width: 70%; - max-height: 100%; - margin: auto; - display: block; - } - } - } - - #header_connect_links { - margin: 0.6em 0.6em 0 auto; - padding: 0.2em; - color: $white-color; - form { - display: inline; - width: 100%; - label { - display: inline; - } - } - } - - #header_bar { - display: flex; - flex: auto; - flex-wrap: wrap; - align-items: center; - width: 80%; - - a { - text-decoration: none; - margin: 0 1em; - font-weight: bold; - color: $white-color; - &:hover { - color: $secondary-color; - text-decoration: underline; - } - } - - #header_bars_infos { - flex: initial; - list-style-type: none; - margin: 0.2em 0.2em; - } - - #header_search { - display: inline-block; - flex: auto; - margin: 0.8em 0; - input { - width: 14ch; - } - } - - #header_user_links { - display: flex; - flex: initial; - flex-wrap: wrap; - text-align: right; - margin: 0; - div { - display: inline; - padding: 1.2em 0; - &:first-child { - flex: auto; - } - } - .white { - background: $white-color; - a { - color: $black-color; - } - } - #header_notif { - display: none; - position: absolute; - max-height: 20em; - width: 22em; - overflow: auto; - list-style-type: none; - box-shadow: grey 1px 1px 5px; - background: white; - text-align: left; - font-size: 80%; - margin: 1.5em 0 0em -14em; - .header_notif_date { - font-weight: bold; - } - .header_notif_time { - color: grey; - } - a { - margin: 0; - color: $black-color; - &:hover { - color: $primary-dark-color; - } - } - li { - padding: 0.2em; - &:hover { - background: hsl(180, 14%, 77%); - } - } - li:last-child { - text-align: center; - a { - color: $primary-dark-color; - &:hover { - color: $primary-light-color; - } - } - } - } - } - } -} - #popupheader { width: 88%; margin: 0 auto; @@ -350,8 +192,13 @@ header { #info_boxes { display: flex; flex-wrap: wrap; - width: 90%; - margin: 1em auto; + margin: 1em; + + @media (max-width: 500px) { + margin: 0; + width: 100%; + } + #alert_box, #info_box { flex: 49%; @@ -388,75 +235,6 @@ header { width: 90%; margin: 20px auto 0; /*---------------------------------NAV---------------------------------*/ - nav { - display: flex; - flex-wrap: wrap; - background-color: $primary-dark-color; - color: $white-color; - border-radius: 6px 6px 0 0; - box-shadow: $shadow-color 0 0 15px; - align-items: center; - - a { - flex: auto; - text-align: center; - padding: 1.5em; - color: $white-color; - font-style: normal; - font-weight: bolder; - text-decoration: none; - - &:hover { - background: $secondary-neutral-color; - color: $white-color; - &:first-of-type { - border-radius: 6px 0 0 0; - } - &:last-of-type { - border-radius: 0 6px 0 0; - } - } - } - - .dropdown { - flex: auto; - text-align: center; - position: relative; - } - - .dropbtn { - all: unset; - padding: 20px; - font-weight: bolder; - } - - .dropdown-content { - display: none; - position: absolute; - overflow: auto; - width: 100%; - background-color: #f9f9f9; - box-shadow: 3px 3px 3px 0 $shadow-color; - z-index: 1; - } - - .dropdown-content a { - float: none; - color: black; - padding: 12px 16px; - display: block; - text-align: center; - &:hover { - border-radius: unset; - color: white; - background: $secondary-neutral-color; - } - } - - .dropdown:hover .dropdown-content { - display: block; - } - } .btn { font-size: 15px; @@ -1120,32 +898,26 @@ h6 { h1 { font-size: 160%; - margin-left: 0; } h2 { font-size: 150%; - margin-left: 10px; } h3 { font-size: 140%; - margin-left: 20px; } h4 { font-size: 130%; - margin-left: 30px; } h5 { font-size: 120%; - margin-left: 40px; } h6 { font-size: 110%; - margin-left: 50px; } p, @@ -1328,88 +1100,6 @@ u, /*-----------------------------USER PROFILE----------------------------*/ -#user_profile_page { - #user_profile { - display: flex; - justify-content: center; - margin-top: 2em; - margin-bottom: 4em; - #user_profile_infos { - flex-basis: 30%; - border-right: solid 1px grey; - div { - margin: 0.5em; - } - #user_profile_infos_items { - margin-top: 3em; - } - .user_profile_infos_item, - .user_profile_infos_item_value { - vertical-align: top; - display: inline-block; - width: 49%; - } - .user_profile_infos_item { - color: grey; - } - #user_profile_infos_promo { - display: flex; - align-items: center; - img { - width: 5em; - margin: 0.5em; - } - } - #user_profile_infos_quote { - text-align: right; - color: grey; - font-style: italic; - &:after, - &:before { - content: "\201C"; - vertical-align: middle; - } - } - } - #user_profile_pictures { - height: 20em; - flex-basis: 30%; - display: flex; - justify-content: flex-end; - #user_profile_pictures_bigone { - flex-grow: 9; - flex-basis: 20em; - display: flex; - justify-content: center; - align-items: center; - img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - } - } - #user_profile_pictures_thumbnails { - flex-grow: 1; - flex-basis: 50px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - img { - margin: 0.1em; - width: 50px; - } - } - } - @media screen and (max-width: $small-devices) { - #user_profile_infos, - #user_profile_pictures { - flex-basis: 50%; - } - } - } -} - .user_mini_profile { height: 100%; width: 100%; @@ -1691,47 +1381,6 @@ textarea { } } -/*------------------------------SAS------------------------------------*/ - -.album { - display: inline-block; - border: solid 1px $black-color; - text-align: center; - padding: 5px; - width: 200px; - height: 140px; - background: hsl(0, 0%, 93%); - box-shadow: black 2px 2px 10px; - margin: 10px; - vertical-align: top; - img { - max-height: 100px; - } -} - -.picture { - display: inline-block; - border: solid 1px $black-color; - width: 150px; - height: 100px; - margin: 5px; - background: #eeeeee; - box-shadow: grey 2px 2px 5px; - padding: 2px; - vertical-align: middle; - img { - max-width: 100%; - max-height: 100px; - display: block; - margin: auto; - } -} - -.not_moderated { - border: solid 1px red; - box-shadow: red 2px 2px 10px; -} - /*--------------------------------FOOTER-------------------------------*/ footer { @@ -1747,6 +1396,7 @@ footer { border-radius: 5px; display: flex; flex-wrap: wrap; + align-items: center; background-color: $primary-neutral-dark-color; box-shadow: $shadow-color 0 0 15px; a { @@ -1759,6 +1409,11 @@ footer { } } } + + > .version { + margin-top: 3px; + color: rgba(0, 0, 0, .3) + } } /*---------------------------------FORMS-------------------------------*/ diff --git a/core/static/counter/activity.scss b/core/static/counter/activity.scss new file mode 100644 index 00000000..1ffb1111 --- /dev/null +++ b/core/static/counter/activity.scss @@ -0,0 +1,24 @@ +.activity-description { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + margin-top: 10px; + + > div { + display: flex; + flex-direction: row; + gap: 10px; + + > span { + text-align: left; + } + + > i { + width: 16px; + display: flex; + justify-content: center; + align-items: center; + } + } +} \ No newline at end of file diff --git a/core/static/sas/album.scss b/core/static/sas/album.scss new file mode 100644 index 00000000..fbbba94b --- /dev/null +++ b/core/static/sas/album.scss @@ -0,0 +1,249 @@ +main { + box-sizing: border-box; + padding: 10px; +} + +.navbar { + margin-top: 10px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; + justify-content: space-between; +} + +.toolbar { + display: flex; + align-items: flex-end; + flex-wrap: wrap; + gap: 5px; + + > a, + > input { + padding: 0.4em; + margin: 0.1em; + font-size: 1.2em; + line-height: 1.2em; + color: black; + background-color: #f2f2f2; + border-radius: 5px; + font-weight: bold; + + &:hover { + background-color: #d4d4d4; + } + + &:disabled { + background-color: #f2f2f2; + color: #d4d4d4; + } + } +} + +.add-files { + display: flex; + flex-direction: column; + + > .inputs { + align-items: flex-end; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; + + > p { + box-sizing: border-box; + max-width: 300px; + width: 100%; + + @media (max-width: 500px) { + max-width: 100%; + } + + > input { + box-sizing: border-box; + max-width: 100%; + width: 100%; + height: 40px; + line-height: normal; + font-size: 16px; + } + } + + > div > input, + > input { + box-sizing: border-box; + height: 40px; + width: 100%; + max-width: 300px; + + @media (max-width: 500px) { + max-width: 100%; + } + } + + > div { + width: 100%; + max-width: 300px; + } + + > input[type=submit]:hover { + background-color: #287fb8; + color: white; + } + } + +} + +.clipboard { + margin-top: 10px; + padding: 10px; + background-color: rgba(0,0,0,.1); + border-radius: 10px; +} + +.paginator { + display: flex; + justify-content: center; + gap: 10px; + width: -moz-fit-content; + width: fit-content; + background-color: rgba(0,0,0,.1); + border-radius: 10px; + padding: 10px; + margin: 10px 0 10px auto; +} + +.photos, +.albums { + box-sizing: border-box; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 5px; + + > div { + background: rgba(0, 0, 0, .5); + cursor: not-allowed; + } + + > div, + > a { + box-sizing: border-box; + position: relative; + height: 128px; + + @media (max-width: 500px) { + width: calc(50% - 5px); + height: 108px; + } + + @media (max-width: 300px) { + width: 100%; + } + + &:hover { + background: rgba(0, 0, 0, .5); + } + + > input[type=checkbox] { + position: absolute; + top: 0; + right: 0; + height: 15px; + width: 15px; + margin: 5px; + + cursor: pointer; + } + + > .photo, + > .album { + box-sizing: border-box; + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + + width: calc(16 / 9 * 128px); + height: 128px; + + margin: 0; + padding: 0; + box-shadow: none; + + border: 1px solid rgba(0, 0, 0, .3); + + @media (max-width: 500px) { + width: 100%; + height: 100%; + } + + &:hover > .text { + background-color: rgba(0, 0, 0, .5); + } + + &:hover > .overlay { + -webkit-backdrop-filter: blur(2px); + backdrop-filter: blur(2px); + + ~ .text { + background-color: transparent; + } + } + + > .text { + position: absolute; + box-sizing: border-box; + top: 0; + left: 0; + width: 100%; + height: 100%; + + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-start; + + padding: 10px; + color: white; + } + + > .overlay { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + + &::before { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + content: '⚠️'; + color: white; + display: flex; + justify-content: center; + align-items: center; + + background: rgba(0, 0, 0, .5); + -webkit-backdrop-filter: blur(5px); + backdrop-filter: blur(5px); + } + } + } + + > .album > div { + background: rgba(0, 0, 0, .5); + background: linear-gradient(0deg, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, 0) 100%); + text-align: left; + word-break: break-word; + } + + > .photo > .text { + align-items: center; + padding-bottom: 30px; + } + } +} \ No newline at end of file diff --git a/core/static/sas/picture.scss b/core/static/sas/picture.scss new file mode 100644 index 00000000..f5c895fe --- /dev/null +++ b/core/static/sas/picture.scss @@ -0,0 +1,309 @@ +#content { + padding: 10px !important; +} + +.title { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.container { + display: flex; + flex-direction: row; + gap: 10px; + + @media (max-width: 1000px) { + flex-direction: column; + } +} + +.main { + display: flex; + flex-direction: column; + width: calc(75% - 5px); + gap: 10px; + + @media (max-width: 1000px) { + width: 100%; + } + + > .photo { + box-sizing: border-box; + height: 500px; + display: flex; + justify-content: center; + background-color: #333333; + padding: 5px; + + @media (max-width: 1000px) { + width: 100%; + height: auto; + } + + > img { + height: 100%; + max-width: 100%; + object-fit: contain; + } + } +} + +.subsection { + width: calc(25% - 5px); + + @media (max-width: 1000px) { + width: 100%; + } + + > .navigation { + display: flex; + flex-direction: row; + gap: 10px; + + @media (max-width: 1000px) { + width: 100%; + } + + > #prev, + > #next { + width: calc(50% - 5px); + aspect-ratio: 16/9; + background: #aaa; + + > a { + display: flex; + position: relative; + width: 100%; + height: 100%; + + > div { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 30px; + color: white; + + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + + &::before { + position: absolute; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, .3); + } + } + } + } + + > #prev > a > div::before { + content: '←'; + } + > #next > a > div::before { + content: '→'; + } + } + + > .tags { + @media (min-width: 1001px) { + margin-right: 5px; + } + + > ul { + list-style-type: none; + margin: 0; + display: flex; + flex-direction: column; + gap: 5px; + + @media (max-width: 1000px) { + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + margin-right: 5px; + } + + > li { + box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + justify-content: space-between; + + @media (max-width: 1000px) { + max-width: calc(50% - 5px); + } + + > a { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + + &.user { + width: 100%; + background-color: #eee; + padding: 5px 10px 5px 5px; + border-radius: 5px; + color: black; + max-width: calc(100% - 40px); + min-height: 30px; + + &:hover { + background-color: #aaa; + } + + > span { + width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + } + + &.delete { + margin-left: 10px; + } + + > img { + width: 25px; + max-height: 25px; + object-fit: contain; + border-radius: 50%; + } + } + } + } + + > form { + > p { + box-sizing: border-box; + + > input { + width: 100%; + max-width: 100%; + box-sizing: border-box; + } + } + + > .results_on_deck > div { + position: relative; + display: flex; + align-items: center; + word-break: break-word; + + > span { + position: absolute; + top: 0; + right: 0; + } + } + + > input { + width: 100%; + max-width: 100%; + box-sizing: border-box; + } + } + } +} + +.general { + display: flex; + flex-direction: row; + gap: 20px; + + @media (max-width: 1000px) { + flex-direction: column; + } + + > .infos { + display: flex; + flex-direction: column; + + > div > div { + display: flex; + flex-direction: row; + justify-content: space-between; + + > *:first-child { + min-width: 150px; + + @media (max-width: 1000px) { + min-width: auto; + } + } + } + } + + > .tools { + display: flex; + flex-direction: column; + width: 100%; + + > div { + display: flex; + flex-direction: row; + justify-content: space-between; + + > div { + > a.button { + box-sizing: border-box; + background-color: #f2f2f2; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + color: black; + border-radius: 5px; + width: 40px; + height: 40px; + + &:hover { + background-color: #aaa; + } + } + + > a.text.danger { + color: red; + + &:hover { + color: darkred; + } + } + + &.buttons { + display: flex; + gap: 5px; + } + } + } + } +} + +.moderation { + box-sizing: border-box; + width: 100%; + border: 2px solid coral; + border-radius: 2px; + padding: 10px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + > div:last-child { + display: flex; + gap: 20px; + } +} diff --git a/core/static/user/login.scss b/core/static/user/login.scss new file mode 100644 index 00000000..fb4a5768 --- /dev/null +++ b/core/static/user/login.scss @@ -0,0 +1,108 @@ +html, +body { + box-sizing: border-box; + height: 100%; +} + +body { + display: flex; + flex-direction: column; +} + +#page { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + #content { + display: flex; + flex-direction: column; + padding: 10px; + box-shadow: none; + background-color: white; + margin: 0; + + > .title { + text-align: center; + margin: 0; + } + + > div, + > form { + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + width: 100%; + max-width: 500px; + margin-top: 20px; + + > p, + > div { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + margin: 0; + + > label { + width: 100%; + + @media (min-width: 500px) { + width: 300px; + } + } + } + + > input, + > p > input, + > div > input { + box-sizing: border-box; + width: 100%; + max-width: 500px; + + @media (min-width: 500px) { + max-width: 300px; + } + } + + > .errorlist { + color: red; + text-align: center; + margin: 10px 0 0 0; + list-style-type: none; + } + + > .required > .helptext { + text-align: center; + font-style: italic; + } + + > .required:last-of-type { + box-sizing: border-box; + max-width: 300px; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + + > label { + width: 100%; + } + + > img { + width: 70px; + object-fit: contain; + } + + > input { + width: 200px; + } + } + } + } +} \ No newline at end of file diff --git a/core/static/user/user_detail.scss b/core/static/user/user_detail.scss new file mode 100644 index 00000000..6ac5065a --- /dev/null +++ b/core/static/user/user_detail.scss @@ -0,0 +1,200 @@ +main { + box-sizing: border-box; + display: flex; + margin-bottom: 4em; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 10px; + width: 100%; + + > .user_profile > .user_profile_infos { + @media (max-width: 960px) { + border-right-color: transparent; + } + } +} + +.user-name { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + max-width: 1080px; + margin: 0 auto; +} + +.infos-and-picture { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + max-width: 1080px; + margin: 0 auto; + + @media (max-width: 960px) { + flex-direction: column-reverse; + gap: 20px; + } + + > .user_profile_infos { + width: 50%; + border-right: solid 1px grey; + + @media (max-width: 960px) { + width: 100%; + } + + @media (min-width: 960px) { + padding-right: 20px; + } + + > .user_profile_infos_promo { + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; + justify-content: center; + width: 100%; + + > img { + width: 5em; + margin: 0.5em; + } + } + + > .user_profile_infos_items { + margin-top: 30px; + display: flex; + flex-direction: column; + gap: 5px; + + > div { + box-sizing: border-box; + display: flex; + + > .user_profile_infos_item, + > .user_profile_infos_item_value { + vertical-align: top; + display: block; + width: 50%; + } + + > .user_profile_infos_item { + color: gray; + } + } + } + + > #user_profile_infos_quote { + text-align: right; + color: grey; + font-style: italic; + + @media (max-width: 960px) { + text-align: center; + } + + &:after, + &:before { + vertical-align: middle; + } + + &:before { + content: "\201C"; + } + + &:after { + content: "\201D"; + } + } + } + + > .user_profile_pictures { + height: 20em; + width: 50%; + display: flex; + flex-direction: row; + justify-content: flex-end; + + @media (max-width: 960px) { + width: 100%; + height: 100%; + flex-direction: column; + } + + @media (min-width: 960px) { + padding-left: 20px; + } + + > .user_profile_pictures_bigone { + flex-grow: 9; + flex-basis: 20em; + display: flex; + justify-content: center; + align-items: center; + + > img { + max-height: 100%; + max-width: 100%; + object-fit: contain; + + @media (max-width: 960px) { + max-width: 300px; + width: 100%; + object-fit: contain; + } + } + } + + > .user_profile_pictures_thumbnails { + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 20px; + flex-grow: 1; + + @media (max-width: 960px) { + flex-direction: row; + height: 50%; + } + + > img { + max-height: calc(100% / 3); + width: 100%; + object-fit: contain; + + @media (max-width: 960px) { + max-height: 100%; + max-width: calc(100% / 3) !important; + height: auto; + } + } + } + } +} + +.form-gifts { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + gap: 10px; + + @media (max-width: 960px) { + flex-direction: column; + } + + >select, + >input { + min-width: 300px; + max-width: 100%; + height: 40px; + + @media (max-width: 960px) { + width: 100%; + } + } +} \ No newline at end of file diff --git a/core/static/user/user_edit.scss b/core/static/user/user_edit.scss new file mode 100644 index 00000000..fdba806e --- /dev/null +++ b/core/static/user/user_edit.scss @@ -0,0 +1,193 @@ + +@media (max-width: 750px) { + .title { + text-align: center; + } +} + +.field-error { + height: auto !important; + + > ul { + list-style-type: none; + margin: 0; + color: indianred; + + > li { + text-align: left !important; + line-height: normal; + margin-top: 5px; + } + } +} + +.profile { + &-visible { + display: flex; + justify-content: center; + align-items: center; + gap: 5px; + padding-top: 10px; + } + + &-pictures { + box-sizing: border-box; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 40px; + align-items: stretch; + + @media (max-width: 750px) { + flex-direction: column; + gap: 10px + } + } + + &-picture { + box-sizing: border-box; + display: flex; + justify-content: space-between; + flex-direction: column; + align-items: center; + flex-wrap: wrap; + gap: 20px; + width: 100%; + height: 100%; + max-width: 300px; + + @media (max-width: 750px) { + max-width: 100%; + padding: 10px 10px 0; + } + + &-display { + display: flex; + flex-direction: column; + justify-content: center; + height: 300px; + gap: 10px; + + @media (max-width: 750px) { + height: auto; + } + + >img { + width: 100% !important; + object-fit: contain; + height: auto; + } + + >p { + text-align: left !important; + width: 100% !important; + } + } + + &-edit { + display: flex; + flex-direction: column-reverse; + align-items: center; + justify-content: center; + width: 100%; + + > a { + margin-bottom: 15px; + } + + > input { + font-size: .8em; + font-weight: normal; + cursor: pointer; + } + + > p { + margin-bottom: 0; + text-align: left !important; + min-height: 50px; + } + } + } + + &-fields { + padding: 10px 10px 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + } + + &-field { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + width: 100%; + max-width: 330px; + min-width: 300px; + + @media (max-width: 750px) { + gap: 4px; + max-width: 100%; + } + + >* { + width: 100%; + max-width: 300px; + + @media (max-width: 750px) { + max-width: 100%; + } + } + + &-label { + text-align: left !important; + } + + &-content { + + >* { + box-sizing: border-box; + text-align: left !important; + line-height: 40px; + max-width: 100%; + width: 100%; + height: 40px; + margin: 0; + + >* { + text-align: left !important; + } + } + + + >textarea { + height: 120px; + min-height: 40px; + min-width: 300px; + max-width: 300px; + line-height: initial; + + @media (max-width: 750px) { + max-width: 100%; + } + } + + >input[type="file"] { + font-size: small; + line-height: 30px; + } + + >input[type="checkbox"] { + width: 20px; + height: 20px; + margin: 0; + float: left; + } + } + } +} \ No newline at end of file diff --git a/core/static/user/user_godfathers.scss b/core/static/user/user_godfathers.scss new file mode 100644 index 00000000..e600ec1b --- /dev/null +++ b/core/static/user/user_godfathers.scss @@ -0,0 +1,113 @@ +.container { + display: flex; + flex-direction: column; + gap: 10px; + padding: 10px; + box-sizing: border-box; + + > form { + margin: 0; + } +} + +.users { + display: flex; + flex-direction: row; + flex-wrap: wrap; + list-style-type: none; + margin: 0; + gap: 10px +} + +.users-card { + display: flex; + flex-direction: column; + gap: 10px; + width: 150px; + padding: 10px; + background-color: rgba(0, 0, 0, .05); + border-radius: 10px; + + @media (max-width: 375px) { + width: 100%; + } + + // Django moment + > div.mini_profile_link { + position: relative; + + > a { + &.mini_profile_link { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + + @media (max-width: 375px) { + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + max-height: 65px; + } + + > span { + height: 150px; + width: 100%; + + @media (max-width: 375px) { + height: 80px; + width: 80px; + } + + > img { + width: 100%; + max-width: 100%; + max-height: 100%; + height: auto; + object-fit: contain; + + @media (max-width: 375px) { + max-width: 100%; + max-height: 80px; + } + } + } + + > em { + box-sizing: border-box; + padding: 0 5px; + text-align: center; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + + @media (max-width: 375px) { + margin-top: 10px; + text-align: left; + max-width: none; + width: 100%; + } + } + } + + &:last-of-type { + margin-top: 10px; + display: block; + text-align: center; + color: orangered; + + @media (max-width: 375px) { + position: absolute; + bottom: 0%; + right: 0; + } + } + } + } + + // Django moment + > a.mini_profile_link { + display: none; + } +} \ No newline at end of file diff --git a/core/static/user/user_group.scss b/core/static/user/user_group.scss new file mode 100644 index 00000000..5c110c4b --- /dev/null +++ b/core/static/user/user_group.scss @@ -0,0 +1,12 @@ +#id_groups { + margin: 0; + + >li { + list-style-type: none; + padding-left: 20px; + + >label { + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/core/static/user/user_preferences.scss b/core/static/user/user_preferences.scss new file mode 100644 index 00000000..c61142d0 --- /dev/null +++ b/core/static/user/user_preferences.scss @@ -0,0 +1,58 @@ +.form { + display: flex; + flex-direction: column; + margin: 10px 0; + gap: 5px; + + &-general { + > p { + display: flex; + flex-direction: row-reverse; + justify-content: left; + align-items: center; + gap: 5px; + margin: 0; + + > label { + cursor: pointer; + margin: 0; + } + } + } + + &-cards, + &-trombi { + >p { + display: flex; + flex-direction: column; + align-items: flex-start; + text-align: justify; + gap: 5px; + margin: 0; + + >input, + >select { + min-width: 300px; + } + } + } + + &-submit-btn { + margin-top: 10px !important; + max-width: 100px; + } +} + +.justify { + text-align: justify; +} + +.main { + padding: 10px; +} + +.no-cards, +.student-cards { + margin-top: 10px; + display: block; +} diff --git a/core/static/user/user_stats.scss b/core/static/user/user_stats.scss new file mode 100644 index 00000000..62edec5c --- /dev/null +++ b/core/static/user/user_stats.scss @@ -0,0 +1,48 @@ +.container { + padding: 10px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.row { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + margin-bottom: 10px; + gap: 30px; + + @media (max-width: 535px) { + gap: 20px; + + >div, + >div>.flexed { + width: 100%; + align-items: stretch; + } + } +} + + +.flexed { + display: flex; + flex-direction: column; + gap: 2px; + align-items: self-start; + + >div { + display: flex; + justify-content: space-between; + + >b, + >span { + width: 120px; + + &:last-child { + text-align: right; + } + } + } +} \ No newline at end of file diff --git a/core/static/user/user_tools.scss b/core/static/user/user_tools.scss new file mode 100644 index 00000000..fb10c042 --- /dev/null +++ b/core/static/user/user_tools.scss @@ -0,0 +1,41 @@ +main { + box-sizing: border-box; + padding: 10px; +} + +.container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + + > div { + border-radius: 10px; + background-color: rgba(0, 0, 0, .05); + width: 210px; + + >h4 { + text-align: center; + } + + >ul { + list-style-type: none; + margin: 20px 10px; + display: flex; + flex-direction: column; + gap: 10px; + } + } + + @media (max-width: 550px) { + >div { + width: 100%; + background-color: transparent; + + >h4 { + text-align: left; + } + } + } +} \ No newline at end of file diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index e343f2a1..51e2d5da 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -2,27 +2,32 @@ {% block head %} - {% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM - - - - - - - {% block jquery_css %} - {# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #} - - {% endblock %} - - - + {% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM + + + + + + + + - - - - - {% block additional_css %}{% endblock %} - {% block additional_js %}{% endblock %} + {% block jquery_css %} + {# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #} + + {% endblock %} + + + + + + + + + + + {% block additional_css %}{% endblock %} + {% block additional_js %}{% endblock %} {% endblock %} @@ -33,197 +38,210 @@ {% csrf_token %} {% block header %} - {% if not popup %} -
    -
    - {% for language in LANGUAGES %} -
    {% csrf_token %} - - - - - {% endfor %} -
    - - {% if not user.is_authenticated %} - + {% if not popup %} +
    + + {% if not user.is_authenticated %} + + {% else %} +
    +
    + + + + + +
    + +
    + {% endif %} +
    + {% for language in LANGUAGES %} +
    + {% csrf_token %} + + + + + {% endfor %} +
    +
    + + {% block info_boxes %} +
    + {% set sith = get_sith() %} + {% if sith.alert_msg %} +
    + {{ sith.alert_msg|markdown }} +
    + {% endif %} + {% if sith.info_msg %} +
    + {{ sith.info_msg|markdown }} +
    + {% endif %} +
    + {% endblock %} + {% else %} -
    - - {% endcache %} - - - - - -
    +
    {{ user.get_display_name() }}
    {% endif %} -
    - -
    - {% block info_boxes %} - {% set sith = get_sith() %} - {% if sith.alert_msg %} -
    - {{ sith.alert_msg|markdown }} -
    - {% endif %} - {% if sith.info_msg %} -
    - {{ sith.info_msg|markdown }} -
    - {% endif %} - {% endblock %} -
    - - {% else %}{# if not popup #} -
    {{ user.get_display_name() }}
    - {% endif %} - {% endblock %} + {% block nav %} + {% if not popup %} + + {% endif %} + {% endblock %} +
    - {% block nav %} - {% if not popup %} - - {% endif %} - {% endblock %}
      {% for n in quick_notifs %} @@ -232,20 +250,15 @@
    - {% if list_of_tabs %} -
    -
    {{ tabs_title }}
    -
    - {% for t in list_of_tabs -%} - {{ t.name }} - {%- endfor %} -
    -
    - {% endif %} + {% if list_of_tabs %} +
    +
    + {% for t in list_of_tabs -%} + {{ t.name }} + {%- endfor %} +
    +
    + {% endif %} {% if error %} {{ error }} @@ -256,18 +269,24 @@
    {% if not popup %} - + {% endif %} -
    -

    {{ profile.get_full_name() }}

    +
    +

    {{ profile.get_full_name() }}

    {% if profile.nick_name %} -
    « {{ profile.nick_name }} »
    + {% endif %} +
    - - - {% if profile.quote %} -
    +
    + {% else %} - {% trans %}No gift given yet{% endtrans %} + {% trans %}No gift given yet{% endtrans %} {% endif %}
    {% endif %} @@ -228,33 +237,33 @@ {% block script %} {{ super() }} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/user_edit.jinja b/core/templates/core/user_edit.jinja index 5bd692e9..35295226 100644 --- a/core/templates/core/user_edit.jinja +++ b/core/templates/core/user_edit.jinja @@ -1,86 +1,184 @@ -{% extends "core/base.jinja" %} +{%- extends "core/base.jinja" -%} -{% block title %} -{% trans %}Edit user{% endtrans %} -{% endblock %} +{%- block title -%} +{%- trans -%}Edit user{%- endtrans -%} +{%- endblock -%} -{% block content %} -

    {% trans %}Edit user profile{% endtrans %}

    +{%- block additional_css -%} + +{%- endblock -%} + +{%- block content -%} +

    {%- trans -%}Edit user profile{%- endtrans -%}

    - {% csrf_token %} + + {%- csrf_token -%} {{ form.non_field_errors() }} - {% for field in form %} -

    {{ field.errors }}

    -
    - {% trans %}Take picture{% endtrans %} + + {# User Pictures #} +
    +
    +
    + + {%- if form.instance.profile_pict -%} + {%- trans -%}Profile{%- endtrans -%} + {%- else -%} + {%- trans -%}Profile{%- endtrans -%} + {%- endif -%} +
    +
    +

    {{ form["profile_pict"].label }}

    + {{ form["profile_pict"] }} + {%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.profile_pict.id -%} + + {%- trans -%}Delete{%- endtrans -%} + + {%- endif -%} +
    +
    +
    +
    + {%- if form.instance.avatar_pict -%} + {%- trans -%}Profile{%- endtrans -%} + {%- else -%} + {%- trans -%}Profile{%- endtrans -%} + {%- endif -%} +
    +
    +

    {{ form["avatar_pict"].label }}

    + {{ form["avatar_pict"] }} + {%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.avatar_pict.id -%} + + {%- trans -%}Delete{%- endtrans -%} + + {%- endif -%} +
    +
    +
    +
    + {%- if form.instance.scrub_pict -%} + {%- trans -%}Profile{%- endtrans -%} + {%- else -%} + {%- trans -%}Profile{%- endtrans -%} + {%- endif -%} +
    +
    +

    {{ form["scrub_pict"].label }}

    + {{ form["scrub_pict"] }} + {%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.scrub_pict.id -%} + + {%- trans -%}Delete{%-endtrans -%} + + {%- endif -%} +
    +
    + + + {# All fields #} +
    + {%- for field in form -%} + {%- + if field.name in ["quote","profile_pict","avatar_pict","scrub_pict","is_subscriber_viewable","forum_signature"] + -%} + {%- continue -%} + {%- endif -%} + +
    +
    {{ field.label }}
    +
    + {{ field }} + {%- if field.errors -%} +
    {{ field.errors }}
    + {%- endif -%} +
    +
    + {%- endfor -%} +
    + + {# Textareas #} +
    + {%- for field in [form["quote"], form["forum_signature"]] -%} +
    +
    {{ field.label }}
    +
    + {{ field }} + {%- if field.errors -%} +
    {{ field.errors }}
    + {%- endif -%} +
    +
    + {%- endfor -%} +
    + + {# Checkboxes #} +
    + {{ form["is_subscriber_viewable"] }} + {{ form["is_subscriber_viewable"].label }} +
    + + {%- if form.instance == user -%}

    - {% endif %}
    - {%- elif field.name == "avatar_pict" and form.instance.avatar_pict -%} -
    {% trans %}Current avatar: {% endtrans %} -
    - {%- elif field.name == "scrub_pict" and form.instance.scrub_pict -%} -
    {% trans %}Current scrub: {% endtrans %} -
    - {%- endif %} {{ field }}

    - {% endfor %} -

    -

    {% trans %}Username: {% endtrans %}{{ form.instance.username }}

    - {% if form.instance.customer %} -

    {% trans %}Account number: {% endtrans %}{{ form.instance.customer.account_id }}

    - {% endif %} - {% if form.instance == user %} -

    {% trans %}Change my password{% endtrans %}

    - {% elif user.is_root %} -

    {% trans %}Change user password{% endtrans %}

    - {% endif %} + {%- trans -%}Change my password{%- endtrans -%} +

    + {%- elif user.is_root -%} +

    + {%- trans -%}Change user password{%- + endtrans -%} +

    + {%- endif -%} + +

    + +

    -{% endblock %} - -{% block script %} - {{ super() }} - {% if not form.instance.profile_pict %} - - - {% endif %} -{% endblock %} +

    + {%- trans -%}Username: {%- endtrans -%} {{ form.instance.username }} +
    + {%- if form.instance.customer -%} + {%- trans -%}Account number: {%- endtrans -%} {{ form.instance.customer.account_id }} + {%- endif -%} +

    +{%- endblock -%} +{%- block script -%} +{{ super() }} +{%- if not form.instance.profile_pict -%} + + +{%- endif -%} +{%- endblock -%} \ No newline at end of file diff --git a/core/templates/core/user_godfathers.jinja b/core/templates/core/user_godfathers.jinja index 9a01be43..c5f48dfd 100644 --- a/core/templates/core/user_godfathers.jinja +++ b/core/templates/core/user_godfathers.jinja @@ -1,45 +1,67 @@ {% extends "core/base.jinja" %} {% from "core/macros.jinja" import user_link_with_pict, delete_godfather %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} {% trans user_name=profile.get_display_name() %}{{ user_name }}'s family{% endtrans %} {% endblock %} {% block content %} -

    - {% trans %}Show family picture{% endtrans %}

    +
    + + {% trans %}Show family picture{% endtrans %} + + +

    {% trans %}Godfathers / Godmothers{% endtrans %}

    {% if profile.godfathers.exists() %} -

    {% trans %}Godfathers / Godmothers{% endtrans %}

    -
    {% endblock %} diff --git a/core/templates/core/user_group.jinja b/core/templates/core/user_group.jinja index b062cafb..3becceb8 100644 --- a/core/templates/core/user_group.jinja +++ b/core/templates/core/user_group.jinja @@ -1,14 +1,16 @@ {% extends "core/base.jinja" %} +{%- block additional_css -%} + +{%- endblock -%} + {% block content %} -

    {% trans user_name=profile.get_full_name() %}Edit user groups for {{ user_name }}{% endtrans %}

    -
    - {% csrf_token %} - {{ form.as_p() }} -

    - -{% endblock %} - - - - +
    +

    {% trans user_name=profile.get_full_name() %}Edit user groups for {{ user_name }}{% endtrans %}

    +
    + {% csrf_token %} + {{ form.as_p() }} +

    + +
    +{%- endblock -%} \ No newline at end of file diff --git a/core/templates/core/user_pictures.jinja b/core/templates/core/user_pictures.jinja index 2d4e26a0..ba98fc95 100644 --- a/core/templates/core/user_pictures.jinja +++ b/core/templates/core/user_pictures.jinja @@ -1,26 +1,48 @@ {% extends "core/base.jinja" %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} {% trans user_name=profile.get_display_name() %}{{ user_name }}'s pictures{% endtrans %} {% endblock %} {% block content %} +
    {% if can_edit(profile, user) %} {% endif %} -{% for a in albums %} -
    + {% for a in albums %}

    {{ a.name }}

    -
    - {% for picture in pictures[a.id] %} -
    - - {{ picture.get_display_name() }} - +
    + {% for p in pictures[a.id] %} + {% if p.can_be_viewed_by(user) %} + +
    + {% if not p.is_moderated %} +
     
    +
    {% trans %}To be moderated{% endtrans %}
    + {% else %} +
     
    + {% endif %} +
    +
    + {% else %} +
    +
    +
    {% trans %}Picture Unavailable{% endtrans %}
    +
    +
    + {% endif %} + {% endfor %}
    +
    {% endfor %} -
    -{% endfor %} +
    {% endblock %} {% block script %} diff --git a/core/templates/core/user_preferences.jinja b/core/templates/core/user_preferences.jinja index 97416f47..58708635 100644 --- a/core/templates/core/user_preferences.jinja +++ b/core/templates/core/user_preferences.jinja @@ -1,46 +1,69 @@ {% extends "core/base.jinja" %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} {% trans %}Preferences{% endtrans %} {% endblock %} {% block content %} -

    {% trans %}Preferences{% endtrans %}

    -
    - {% csrf_token %} - {{ form.as_p() }} -

    - -

    {% trans %}Trombi{% endtrans %}

    -{% if trombi_form %} -
    - {% csrf_token %} - {{ trombi_form.as_p() }} -

    - -{% else %} -

    {% trans trombi=user.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 %}

    -

    {% 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 %}

    -
    - {% csrf_token %} - {{ student_card_form.as_p() }} -

    - -{% if profile.customer.student_cards.exists() %} - -{% else %} -

    {% trans %}No student cards registered.{% endtrans %}

    -{% endif %} -{% endif %} -{% endblock %} +
    +

    {% trans %}Preferences{% endtrans %}

    +

    {% trans %}General{% endtrans %}

    +
    + {% csrf_token %} + {{ form.as_p() }} + + + +

    {% trans %}Trombi{% endtrans %}

    + + {% if trombi_form %} +
    + {% csrf_token %} + {{ trombi_form.as_p() }} + + + + {% else %} +

    {% trans trombi=user.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() }} + + + {% endif %} +
    +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/user_stats.jinja b/core/templates/core/user_stats.jinja index a481ca99..be7c44cc 100644 --- a/core/templates/core/user_stats.jinja +++ b/core/templates/core/user_stats.jinja @@ -1,25 +1,41 @@ {% extends "core/base.jinja" %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} {% trans user_name=profile.get_display_name() %}{{ user_name }}'s stats{% endtrans %} {% endblock %} {% block content %} +
    +
    {% if profile.permanencies %} -

    {% trans %}Permanencies{% endtrans %}

    -

    Total: {{ total_perm_time }}

    -

    Foyer: {{ total_foyer_time }}

    -

    MDE: {{ total_mde_time }}

    -

    La Gommette: {{ total_gommette_time }}

    +

    {% trans %}Permanencies{% endtrans %}

    +
    +
    Foyer :{{ total_foyer_time }}
    +
    Gommette :{{ total_gommette_time }}
    +
    MDE :{{ total_mde_time }}
    +
    Total :{{ total_perm_time }}
    +
    {% endif %} -

    {% trans %}Buyings{% endtrans %}

    +
    -

    Foyer: {{ total_foyer_buyings }} €

    -

    MDE: {{ total_mde_buyings }} €

    -

    La Gommette: {{ total_gommette_buyings }} €

    +

    {% trans %}Buyings{% endtrans %}

    +
    +
    Foyer :{{ total_foyer_buyings }} €
    +
    Gommette :{{ total_gommette_buyings }} €
    +
    MDE :{{ total_mde_buyings }} €
    +
    Total :{{ total_foyer_buyings + total_gommette_buyings + total_mde_buyings }} € +
    +
    +
    + +

    {% trans %}Product top 10{% endtrans %}

    {% trans %}Nb{% endtrans %}{% trans %}User{% endtrans %}{% trans %}Time{% endtrans %}
    {% trans %}User{% endtrans %}{% trans %}Promo{% endtrans %}{% trans %}Time{% endtrans %}
    {{ loop.index }}{{ u.get_display_name() }}{{ r.perm_sum }}{{ barman.name }} {% if barman.nickname %}({{ barman.nickname }}){% endif %}{{ barman.promo or '' }}{{ barman.perm_sum|format_timedelta|truncate_time("millis") }}
    @@ -29,14 +45,14 @@ - {% for p in top_product %} + {% for p in top_product %} - {% endfor %} + {% endfor %}
    {{ p['product__name'] }} {{ p['product_sum'] }}
    -{% endblock %} - - +
    +
    +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja index 4804f016..b0fb9b8e 100644 --- a/core/templates/core/user_tools.jinja +++ b/core/templates/core/user_tools.jinja @@ -1,137 +1,176 @@ {% extends "core/base.jinja" %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} {% trans user_name=user.get_display_name() %}{{ user_name }}'s tools{% endtrans %} {% endblock %} {% block content %} -

    {% trans %}User Tools{% endtrans %}

    +
    +

    {% trans %}User Tools{% endtrans %}

    -
    -

    {% trans %}Sith management{% endtrans %}

    - - -
    -

    {% trans %}Counters{% endtrans %}

    - -
    -

    {% trans %}Accounting{% endtrans %}

    - + {% set is_admin_on_a_counter = false %} + {% for b in settings.SITH_COUNTER_BARS if user.is_in_group(b[1] + " admin") %} + {% set is_admin_on_a_counter = true %} + {% endfor %} -
    -

    {% trans %}Communication{% endtrans %}

    - + {% if + user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) or user.is_root + or is_admin_on_a_counter + %} +
    +

    {% trans %}Counters{% endtrans %}

    + +
    + {% endif %} + {% if + user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_root + or user.memberships.filter(end_date=None).filter(role__gte=7).all() | length > 10 + %} +
    +

    {% trans %}Accounting{% endtrans %}

    + +
    + {% endif %} -
    -

    {% trans %}Elections{% endtrans %}

    - + {% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user.is_root %} + + {% endif %} -
    -

    {% trans %}Other tools{% endtrans %}

    - - -{% endblock %} + {% if user.memberships.filter(end_date=None).all().count() > 0 %} +
    +

    {% trans %}Club tools{% endtrans %}

    +
      + {% for m in user.memberships.filter(end_date=None).all() %} +
    • {{ m.club }}
    • + {% endfor %} +
    +
    + {% endif %} + {% if user.is_in_group(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) or user.is_root %} + + {% endif %} +
    +

    {% trans %}Elections{% endtrans %}

    + +
    + +
    + +{% endblock %} \ No newline at end of file diff --git a/core/tests.py b/core/tests.py index ddb9f03d..fd44ae24 100644 --- a/core/tests.py +++ b/core/tests.py @@ -272,7 +272,7 @@ class UserRegistrationTest(TestCase): ) self.assertTrue(response.status_code == 200) self.assertTrue( - """

    Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

    """ + """

    Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

    """ in str(response.content) ) diff --git a/core/utils.py b/core/utils.py index 0c5fa10c..37ff611e 100644 --- a/core/utils.py +++ b/core/utils.py @@ -22,6 +22,7 @@ # # +import subprocess import re # Image utils @@ -37,6 +38,17 @@ from django.conf import settings from django.core.files.base import ContentFile +def get_git_revision_short_hash() -> str: + """ + Return the short hash of the current commit + """ + return ( + subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]) + .decode("ascii") + .strip() + ) + + def get_start_of_semester(d=date.today()): """ This function computes the start date of the semester with respect to the given date (default is today), diff --git a/core/views/forms.py b/core/views/forms.py index 9359cd28..936abb26 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -250,6 +250,7 @@ class UserProfileForm(forms.ModelForm): "scrub_pict": forms.ClearableFileInput, "phone": PhoneNumberInternationalFallbackWidget, "parent_phone": PhoneNumberInternationalFallbackWidget, + "quote": forms.Textarea, } labels = { "profile_pict": _( diff --git a/counter/templates/counter/activity.jinja b/counter/templates/counter/activity.jinja index 08e405ff..701dbc05 100644 --- a/counter/templates/counter/activity.jinja +++ b/counter/templates/counter/activity.jinja @@ -5,23 +5,41 @@ {% trans counter_name=counter %}{{ counter_name }} activity{% endtrans %} {% endblock %} -{% block content %} -

    {% trans counter_name=counter %}{{ counter_name }} activity{% endtrans %}

    - {% if counter.type == 'BAR' %} -

    {% trans %}Barmen list{% endtrans %}

    -
      - {% for b in counter.get_barmen_list() %} -
    • {{ user_profile_link(b) }}
    • - {% endfor %} -
    - {% endif %} +{%- block additional_css -%} + +{%- endblock -%} -
    {% trans %}Legend{% endtrans %}
    - : {% trans %}counter is open, there's at least one barman connected{% endtrans %} -
    - ? : {% trans minutes=settings.SITH_COUNTER_MINUTE_INACTIVE %}counter is open but not active, the last sale was done at least {{ minutes }} minutes ago {% endtrans %} -
    - : {% trans %}counter is not open : no one is connected{% endtrans %} +{% block content %} +

    {% trans counter_name=counter %}{{ counter_name }} activity{% endtrans %}

    + {% if counter.type == 'BAR' %} +

    {% trans %}Barmen list{% endtrans %}

    +
      + {% set barmans_list = counter.get_barmen_list() %} + {% if barmans_list | length > 0 %} + {% for b in barmans_list %} +
    • {{ user_profile_link(b) }}
    • + {% endfor %} + {% else %} + {% trans %}There is currently no barman connected.{% endtrans %} + {% endif %} +
    + {% endif %} + +
    {% trans %}Legend{% endtrans %}
    +
    +
    + + {% trans %}counter is open, there's at least one barman connected{% endtrans %} +
    +
    + + {% trans minutes=settings.SITH_COUNTER_MINUTE_INACTIVE %}counter is open but not active, the last sale was done at least {{ minutes }} minutes ago {% endtrans %} +
    +
    + + {% trans %}counter is not open : no one is connected{% endtrans %} +
    +
    {% endblock %} diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 977437bc..04f0ff27 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -1641,6 +1641,10 @@ msgstr "Appels affichés" msgid "Calls to moderate" msgstr "Appels à modérer" +#: core/templates/core/base.jinja +msgid "Site version:" +msgstr "Version du site :" + #: com/templates/com/news_admin_list.jinja:242 #: core/templates/core/base.jinja:183 msgid "Events" @@ -2211,8 +2215,8 @@ msgid "Visitor" msgstr "Visiteur" #: core/models.py:787 -msgid "do you want to receive the weekmail" -msgstr "voulez-vous recevoir le Weekmail" +msgid "receive the Weekmail" +msgstr "recevoir le Weekmail" #: core/models.py:789 msgid "show your stats to others" @@ -2220,11 +2224,11 @@ msgstr "montrez vos statistiques aux autres" #: core/models.py:791 msgid "get a notification for every click" -msgstr "recevez une notification pour chaque click" +msgstr "avoir une notification pour chaque click" #: core/models.py:794 msgid "get a notification for every refilling" -msgstr "recevez une notification pour chaque rechargement" +msgstr "avoir une notification pour chaque rechargement" #: core/models.py:817 msgid "file name" @@ -2399,7 +2403,7 @@ msgstr "Connexion" #: core/templates/core/base.jinja:62 core/templates/core/register.jinja:18 msgid "Register" -msgstr "S'enregister" +msgstr "Inscription" #: core/templates/core/base.jinja:91 core/templates/core/base.jinja:92 #: forum/templates/forum/macros.jinja:171 @@ -2559,9 +2563,9 @@ msgstr "Aide & Documentation" msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:268 -msgid "Site made by good people" -msgstr "Site réalisé par des gens bons" +#: core/templates/core/base.jinja:262 +msgid "Site created by the IT Department of the AE" +msgstr "Site réalisé par le Pôle Informatique de l'AE" #: core/templates/core/create.jinja:4 core/templates/core/create.jinja:8 #, python-format @@ -2641,6 +2645,11 @@ msgstr "Coller" msgid "Clipboard: " msgstr "Presse-papier : " +#: sas/templates/sas/album.jinja:69 +#: sas/templates/sas/album.jinja:97 +msgid "To be moderated" +msgstr "A modérer" + #: core/templates/core/file_detail.jinja:53 msgid "Real name: " msgstr "Nom réel : " @@ -3323,6 +3332,10 @@ msgstr "Télécharger toutes mes photos" msgid "Error downloading your pictures" msgstr "Erreur de téléchargement de vos photos" +#: core/templates/core/user_picture.jinja: +msgid "Picture Unavailable" +msgstr "Photo Indisponible" + #: core/templates/core/user_preferences.jinja:4 #: core/templates/core/user_preferences.jinja:8 core/views/user.py:234 msgid "Preferences" @@ -3357,8 +3370,8 @@ msgstr "" "14 caractères de long." #: core/templates/core/user_preferences.jinja:40 -msgid "No student cards registered." -msgstr "Aucune cartes étudiante enregistré." +msgid "No student card registered." +msgstr "Aucune carte étudiante enregistrée." #: core/templates/core/user_stats.jinja:4 #, python-format @@ -3981,6 +3994,10 @@ msgstr "Activité sur %(counter_name)s" msgid "Barmen list" msgstr "Barmans" +#: counter/templates/counter/activity.jinja:23 +msgid "There is currently no barman connected." +msgstr "Il n'y a actuellement aucun barman connecté." + #: counter/templates/counter/activity.jinja:19 msgid "Legend" msgstr "Légende" @@ -5328,12 +5345,20 @@ msgstr "photo" msgid "SAS" msgstr "SAS" +#: sas/templates/sas/album.jinja:102 +msgid "This album does not contain any photos." +msgstr "Cet album ne contient aucune photo." + #: sas/templates/sas/album.jinja:53 sas/templates/sas/album.jinja:55 #: sas/templates/sas/main.jinja:13 sas/templates/sas/main.jinja:15 #: sas/templates/sas/main.jinja:17 msgid "preview" msgstr "miniature" +#: sas/templates/sas/main.jinja:42 +msgid "You must be logged in to see the SAS." +msgstr "Vous devez être connecté pour voir les photos." + #: sas/templates/sas/album.jinja:89 msgid "Upload" msgstr "Envoyer" @@ -5752,6 +5777,12 @@ msgstr "Vous avez acheté %s" msgid "You have a notification" msgstr "Vous avez une notification" +#: core/templates/core/base.jinja +msgid "You do not have any unread notification" +msgstr "Vous n'avez aucune notification non lue" + +#: sith/settings.py:624 +#: sith/settings.py:648 #: sith/settings.py:650 msgid "Success!" msgstr "Succès !" diff --git a/sas/models.py b/sas/models.py index db82aef3..c66c6498 100644 --- a/sas/models.py +++ b/sas/models.py @@ -69,7 +69,6 @@ class Picture(SithFile): im = Image.open(BytesIO(f.read())) (w, h) = im.size return (w / h) < 1 - return False def can_be_edited_by(self, user): return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) @@ -79,11 +78,20 @@ class Picture(SithFile): # Result is cached 4s for this user if user.is_anonymous: return False + perm = cache.get("%d_can_view_pictures" % (user.id), False) - if perm: + + # use cache only when user is in SAS Admins or when picture is moderated + if perm and (self.is_moderated or self.can_be_edited_by(user)): return perm - perm = self.is_in_sas and self.is_moderated and user.was_subscribed + + perm = ( + self.is_in_sas + and (self.is_moderated or self.can_be_edited_by(user)) + and user.was_subscribed + ) cache.set("%d_can_view_pictures" % (user.id), perm, timeout=4) + return perm def get_download_url(self): diff --git a/sas/templates/sas/album.jinja b/sas/templates/sas/album.jinja index 6958adc6..16ae6d29 100644 --- a/sas/templates/sas/album.jinja +++ b/sas/templates/sas/album.jinja @@ -1,202 +1,248 @@ {% extends "core/base.jinja" %} {% from "core/macros.jinja" import paginate %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} -{% trans %}SAS{% endtrans %} + {% trans %}SAS{% endtrans %} {% endblock %} {% macro print_path(file) %} -{% if file and file.parent %} -{{ print_path(file.parent) }} -{{ file.get_display_name() }} > -{% endif %} + {% if file and file.parent %} + {{ print_path(file.parent) }} + {{ file.get_display_name() }} / + {% endif %} {% endmacro %} {% block content %} -SAS > {{ print_path(album.parent) }} {{ album.get_display_name() }} -

    {{ album.get_display_name() }}

    -{% trans %}Edit{% endtrans %}
    -{% set start = timezone.now() %} -
    -{% set edit_mode = user.can_edit(album) %} -{% if edit_mode %} -
    - {% csrf_token %} -

    - | - | - | - -

    - {% if clipboard %} -

    {% trans %}Clipboard: {% endtrans %} -

      - {% for f in clipboard %} -
    • {{ f.get_full_path() }}
    • - {% endfor %} -
    -

    - {% endif %} -{% endif %} -
    - {% for a in album.children_albums.order_by('-date') %} -
    - {% if edit_mode %} - - {% endif %} - {% if user.can_view(a) %} - -
    -
    - {% if a.file %} - {% trans %}preview{% endtrans %} - {% else %} - {% trans %}preview{% endtrans %} - {% endif %} + + SAS / {{ print_path(album.parent) }} {{ album.get_display_name() }} + + + {% set edit_mode = user.can_edit(album) %} + {% set start = timezone.now() %} + + {% if edit_mode %} + + {% csrf_token %} + + - {{ a.name }} + + {% if clipboard %} +
    + {% trans %}Clipboard: {% endtrans %} +
      + {% for f in clipboard %} +
    • {{ f.get_full_path() }}
    • + {% endfor %} +
    + +
    + {% endif %} + {% endif %} + + {% if album.children_albums.count() > 0 %} +

    {% trans %}Albums{% endtrans %}

    +
    + {% for a in album.children_albums.order_by('-date') %} + {% if a.can_be_viewed_by(user) %} + +
    + {% if not a.is_moderated %} +
     
    +
    {% trans %}To be moderated{% endtrans %}
    + {% else %} +
    {{ a.name }}
    + {% endif %} +
    + {% if edit_mode %} + + {% endif %} +
    + {% endif %} + {% endfor %}
    - - {% endif %} + +
    + {% endif %} + +

    {% trans %}Pictures{% endtrans %}

    + {% if pictures | length != 0 %} + - {% endfor %} -
    -
    - {% for p in pictures %} -
    - {% if edit_mode %} - - {% endif %} - {% if user.can_view(p) %} -
    - - {{ p.get_display_name() }} - + {% else %} + {% trans %}This album does not contain any photos.{% endtrans %} + {% endif %} + + {% if pictures.has_previous() or pictures.has_next() %} +
    + {{ paginate(pictures, paginator) }}
    - {% endif %} -
    - {% endfor %} -
    -
    - {{ paginate(pictures, paginator) }} -{% if edit_mode %} - -{% endif %} -
    - {% csrf_token %} - {{ form.as_p() }} -

    -
    -

    {% trans %}Template generation time: {% endtrans %} -{{ timezone.now() - start }} -

    + {% endif %} + + {% if edit_mode %} + + {% endif %} + + {% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %} +
    + {% csrf_token %} +
    + {{ form.as_p() }} + + +
    +
    + {% endif %} + +
    + +

    {% trans %}Template generation time: {% endtrans %} + {{ timezone.now() - start }} +

    {% endblock %} {% block script %} -{{ super() }} - + while(errorList.childElementCount > 0) + errorList.removeChild(errorList.firstElementChild); + + let progress; + if((progress = this.querySelector('progress')) === null) { + progress = document.createElement('progress'); + progress.value = 0; + let p = document.createElement('p'); + p.appendChild(progress); + this.insertBefore(p, this.lastElementChild); + } + + let dataHolder; + + if(formData.get('album_name')) { + dataHolder = new FormData(); + dataHolder.set('csrfmiddlewaretoken', '{{ csrf_token }}'); + dataHolder.set('album_name', formData.get('album_name')); + $.ajax({ + method: 'POST', + url: "{{ url('sas:album_upload', album_id=object.id) }}", + data: dataHolder, + processData: false, + contentType: false, + success: onSuccess + }); + } + + let images = formData.getAll('images'); + let imagesCount = images.length; + let completeCount = 0; + + let poolSize = 1; + let imagePool = []; + + while(images.length > 0 && imagePool.length < poolSize) { + let image = images.shift(); + imagePool.push(image); + sendImage(image); + } + + function sendImage(image) { + dataHolder = new FormData(); + dataHolder.set('csrfmiddlewaretoken', '{{ csrf_token }}'); + dataHolder.set('images', image); + + $.ajax({ + method: 'POST', + url: "{{ url('sas:album_upload', album_id=object.id) }}", + data: dataHolder, + processData: false, + contentType: false, + }) + .fail(onSuccess.bind(undefined, image)) + .done(onSuccess.bind(undefined, image)) + .always(next.bind(undefined, image)); + } + + function next(image, _, __) { + let index = imagePool.indexOf(image); + let nextImage = images.shift(); + + if(index !== -1) + imagePool.splice(index, 1); + + if(nextImage) { + imagePool.push(nextImage); + sendImage(nextImage); + } + } + + function onSuccess(image, data, _, __) { + let errors = []; + + if ($(data.responseText).find('.errorlist.nonfield')[0]) + errors = Array.from($(data.responseText).find('.errorlist.nonfield')[0].children); + + while(errors.length > 0) + errorList.appendChild(errors.shift()); + + progress.value = ++completeCount / imagesCount; + if(progress.value === 1 && errorList.children.length === 0) + document.location.reload() + } + }); + {% endblock %} diff --git a/sas/templates/sas/main.jinja b/sas/templates/sas/main.jinja index 3982bf47..fb9cd594 100644 --- a/sas/templates/sas/main.jinja +++ b/sas/templates/sas/main.jinja @@ -1,56 +1,110 @@ {% extends "core/base.jinja" %} +{%- block additional_css -%} + +{%- endblock -%} + {% block title %} -{% trans %}SAS{% endtrans %} + {% trans %}SAS{% endtrans %} {% endblock %} -{% macro display_album(a) %} -{% if a.is_moderated %} - -
    -
    - {% if a.file %} - {% trans %}preview{% endtrans %} - {% elif a.children.filter(is_folder=False, is_moderated=True).exists() %} - {% trans %}preview{% endtrans %} - {% else %} - {% trans %}preview{% endtrans %} - {% endif %} -
    - {{ a.name }} -
    -
    -{% elif user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %} - -{% endif %} +{% set edit_mode = user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %} + +{% macro display_album(a, checkbox) %} + + {% if a.file %} + {% set img = a.get_download_url() %} + {% elif a.children.filter(is_folder=False, is_moderated=True).exists() %} + {% set img = a.children.filter(is_folder=False).first().as_picture.get_download_thumb_url() %} + {% else %} + {% set img = static('core/img/sas.jpg') %} + {% endif %} + +
    +
    + {{ a.name }} +
    +
    + {# {% if edit_mode and checkbox %} + + {% endif %} #} +
    {% endmacro %} {% block content %} -

    {% trans %}SAS{% endtrans %}

    -
    -

    {% trans %}Latest albums{% endtrans %}

    -
    - {% for a in latest %} - {{ display_album(a) }} - {% endfor %} -
    -
    -

    {% trans %}All categories{% endtrans %}

    -
    - {% for a in categories %} - {{ display_album(a) }} - {% endfor %} -
    -{% if user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) %} -
    - {% csrf_token %} - {{ form.non_field_errors() }} -

    {{ form.album_name.errors }} - {{ form.album_name }}

    -

    -
    -{% endif %} +
    +

    {% trans %}SAS{% endtrans %}

    + + {% if not user.is_authenticated %} +

    {% trans %}You must be logged in to see the SAS.{% endtrans %}

    + {% else %} +
    +

    {% trans %}Latest albums{% endtrans %}

    + +
    + {% for a in latest %} + {{ display_album(a) }} + {% endfor %} +
    + +
    + + {% if edit_mode %} +
    + {% csrf_token %} + + + + {% if clipboard %} +
    + {% trans %}Clipboard: {% endtrans %} +
      + {% for f in clipboard %} +
    • {{ f.get_full_path() }}
    • + {% endfor %} +
    + +
    + {% endif %} + {% else %} +

    {% trans %}All categories{% endtrans %}

    + {% endif %} + +
    + {% for a in categories %} + {{ display_album(a, true) }} + {% endfor %} +
    + + {% if edit_mode %} +
    + +
    + +
    + {% csrf_token %} + +
    +
    + + {{ form.album_name }} +
    + +
    + + {{ form.non_field_errors() }} + {{ form.album_name.errors }} +
    + {% endif %} + {% endif %} +
    {% endblock %} diff --git a/sas/templates/sas/picture.jinja b/sas/templates/sas/picture.jinja index 68253ea4..f04f1112 100644 --- a/sas/templates/sas/picture.jinja +++ b/sas/templates/sas/picture.jinja @@ -1,150 +1,176 @@ {% extends "core/base.jinja" %} -{% block head %} -{{ super() }} - +{%- block additional_css -%} + +{%- endblock -%} -{% if picture.get_previous() %} - -{% endif %} -{% if picture.get_next() %} - -{% endif %} +{% block head %} + {{ super() }} + + {% if picture.get_previous() %} + + {% endif %} + {% if picture.get_next() %} + + {% endif %} {% endblock %} {% block title %} -{% trans %}SAS{% endtrans %} + {% trans %}SAS{% endtrans %} {% endblock %} {% macro print_path(file) %} -{% if file and file.parent %} -{{ print_path(file.parent) }} -{{ file.get_display_name() }} > -{% endif %} + {% if file and file.parent %} + {{ print_path(file.parent) }} + {{ file.get_display_name() }} / + {% endif %} {% endmacro %} {% block content %} -SAS > {{ print_path(picture.parent) }} {{ picture.get_display_name() }} -({{ picture.parent.children.filter(id__lte=picture.id).count() }} / {{ picture.parent.children.count() }}) -

    {{ picture.get_display_name() }}

    -
    -
    - - + + SAS / {{ print_path(picture.parent) }} {{ picture.get_display_name() }} + + +
    + +
    +

    {{ picture.get_display_name() }}

    +

    {{ picture.parent.children.filter(id__lte=picture.id).count() }} / {{ picture.parent.children.count() }}

    -
    -
    {% trans %}People{% endtrans %}
    -
      - {% for r in picture.people.all() %} -
    • - {{ r.user.get_short_name() }} - {% if user == r.user or user.can_edit(picture) %} - {% trans %}Delete{% endtrans %} + + + {% if not picture.is_moderated %} + {% set next = picture.get_next() %} + {% if not next %} + {% set next = url('sas:moderation') %} + {% else %} + {% set next = next.get_absolute_url() + "#pict" %} + {% endif %} + +
      +
      + {% if picture.asked_for_removal %} + {% trans %}Asked for removal{% endtrans %} + {% else %} +   {% endif %} -
    • - {% endfor %} -
    -
    - {% csrf_token %} - {{ form.as_p() }} -

    -
    -
    -
    -
    {% trans %}Infos{% endtrans %}
    -

    {% trans %}Date: {% endtrans %}{{ picture.date|date(DATETIME_FORMAT) }}

    -

    {% trans %}Owner: {% endtrans %}{{ picture.owner.get_short_name() }}

    - {% if picture.moderator %} -

    {% trans %}Moderator: {% endtrans %}{{ picture.moderator.get_short_name() }}

    - {% endif %} -

    {{ picture.parent.children.filter(id__lte=picture.id).count() }} / {{ picture.parent.children.count() }}

    -
    - -
    -{% if picture.is_moderated %} -
    -{% else %} -
    -{% set next = picture.get_next() %} -{% if not next %} - {% set next = url('sas:moderation') %} -{% else %} - {% set next = next.get_absolute_url() + "#pict" %} -{% endif %} -
    - {% if picture.asked_for_removal %} - {% trans %}Asked for removal{% endtrans %} +
    + +
    {% endif %} - - {% trans %}Moderate{% endtrans %} | - - {% trans %}Delete{% endtrans %} -
    -{% endif %} - {% if picture.is_vertical %} - {{ picture.get_display_name() }} - {% else %} - {{ picture.get_display_name() }} - {% endif %} -
    + +
    +
    + +
    + {{ picture.get_display_name() }} +
    + +
    +
    +
    {% trans %}Infos{% endtrans %}
    +
    +
    + {% trans %}Date: {% endtrans %} + {{ picture.date|date(DATETIME_FORMAT) }} +
    +
    + {% trans %}Owner: {% endtrans %} + {{ picture.owner.get_short_name() }} +
    + + {% if picture.moderator %} +
    + {% trans %}Moderator: {% endtrans %} + {{ picture.moderator.get_short_name() }} +
    + {% endif %} +
    +
    + +
    +
    {% trans %}Tools{% endtrans %}
    + +
    +
    +
    + +
    + + +
    +
    {% trans %}People{% endtrans %}
    +
    + {% csrf_token %} + {{ form.as_p() }} + +
    + +
    +
    +
    {% endblock %} {% block script %} -{{ super() }} - + {% endblock %} diff --git a/sith/settings.py b/sith/settings.py index d650f8ba..7a74a0fd 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -357,7 +357,7 @@ SITH_FORUM_PAGE_LENGTH = 30 # SAS variables SITH_SAS_ROOT_DIR_ID = 4 -SITH_SAS_IMAGES_PER_PAGE = 30 +SITH_SAS_IMAGES_PER_PAGE = 60 SITH_BOARD_SUFFIX = "-bureau" SITH_MEMBER_SUFFIX = "-membres" From 1f10a284f23d293188f893a030c3babc87da2403 Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:54:12 +0200 Subject: [PATCH 36/95] Added GA/Clubs Google Calendar to main page (#585) * Added GA/Clubs google calendar to main page * Made tables full width --- com/templates/com/news_list.jinja | 13 +++++++++++-- core/static/core/header.scss | 4 ++++ core/static/core/style.scss | 17 +++++++++++++---- locale/fr/LC_MESSAGES/django.po | 4 ++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/com/templates/com/news_list.jinja b/com/templates/com/news_list.jinja index dfc664e6..15eb7ea3 100644 --- a/com/templates/com/news_list.jinja +++ b/com/templates/com/news_list.jinja @@ -8,13 +8,13 @@ {% block content %} {% if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) %} +
    {% endif %}
    - {% for news in object_list.filter(type="NOTICE") %}

    {{ news.title }}

    @@ -97,6 +97,15 @@
    {% endfor %} {% endif %} + +

    {% trans %}All coming events{% endtrans %}

    +
    diff --git a/core/static/core/header.scss b/core/static/core/header.scss index d1b9581f..f69692c7 100644 --- a/core/static/core/header.scss +++ b/core/static/core/header.scss @@ -23,6 +23,10 @@ flex-direction: row; gap: 10px; + > a { + color: #fff; + } + &:hover > a { color: #1a78b3; } diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 9068e8c5..7abfa8ca 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -48,6 +48,7 @@ body { font-family: sans-serif; } +a.button, button, input[type="button"], input[type="submit"], @@ -60,11 +61,13 @@ input[type="file"] { margin: 0.1em; font-size: 1.2em; border-radius: 5px; + color: black; &:hover { background: hsl(0, 0%, 83%); } } +a.button, input[type="button"], input[type="submit"], input[type="reset"], @@ -72,6 +75,7 @@ input[type="file"] { font-weight: bold; } +a.button:not(:disabled), button:not(:disabled), input[type="button"]:not(:disabled), input[type="submit"]:not(:disabled), @@ -111,7 +115,8 @@ select { border-radius: 5px; cursor: pointer; } -a { + +a:not(.button) { text-decoration: none; color: $primary-dark-color; &:hover { @@ -348,7 +353,11 @@ a { /*---------------------------------NEWS--------------------------------*/ #news { display: flex; - flex-wrap: wrap; + + @media (max-width: 800px) { + flex-direction: column; + } + .news_column { display: inline-block; margin: 0; @@ -970,7 +979,7 @@ blockquote h5:first-child { } table { - width: 90%; + width: 100%; margin: 15px auto; border-collapse: collapse; border-spacing: 0; @@ -1403,7 +1412,7 @@ footer { padding: 0.8em; flex: 1; font-weight: bold; - color: $white-color; + color: $white-color !important; &:hover { color: $primary-dark-color; } diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 04f0ff27..d0b2bd16 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -1713,6 +1713,10 @@ msgstr "Administrer les news" msgid "Events today and the next few days" msgstr "Événements aujourd'hui et dans les prochains jours" +#: com/templates/com/news_list.jinja:100 +msgid "All coming events" +msgstr "Tous les événements à venir" + #: com/templates/com/news_list.jinja:82 msgid "Nothing to come..." msgstr "Rien à venir..." From 44290a20a6b9a5946db424047e6372f8c6ad371e Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Mon, 3 Apr 2023 17:18:16 +0200 Subject: [PATCH 37/95] Create dependabot.yml (#587) --- .github/dependabot.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..fde3ebe0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + # Raise pull requests for version updates + # to pip against the `develop` branch + target-branch: "taiste" + reviewers: + - "ae-utbm/developpers-v3" + commit-message: + prefix: "[UPDATE] " \ No newline at end of file From 93cc2c883ea9d52c1d9175eeb6cad9de789455fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 10:16:55 +0200 Subject: [PATCH 38/95] Bump django from 3.2.16 to 3.2.18 (#574) --- poetry.lock | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 898b312d..20075329 100644 --- a/poetry.lock +++ b/poetry.lock @@ -334,10 +334,10 @@ files = [ cffi = ">=1.12" [package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] +sdist = ["setuptools-rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] @@ -369,14 +369,14 @@ tests = ["noseOfYeti (==2.3.1)", "pytest (==7.1.3)"] [[package]] name = "django" -version = "3.2.16" +version = "3.2.18" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "Django-3.2.16-py3-none-any.whl", hash = "sha256:18ba8efa36b69cfcd4b670d0fa187c6fe7506596f0ababe580e16909bcdec121"}, - {file = "Django-3.2.16.tar.gz", hash = "sha256:3adc285124244724a394fa9b9839cc8cd116faf7d159554c43ecdaa8cdf0b94d"}, + {file = "Django-3.2.18-py3-none-any.whl", hash = "sha256:4d492d9024c7b3dfababf49f94511ab6a58e2c9c3c7207786f1ba4eb77750706"}, + {file = "Django-3.2.18.tar.gz", hash = "sha256:08208dfe892eb64fff073ca743b3b952311104f939e7f6dae954fe72dcc533ba"}, ] [package.dependencies] @@ -887,6 +887,13 @@ files = [ {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, + {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, + {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, + {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, + {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, + {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, + {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, + {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, From 1bfe929ab3fc9dbbff68921e897f39a8380d8028 Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Tue, 4 Apr 2023 15:21:09 +0200 Subject: [PATCH 39/95] [CSS] Follow up of #578 (#589) --- core/static/core/header.scss | 117 ++++++++++++++++----------- core/static/user/user_tools.scss | 54 ++++++++++++- core/templates/core/base.jinja | 13 ++- core/templates/core/user_clubs.jinja | 2 + core/templates/core/user_tools.jinja | 36 ++++++--- 5 files changed, 156 insertions(+), 66 deletions(-) diff --git a/core/static/core/header.scss b/core/static/core/header.scss index f69692c7..82d4bbfe 100644 --- a/core/static/core/header.scss +++ b/core/static/core/header.scss @@ -23,11 +23,11 @@ flex-direction: row; gap: 10px; - > a { + >a { color: #fff; } - &:hover > a { + &:hover>a { color: #1a78b3; } @@ -57,11 +57,11 @@ justify-content: center; align-items: flex-start; - > span:first-child { + >span:first-child { font-size: 1.43em; } - > span:last-child { + >span:last-child { font-size: .7em; } } @@ -80,12 +80,12 @@ flex-direction: row; } - > form { + >form { margin: 0; box-sizing: border-box; position: relative; - > input[type=submit] { + >input[type=submit] { border-radius: 0; margin: 0; box-sizing: border-box; @@ -115,7 +115,7 @@ justify-content: center; } - > .button { + >.button { box-sizing: border-box; height: 35px; background-color: transparent; @@ -137,7 +137,7 @@ } } - &-disconnected ~ &-lang { + &-disconnected~&-lang { @media (max-width: 662px) { flex-direction: row; width: 100%; @@ -165,8 +165,8 @@ padding: 0 10px; } - > .right, - > .left { + >.right, + >.left { box-sizing: border-box; display: flex; flex-direction: row; @@ -179,11 +179,11 @@ } } - > .right { + >.right { flex: 1; justify-content: flex-end; - > .user { + >.user { display: flex; flex-direction: row; align-items: center; @@ -193,47 +193,65 @@ @media (max-width: 1200px) { width: 100%; flex-direction: row-reverse; + justify-content: flex-end; } - > a > img { + >a>img { width: 40px; height: 40px; border-radius: 50%; } - > .options { - width: 100%; + >.options { display: flex; - justify-content: flex-end; flex-direction: column; - gap: 5px; + gap: 2px; - @media (max-width: 1200px) { - justify-content: flex-start; - flex-direction: row; - gap: 15px; - } + >.username { + display: flex; + justify-content: flex-end; + gap: 5px; - > a { - text-align: right; - color: white; - - &:hover { - color: #1a78b3; - } - - &:last-child { - color: #eb2f06; + >a { + color: white; &:hover { - color: #cc2804; + color: #1a78b3; + } + } + } + + >.links { + width: 100%; + display: flex; + justify-content: flex-end; + gap: 5px; + + @media (max-width: 1200px) { + justify-content: flex-start; + } + + >a { + text-align: right; + color: white; + + &:hover { + color: #1a78b3; + } + + &:last-child { + color: #eb2f06; + + &:hover { + color: #cc2804; + } } } } } } - > .notification { + >.notification { height: 100%; width: 55px; display: flex; @@ -241,7 +259,7 @@ align-items: center; position: relative; - > a { + >a { color: white; position: relative; font-size: 25px; @@ -250,7 +268,7 @@ color: #1a78b3; } - > span { + >span { color: white; font-size: 14px; display: flex; @@ -267,7 +285,7 @@ } } - > #header_notif { + >#header_notif { box-sizing: border-box; display: none; position: absolute; @@ -282,17 +300,18 @@ border-radius: 10px; box-shadow: 3px 3px 3px 0 #767676; - > ul { + >ul { list-style-type: none; margin: 0; display: flex; flex-direction: column; gap: 10px; + min-height: 20px; max-height: 120px; overflow-y: auto; - > li { - > a { + >li { + >a { .datetime { display: flex; justify-content: flex-start; @@ -314,8 +333,9 @@ display: flex; justify-content: space-between; margin-top: 10px; + gap: 5px; - > a { + >a { color: black; padding: 5px; width: 50%; @@ -324,6 +344,7 @@ text-align: center; align-items: center; border-radius: 5px; + background-color: #ddd; &:hover { background-color: rgba(0, 0, 0, .2); @@ -334,7 +355,7 @@ } } - > .left { + >.left { gap: 10px; display: flex; @@ -346,7 +367,7 @@ flex-direction: column-reverse; } - > form { + >form { margin: 0; width: 200px; @@ -354,7 +375,7 @@ width: 100%; } - > input[type=text] { + >input[type=text] { box-sizing: border-box; max-width: 100%; width: 100%; @@ -370,7 +391,7 @@ } } - &-connected ~ &-lang { + &-connected~&-lang { @media (max-width: 1200px) { flex-direction: row; width: 100%; @@ -390,7 +411,7 @@ gap: 20px; } - > li > a { + >li>a { display: flex; color: white; @@ -398,11 +419,11 @@ color: #1a78b3; } - > span { + >span { margin-left: 10px; } - > i { + >i { width: 16px; display: flex; justify-content: center; diff --git a/core/static/user/user_tools.scss b/core/static/user/user_tools.scss index fb10c042..82cb5cbf 100644 --- a/core/static/user/user_tools.scss +++ b/core/static/user/user_tools.scss @@ -8,6 +8,7 @@ main { flex-direction: row; flex-wrap: wrap; justify-content: center; + margin-top: 20px; gap: 10px; > div { @@ -15,16 +16,65 @@ main { background-color: rgba(0, 0, 0, .05); width: 210px; - >h4 { + > h4 { text-align: center; } - >ul { + > ul { list-style-type: none; margin: 20px 10px; display: flex; flex-direction: column; gap: 10px; + + > .rows { + display: flex; + flex-direction: column; + gap: 5px; + + > span { + margin-top: 5px; + } + + > span > span, + > span { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 5px; + width: 100%; + + >.button { + font-size: smaller; + width: 100%; + margin: 0; + } + + > span { + display: flex; + flex-direction: row; + width: 100%; + + > .button { + width: 100%; + } + } + } + + > span > span { + flex-wrap: nowrap; + } + } + + > .counter { + background-color: rgba(0, 0, 0, .05); + border-radius: 5px; + padding: 10px; + + @media (max-width: 550px) { + background-color: rgba(0, 0, 0, .1); + } + } } } diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 51e2d5da..1cf84aaf 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -89,8 +89,13 @@
    {% if user.profile_pict %} @@ -284,7 +289,9 @@ {% endblock %}
    - {% trans %}Sith version:{% endtrans %} {{ get_sith().version }} + {% cache 1000 "sith_version" %} + {% trans %}Sith version:{% endtrans %} {{ get_sith().version }} + {% endcache %} {% endif %} diff --git a/core/templates/core/user_clubs.jinja b/core/templates/core/user_clubs.jinja index a972ff6f..5bdc91f3 100644 --- a/core/templates/core/user_clubs.jinja +++ b/core/templates/core/user_clubs.jinja @@ -16,6 +16,8 @@ {% trans %}Role{% endtrans %} {% trans %}Description{% endtrans %} {% trans %}Since{% endtrans %} + + diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja index b0fb9b8e..66e64d58 100644 --- a/core/templates/core/user_tools.jinja +++ b/core/templates/core/user_tools.jinja @@ -54,19 +54,29 @@
  • {% trans %}Invoices call{% endtrans %}
  • {% trans %}Etickets{% endtrans %}
  • {% endif %} + +
      {% for b in settings.SITH_COUNTER_BARS %} {% if user.is_in_group(b[1]+" admin") %} -
    • - {{ b[1] }} - - {% trans %}Edit{% endtrans %} - - {% trans %}Stats{% endtrans %} - - {% set c = Counter.objects.filter(id=b[0]).first() %} - {% if c.stock %} - Stock - - {% trans %}Shopping lists{% endtrans %} - {% else %} - {% trans %}Create new stock{% endtrans%} - {% endif %} + {% set c = Counter.objects.filter(id=b[0]).first() %} + +
    • + {{ b[1] }} + + + + {% trans %}Edit{% endtrans %} + {% trans %}Stats{% endtrans %} + {% if c.stock %} + Stock + {% endif %} + + {% if c.stock %} + {% trans %}Shopping lists{% endtrans %} + {% else %} + {% trans %}Create new stock{% endtrans%} + {% endif %} +
    • {% endif %} {% endfor %} @@ -89,7 +99,7 @@ {% for m in user.memberships.filter(end_date=None).filter(role__gte=7).all() -%} {%- for b in m.club.bank_accounts.all() %} -
    • +
    • {% trans %}Bank account: {% endtrans %} {{ b }}
    • @@ -97,7 +107,7 @@ {% if m.club.club_account.exists() -%} {% for ca in m.club.club_account.all() %} -
    • +
    • {% trans %}Club account: {% endtrans %} {{ ca }}
    • From 8e7c025e471aa545c89a6e740d8e50d64178b762 Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Tue, 4 Apr 2023 18:39:45 +0200 Subject: [PATCH 40/95] [FIX] Broken link in readme and license fix (& update) (#591) --- .readthedocs.yml | 2 +- LICENSE.old | 21 --------------------- README.md | 5 ++--- accounting/__init__.py | 24 ++++++++---------------- accounting/admin.py | 24 ++++++++---------------- accounting/models.py | 24 ++++++++---------------- accounting/tests.py | 24 ++++++++---------------- accounting/urls.py | 24 ++++++++---------------- accounting/views.py | 24 ++++++++---------------- api/__init__.py | 24 ++++++++---------------- api/admin.py | 24 ++++++++---------------- api/models.py | 24 ++++++++---------------- api/tests.py | 24 ++++++++---------------- api/urls.py | 24 ++++++++---------------- api/views/__init__.py | 24 ++++++++---------------- api/views/api.py | 24 ++++++++---------------- api/views/club.py | 24 ++++++++---------------- api/views/counter.py | 24 ++++++++---------------- api/views/group.py | 24 ++++++++---------------- api/views/launderette.py | 24 ++++++++---------------- api/views/user.py | 24 ++++++++---------------- club/__init__.py | 24 ++++++++---------------- club/admin.py | 24 ++++++++---------------- club/tests.py | 24 ++++++++---------------- com/__init__.py | 24 ++++++++---------------- com/admin.py | 24 ++++++++---------------- com/tests.py | 24 ++++++++---------------- com/urls.py | 24 ++++++++---------------- core/__init__.py | 24 ++++++++---------------- core/admin.py | 24 ++++++++---------------- core/lookups.py | 24 ++++++++---------------- core/management/__init__.py | 24 ++++++++---------------- core/management/commands/__init__.py | 24 ++++++++---------------- core/management/commands/setup.py | 24 ++++++++---------------- core/markdown.py | 24 ++++++++---------------- core/middleware.py | 24 ++++++++---------------- core/templatetags/__init__.py | 24 ++++++++---------------- core/tests.py | 24 ++++++++---------------- core/utils.py | 24 ++++++++---------------- core/views/files.py | 24 ++++++++---------------- core/views/group.py | 24 ++++++++---------------- core/views/page.py | 24 ++++++++---------------- counter/admin.py | 24 ++++++++---------------- counter/models.py | 24 ++++++++---------------- counter/tests.py | 24 ++++++++---------------- counter/urls.py | 24 ++++++++---------------- counter/views.py | 24 ++++++++---------------- doc/header | 27 +++++++++------------------ eboutic/__init__.py | 24 ++++++++---------------- eboutic/admin.py | 24 ++++++++---------------- eboutic/models.py | 24 ++++++++---------------- eboutic/views.py | 24 ++++++++---------------- forum/__init__.py | 24 ++++++++---------------- forum/admin.py | 24 ++++++++---------------- forum/tests.py | 24 ++++++++---------------- launderette/__init__.py | 24 ++++++++---------------- launderette/admin.py | 24 ++++++++---------------- launderette/models.py | 24 ++++++++---------------- launderette/tests.py | 24 ++++++++---------------- launderette/urls.py | 24 ++++++++---------------- launderette/views.py | 24 ++++++++---------------- manage.py | 24 ++++++++---------------- pyproject.toml | 2 +- rootplace/__init__.py | 24 ++++++++---------------- rootplace/admin.py | 24 ++++++++---------------- rootplace/models.py | 24 ++++++++---------------- rootplace/tests.py | 24 ++++++++---------------- sas/__init__.py | 24 ++++++++---------------- sas/admin.py | 24 ++++++++---------------- sas/models.py | 24 ++++++++---------------- sas/tests.py | 24 ++++++++---------------- sas/urls.py | 24 ++++++++---------------- sas/views.py | 24 ++++++++---------------- sith/__init__.py | 24 ++++++++---------------- sith/urls.py | 24 ++++++++---------------- sith/wsgi.py | 24 ++++++++---------------- subscription/__init__.py | 24 ++++++++---------------- subscription/admin.py | 24 ++++++++---------------- subscription/models.py | 24 ++++++++---------------- subscription/tests.py | 24 ++++++++---------------- subscription/urls.py | 24 ++++++++---------------- subscription/views.py | 24 ++++++++---------------- 82 files changed, 629 insertions(+), 1276 deletions(-) delete mode 100644 LICENSE.old diff --git a/.readthedocs.yml b/.readthedocs.yml index afd7b4e3..481160ff 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -18,7 +18,7 @@ formats: all # Optionally set the version of Python and requirements required to build your docs python: - version: 3.8 + version: "3.8" install: - method: pip path: . diff --git a/LICENSE.old b/LICENSE.old deleted file mode 100644 index 4e272d3f..00000000 --- a/LICENSE.old +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Skia - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 99c5a34c..bf818ec6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - +

      @@ -37,5 +37,4 @@
    -> This project is licenced under GNU GPL, see the LICENSE file at the top of the repository for more details. - +> This project is licensed under GNU GPL, see the LICENSE file at the top of the repository for more details. diff --git a/accounting/__init__.py b/accounting/__init__.py index 0ace29c4..0aa913c4 100644 --- a/accounting/__init__.py +++ b/accounting/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/accounting/admin.py b/accounting/admin.py index 93960568..e485392d 100644 --- a/accounting/admin.py +++ b/accounting/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/accounting/models.py b/accounting/models.py index 285a615a..8579e436 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/accounting/tests.py b/accounting/tests.py index f1d7e855..4b44e599 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/accounting/urls.py b/accounting/urls.py index 4ae59a1e..7363cd48 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/accounting/views.py b/accounting/views.py index e3a0f83e..820c34cd 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/__init__.py b/api/__init__.py index 0ace29c4..0aa913c4 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/admin.py b/api/admin.py index 84bb227c..362a5c4f 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/models.py b/api/models.py index 5877743e..5672eba4 100644 --- a/api/models.py +++ b/api/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/tests.py b/api/tests.py index b8fbbe1e..46a200c2 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/urls.py b/api/urls.py index 14ed5172..ca267eee 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/__init__.py b/api/views/__init__.py index 96ee04d1..ae83fbe5 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/api.py b/api/views/api.py index 94dcf35c..732ee654 100644 --- a/api/views/api.py +++ b/api/views/api.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/club.py b/api/views/club.py index d379d0b4..24377073 100644 --- a/api/views/club.py +++ b/api/views/club.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/counter.py b/api/views/counter.py index a57093c3..ddc1edfe 100644 --- a/api/views/counter.py +++ b/api/views/counter.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/group.py b/api/views/group.py index f4bb9951..f6fd7594 100644 --- a/api/views/group.py +++ b/api/views/group.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/launderette.py b/api/views/launderette.py index 96c831ef..a5d62e9f 100644 --- a/api/views/launderette.py +++ b/api/views/launderette.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/api/views/user.py b/api/views/user.py index be3d97f8..ed3b6b1a 100644 --- a/api/views/user.py +++ b/api/views/user.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/club/__init__.py b/club/__init__.py index 0ace29c4..0aa913c4 100644 --- a/club/__init__.py +++ b/club/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/club/admin.py b/club/admin.py index d72a66b2..c9b547b5 100644 --- a/club/admin.py +++ b/club/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from ajax_select import make_ajax_form diff --git a/club/tests.py b/club/tests.py index b7f3226f..e6df319b 100644 --- a/club/tests.py +++ b/club/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/com/__init__.py b/com/__init__.py index 0ace29c4..0aa913c4 100644 --- a/com/__init__.py +++ b/com/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/com/admin.py b/com/admin.py index f7ffbeb3..7e31cd52 100644 --- a/com/admin.py +++ b/com/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from ajax_select import make_ajax_form diff --git a/com/tests.py b/com/tests.py index a18c9819..def450d6 100644 --- a/com/tests.py +++ b/com/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/com/urls.py b/com/urls.py index 433ec3b3..ca4ee41e 100644 --- a/com/urls.py +++ b/com/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/__init__.py b/core/__init__.py index 0ace29c4..0aa913c4 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/admin.py b/core/admin.py index 4698a489..33ce50e4 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/lookups.py b/core/lookups.py index 18e61f70..f245442b 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/management/__init__.py b/core/management/__init__.py index 0ace29c4..0aa913c4 100644 --- a/core/management/__init__.py +++ b/core/management/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py index 0ace29c4..0aa913c4 100644 --- a/core/management/commands/__init__.py +++ b/core/management/commands/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index 128b762d..b2e9ae27 100644 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/markdown.py b/core/markdown.py index 8ce70265..72b1cd02 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/middleware.py b/core/middleware.py index 538fc7cb..39afa266 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py index 0ace29c4..0aa913c4 100644 --- a/core/templatetags/__init__.py +++ b/core/templatetags/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/tests.py b/core/tests.py index fd44ae24..7ebe697b 100644 --- a/core/tests.py +++ b/core/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/utils.py b/core/utils.py index 37ff611e..4d072c30 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/views/files.py b/core/views/files.py index dd288102..2833dc7b 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/views/group.py b/core/views/group.py index 662f257e..a6b61866 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/core/views/page.py b/core/views/page.py index 8a5e10ac..94356c33 100644 --- a/core/views/page.py +++ b/core/views/page.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/counter/admin.py b/counter/admin.py index 573c117e..1e8d9a03 100644 --- a/counter/admin.py +++ b/counter/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from ajax_select import make_ajax_form diff --git a/counter/models.py b/counter/models.py index 2d19cc00..f0e96099 100644 --- a/counter/models.py +++ b/counter/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from __future__ import annotations diff --git a/counter/tests.py b/counter/tests.py index 521127ed..13c4403b 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # import json diff --git a/counter/urls.py b/counter/urls.py index 449c0fe0..3edf1faa 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/counter/views.py b/counter/views.py index e4cbd120..e0e5b658 100644 --- a/counter/views.py +++ b/counter/views.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # import json diff --git a/doc/header b/doc/header index 0a9419f8..9ab735b7 100644 --- a/doc/header +++ b/doc/header @@ -1,24 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. -# -# - +# \ No newline at end of file diff --git a/eboutic/__init__.py b/eboutic/__init__.py index 0ace29c4..0aa913c4 100644 --- a/eboutic/__init__.py +++ b/eboutic/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/eboutic/admin.py b/eboutic/admin.py index 30900f5b..84ca1afb 100644 --- a/eboutic/admin.py +++ b/eboutic/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from ajax_select import make_ajax_form diff --git a/eboutic/models.py b/eboutic/models.py index ebfb878b..556eb8ab 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # import hmac diff --git a/eboutic/views.py b/eboutic/views.py index 8c816db6..a54ed793 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/forum/__init__.py b/forum/__init__.py index 0ace29c4..0aa913c4 100644 --- a/forum/__init__.py +++ b/forum/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/forum/admin.py b/forum/admin.py index 330243e8..6d0c088a 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/forum/tests.py b/forum/tests.py index b8fbbe1e..46a200c2 100644 --- a/forum/tests.py +++ b/forum/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/launderette/__init__.py b/launderette/__init__.py index 0ace29c4..0aa913c4 100644 --- a/launderette/__init__.py +++ b/launderette/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/launderette/admin.py b/launderette/admin.py index 0346ae22..a4499d0e 100644 --- a/launderette/admin.py +++ b/launderette/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from ajax_select import make_ajax_form diff --git a/launderette/models.py b/launderette/models.py index 795dd908..7d8b2267 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/launderette/tests.py b/launderette/tests.py index b8fbbe1e..46a200c2 100644 --- a/launderette/tests.py +++ b/launderette/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/launderette/urls.py b/launderette/urls.py index 7bc214c0..ac270aec 100644 --- a/launderette/urls.py +++ b/launderette/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/launderette/views.py b/launderette/views.py index 7c15dc6d..716b41f4 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/manage.py b/manage.py index 0e28fd20..068281a9 100755 --- a/manage.py +++ b/manage.py @@ -1,25 +1,17 @@ #!/usr/bin/env python3 # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/pyproject.toml b/pyproject.toml index 4206ccfd..51a76a9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "Sith" -version = "1" +version = "3" description = "Le web Sith de l'AE" authors = [ "Skia ", diff --git a/rootplace/__init__.py b/rootplace/__init__.py index 0ace29c4..0aa913c4 100644 --- a/rootplace/__init__.py +++ b/rootplace/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/rootplace/admin.py b/rootplace/admin.py index 84bb227c..362a5c4f 100644 --- a/rootplace/admin.py +++ b/rootplace/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/rootplace/models.py b/rootplace/models.py index 5877743e..5672eba4 100644 --- a/rootplace/models.py +++ b/rootplace/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/rootplace/tests.py b/rootplace/tests.py index 19ee86e4..3fd31fdb 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from datetime import date, timedelta diff --git a/sas/__init__.py b/sas/__init__.py index 0ace29c4..0aa913c4 100644 --- a/sas/__init__.py +++ b/sas/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sas/admin.py b/sas/admin.py index add09607..d1001ef8 100644 --- a/sas/admin.py +++ b/sas/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sas/models.py b/sas/models.py index c66c6498..d6a10e49 100644 --- a/sas/models.py +++ b/sas/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sas/tests.py b/sas/tests.py index b8fbbe1e..46a200c2 100644 --- a/sas/tests.py +++ b/sas/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sas/urls.py b/sas/urls.py index 28a2a152..a425c0e6 100644 --- a/sas/urls.py +++ b/sas/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sas/views.py b/sas/views.py index 01aa7577..75a8f4bd 100644 --- a/sas/views.py +++ b/sas/views.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sith/__init__.py b/sith/__init__.py index 0ace29c4..0aa913c4 100644 --- a/sith/__init__.py +++ b/sith/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sith/urls.py b/sith/urls.py index 86f59649..6a098b5e 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/sith/wsgi.py b/sith/wsgi.py index b440975f..017c575a 100644 --- a/sith/wsgi.py +++ b/sith/wsgi.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/subscription/__init__.py b/subscription/__init__.py index 0ace29c4..0aa913c4 100644 --- a/subscription/__init__.py +++ b/subscription/__init__.py @@ -1,23 +1,15 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/subscription/admin.py b/subscription/admin.py index 06a83e6f..f81e6fde 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from ajax_select import make_ajax_form diff --git a/subscription/models.py b/subscription/models.py index 8461ac6e..4ac87514 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/subscription/tests.py b/subscription/tests.py index 5a98b71e..c3efa9c3 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # from datetime import date diff --git a/subscription/urls.py b/subscription/urls.py index daf1c7d1..30992187 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # diff --git a/subscription/views.py b/subscription/views.py index f92db2bc..bd10ac2c 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -1,24 +1,16 @@ # -*- coding:utf-8 -* # -# Copyright 2016,2017 -# - Skia +# Copyright 2023 © AE UTBM +# ae@utbm.fr / ae.info@utbm.fr # -# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, -# http://ae.utbm.fr. +# This file is part of the website of the UTBM Student Association (AE UTBM), +# https://ae.utbm.fr. # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License a published by the Free Software -# Foundation; either version 3 of the License, or (at your option) any later -# version. +# You can find the source code of the website at https://github.com/ae-utbm/sith3 # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple -# Place - Suite 330, Boston, MA 02111-1307, USA. +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" # # From f605f7dcc6459c4a3a7c36deadac8d168ef3625b Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Tue, 4 Apr 2023 22:50:19 +0200 Subject: [PATCH 41/95] =?UTF-8?q?Fixes=20pour=20la=20mise=20=C3=A0=20jour?= =?UTF-8?q?=20de=20mars=20(#598)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/models.py | 4 ++++ core/static/core/header.scss | 7 ++++++- core/static/sas/picture.scss | 11 +++++++++++ core/static/user/user_detail.scss | 15 +++++++++++++++ core/static/user/user_edit.scss | 2 ++ core/templates/core/base.jinja | 13 +++++++------ core/templates/core/user_detail.jinja | 10 ++++++++-- core/templates/core/user_edit.jinja | 8 ++++++-- core/utils.py | 5 +++++ locale/fr/LC_MESSAGES/django.po | 4 ++++ sas/templates/sas/picture.jinja | 2 +- 11 files changed, 69 insertions(+), 12 deletions(-) diff --git a/core/models.py b/core/models.py index dff97cf0..276ec559 100644 --- a/core/models.py +++ b/core/models.py @@ -46,6 +46,7 @@ from django.utils.html import escape from django.utils.functional import cached_property import os +from core import utils from phonenumber_field.modelfields import PhoneNumberField @@ -296,6 +297,9 @@ class User(AbstractBaseUser): USERNAME_FIELD = "username" # REQUIRED_FIELDS = ['email'] + def promo_has_logo(self): + return utils.file_exist("./core/static/core/img/promo_%02d.png" % self.promo) + def has_module_perms(self, package_name): return self.is_active diff --git a/core/static/core/header.scss b/core/static/core/header.scss index 82d4bbfe..dea1aab4 100644 --- a/core/static/core/header.scss +++ b/core/static/core/header.scss @@ -196,10 +196,15 @@ justify-content: flex-end; } - >a>img { + > a { + display: block; width: 40px; height: 40px; border-radius: 50%; + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + background-color: #354a5f; } >.options { diff --git a/core/static/sas/picture.scss b/core/static/sas/picture.scss index f5c895fe..520c8d9d 100644 --- a/core/static/sas/picture.scss +++ b/core/static/sas/picture.scss @@ -145,6 +145,7 @@ } > a { + box-sizing: border-box; display: flex; flex-direction: row; align-items: center; @@ -181,6 +182,16 @@ object-fit: contain; border-radius: 50%; } + + > .profile-pic { + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + min-width: 25px; + height: 25px; + border-radius: 50%; + display: block; + } } } } diff --git a/core/static/user/user_detail.scss b/core/static/user/user_detail.scss index 6ac5065a..4f67c6f6 100644 --- a/core/static/user/user_detail.scss +++ b/core/static/user/user_detail.scss @@ -61,6 +61,21 @@ main { width: 5em; margin: 0.5em; } + + > div { + display: flex; + width: 5em; + height: 5em; + border-radius: 50%; + justify-content: center; + align-items: center; + background-color: #f2f2f2; + + > span { + font-size: small; + color: #ccc; + } + } } > .user_profile_infos_items { diff --git a/core/static/user/user_edit.scss b/core/static/user/user_edit.scss index fdba806e..a0dcf559 100644 --- a/core/static/user/user_edit.scss +++ b/core/static/user/user_edit.scss @@ -56,6 +56,7 @@ width: 100%; height: 100%; max-width: 300px; + margin-top: 10px; @media (max-width: 750px) { max-width: 100%; @@ -77,6 +78,7 @@ width: 100% !important; object-fit: contain; height: auto; + max-height: 100%; } >p { diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 1cf84aaf..79e15d8b 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -97,13 +97,14 @@ {% trans %}Logout{% endtrans %}
    - - {% if user.profile_pict %} - {% trans %}Profile{% endtrans %} + - {% endif %} - + style="background-image: url('{{ static('core/img/unknown.jpg') }}')" + {% endif %} + >
    {% if profile.promo %} +
    {% endif %}
    diff --git a/core/templates/core/user_edit.jinja b/core/templates/core/user_edit.jinja index 35295226..1bac52dc 100644 --- a/core/templates/core/user_edit.jinja +++ b/core/templates/core/user_edit.jinja @@ -29,8 +29,12 @@ {%- endif -%}
    -

    {{ form["profile_pict"].label }}

    - {{ form["profile_pict"] }} + {%- if form["profile_pict"] -%} +

    {{ form["profile_pict"].label }}

    + {{ form["profile_pict"] }} + {%- else -%} + {% trans %}To edit your profile picture, ask a member of the AE{% endtrans %} + {%- endif -%} {%- if user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) and form.instance.profile_pict.id -%}
    {%- trans -%}Delete{%- endtrans -%} diff --git a/core/utils.py b/core/utils.py index 4d072c30..0e98da6b 100644 --- a/core/utils.py +++ b/core/utils.py @@ -14,6 +14,7 @@ # # +import os import subprocess import re @@ -71,6 +72,10 @@ def get_semester(d=date.today()): return "A" + str(start.year)[-2:] +def file_exist(path): + return os.path.exists(path) + + def scale_dimension(width, height, long_edge): if width > height: ratio = long_edge * 1.0 / width diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index d0b2bd16..b545b4a9 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3246,6 +3246,10 @@ msgstr "Changer mon mot de passe" msgid "Change user password" msgstr "Changer le mot de passe" +#: core/templates/core/user_edit.jinja:50 +msgid "To edit your profile picture, ask a member of the AE" +msgstr "Pour changer votre photo de profil, demandez à un membre de l'AE" + #: core/templates/core/user_godfathers.jinja:5 #, python-format msgid "%(user_name)s's family" diff --git a/sas/templates/sas/picture.jinja b/sas/templates/sas/picture.jinja index f04f1112..f22fbdb0 100644 --- a/sas/templates/sas/picture.jinja +++ b/sas/templates/sas/picture.jinja @@ -144,7 +144,7 @@
  • {% if r.user.profile_pict %} - +
    {% endif %} {{ r.user.get_short_name() }}
    From d83842af27efc34b2ba0e0b1c5d485eb91a78f4a Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Wed, 5 Apr 2023 14:32:32 +0200 Subject: [PATCH 42/95] =?UTF-8?q?Fix=20probl=C3=A8me=20de=20cache=20dans?= =?UTF-8?q?=20le=20SAS=20&=20am=C3=A9liore=20le=20CSS=20du=20SAS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartuccio Antoine --- core/static/sas/album.scss | 5 +++++ core/static/sas/picture.scss | 5 +++-- sas/models.py | 23 +++++++++++------------ sas/templates/sas/album.jinja | 2 +- sas/templates/sas/picture.jinja | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/core/static/sas/album.scss b/core/static/sas/album.scss index fbbba94b..b84d0dd3 100644 --- a/core/static/sas/album.scss +++ b/core/static/sas/album.scss @@ -160,6 +160,7 @@ main { > .photo, > .album { box-sizing: border-box; + background-color: #333333; background-size: cover; background-repeat: no-repeat; background-position: center center; @@ -167,6 +168,10 @@ main { width: calc(16 / 9 * 128px); height: 128px; + &.vertical { + background-size: contain; + } + margin: 0; padding: 0; box-shadow: none; diff --git a/core/static/sas/picture.scss b/core/static/sas/picture.scss index 520c8d9d..d65bf826 100644 --- a/core/static/sas/picture.scss +++ b/core/static/sas/picture.scss @@ -70,7 +70,7 @@ > #next { width: calc(50% - 5px); aspect-ratio: 16/9; - background: #aaa; + background: #333333; > a { display: flex; @@ -241,6 +241,7 @@ > .infos { display: flex; flex-direction: column; + width: 50%; > div > div { display: flex; @@ -260,7 +261,7 @@ > .tools { display: flex; flex-direction: column; - width: 100%; + width: 50%; > div { display: flex; diff --git a/sas/models.py b/sas/models.py index d6a10e49..639c3898 100644 --- a/sas/models.py +++ b/sas/models.py @@ -63,7 +63,12 @@ class Picture(SithFile): return (w / h) < 1 def can_be_edited_by(self, user): - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) + perm = cache.get("%d_can_edit_pictures" % (user.id), None) + if perm is None: + perm = user.is_root or user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) + + cache.set("%d_can_edit_pictures" % (user.id), perm, timeout=4) + return perm def can_be_viewed_by(self, user): # SAS pictures are visible to old subscribers @@ -72,19 +77,13 @@ class Picture(SithFile): return False perm = cache.get("%d_can_view_pictures" % (user.id), False) + if not perm: + perm = user.was_subscribed - # use cache only when user is in SAS Admins or when picture is moderated - if perm and (self.is_moderated or self.can_be_edited_by(user)): - return perm - - perm = ( - self.is_in_sas - and (self.is_moderated or self.can_be_edited_by(user)) - and user.was_subscribed - ) cache.set("%d_can_view_pictures" % (user.id), perm, timeout=4) - - return perm + return (perm and self.is_moderated and self.is_in_sas) or self.can_be_edited_by( + user + ) def get_download_url(self): return reverse("sas:download", kwargs={"picture_id": self.id}) diff --git a/sas/templates/sas/album.jinja b/sas/templates/sas/album.jinja index 16ae6d29..ca3f7804 100644 --- a/sas/templates/sas/album.jinja +++ b/sas/templates/sas/album.jinja @@ -88,7 +88,7 @@ {% if p.can_be_viewed_by(user) %}
    {% if not p.is_moderated %} diff --git a/sas/templates/sas/picture.jinja b/sas/templates/sas/picture.jinja index f22fbdb0..848f0aec 100644 --- a/sas/templates/sas/picture.jinja +++ b/sas/templates/sas/picture.jinja @@ -38,7 +38,7 @@

    {{ picture.get_display_name() }}

    {{ picture.parent.children.filter(id__lte=picture.id).count() }} / {{ picture.parent.children.count() }}

    - +
    {% if not picture.is_moderated %} {% set next = picture.get_next() %} From a198f5252d3013b0469bc4480d7e3a3254a8958d Mon Sep 17 00:00:00 2001 From: Julien Constant <49886317+Juknum@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:03:43 +0200 Subject: [PATCH 43/95] =?UTF-8?q?Fixes=20&=20am=C3=A9liorations=20du=20nou?= =?UTF-8?q?veau=20CSS=20(#616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/static/core/header.scss | 71 +++++++----- core/static/core/navbar.scss | 194 +++++++++++++++++++-------------- core/templates/core/base.jinja | 102 +++++++++-------- 3 files changed, 211 insertions(+), 156 deletions(-) diff --git a/core/static/core/header.scss b/core/static/core/header.scss index dea1aab4..5c90e319 100644 --- a/core/static/core/header.scss +++ b/core/static/core/header.scss @@ -1,6 +1,15 @@ +$hovered-text-color: #c2c2c2; +$text-color: white; + +$background-color: #354a5f; +$background-color-hovered: #283747; + +$red-text-color: #eb2f06; +$hovered-red-text-color: #ff4d4d; + .header { box-sizing: border-box; - background-color: #354a5f; + background-color: $background-color; box-shadow: 3px 3px 3px 0 #dfdfdf; border-radius: 0; width: 100%; @@ -24,11 +33,11 @@ gap: 10px; >a { - color: #fff; + color: $text-color; } &:hover>a { - color: #1a78b3; + color: $hovered-text-color;; } @media (max-width: 607px) { @@ -51,7 +60,7 @@ } &-text { - color: white; + color: $text-color; display: flex; flex-direction: column; justify-content: center; @@ -89,16 +98,16 @@ border-radius: 0; margin: 0; box-sizing: border-box; - background-color: #354a5f; + background-color: $background-color; width: 45px; height: 25px; padding: 0; - color: white; + color: $text-color; font-weight: normal; line-height: 1.3em; &:hover { - background-color: #283747; + background-color: $background-color-hovered; } } } @@ -126,13 +135,13 @@ align-items: center; text-transform: uppercase; text-decoration: none; - color: white; + color: $text-color; margin: 0; font-size: .9em; width: 120px; &:hover { - background-color: #283747; + background-color: $background-color-hovered; } } } @@ -198,30 +207,34 @@ > a { display: block; - width: 40px; + min-width: 40px; height: 40px; border-radius: 50%; background-position: center center; background-size: cover; background-repeat: no-repeat; - background-color: #354a5f; + background-color: $background-color; } >.options { display: flex; flex-direction: column; - gap: 2px; + gap: 5px; >.username { display: flex; justify-content: flex-end; gap: 5px; + @media (max-width: 500px) { + justify-content: flex-start; + } + >a { - color: white; + color: $text-color; &:hover { - color: #1a78b3; + color: $hovered-text-color; } } } @@ -230,7 +243,7 @@ width: 100%; display: flex; justify-content: flex-end; - gap: 5px; + gap: 15px; @media (max-width: 1200px) { justify-content: flex-start; @@ -238,17 +251,17 @@ >a { text-align: right; - color: white; + color: $text-color; &:hover { - color: #1a78b3; + color: $hovered-text-color; } &:last-child { - color: #eb2f06; + color: $red-text-color; &:hover { - color: #cc2804; + color: $hovered-red-text-color; } } } @@ -265,16 +278,16 @@ position: relative; >a { - color: white; + color: $text-color; position: relative; font-size: 25px; &:hover { - color: #1a78b3; + color: $hovered-text-color; } >span { - color: white; + color: $text-color; font-size: 14px; display: flex; justify-content: center; @@ -282,7 +295,7 @@ width: 10px; height: 10px; padding: 5px; - background-color: #eb2f06; + background-color: $red-text-color; border-radius: 50%; position: absolute; top: -50%; @@ -388,9 +401,13 @@ border-radius: 5px; font-size: .9em; margin: 0; - background-color: #283747; + background-color: $background-color-hovered; padding: 0 10px; - color: white; + color: $text-color; + + &::placeholder { + color: $hovered-text-color; + } } } } @@ -418,10 +435,10 @@ >li>a { display: flex; - color: white; + color: $text-color; &:hover { - color: #1a78b3; + color: $hovered-text-color; } >span { diff --git a/core/static/core/navbar.scss b/core/static/core/navbar.scss index 2eede6c0..6566c9e5 100644 --- a/core/static/core/navbar.scss +++ b/core/static/core/navbar.scss @@ -1,13 +1,9 @@ nav.navbar { - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: center; - justify-content: center; background-color: hsl(203, 75%, 40%); margin: 1em; color: white; border-radius: 0.6em; + min-height: 40px; @media (max-width: 500px) { position: relative; @@ -17,94 +13,126 @@ nav.navbar { margin: .2em; } - > .menu, - > .link { - box-sizing: border-box; - width: 130px; - height: 52px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - - @media (max-width: 500px) { - width: 100%; - height: auto; - justify-content: flex-start; - - &:first-child { - border-radius: .6em .6em 0 0; - } - - &:last-child { - border-radius: 0 0 .6em .6em; - - > .content { - box-shadow: 3px 3px 3px 0 #dfdfdf; - } - } - } - } - - > .menu > .head, - > .link { - color: white; - padding: 10px 20px; - box-sizing: border-box; - - @media (max-width: 500px) { - padding: 10px; - } - } - - .link:hover, - .menu:hover { - background-color: rgba(0, 0, 0, .2); - } - - > .menu:hover > .content, - > .menu > .head:hover + .content, - > .menu > .content:hover { - display: flex; - } - - > .menu { - display: flex; + > .expand-button { + background-color: transparent; + display: none; position: relative; + padding: 10px; + cursor: pointer; + width: 40px; + height: 40px; + justify-content: center; + align-items: center; + margin: 0; - > .content { - z-index: 10; - display: none; - position: absolute; - top: 100%; - background-color: white; - margin: 0; - list-style-type: none; + > i { + font-size: 1.5em; + color: white; + } + + @media (max-width: 500px) { + display: flex; + } + } + + > .content { + @media (min-width: 500px) {display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: center; + display: flex !important; + } + + > .menu, + > .link { + box-sizing: border-box; width: 130px; - box-shadow: 3px 3px 3px 0 #dfdfdf; - flex-direction: column; + height: 52px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; @media (max-width: 500px) { - position: absolute; - flex-direction: row; - flex-wrap: wrap; width: 100%; - box-shadow: inset 3px 3px 3px 0 #dfdfdf; - } + height: auto; + justify-content: flex-start; - > li > a { - display: flex; - padding: 15px 20px; - - @media (max-width: 500px) { - padding: 10px; + &:first-child { + border-radius: .6em .6em 0 0; } - &:hover { - color: hsl(203, 75%, 40%); - background-color: rgba(0, 0, 0, .05); + &:last-child { + border-radius: 0 0 .6em .6em; + + > .content { + box-shadow: 3px 3px 3px 0 #dfdfdf; + } } } } - } + + > .menu > .head, + > .link { + color: white; + padding: 10px 20px; + box-sizing: border-box; + + @media (max-width: 500px) { + padding: 10px; + } + } + + .link:hover, + .menu:hover { + background-color: rgba(0, 0, 0, .2); + } + + > .menu:hover > .content, + > .menu > .head:hover + .content, + > .menu > .content:hover { + display: flex; + } + + > .menu { + display: flex; + position: relative; + + > .content { + z-index: 10; + display: none; + position: absolute; + top: 100%; + background-color: white; + margin: 0; + list-style-type: none; + width: 130px; + box-shadow: 3px 3px 3px 0 #dfdfdf; + flex-direction: column; + + @media (max-width: 500px) { + position: absolute; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + box-shadow: inset 3px 3px 3px 0 #dfdfdf; + } + + > li > a { + display: flex; + padding: 15px 20px; + + @media (max-width: 500px) { + padding: 10px; + } + + &:hover { + color: hsl(203, 75%, 40%); + background-color: rgba(0, 0, 0, .05); + } + } + } + } + } } \ No newline at end of file diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 79e15d8b..bc93d967 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -196,52 +196,55 @@ {% block nav %} {% if not popup %}
    {% endif %} @@ -308,6 +311,13 @@ + {% endblock %} diff --git a/galaxy/tests.py b/galaxy/tests.py index c30ec8cf..70314574 100644 --- a/galaxy/tests.py +++ b/galaxy/tests.py @@ -22,14 +22,19 @@ # # -from django.test import TestCase +import json + +from pathlib import Path + from django.core.management import call_command +from django.test import TestCase +from django.urls import reverse from core.models import User from galaxy.models import Galaxy -class GalaxyTest(TestCase): +class GalaxyTestModel(TestCase): def setUp(self): self.root = User.objects.get(username="root") self.skia = User.objects.get(username="skia") @@ -41,6 +46,9 @@ class GalaxyTest(TestCase): self.com = User.objects.get(username="comunity") def test_user_self_score(self): + """ + Test that individual user scores are correct + """ with self.assertNumQueries(8): self.assertEqual(Galaxy.compute_user_score(self.root), 9) self.assertEqual(Galaxy.compute_user_score(self.skia), 10) @@ -52,6 +60,10 @@ class GalaxyTest(TestCase): self.assertEqual(Galaxy.compute_user_score(self.com), 1) def test_users_score(self): + """ + Test on the default dataset generated by the `populate` command + that the relation scores are correct + """ expected_scores = { "krophil": { "comunity": {"clubs": 0, "family": 0, "pictures": 0, "score": 0}, @@ -112,33 +124,78 @@ class GalaxyTest(TestCase): while len(users) > 0: user1 = users.pop(0) for user2 in users: - score, family, pictures, clubs = Galaxy.compute_users_score( - user1, user2 - ) + score = Galaxy.compute_users_score(user1, user2) u1 = computed_scores.get(user1.username, {}) u1[user2.username] = { - "score": score, - "family": family, - "pictures": pictures, - "clubs": clubs, + "score": sum(score), + "family": score.family, + "pictures": score.pictures, + "clubs": score.clubs, } computed_scores[user1.username] = u1 self.maxDiff = None # Yes, we want to see the diff if any self.assertDictEqual(expected_scores, computed_scores) + def test_rule(self): + """ + Test on the default dataset generated by the `populate` command + that the number of queries to rule the galaxy is stable. + """ + galaxy = Galaxy.objects.create() + with self.assertNumQueries(58): + galaxy.rule(0) # We want everybody here + + +class GalaxyTestView(TestCase): + @classmethod + def setUpTestData(cls): + """ + Generate a plausible Galaxy once for every test + """ + call_command("generate_galaxy_test_data", "-v", "0") + galaxy = Galaxy.objects.create() + galaxy.rule(26) # We want a fast test + def test_page_is_citizen(self): - Galaxy.rule() + """ + Test that users can access the galaxy page of users who are citizens + """ self.client.login(username="root", password="plop") - response = self.client.get("/galaxy/1/") + user = User.objects.get(last_name="n°500") + response = self.client.get(reverse("galaxy:user", args=[user.id])) self.assertContains( response, - 'Locate', + f'Reset on {user}', status_code=200, ) def test_page_not_citizen(self): - Galaxy.rule() + """ + Test that trying to access the galaxy page of a user who is not + citizens return a 404 + """ self.client.login(username="root", password="plop") - response = self.client.get("/galaxy/2/") + user = User.objects.get(last_name="n°1") + response = self.client.get(reverse("galaxy:user", args=[user.id])) self.assertEquals(response.status_code, 404) + + def test_full_galaxy_state(self): + """ + Test on the more complex dataset generated by the `generate_galaxy_test_data` + command that the relation scores are correct, and that the view exposes the + right data. + """ + self.client.login(username="root", password="plop") + response = self.client.get(reverse("galaxy:data")) + state = response.json() + + galaxy_dir = Path(__file__).parent + + # Dump computed state, either for easier debugging, or to copy as new reference if changes are legit + (galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state)) + + self.assertEqual( + state, + json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()), + ) diff --git a/galaxy/views.py b/galaxy/views.py index 47228505..3dda003e 100644 --- a/galaxy/views.py +++ b/galaxy/views.py @@ -45,32 +45,29 @@ class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView): def get_object(self, *args, **kwargs): user: User = super(GalaxyUserView, self).get_object(*args, **kwargs) - if not hasattr(user, "galaxy_user"): + if user.current_star is None: raise Http404(_("This citizen has not yet joined the galaxy")) return user - def get_queryset(self): - return super(GalaxyUserView, self).get_queryset().select_related("galaxy_user") - def get_context_data(self, **kwargs): kwargs = super(GalaxyUserView, self).get_context_data(**kwargs) kwargs["lanes"] = ( GalaxyLane.objects.filter( - Q(star1=self.object.galaxy_user) | Q(star2=self.object.galaxy_user) + Q(star1=self.object.current_star) | Q(star2=self.object.current_star) ) .order_by("distance") .annotate( other_star_id=Case( - When(star1=self.object.galaxy_user, then=F("star2__owner__id")), + When(star1=self.object.current_star, then=F("star2__owner__id")), default=F("star1__owner__id"), ), other_star_mass=Case( - When(star1=self.object.galaxy_user, then=F("star2__mass")), + When(star1=self.object.current_star, then=F("star2__mass")), default=F("star1__mass"), ), other_star_name=Case( When( - star1=self.object.galaxy_user, + star1=self.object.current_star, then=Case( When( star2__owner__nick_name=None, @@ -101,4 +98,4 @@ class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView): class GalaxyDataView(FormerSubscriberMixin, View): def get(self, request, *args, **kwargs): - return JsonResponse(Galaxy.objects.first().state) + return JsonResponse(Galaxy.get_current_galaxy().state) From 8852ef990e0247a80188bdf8e02e5bed0d7347a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 May 2023 14:19:19 +0200 Subject: [PATCH 58/95] [UPDATE] Bump libsass from 0.21.0 to 0.22.0 (#640) Bumps [libsass](https://github.com/sass/libsass-python) from 0.21.0 to 0.22.0. - [Release notes](https://github.com/sass/libsass-python/releases) - [Changelog](https://github.com/sass/libsass-python/blob/main/docs/changes.rst) - [Commits](https://github.com/sass/libsass-python/compare/0.21.0...0.22.0) --- updated-dependencies: - dependency-name: libsass dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 24 ++++++++---------------- pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index debfd0c5..8c52d361 100644 --- a/poetry.lock +++ b/poetry.lock @@ -698,27 +698,19 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "libsass" -version = "0.21.0" +version = "0.22.0" description = "Sass for Python: A straightforward binding of libsass for Python." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, - {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"}, - {file = "libsass-0.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6b984510ed94993708c0d697b4fef2d118929bbfffc3b90037be0f5ccadf55e7"}, - {file = "libsass-0.21.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e25dd9047a9392d3c59a0b869e0404f2b325a03871ee45285ee33b3664f5613"}, - {file = "libsass-0.21.0-cp36-abi3-macosx_10_14_x86_64.whl", hash = "sha256:12f39712de38689a8b785b7db41d3ba2ea1d46f9379d81ea4595802d91fa6529"}, - {file = "libsass-0.21.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e2b1a7d093f2e76dc694c17c0c285e846d0b0deb0e8b21dc852ba1a3a4e2f1d6"}, - {file = "libsass-0.21.0-cp36-abi3-win32.whl", hash = "sha256:abc29357ee540849faf1383e1746d40d69ed5cb6d4c346df276b258f5aa8977a"}, - {file = "libsass-0.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:659ae41af8708681fa3ec73f47b9735a6725e71c3b66ff570bfce78952f2314e"}, - {file = "libsass-0.21.0-cp38-abi3-macosx_12_0_arm64.whl", hash = "sha256:c9ec490609752c1d81ff6290da33485aa7cb6d7365ac665b74464c1b7d97f7da"}, - {file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"}, + {file = "libsass-0.22.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f1efc1b612299c88aec9e39d6ca0c266d360daa5b19d9430bdeaffffa86993f9"}, + {file = "libsass-0.22.0-cp37-abi3-macosx_10_15_x86_64.whl", hash = "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a"}, + {file = "libsass-0.22.0-cp37-abi3-win32.whl", hash = "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f"}, + {file = "libsass-0.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf"}, + {file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"}, ] -[package.dependencies] -six = "*" - [[package]] name = "markupsafe" version = "2.1.1" @@ -1640,4 +1632,4 @@ testing = ["coverage"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "5069f58a9ba4b47c16b08e1a4191b0d2be68c20163300fc550b41d68c8e26d73" +content-hash = "95b9a07660bd8f95a5a90cc273071a675d884424479cd1ed922438dc767d230a" diff --git a/pyproject.toml b/pyproject.toml index 742f7a2e..88b70190 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ reportlab = "^3.6" django-haystack = "^3.2.1" xapian-haystack = "^3.0.1" xapian-bindings = "^0.1.0" -libsass = "^0.21" +libsass = "^0.22" django-ordered-model = "^3.6" django-simple-captcha = "^0.5.17" python-dateutil = "^2.8.2" From 84768eb74ebf664ec3b087321199e0bce3eb63ba Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Fri, 12 May 2023 13:27:51 +0200 Subject: [PATCH 59/95] [FIX] Fix cached groups (#647) --- core/models.py | 9 +++++---- core/tests.py | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/models.py b/core/models.py index 8a01c2d5..fb28faa6 100644 --- a/core/models.py +++ b/core/models.py @@ -180,14 +180,15 @@ def get_group(*, pk: int = None, name: str = None) -> Optional[Group]: :param pk: The primary key of the group :param name: The name of the group :return: The group if it exists, else None - :raises ValueError: If no group matches the criteria + :raise ValueError: If no group matches the criteria """ if pk is None and name is None: raise ValueError("Either pk or name must be set") - if name is not None: - name = name.replace(" ", "_") # avoid errors with memcached backend - pk_or_name: Union[str, int] = pk if pk is not None else name + + # replace space characters to hide warnings with memcached backend + pk_or_name: Union[str, int] = pk if pk is not None else name.replace(" ", "_") group = cache.get(f"sith_group_{pk_or_name}") + if group == "not_found": # Using None as a cache value is a little bit tricky, # so we use a special string to represent None diff --git a/core/tests.py b/core/tests.py index 4c84662b..a2e9c526 100644 --- a/core/tests.py +++ b/core/tests.py @@ -597,12 +597,20 @@ class UserIsInGroupTest(TestCase): Test that when a user is removed from a group, the is_in_group_method return False when calling it again """ + # testing with pk self.toto.groups.add(self.com_admin.pk) self.assertTrue(self.toto.is_in_group(pk=self.com_admin.pk)) self.toto.groups.remove(self.com_admin.pk) self.assertFalse(self.toto.is_in_group(pk=self.com_admin.pk)) + # testing with name + self.toto.groups.add(self.sas_admin.pk) + self.assertTrue(self.toto.is_in_group(name="SAS admin")) + + self.toto.groups.remove(self.sas_admin.pk) + self.assertFalse(self.toto.is_in_group(name="SAS admin")) + def test_not_existing_group(self): """ Test that searching for a not existing group From 2bccf633d53501ea263bb6637b7b1d74883a81e3 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Wed, 6 Sep 2023 11:37:28 +0200 Subject: [PATCH 60/95] Bump sqlparse from 0.4.3 to 0.4.4 (#645) Bumps [sqlparse](https://github.com/andialbrecht/sqlparse) from 0.4.3 to 0.4.4. - [Release notes](https://github.com/andialbrecht/sqlparse/releases) - [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG) - [Commits](https://github.com/andialbrecht/sqlparse/compare/0.4.3...0.4.4) --- updated-dependencies: - dependency-name: sqlparse dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8c52d361..5069c26c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1504,16 +1504,21 @@ test = ["pytest"] [[package]] name = "sqlparse" -version = "0.4.3" +version = "0.4.4" description = "A non-validating SQL parser." category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"}, - {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"}, + {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, + {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, ] +[package.extras] +dev = ["build", "flake8"] +doc = ["sphinx"] +test = ["pytest", "pytest-cov"] + [[package]] name = "tomli" version = "2.0.1" From 544b0248b2e62398feb45ee93f4f91801d4557d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:01:44 +0200 Subject: [PATCH 61/95] [UPDATE] Bump django-ordered-model from 3.6 to 3.7.4 (#625) Bumps [django-ordered-model](https://github.com/django-ordered-model/django-ordered-model) from 3.6 to 3.7.4. - [Release notes](https://github.com/django-ordered-model/django-ordered-model/releases) - [Changelog](https://github.com/django-ordered-model/django-ordered-model/blob/master/CHANGES.md) - [Commits](https://github.com/django-ordered-model/django-ordered-model/compare/3.6...3.7.4) --- updated-dependencies: - dependency-name: django-ordered-model dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 91 ++++---------------------------------------------- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 86 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5069c26c..ff8f76b3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" -category = "main" optional = true python-versions = "*" files = [ @@ -16,7 +15,6 @@ files = [ name = "appnope" version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" -category = "dev" optional = false python-versions = "*" files = [ @@ -28,7 +26,6 @@ files = [ name = "asgiref" version = "3.6.0" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -43,7 +40,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "babel" version = "2.11.0" description = "Internationalization utilities" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -58,7 +54,6 @@ pytz = ">=2015.7" name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" -category = "dev" optional = false python-versions = "*" files = [ @@ -70,7 +65,6 @@ files = [ name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -120,7 +114,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -132,7 +125,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -209,7 +201,6 @@ pycparser = "*" name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = true python-versions = ">=3.6.0" files = [ @@ -224,7 +215,6 @@ unicode-backport = ["unicodedata2"] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -239,7 +229,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -251,7 +240,6 @@ files = [ name = "coverage" version = "5.5" description = "Code coverage measurement for Python" -category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" files = [ @@ -316,7 +304,6 @@ toml = ["toml"] name = "cryptography" version = "40.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -358,7 +345,6 @@ tox = ["tox"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -370,7 +356,6 @@ files = [ name = "dict2xml" version = "1.7.3" description = "Small utility to convert a python dictionary into an XML string" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -385,7 +370,6 @@ tests = ["noseofyeti[black] (==2.4.1)", "pytest (==7.2.1)"] name = "django" version = "3.2.18" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -406,7 +390,6 @@ bcrypt = ["bcrypt"] name = "django-ajax-selects" version = "2.2.0" description = "Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete." -category = "main" optional = false python-versions = "*" files = [ @@ -417,7 +400,6 @@ files = [ name = "django-countries" version = "7.5.1" description = "Provides a country field for Django models." -category = "main" optional = false python-versions = "*" files = [ @@ -439,7 +421,6 @@ test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytes name = "django-debug-toolbar" version = "4.0.0" description = "A configurable set of panels that display various debug information about the current request/response." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -455,7 +436,6 @@ sqlparse = ">=0.2" name = "django-haystack" version = "3.2.1" description = "Pluggable search for Django." -category = "main" optional = false python-versions = "*" files = [ @@ -472,7 +452,6 @@ elasticsearch = ["elasticsearch (>=5,<8)"] name = "django-jinja" version = "2.10.2" description = "Jinja2 templating language integrated in Django." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -486,21 +465,19 @@ jinja2 = ">=3" [[package]] name = "django-ordered-model" -version = "3.6" +version = "3.7.4" description = "Allows Django models to be ordered and provides a simple admin interface for reordering them." -category = "main" optional = false python-versions = "*" files = [ - {file = "django-ordered-model-3.6.tar.gz", hash = "sha256:62161a6bc51d8b402644854b257605d7b5183d01fd349826682a87e9227c05b5"}, - {file = "django_ordered_model-3.6-py3-none-any.whl", hash = "sha256:0006b111f472a2348f75554a4e77bee2b1f379a0f96726af6b1a3ebf3a950789"}, + {file = "django-ordered-model-3.7.4.tar.gz", hash = "sha256:f258b9762525c00a53009e82f8b8bf2a3aa315e8b453e281e8fdbbfe2b8cb3ba"}, + {file = "django_ordered_model-3.7.4-py3-none-any.whl", hash = "sha256:dfcd3183fe0749dad1c9971cba1d6240ce7328742a30ddc92feca41107bb241d"}, ] [[package]] name = "django-phonenumber-field" version = "6.4.0" description = "An international phone number field for django models." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -519,7 +496,6 @@ phonenumberslite = ["phonenumberslite (>=7.0.2)"] name = "django-ranged-response" version = "0.2.0" description = "Modified Django FileResponse that adds Content-Range headers." -category = "main" optional = false python-versions = "*" files = [ @@ -533,7 +509,6 @@ django = "*" name = "django-simple-captcha" version = "0.5.17" description = "A very simple, yet powerful, Django captcha application" -category = "main" optional = false python-versions = "*" files = [ @@ -553,7 +528,6 @@ test = ["testfixtures"] name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -569,7 +543,6 @@ pytz = "*" name = "docutils" version = "0.17.1" description = "Docutils -- Python Documentation Utilities" -category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -581,7 +554,6 @@ files = [ name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -593,7 +565,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -605,7 +576,6 @@ files = [ name = "importlib-metadata" version = "6.0.0" description = "Read metadata from Python packages" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -625,7 +595,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "ipython" version = "7.34.0" description = "IPython: Productive Interactive Computing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -662,7 +631,6 @@ test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments" name = "jedi" version = "0.18.2" description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -682,7 +650,6 @@ testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -700,7 +667,6 @@ i18n = ["Babel (>=2.7)"] name = "libsass" version = "0.22.0" description = "Sass for Python: A straightforward binding of libsass for Python." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -715,7 +681,6 @@ files = [ name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -765,7 +730,6 @@ files = [ name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -780,7 +744,6 @@ traitlets = "*" name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" -category = "main" optional = false python-versions = "*" files = [ @@ -792,7 +755,6 @@ files = [ name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" optional = false python-versions = "*" files = [ @@ -804,7 +766,6 @@ files = [ name = "packaging" version = "23.0" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -816,7 +777,6 @@ files = [ name = "parso" version = "0.8.3" description = "A Python Parser" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -832,7 +792,6 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pathspec" version = "0.10.3" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -844,7 +803,6 @@ files = [ name = "pexpect" version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." -category = "dev" optional = false python-versions = "*" files = [ @@ -859,7 +817,6 @@ ptyprocess = ">=0.5" name = "phonenumbers" version = "8.13.4" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." -category = "main" optional = false python-versions = "*" files = [ @@ -871,7 +828,6 @@ files = [ name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" -category = "dev" optional = false python-versions = "*" files = [ @@ -883,7 +839,6 @@ files = [ name = "pillow" version = "9.4.0" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -974,7 +929,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "platformdirs" version = "2.6.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -990,7 +944,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest- name = "prompt-toolkit" version = "3.0.36" description = "Library for building powerful interactive command lines in Python" -category = "dev" optional = false python-versions = ">=3.6.2" files = [ @@ -1005,7 +958,6 @@ wcwidth = "*" name = "psycopg2-binary" version = "2.9.3" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1074,7 +1026,6 @@ files = [ name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" -category = "dev" optional = false python-versions = "*" files = [ @@ -1086,7 +1037,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1098,7 +1048,6 @@ files = [ name = "pygments" version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1113,7 +1062,6 @@ plugins = ["importlib-metadata"] name = "pygraphviz" version = "1.10" description = "Python interface to Graphviz" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1124,7 +1072,6 @@ files = [ name = "pyopenssl" version = "23.1.1" description = "Python wrapper module around the OpenSSL library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1143,7 +1090,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1158,7 +1104,6 @@ six = ">=1.5" name = "pytz" version = "2021.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1170,7 +1115,6 @@ files = [ name = "reportlab" version = "3.6.12" description = "The Reportlab Toolkit" -category = "main" optional = false python-versions = ">=3.7,<4" files = [ @@ -1232,7 +1176,6 @@ rlpycairo = ["rlPyCairo (>=0.1.0)"] name = "requests" version = "2.28.1" description = "Python HTTP for Humans." -category = "main" optional = true python-versions = ">=3.7, <4" files = [ @@ -1254,7 +1197,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "sentry-sdk" version = "1.21.0" description = "Python client for Sentry (https://sentry.io)" -category = "main" optional = false python-versions = "*" files = [ @@ -1296,7 +1238,6 @@ tornado = ["tornado (>=5)"] name = "setuptools" version = "65.6.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1313,7 +1254,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1325,7 +1265,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "main" optional = true python-versions = "*" files = [ @@ -1337,7 +1276,6 @@ files = [ name = "sphinx" version = "4.5.0" description = "Python documentation generator" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -1373,7 +1311,6 @@ test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] name = "sphinx-copybutton" version = "0.4.0" description = "Add a copy button to each of your code cells." -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -1392,7 +1329,6 @@ rtd = ["ipython", "sphinx", "sphinx-book-theme"] name = "sphinx-rtd-theme" version = "1.1.1" description = "Read the Docs theme for Sphinx" -category = "main" optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ @@ -1411,7 +1347,6 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.3" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -1427,7 +1362,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -1443,7 +1377,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -1459,7 +1392,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -1474,7 +1406,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -1490,7 +1421,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -1506,7 +1436,6 @@ test = ["pytest"] name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1523,7 +1452,6 @@ test = ["pytest", "pytest-cov"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1535,7 +1463,6 @@ files = [ name = "traitlets" version = "5.8.1" description = "Traitlets Python configuration system" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1551,7 +1478,6 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1563,7 +1489,6 @@ files = [ name = "urllib3" version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -1580,7 +1505,6 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "wcwidth" version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" optional = false python-versions = "*" files = [ @@ -1592,7 +1516,6 @@ files = [ name = "xapian-bindings" version = "0.1.0" description = "Meta-package to build and install xapian-bindings extension." -category = "main" optional = false python-versions = "*" files = [ @@ -1603,7 +1526,6 @@ files = [ name = "xapian-haystack" version = "3.0.1" description = "A Xapian backend for Haystack" -category = "main" optional = false python-versions = "*" files = [ @@ -1618,7 +1540,6 @@ django-haystack = ">=2.8.0" name = "zipp" version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1631,10 +1552,10 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] -docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] +docs = ["Sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] testing = ["coverage"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "95b9a07660bd8f95a5a90cc273071a675d884424479cd1ed922438dc767d230a" +content-hash = "9d38fb0dd50ef0a154bd6690afcb24be9fbdb2adbba1c4762b1ed0cdb9508eb2" diff --git a/pyproject.toml b/pyproject.toml index 88b70190..d2fca6f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ django-haystack = "^3.2.1" xapian-haystack = "^3.0.1" xapian-bindings = "^0.1.0" libsass = "^0.22" -django-ordered-model = "^3.6" +django-ordered-model = "^3.7" django-simple-captcha = "^0.5.17" python-dateutil = "^2.8.2" psycopg2-binary = "2.9.3" From 38295e591debb9a6bf3fdca94e0b2261237ed3ac Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Thu, 7 Sep 2023 23:11:58 +0200 Subject: [PATCH 62/95] Fix immutable default variable in `get_start_of_semester` (#656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le serveur ne percevait pas le changement de semestre, parce que la valeur par défaut passée à la fonction `get_start_of_semester()` était une fonction appelée une seule fois, lors du lancement du serveur. Bref, c'était ça : https://beta.ruff.rs/docs/rules/function-call-in-default-argument/ --------- Co-authored-by: imperosol --- core/static/core/style.scss | 2 +- core/tests.py | 79 ++++++++++++++++++++++++++- core/utils.py | 73 ++++++++++++++++--------- counter/models.py | 15 +++-- counter/templates/counter/stats.jinja | 8 ++- counter/tests.py | 40 ++++++++------ counter/views.py | 5 +- poetry.lock | 18 +++++- pyproject.toml | 1 + sith/settings.py | 3 +- subscription/models.py | 12 ++-- trombi/models.py | 6 +- 12 files changed, 194 insertions(+), 68 deletions(-) diff --git a/core/static/core/style.scss b/core/static/core/style.scss index 7abfa8ca..9ba72e7d 100644 --- a/core/static/core/style.scss +++ b/core/static/core/style.scss @@ -1031,7 +1031,7 @@ thead { } tbody > tr { - &:nth-child(even) { + &:nth-child(even):not(.highlight) { background: $primary-neutral-light-color; } &.clickable:hover { diff --git a/core/tests.py b/core/tests.py index a2e9c526..61d497a5 100644 --- a/core/tests.py +++ b/core/tests.py @@ -15,17 +15,18 @@ # import os -from datetime import timedelta +from datetime import date, timedelta +import freezegun from django.core.cache import cache from django.test import Client, TestCase from django.urls import reverse -from django.core.management import call_command from django.utils.timezone import now from club.models import Membership -from core.models import User, Group, Page, AnonymousUser from core.markdown import markdown +from core.models import AnonymousUser, Group, Page, User +from core.utils import get_semester_code, get_start_of_semester from sith import settings """ @@ -617,3 +618,75 @@ class UserIsInGroupTest(TestCase): returns False """ self.assertFalse(self.skia.is_in_group(name="This doesn't exist")) + + +class DateUtilsTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.autumn_month = settings.SITH_SEMESTER_START_AUTUMN[0] + cls.autumn_day = settings.SITH_SEMESTER_START_AUTUMN[1] + cls.spring_month = settings.SITH_SEMESTER_START_SPRING[0] + cls.spring_day = settings.SITH_SEMESTER_START_SPRING[1] + + cls.autumn_semester_january = date(2025, 1, 4) + cls.autumn_semester_september = date(2024, 9, 4) + cls.autumn_first_day = date(2024, cls.autumn_month, cls.autumn_day) + + cls.spring_semester_march = date(2023, 3, 4) + cls.spring_first_day = date(2023, cls.spring_month, cls.spring_day) + + def test_get_semester(self): + """ + Test that the get_semester function returns the correct semester string + """ + self.assertEqual(get_semester_code(self.autumn_semester_january), "A24") + self.assertEqual(get_semester_code(self.autumn_semester_september), "A24") + self.assertEqual(get_semester_code(self.autumn_first_day), "A24") + + self.assertEqual(get_semester_code(self.spring_semester_march), "P23") + self.assertEqual(get_semester_code(self.spring_first_day), "P23") + + def test_get_start_of_semester_fixed_date(self): + """ + Test that the get_start_of_semester correctly the starting date of the semester. + """ + automn_2024 = date(2024, self.autumn_month, self.autumn_day) + self.assertEqual( + get_start_of_semester(self.autumn_semester_january), automn_2024 + ) + self.assertEqual( + get_start_of_semester(self.autumn_semester_september), automn_2024 + ) + self.assertEqual(get_start_of_semester(self.autumn_first_day), automn_2024) + + spring_2023 = date(2023, self.spring_month, self.spring_day) + self.assertEqual(get_start_of_semester(self.spring_semester_march), spring_2023) + self.assertEqual(get_start_of_semester(self.spring_first_day), spring_2023) + + def test_get_start_of_semester_today(self): + """ + Test that the get_start_of_semester returns the start of the current semester + when no date is given + """ + with freezegun.freeze_time(self.autumn_semester_september): + self.assertEqual(get_start_of_semester(), self.autumn_first_day) + + with freezegun.freeze_time(self.spring_semester_march): + self.assertEqual(get_start_of_semester(), self.spring_first_day) + + def test_get_start_of_semester_changing_date(self): + """ + Test that the get_start_of_semester correctly gives the starting date of the semester, + even when the semester changes while the server isn't restarted. + """ + spring_2023 = date(2023, self.spring_month, self.spring_day) + autumn_2023 = date(2023, self.autumn_month, self.autumn_day) + mid_spring = spring_2023 + timedelta(days=45) + mid_autumn = autumn_2023 + timedelta(days=45) + + with freezegun.freeze_time(mid_spring) as frozen_time: + self.assertEqual(get_start_of_semester(), spring_2023) + + # forward time to the middle of the next semester + frozen_time.move_to(mid_autumn) + self.assertEqual(get_start_of_semester(), autumn_2023) diff --git a/core/utils.py b/core/utils.py index a053e2d5..d30e3ebf 100644 --- a/core/utils.py +++ b/core/utils.py @@ -15,20 +15,19 @@ # import os -import subprocess import re - -# Image utils - -from io import BytesIO +import subprocess from datetime import date -from PIL import ExifTags +# Image utils +from io import BytesIO +from typing import Optional import PIL - from django.conf import settings from django.core.files.base import ContentFile +from PIL import ExifTags +from django.utils import timezone def get_git_revision_short_hash() -> str: @@ -44,34 +43,54 @@ def get_git_revision_short_hash() -> str: return "" -def get_start_of_semester(d=date.today()): +def get_start_of_semester(today: Optional[date] = None) -> date: """ - This function computes the start date of the semester with respect to the given date (default is today), - and the start date given in settings.SITH_START_DATE. - It takes the nearest past start date. - Exemples: with SITH_START_DATE = (8, 15) - Today -> Start date - 2015-03-17 -> 2015-02-15 - 2015-01-11 -> 2014-08-15 + 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. + + The current semester is computed as follows: + + - If the date is between 15/08 and 31/12 => Autumn semester. + - If the date is between 01/01 and 15/02 => Autumn semester of the previous year. + - If the date is between 15/02 and 15/08 => Spring semester + + :param today: the date to use to compute the semester. If None, use today's date. + :return: the date of the start of the semester """ - today = d - year = today.year - start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1]) - start2 = start.replace(month=(start.month + 6) % 12) - spring, autumn = min(start, start2), max(start, start2) - if today > autumn: # autumn semester + if today is None: + today = timezone.now().date() + + autumn = date(today.year, *settings.SITH_SEMESTER_START_AUTUMN) + spring = date(today.year, *settings.SITH_SEMESTER_START_SPRING) + + if today >= autumn: # between 15/08 (included) and 31/12 -> autumn semester return autumn - if today > spring: # spring semester + if today >= spring: # between 15/02 (included) and 15/08 -> spring semester return spring - return autumn.replace(year=year - 1) # autumn semester of last year + # between 01/01 and 15/02 -> autumn semester of the previous year + return autumn.replace(year=autumn.year - 1) -def get_semester(d=date.today()): +def get_semester_code(d: Optional[date] = None) -> str: + """ + Return the semester code of the given date. + If no date is given, return the semester code of the current semester. + + The semester code is an upper letter (A for autumn, P for spring), + followed by the last two digits of the year. + For example, the autumn semester of 2018 is "A18". + + :param d: the date to use to compute the semester. If None, use today's date. + :return: the semester code corresponding to the given date + """ + if d is None: + d = timezone.now().date() + start = get_start_of_semester(d) - if start.month <= 6: - return "P" + str(start.year)[-2:] - else: + + if (start.month, start.day) == settings.SITH_SEMESTER_START_AUTUMN: return "A" + str(start.year)[-2:] + return "P" + str(start.year)[-2:] def file_exist(path): diff --git a/counter/models.py b/counter/models.py index c6389349..476aaf13 100644 --- a/counter/models.py +++ b/counter/models.py @@ -15,7 +15,7 @@ # from __future__ import annotations -from typing import Tuple +from typing import Tuple, Optional from django.db import models from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists @@ -536,7 +536,7 @@ class Counter(models.Model): .order_by("-perm_sum") ) - def get_top_customers(self, since=get_start_of_semester()) -> QuerySet: + def get_top_customers(self, since: Optional[date] = None) -> QuerySet: """ Return a QuerySet querying the money spent by customers of this counter since the specified date, ordered by descending amount of money spent. @@ -546,6 +546,8 @@ class Counter(models.Model): - the nickname of the customer - the amount of money spent by the customer """ + if since is None: + since = get_start_of_semester() return ( self.sellings.filter(date__gte=since) .annotate( @@ -557,7 +559,8 @@ class Counter(models.Model): ) .annotate(nickname=F("customer__user__nick_name")) .annotate(promo=F("customer__user__promo")) - .values("customer__user", "name", "nickname") + .annotate(user=F("customer__user")) + .values("user", "promo", "name", "nickname") .annotate( selling_sum=Sum( F("unit_price") * F("quantity"), output_field=CurrencyField() @@ -567,15 +570,17 @@ class Counter(models.Model): .order_by("-selling_sum") ) - def get_total_sales(self, since=get_start_of_semester()) -> CurrencyField: + def get_total_sales(self, since=None) -> CurrencyField: """ Compute and return the total turnover of this counter since the date specified in parameter (by default, since the start of the current semester) :param since: timestamp from which to perform the calculation - :type since: datetime | date + :type since: datetime | date | None :return: Total revenue earned at this counter """ + if since is None: + since = get_start_of_semester() if isinstance(since, date): since = datetime.combine(since, datetime.min.time()) total = self.sellings.filter(date__gte=since).aggregate( diff --git a/counter/templates/counter/stats.jinja b/counter/templates/counter/stats.jinja index 03b7f4e0..d6cc14f0 100644 --- a/counter/templates/counter/stats.jinja +++ b/counter/templates/counter/stats.jinja @@ -11,7 +11,9 @@ {% block content %}

    {% trans counter_name=counter %}{{ counter_name }} stats{% endtrans %}

    -

    {% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %}

    +

    + {% trans counter_name=counter.name %}Top 100 {{ counter_name }}{% endtrans %} ({{ current_semester }}) +

    @@ -35,7 +37,9 @@
    -

    {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %}

    +

    + {% trans counter_name=counter.name %}Top 100 barman {{ counter_name }}{% endtrans %} ({{ current_semester }}) +

    diff --git a/counter/tests.py b/counter/tests.py index ed83e935..6079099a 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -13,6 +13,7 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +from datetime import date, timedelta import json import re import string @@ -322,42 +323,49 @@ class CounterStatsTest(TestCase): Test the result of Counter.get_top_customers() is correct """ top = iter(self.counter.get_top_customers()) - self.assertEqual( - next(top), + expected_results = [ { - "customer__user": self.sli.id, + "user": self.sli.id, "name": f"{self.sli.first_name} {self.sli.last_name}", + "promo": self.sli.promo, "nickname": self.sli.nick_name, "selling_sum": 2000, }, - ) - self.assertEqual( - next(top), { - "customer__user": self.skia.id, + "user": self.skia.id, "name": f"{self.skia.first_name} {self.skia.last_name}", + "promo": self.skia.promo, "nickname": self.skia.nick_name, "selling_sum": 1000, }, - ) - self.assertEqual( - next(top), { - "customer__user": self.krophil.id, + "user": self.krophil.id, "name": f"{self.krophil.first_name} {self.krophil.last_name}", + "promo": self.krophil.promo, "nickname": self.krophil.nick_name, "selling_sum": 100, }, - ) - self.assertEqual( - next(top), { - "customer__user": self.root.id, + "user": self.root.id, "name": f"{self.root.first_name} {self.root.last_name}", + "promo": self.root.promo, "nickname": self.root.nick_name, "selling_sum": 2, }, - ) + ] + + for result in expected_results: + self.assertEqual( + next(top), + { + "user": result["user"], + "name": result["name"], + "promo": result["promo"], + "nickname": result["nickname"], + "selling_sum": result["selling_sum"], + }, + ) + self.assertIsNone(next(top, None)) diff --git a/counter/views.py b/counter/views.py index 4d5af292..6bbc819d 100644 --- a/counter/views.py +++ b/counter/views.py @@ -48,7 +48,7 @@ import pytz from datetime import timedelta, datetime from http import HTTPStatus -from core.utils import get_start_of_semester +from core.utils import get_start_of_semester, get_semester_code from core.views import CanViewMixin, TabedViewMixin, CanEditMixin from core.views.forms import LoginForm from core.models import User @@ -1354,13 +1354,14 @@ class CounterStatView(DetailView, CounterAdminMixin): def get_context_data(self, **kwargs): """Add stats to the context""" - counter = self.object + counter: Counter = self.object semester_start = get_start_of_semester() office_hours = counter.get_top_barmen() kwargs = super(CounterStatView, self).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], diff --git a/poetry.lock b/poetry.lock index ff8f76b3..23ef0bda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -550,6 +550,20 @@ files = [ {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] +[[package]] +name = "freezegun" +version = "1.2.2" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.6" +files = [ + {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, + {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + [[package]] name = "idna" version = "3.4" @@ -1558,4 +1572,4 @@ testing = ["coverage"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "9d38fb0dd50ef0a154bd6690afcb24be9fbdb2adbba1c4762b1ed0cdb9508eb2" +content-hash = "62519616aff5a472dac3dd8071a6404b1ee8eab12a197af717a0520f7ded0331" diff --git a/pyproject.toml b/pyproject.toml index d2fca6f0..cb1a6aaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ testing = ["coverage"] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] [tool.poetry.dev-dependencies] +freezegun = "^1.2.2" # used to test time-dependent code django-debug-toolbar = "^4.0.0" ipython = "^7.28.0" black = "^23.3.0" diff --git a/sith/settings.py b/sith/settings.py index 26a013a0..5ed279af 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -327,7 +327,8 @@ SITH_CLUB_ROOT_PAGE = "clubs" # Define the date in the year serving as reference for the subscriptions calendar # (month, day) -SITH_START_DATE = (8, 15) # 15th August +SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August +SITH_SEMESTER_START_SPRING = (2, 15) # 15 February # Used to determine the valid promos SITH_SCHOOL_START_YEAR = 1999 diff --git a/subscription/models.py b/subscription/models.py index ee4334f1..f1c2b2d5 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -114,12 +114,12 @@ class Subscription(models.Model): return "No user - " + str(self.pk) @staticmethod - def compute_start(d=None, duration=1, user=None): + def compute_start(d: date = None, duration: int = 1, user: User = None) -> date: """ This function computes the start date of the subscription with respect to the given date (default is today), - and the start date given in settings.SITH_START_DATE. + and the start date given in settings.SITH_SEMESTER_START_AUTUMN. It takes the nearest past start date. - Exemples: with SITH_START_DATE = (8, 15) + Exemples: with SITH_SEMESTER_START_AUTUMN = (8, 15) Today -> Start date 2015-03-17 -> 2015-02-15 2015-01-11 -> 2014-08-15 @@ -135,9 +135,9 @@ class Subscription(models.Model): return get_start_of_semester(d) @staticmethod - def compute_end(duration, start=None, user=None): + def compute_end(duration: int, start: date = None, user: User = None) -> date: """ - This function compute the end date of the subscription given a start date and a duration in number of semestre + This function compute the end date of the subscription given a start date and a duration in number of semester Exemple: Start - Duration -> End date 2015-09-18 - 1 -> 2016-03-18 @@ -153,7 +153,7 @@ class Subscription(models.Model): days=math.ceil((6 * duration - round(6 * duration)) * 30), ) - def can_be_edited_by(self, user): + def can_be_edited_by(self, user: User): return user.is_board_member or user.is_root def is_valid_now(self): diff --git a/trombi/models.py b/trombi/models.py index e4439c1a..18f36526 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -31,7 +31,7 @@ from django.core.exceptions import ValidationError from datetime import timedelta, date from core.models import User -from core.utils import get_start_of_semester, get_semester +from core.utils import get_start_of_semester, get_semester_code from club.models import Club @@ -164,14 +164,14 @@ class TrombiUser(models.Model): if m.description: role += " (%s)" % m.description if m.end_date: - end_date = get_semester(m.end_date) + end_date = get_semester_code(m.end_date) else: end_date = "" TrombiClubMembership( user=self, club=str(m.club), role=role[:64], - start=get_semester(m.start_date), + start=get_semester_code(m.start_date), end=end_date, ).save() From aaf30ab965f82d98cce5f9729f797e6ac8193551 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Thu, 7 Sep 2023 23:53:42 +0200 Subject: [PATCH 63/95] Add missing method on AnonymousUser (#649) --- com/tests.py | 27 +++++++++++++++++++++++++++ core/models.py | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/com/tests.py b/com/tests.py index 2a5206b2..6dde46db 100644 --- a/com/tests.py +++ b/com/tests.py @@ -159,6 +159,33 @@ class NewsTest(TestCase): self.assertFalse(self.new.is_owned_by(self.anonymous)) self.assertFalse(self.new.is_owned_by(self.sli)) + def test_news_viewer(self): + """ + Test that moderated news can be viewed by anyone + and not moderated news only by com admins + """ + # by default a news isn't moderated + self.assertTrue(self.new.can_be_viewed_by(self.com_admin)) + self.assertFalse(self.new.can_be_viewed_by(self.sli)) + self.assertFalse(self.new.can_be_viewed_by(self.anonymous)) + self.assertFalse(self.new.can_be_viewed_by(self.author)) + + self.new.is_moderated = True + self.new.save() + self.assertTrue(self.new.can_be_viewed_by(self.com_admin)) + self.assertTrue(self.new.can_be_viewed_by(self.sli)) + self.assertTrue(self.new.can_be_viewed_by(self.anonymous)) + self.assertTrue(self.new.can_be_viewed_by(self.author)) + + def test_news_editor(self): + """ + Test that only com admins can edit news + """ + self.assertTrue(self.new.can_be_edited_by(self.com_admin)) + self.assertFalse(self.new.can_be_edited_by(self.sli)) + self.assertFalse(self.new.can_be_edited_by(self.anonymous)) + self.assertFalse(self.new.can_be_edited_by(self.author)) + class WeekmailArticleTest(TestCase): @classmethod diff --git a/core/models.py b/core/models.py index fb28faa6..c8a38426 100644 --- a/core/models.py +++ b/core/models.py @@ -810,6 +810,10 @@ class AnonymousUser(AuthAnonymousUser): def can_edit(self, obj): return False + @property + def is_com_admin(self): + return False + def can_view(self, obj): if ( hasattr(obj, "view_groups") From 193c820757c41b5a08795718e3ddf6b33e784c53 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Tue, 19 Sep 2023 20:59:22 +0200 Subject: [PATCH 64/95] Add eurocks partnership in the eboutic (#661) --- eboutic/templates/eboutic/eboutic_main.jinja | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 529fd067..3493742e 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -90,6 +90,24 @@ {% endif %} +
    +
    +

    Partenariat Eurockéenes

    +
    + {% if user.is_subscribed %} + Billetterie Weezevent + + {% else %} +
    Vous devez être cotisant pour accéder à la billeterie des Eurockéennes
    + {% endif %} +
    +
    + {% for priority_groups in products|groupby('priority')|reverse %} {% for category, items in priority_groups.list|groupby('category') %} {% if items|count > 0 %} From d2f377b54fc2d763db7e90aea5f91885a815f439 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Tue, 19 Sep 2023 20:59:22 +0200 Subject: [PATCH 65/95] Add eurocks partnership in the eboutic (#661) Revert "Add eurocks partnership in the eboutic (#661)" This reverts commit 193c820757c41b5a08795718e3ddf6b33e784c53. Add eurocks partnership in the eboutic (#661) --- eboutic/templates/eboutic/eboutic_main.jinja | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 529fd067..caff0d30 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -90,6 +90,24 @@ {% endif %} +
    +
    +

    Partenariat Eurockéenes

    +
    + {% if user.is_subscribed %} + Billetterie Weezevent + + {% else %} +
    Vous devez être cotisant pour accéder à la billeterie des Eurockéennes
    + {% endif %} +
    +
    + {% for priority_groups in products|groupby('priority')|reverse %} {% for category, items in priority_groups.list|groupby('category') %} {% if items|count > 0 %} From 00ae6e46238996e66d728ba8a42b5e7cb8e015eb Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Tue, 19 Sep 2023 22:04:46 +0200 Subject: [PATCH 66/95] Update workflow Following this update : https://github.blog/changelog/2023-09-13-github-actions-updates-to-github_ref-and-github-ref/ --- .github/workflows/ci.yml | 8 ++------ .github/workflows/deploy.yml | 2 +- .github/workflows/taiste.yml | 3 ++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da2bf91f..245c5433 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,13 +2,9 @@ name: Sith 3 CI on: push: - branches: - - master - - taiste + branches: [master, taiste, refs/heads/master, refs/heads/taiste] pull_request: - branches: - - master - - taiste + branches: [master, taiste, refs/heads/master, refs/heads/taiste] jobs: black: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2baa9578..9cd0582e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,7 +3,7 @@ concurrency: production on: push: - branches: [master] + branches: [master, refs/heads/master] workflow_dispatch: jobs: diff --git a/.github/workflows/taiste.yml b/.github/workflows/taiste.yml index 47cf853c..a2ccaff0 100644 --- a/.github/workflows/taiste.yml +++ b/.github/workflows/taiste.yml @@ -2,7 +2,8 @@ name: Sith3 taiste on: push: - branches: [ taiste ] + branches: [taiste, refs/heads/taiste] + workflow_dispatch: jobs: deployment: From 51a12814f9789420eebf61030d9bbc4ccb125fcc Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Tue, 19 Sep 2023 22:17:26 +0200 Subject: [PATCH 67/95] Update workflow --- .github/workflows/ci.yml | 5 +++-- .github/workflows/deploy.yml | 2 +- .github/workflows/taiste.yml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 245c5433..7cd386d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,10 @@ name: Sith 3 CI on: push: - branches: [master, taiste, refs/heads/master, refs/heads/taiste] + branches: [master, taiste] pull_request: - branches: [master, taiste, refs/heads/master, refs/heads/taiste] + branches: [master, taiste] + workflow_dispatch: jobs: black: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9cd0582e..2baa9578 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,7 +3,7 @@ concurrency: production on: push: - branches: [master, refs/heads/master] + branches: [master] workflow_dispatch: jobs: diff --git a/.github/workflows/taiste.yml b/.github/workflows/taiste.yml index a2ccaff0..83cab1d6 100644 --- a/.github/workflows/taiste.yml +++ b/.github/workflows/taiste.yml @@ -2,7 +2,7 @@ name: Sith3 taiste on: push: - branches: [taiste, refs/heads/taiste] + branches: [taiste] workflow_dispatch: jobs: From 4231a7972d62b24a733f1a81b7e030e23f2321c8 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Wed, 4 Oct 2023 14:27:21 +0200 Subject: [PATCH 68/95] Remove eurocks tickets from eboutic (event is finished) --- eboutic/templates/eboutic/eboutic_main.jinja | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 3493742e..529fd067 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -90,24 +90,6 @@ {% endif %} -
    -
    -

    Partenariat Eurockéenes

    -
    - {% if user.is_subscribed %} - Billetterie Weezevent - - {% else %} -
    Vous devez être cotisant pour accéder à la billeterie des Eurockéennes
    - {% endif %} -
    -
    - {% for priority_groups in products|groupby('priority')|reverse %} {% for category, items in priority_groups.list|groupby('category') %} {% if items|count > 0 %} From d16bf126118603e61073bda4e6ab5b8e74e3d5a7 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Tue, 10 Oct 2023 15:29:02 +0200 Subject: [PATCH 69/95] Links update & translations typos fixes (#671) * Remove BDF link (as BDF is now part of AE) * Remove unused pages * Fix typos * Fix typo again --- club/templates/club/club_sellings.jinja | 2 +- club/templates/club/club_tools.jinja | 2 +- com/templates/com/news_detail.jinja | 2 +- core/templates/core/base.jinja | 6 +- core/templates/core/register.jinja | 2 +- core/templates/core/user_account.jinja | 4 +- core/templates/core/user_account_detail.jinja | 4 +- .../templates/counter/cash_summary_list.jinja | 2 +- counter/templates/counter/counter_list.jinja | 4 +- counter/templates/counter/counter_main.jinja | 2 +- counter/templates/counter/last_ops.jinja | 4 +- .../templates/counter/refilling_list.jinja | 2 +- locale/fr/LC_MESSAGES/django.po | 86 ++++++++----------- pedagogy/templates/pedagogy/uv_detail.jinja | 2 +- pedagogy/templates/pedagogy/uv_edit.jinja | 8 +- 15 files changed, 56 insertions(+), 76 deletions(-) diff --git a/club/templates/club/club_sellings.jinja b/club/templates/club/club_sellings.jinja index 6759fb3d..6fdee86a 100644 --- a/club/templates/club/club_sellings.jinja +++ b/club/templates/club/club_sellings.jinja @@ -2,7 +2,7 @@ {% from 'core/macros.jinja' import user_profile_link, paginate %} {% block content %} -

    {% trans %}Sellings{% endtrans %}

    +

    {% trans %}Sales{% endtrans %}

    {% csrf_token %} {{ form }} diff --git a/club/templates/club/club_tools.jinja b/club/templates/club/club_tools.jinja index e35c9bc2..49e2b0df 100644 --- a/club/templates/club/club_tools.jinja +++ b/club/templates/club/club_tools.jinja @@ -30,7 +30,7 @@ {% endif %} {% if object.club_account.exists() %} -

    {% trans %}Accouting: {% endtrans %}

    +

    {% trans %}Accounting: {% endtrans %}

      {% for ca in object.club_account.all() %}
    • {{ ca.get_display_name() }}
    • diff --git a/com/templates/com/news_detail.jinja b/com/templates/com/news_detail.jinja index a1b30602..18fc633f 100644 --- a/com/templates/com/news_detail.jinja +++ b/com/templates/com/news_detail.jinja @@ -39,7 +39,7 @@

      {% trans %}Moderate{% endtrans %}

      {% endif %} {% if user.can_edit(news) %} -

      {% trans %}Edit (will be remoderated){% endtrans %}

      +

      {% trans %}Edit (will be moderated again){% endtrans %}

      {% endif %} diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index ea610aa8..c45d30e0 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -203,11 +203,7 @@
    @@ -43,7 +43,7 @@
    {% endif %} {% if customer.refillings.exists() %} -

    {% trans %}Refillings{% endtrans %}

    +

    {% trans %}Reloads{% endtrans %}

    diff --git a/counter/templates/counter/cash_summary_list.jinja b/counter/templates/counter/cash_summary_list.jinja index 80f80f81..972ff9ec 100644 --- a/counter/templates/counter/cash_summary_list.jinja +++ b/counter/templates/counter/cash_summary_list.jinja @@ -14,7 +14,7 @@ {{ form }}

    -
    {% trans %}Refillings{% endtrans %}
    +
    {% trans %}Reloads{% endtrans %}

    {% for b,s in refilling_sums.items() %} {{ b }}: {{ s }} €
    diff --git a/counter/templates/counter/counter_list.jinja b/counter/templates/counter/counter_list.jinja index b807a7d3..ba8e4197 100644 --- a/counter/templates/counter/counter_list.jinja +++ b/counter/templates/counter/counter_list.jinja @@ -19,7 +19,7 @@ {% endif %} {% if user.is_owner(c) %} {% trans %}Props{% endtrans %} - - {% trans %}Refillings list{% endtrans %} + {% trans %}Reloads list{% endtrans %} {% endif %} {% endfor %} @@ -41,7 +41,7 @@ {% endif %} {% if user.is_owner(c) %} {% trans %}Props{% endtrans %} - - {% trans %}Refillings list{% endtrans %} + {% trans %}Reloads list{% endtrans %} {% endif %} {% endfor %} diff --git a/counter/templates/counter/counter_main.jinja b/counter/templates/counter/counter_main.jinja index 90fdeae7..826cca81 100644 --- a/counter/templates/counter/counter_main.jinja +++ b/counter/templates/counter/counter_main.jinja @@ -21,7 +21,7 @@ {% block content %}

    {% trans counter_name=counter %}{{ counter_name }} counter{% endtrans %}

    -

    {% trans %}Sellings{% endtrans %}

    +

    {% trans %}Sales{% endtrans %}

    {% if last_basket %}

    {% trans %}Last selling: {% endtrans %}

    {% trans %}Client: {% endtrans %}{{ last_customer }} - {% trans %}New amount: {% endtrans %}{{ new_customer_amount }} €.

    diff --git a/counter/templates/counter/last_ops.jinja b/counter/templates/counter/last_ops.jinja index 00e10bfa..c7345088 100644 --- a/counter/templates/counter/last_ops.jinja +++ b/counter/templates/counter/last_ops.jinja @@ -13,7 +13,7 @@ {% block content %}

    {% trans counter_name=counter %}{{ counter_name }} last operations{% endtrans %}

    -

    {% trans %}Refillings{% endtrans %}

    +

    {% trans %}Reloads{% endtrans %}

    @@ -38,7 +38,7 @@
    -

    {% trans %}Sellings{% endtrans %}

    +

    {% trans %}Sales{% endtrans %}

    diff --git a/counter/templates/counter/refilling_list.jinja b/counter/templates/counter/refilling_list.jinja index a50ed3bc..ae3c5681 100644 --- a/counter/templates/counter/refilling_list.jinja +++ b/counter/templates/counter/refilling_list.jinja @@ -2,7 +2,7 @@ {% from 'core/macros.jinja' import paginate %} {% block title %} -{%- trans %}Refillings list{% endtrans %} -- {{ counter.name }} +{%- trans %}Reloads list{% endtrans %} -- {{ counter.name }} {% endblock %} {% block content %} diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 861e2988..fd44743c 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -63,7 +63,7 @@ msgstr "IBAN" #: accounting/models.py:112 msgid "account number" -msgstr "numero de compte" +msgstr "numéro de compte" #: accounting/models.py:116 accounting/models.py:147 club/models.py:275 #: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:273 @@ -1173,7 +1173,7 @@ msgstr "Au" #: club/templates/club/club_sellings.jinja:5 club/views.py:154 #: club/views.py:483 counter/templates/counter/counter_main.jinja:24 #: counter/templates/counter/last_ops.jinja:41 -msgid "Sellings" +msgid "Sales" msgstr "Ventes" #: club/templates/club/club_sellings.jinja:9 club/templates/club/stats.jinja:19 @@ -1278,7 +1278,7 @@ msgid "Counters:" msgstr "Comptoirs : " #: club/templates/club/club_tools.jinja:33 -msgid "Accouting: " +msgid "Accounting: " msgstr "Comptabilité : " #: club/templates/club/club_tools.jinja:41 @@ -1295,8 +1295,8 @@ msgid "" "not shown wait until moderation takes action" msgstr "" "Rappelez vous : les mailing listes doivent être modérées, si votre liste " -"nouvellement créee n'est pas affichée, attendez jusqu'à qu'un modérateur " -"entre en action" +"nouvellement créée n'est pas affichée, attendez jusqu'à ce qu'un modérateur " +"prenne une décision" #: club/templates/club/mailing.jinja:13 msgid "Mailing lists waiting for moderation" @@ -1671,8 +1671,8 @@ msgid "Moderator: " msgstr "Modérateur : " #: com/templates/com/news_detail.jinja:42 -msgid "Edit (will be remoderated)" -msgstr "Éditer (sera resoumise à modération)" +msgid "Edit (will be moderated again)" +msgstr "Éditer (sera soumise de nouveau à la modération)" #: com/templates/com/news_edit.jinja:6 com/templates/com/news_edit.jinja:29 msgid "Edit news" @@ -1680,7 +1680,7 @@ msgstr "Éditer la nouvelle" #: com/templates/com/news_edit.jinja:39 msgid "Notice: Information, election result - no date" -msgstr "Information, resultat d'élection - sans date" +msgstr "Information, résultat d'élection - sans date" #: com/templates/com/news_edit.jinja:40 msgid "Event: punctual event, associated with one date" @@ -1951,7 +1951,7 @@ msgstr "T'es fou? Un événement ne peut pas finir avant même de commencer." #: com/views.py:466 msgid "Delete and save to regenerate" -msgstr "Supprimer et sauver pour regénérer" +msgstr "Supprimer et sauver pour régénérer" #: com/views.py:481 msgid "Weekmail of the " @@ -2036,7 +2036,7 @@ msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -"Est-ce que l'utilisateur doit être traité comme actif. Déselectionnez au " +"Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " "lieu de supprimer les comptes." #: core/models.py:185 @@ -2085,7 +2085,7 @@ msgstr "pronoms" #: core/models.py:234 msgid "tshirt size" -msgstr "taille de tshirt" +msgstr "taille de t-shirt" #: core/models.py:237 msgid "-" @@ -2426,7 +2426,7 @@ msgstr "Voir plus" #: core/templates/core/base.jinja:122 #: forum/templates/forum/last_unread.jinja:17 msgid "Mark all as read" -msgstr "Marquer tout commme lu" +msgstr "Marquer tout comme lu" #: core/templates/core/base.jinja:132 msgid "Logout" @@ -2440,33 +2440,17 @@ msgstr "Accueil" msgid "Associations & Clubs" msgstr "Associations & Clubs" -#: core/templates/core/base.jinja:173 +#: core/templates/core/base.jinja:204 msgid "AE" msgstr "L'AE" -#: core/templates/core/base.jinja:174 +#: core/templates/core/base.jinja:205 msgid "AE's clubs" msgstr "Les clubs de L'AE" -#: core/templates/core/base.jinja:175 -msgid "BdF" -msgstr "Le BdF" - -#: core/templates/core/base.jinja:176 -msgid "BDS" -msgstr "Le BDS" - -#: core/templates/core/base.jinja:177 -msgid "CETU" -msgstr "Le CETU" - -#: core/templates/core/base.jinja:178 -msgid "Doceo" -msgstr "Doceo" - -#: core/templates/core/base.jinja:179 -msgid "Positions" -msgstr "Postes à pourvoir" +#: core/templates/core/base.jinja:206 +msgid "Others UTBM's Associations" +msgstr "Les autres associations de l'UTBM" #: core/templates/core/base.jinja:187 core/templates/core/user_tools.jinja:118 msgid "Elections" @@ -2780,7 +2764,7 @@ msgstr "Cotisant jusqu'au %(subscription_end)s" #: core/templates/core/macros.jinja:86 core/templates/core/user_edit.jinja:40 msgid "Account number: " -msgstr "Numero de compte : " +msgstr "Numéro de compte : " #: core/templates/core/macros.jinja:91 launderette/models.py:217 msgid "Slot" @@ -3004,7 +2988,7 @@ msgstr "Bienvenue, %(user_name)s!" #: core/templates/core/register.jinja:10 msgid "" -"You successfully registred and you will soon receive a confirmation mail." +"You successfully registered and you will soon receive a confirmation mail." msgstr "" "Vous vous êtes correctement enregistré, et vous devriez recevoir rapidement " "un email de confirmation." @@ -3063,14 +3047,14 @@ msgstr "Compte utilisateur" #: core/templates/core/user_account.jinja:44 #: core/templates/core/user_account_detail.jinja:13 -msgid "Account buyings" -msgstr "Achat sur compte utilisateur" +msgid "Account purchases" +msgstr "Achats du compte" #: core/templates/core/user_account.jinja:48 #: core/templates/core/user_account_detail.jinja:46 #: counter/templates/counter/cash_summary_list.jinja:17 #: counter/templates/counter/last_ops.jinja:16 -msgid "Refillings" +msgid "Reloads" msgstr "Rechargements" #: core/templates/core/user_account.jinja:53 @@ -4114,7 +4098,7 @@ msgstr "Nouveau comptoir" #: counter/templates/counter/counter_list.jinja:22 #: counter/templates/counter/counter_list.jinja:44 #: counter/templates/counter/refilling_list.jinja:5 -msgid "Refillings list" +msgid "Reloads list" msgstr "Liste de rechargements" #: counter/templates/counter/counter_list.jinja:27 @@ -5033,7 +5017,7 @@ msgid "" "The code of an UV must only contains uppercase characters without accent and " "numbers" msgstr "" -"Le code d'une UV doit seulement contenire des caractères majuscule sans " +"Le code d'une UV doit seulement contenir des caractères majuscule sans " "accents et nombres" #: pedagogy/models.py:67 @@ -5058,7 +5042,7 @@ msgstr "département" #: pedagogy/models.py:103 msgid "objectives" -msgstr "objecifs" +msgstr "objectifs" #: pedagogy/models.py:104 msgid "program" @@ -5094,7 +5078,7 @@ msgstr "heures TE" #: pedagogy/models.py:217 pedagogy/models.py:291 msgid "uv" -msgstr "uv" +msgstr "UE" #: pedagogy/models.py:221 msgid "global grade" @@ -5153,7 +5137,7 @@ msgstr "%(credit_type)s" #: pedagogy/templates/pedagogy/guide.jinja:59 #: pedagogy/templates/pedagogy/moderation.jinja:12 msgid "UV" -msgstr "UV" +msgstr "UE" #: pedagogy/templates/pedagogy/guide.jinja:61 msgid "Department" @@ -5253,8 +5237,8 @@ msgid "Key concepts" msgstr "Concepts clefs" #: pedagogy/templates/pedagogy/uv_detail.jinja:79 -msgid "UV manager: " -msgstr "Gestionnaire d'UV : " +msgid "UE manager: " +msgstr "Gestionnaire d'UE : " #: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:453 msgid "" @@ -5284,23 +5268,23 @@ msgstr "Signaler ce commentaire" #: pedagogy/templates/pedagogy/uv_edit.jinja:4 #: pedagogy/templates/pedagogy/uv_edit.jinja:8 -msgid "Edit UV" -msgstr "Éditer" +msgid "Edit UE" +msgstr "Éditer l'UE" #: pedagogy/templates/pedagogy/uv_edit.jinja:27 msgid "Import from UTBM" msgstr "Importer depuis l'UTBM" #: pedagogy/templates/pedagogy/uv_edit.jinja:62 -msgid "Unknown UV code" -msgstr "Code d'UV inconnu" +msgid "Unknown UE code" +msgstr "Code d'UE inconnu" #: pedagogy/templates/pedagogy/uv_edit.jinja:77 msgid "Successful autocomplete" msgstr "Autocomplétion réussite" #: pedagogy/templates/pedagogy/uv_edit.jinja:80 -msgid "An error occured: " +msgid "An error occurred: " msgstr "Une erreur est survenue : " #: rootplace/templates/rootplace/delete_user_messages.jinja:8 @@ -5566,7 +5550,7 @@ msgstr "Automne et printemps" #: sith/settings.py:448 msgid "German" -msgstr "Allemant" +msgstr "Allemand" #: sith/settings.py:449 msgid "Spanish" diff --git a/pedagogy/templates/pedagogy/uv_detail.jinja b/pedagogy/templates/pedagogy/uv_detail.jinja index 255ee024..de741b96 100644 --- a/pedagogy/templates/pedagogy/uv_detail.jinja +++ b/pedagogy/templates/pedagogy/uv_detail.jinja @@ -76,7 +76,7 @@

    {{ object.skills|markdown }}

    {% trans %}Key concepts{% endtrans %}

    {{ object.key_concepts|markdown }}

    -

    {% trans %}UV manager: {% endtrans %}{{ object.manager }}

    +

    {% trans %}UE manager: {% endtrans %}{{ object.manager }}

    diff --git a/pedagogy/templates/pedagogy/uv_edit.jinja b/pedagogy/templates/pedagogy/uv_edit.jinja index a47e10e6..b6994a69 100644 --- a/pedagogy/templates/pedagogy/uv_edit.jinja +++ b/pedagogy/templates/pedagogy/uv_edit.jinja @@ -1,11 +1,11 @@ {% extends "core/base.jinja" %} {% block title %} -{% trans %}Edit UV{% endtrans %} +{% trans %}Edit UE{% endtrans %} {% endblock %} {% block content %} -

    {% trans %}Edit UV{% endtrans %}

    +

    {% trans %}Edit UE{% endtrans %}

    {% csrf_token %} {{ form.non_field_errors() }} @@ -59,7 +59,7 @@ url: url, success: function(data, _, xhr) { if (xhr.status != 200) { - createQuickNotif("{% trans %}Unknown UV code{% endtrans %}") + createQuickNotif("{% trans %}Unknown UE code{% endtrans %}") return } for (let key in data) { @@ -77,7 +77,7 @@ createQuickNotif('{% trans %}Successful autocomplete{% endtrans %}') }, error: function(_, _, statusMessage) { - createQuickNotif('{% trans %}An error occured: {% endtrans %}' + statusMessage) + createQuickNotif('{% trans %}An error occurred: {% endtrans %}' + statusMessage) }, }) }) From ee437649f0be813fb288e9fa8f6fea110b756c57 Mon Sep 17 00:00:00 2001 From: Julien Constant Date: Tue, 10 Oct 2023 15:47:02 +0200 Subject: [PATCH 70/95] Revert "Merge branch 'master' into taiste" This reverts commit 4303d51c0a8018c2b9118e83bba312d40200a31f, reversing changes made to d16bf126118603e61073bda4e6ab5b8e74e3d5a7. --- eboutic/templates/eboutic/eboutic_main.jinja | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 3493742e..529fd067 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -90,24 +90,6 @@ {% endif %} -
    -
    -

    Partenariat Eurockéenes

    -
    - {% if user.is_subscribed %} - Billetterie Weezevent - - {% else %} -
    Vous devez être cotisant pour accéder à la billeterie des Eurockéennes
    - {% endif %} -
    -
    - {% for priority_groups in products|groupby('priority')|reverse %} {% for category, items in priority_groups.list|groupby('category') %} {% if items|count > 0 %} From 5416d88c971a42089914c028456c3d3ebf28d8d2 Mon Sep 17 00:00:00 2001 From: Sli Date: Sat, 22 Jun 2024 21:15:37 +0200 Subject: [PATCH 71/95] Upgrade dependencies and install xapian from sources --- core/management/commands/install_xapian.py | 67 + core/management/commands/install_xapian.sh | 49 + doc/start/install.rst | 27 +- poetry.lock | 1359 ++++++++++---------- pyproject.toml | 16 +- 5 files changed, 852 insertions(+), 666 deletions(-) create mode 100644 core/management/commands/install_xapian.py create mode 100755 core/management/commands/install_xapian.sh diff --git a/core/management/commands/install_xapian.py b/core/management/commands/install_xapian.py new file mode 100644 index 00000000..b2009cd4 --- /dev/null +++ b/core/management/commands/install_xapian.py @@ -0,0 +1,67 @@ +# -*- coding:utf-8 -* +# +# Copyright 2024 © 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/sith3 +# +# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) +# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE +# OR WITHIN THE LOCAL FILE "LICENSE" +# +# + +import os +import tomli +import subprocess +from django.core.management.base import BaseCommand, CommandParser +from pathlib import Path + + +class Command(BaseCommand): + help = "Install xapian" + + def add_arguments(self, parser: CommandParser): + parser.add_argument( + "-f", + "--force", + action="store_true", + help="Force installation even if already installed", + ) + + def _current_version(self) -> str | None: + try: + import xapian + except ImportError: + return None + return xapian.version_string() + + def _desired_version(self) -> str: + with open( + Path(__file__).parent.parent.parent.parent / "pyproject.toml", "rb" + ) as f: + pyproject = tomli.load(f) + return pyproject["tool"]["xapian"]["version"] + + def handle(self, force: bool, *args, **options): + if not os.environ.get("VIRTUAL_ENV", None): + print("No virtual environment detected, this command can't be used") + return + + desired = self._desired_version() + if desired == self._current_version(): + if not force: + print( + f"Version {desired} is already installed, use --force to re-install" + ) + return + print(f"Version {desired} is already installed, re-installing") + print(f"Installing xapian version {desired} at {os.environ['VIRTUAL_ENV']}") + subprocess.run( + [str(Path(__file__).parent / "install_xapian.sh"), desired], + env=dict(os.environ), + ).check_returncode() + print("Installation success") diff --git a/core/management/commands/install_xapian.sh b/core/management/commands/install_xapian.sh new file mode 100755 index 00000000..c517fda6 --- /dev/null +++ b/core/management/commands/install_xapian.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Originates from https://gist.github.com/jorgecarleitao/ab6246c86c936b9c55fd +# first argument of the script is Xapian version (e.g. 1.2.19) +VERSION=$1 + +# Cleanup env vars for auto discovery mechanism +export CPATH= +export LIBRARY_PATH= +export CFLAGS= +export LDFLAGS= +export CCFLAGS= +export CXXFLAGS= +export CPPFLAGS= + +# prepare +rm -rf $VIRTUAL_ENV/packages +mkdir -p $VIRTUAL_ENV/packages && cd $VIRTUAL_ENV/packages + +CORE=xapian-core-$VERSION +BINDINGS=xapian-bindings-$VERSION + +# download +echo "Downloading source..." +curl -O https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz +curl -O https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz + +# extract +echo "Extracting source..." +tar xf ${CORE}.tar.xz +tar xf ${BINDINGS}.tar.xz + +# install +echo "Installing Xapian-core..." +cd $VIRTUAL_ENV/packages/${CORE} +./configure --prefix=$VIRTUAL_ENV && make && make install + +PYV=`python -c "import sys;t='{v[0]}'.format(v=list(sys.version_info[:1]));sys.stdout.write(t)";` + +PYTHON_FLAG=--with-python3 + +echo "Installing Xapian-bindings..." +cd $VIRTUAL_ENV/packages/${BINDINGS} +./configure --prefix=$VIRTUAL_ENV $PYTHON_FLAG XAPIAN_CONFIG=$VIRTUAL_ENV/bin/xapian-config && make && make install + +# clean +rm -rf $VIRTUAL_ENV/packages + +# test +python -c "import xapian" diff --git a/doc/start/install.rst b/doc/start/install.rst index 0e97ab15..d07e2ac4 100644 --- a/doc/start/install.rst +++ b/doc/start/install.rst @@ -9,7 +9,6 @@ Certaines dépendances sont nécessaires niveau système : * poetry * libssl * libjpeg -* libxapian-dev * zlib1g-dev * python * gettext @@ -76,13 +75,13 @@ Sur Ubuntu # Sait-on jamais sudo apt update - sudo apt install python-is-python3 # Permet d'utiliser python au lieu de python3, c'est optionel + sudo apt install python-is-python3 # Permet d'utiliser python au lieu de python3, c'est optionnel sudo apt install build-essentials libssl-dev libjpeg-dev zlib1g-dev python-dev \ - libffi-dev python-dev-is-python3 libgraphviz-dev pkg-config libxapian-dev \ - gettext git + libffi-dev python-dev-is-python3 libgraphviz-dev pkg-config \ + gettext git pipx - curl -sSL https://install.python-poetry.org | python - + pipx install poetry .. note:: @@ -92,22 +91,21 @@ Sur Ubuntu Sur MacOS ~~~~~~~~~ -Pour installer les dépendances, il est fortement recommandé d'installer le gestionnaire de paquets `homebrew `_. +Pour installer les dépendances, il est fortement recommandé d'installer le gestionnaire de paquets `homebrew `_. +Il est également nécessaire d'avoir installé xcode .. sourcecode:: bash - brew install git python xapian graphviz poetry - - # Si vous aviez une version de python ne venant pas de homebrew - brew link --overwrite python + echo 'export PATH="$(brew --prefix graphviz)/bin:$PATH"' >> ~/.zshrc + echo 'export CFLAGS="-isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I $(brew --prefix graphviz)/include"' >> ~/.zshrc + echo 'export LDFLAGS="-L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -L $(brew --prefix graphviz)/lib"' >> ~/.zshrc + brew install git python graphviz pipx + pipx install poetry # Pour bien configurer gettext brew link gettext # (suivez bien les instructions supplémentaires affichées) - # Pour installer poetry - pip3 install poetry - .. note:: Si vous rencontrez des erreurs lors de votre configuration, n'hésitez pas à vérifier l'état de votre installation homebrew avec :code:`brew doctor` @@ -134,6 +132,9 @@ Finaliser l'installation # Activation de l'environnement virtuel poetry shell + # Installe xapian + python manage.py install_xapian + # Prépare la base de données python manage.py setup diff --git a/poetry.lock b/poetry.lock index 23ef0bda..00ba5bb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,54 +1,57 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" -version = "0.7.12" -description = "A configurable sidebar-enabled Sphinx theme" -optional = true -python-versions = "*" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" files = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] name = "appnope" -version = "0.1.3" +version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, ] [[package]] name = "asgiref" -version = "3.6.0" +version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "asgiref-3.6.0-py3-none-any.whl", hash = "sha256:71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac"}, - {file = "asgiref-3.6.0.tar.gz", hash = "sha256:9567dfe7bd8d3c8c892227827c41cce860b368104c3431da67a0c5a65a949506"}, + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, ] +[package.dependencies] +typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} + [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] name = "babel" -version = "2.11.0" +version = "2.15.0" description = "Internationalization utilities" -optional = true -python-versions = ">=3.6" +optional = false +python-versions = ">=3.8" files = [ - {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, - {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] -[package.dependencies] -pytz = ">=2015.7" +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "backcall" @@ -63,36 +66,33 @@ files = [ [[package]] name = "black" -version = "23.3.0" +version = "23.12.1" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, - {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, - {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, - {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, - {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, - {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, - {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, - {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, - {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, - {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, - {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, - {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, - {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, - {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, - {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, - {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, - {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, - {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, ] [package.dependencies] @@ -102,124 +102,208 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] pycparser = "*" [[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = true -python-versions = ">=3.6.0" +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" files = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, ] -[package.extras] -unicode-backport = ["unicodedata2"] +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -302,30 +386,30 @@ toml = ["toml"] [[package]] name = "cryptography" -version = "40.0.1" +version = "40.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.6" files = [ - {file = "cryptography-40.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:918cb89086c7d98b1b86b9fdb70c712e5a9325ba6f7d7cfb509e784e0cfc6917"}, - {file = "cryptography-40.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9618a87212cb5200500e304e43691111570e1f10ec3f35569fdfcd17e28fd797"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a4805a4ca729d65570a1b7cac84eac1e431085d40387b7d3bbaa47e39890b88"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63dac2d25c47f12a7b8aa60e528bfb3c51c5a6c5a9f7c86987909c6c79765554"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0a4e3406cfed6b1f6d6e87ed243363652b2586b2d917b0609ca4f97072994405"}, - {file = "cryptography-40.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1e0af458515d5e4028aad75f3bb3fe7a31e46ad920648cd59b64d3da842e4356"}, - {file = "cryptography-40.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d8aa3609d337ad85e4eb9bb0f8bcf6e4409bfb86e706efa9a027912169e89122"}, - {file = "cryptography-40.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cf91e428c51ef692b82ce786583e214f58392399cf65c341bc7301d096fa3ba2"}, - {file = "cryptography-40.0.1-cp36-abi3-win32.whl", hash = "sha256:650883cc064297ef3676b1db1b7b1df6081794c4ada96fa457253c4cc40f97db"}, - {file = "cryptography-40.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:a805a7bce4a77d51696410005b3e85ae2839bad9aa38894afc0aa99d8e0c3160"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd033d74067d8928ef00a6b1327c8ea0452523967ca4463666eeba65ca350d4c"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d36bbeb99704aabefdca5aee4eba04455d7a27ceabd16f3b3ba9bdcc31da86c4"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:32057d3d0ab7d4453778367ca43e99ddb711770477c4f072a51b3ca69602780a"}, - {file = "cryptography-40.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f5d7b79fa56bc29580faafc2ff736ce05ba31feaa9d4735048b0de7d9ceb2b94"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7c872413353c70e0263a9368c4993710070e70ab3e5318d85510cc91cce77e7c"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:28d63d75bf7ae4045b10de5413fb1d6338616e79015999ad9cf6fc538f772d41"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6f2bbd72f717ce33100e6467572abaedc61f1acb87b8d546001328d7f466b778"}, - {file = "cryptography-40.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cc3a621076d824d75ab1e1e530e66e7e8564e357dd723f2533225d40fe35c60c"}, - {file = "cryptography-40.0.1.tar.gz", hash = "sha256:2803f2f8b1e95f614419926c7e6f55d828afc614ca5ed61543877ae668cc3472"}, + {file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b"}, + {file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440"}, + {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d"}, + {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288"}, + {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2"}, + {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b"}, + {file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"}, + {file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c"}, + {file = "cryptography-40.0.2-cp36-abi3-win32.whl", hash = "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9"}, + {file = "cryptography-40.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b"}, + {file = "cryptography-40.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b"}, + {file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e"}, + {file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a"}, + {file = "cryptography-40.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958"}, + {file = "cryptography-40.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b"}, + {file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636"}, + {file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e"}, + {file = "cryptography-40.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404"}, + {file = "cryptography-40.0.2.tar.gz", hash = "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99"}, ] [package.dependencies] @@ -354,27 +438,27 @@ files = [ [[package]] name = "dict2xml" -version = "1.7.3" +version = "1.7.5" description = "Small utility to convert a python dictionary into an XML string" optional = false python-versions = ">=3.5" files = [ - {file = "dict2xml-1.7.3-py3-none-any.whl", hash = "sha256:f849e1aec277f93d087482461b6b8afdde61df346918298aca4c42bcf9895f6d"}, - {file = "dict2xml-1.7.3.tar.gz", hash = "sha256:02a5c198d0fecdfeb52644e9d905200a36c031e11c201362d7d217df684bc15d"}, + {file = "dict2xml-1.7.5-py3-none-any.whl", hash = "sha256:f5380dcda0039807bff5543801009f36e5bfff355705863628835cb69f4711b6"}, + {file = "dict2xml-1.7.5.tar.gz", hash = "sha256:e279f4707cf7733f1e56b2cea39e257c727b86f74e449deccc6a712a1cfe4e45"}, ] [package.extras] -tests = ["noseofyeti[black] (==2.4.1)", "pytest (==7.2.1)"] +tests = ["noseofyeti[black] (==2.4.4)", "pytest (==7.4.4)"] [[package]] name = "django" -version = "3.2.18" +version = "3.2.25" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.6" files = [ - {file = "Django-3.2.18-py3-none-any.whl", hash = "sha256:4d492d9024c7b3dfababf49f94511ab6a58e2c9c3c7207786f1ba4eb77750706"}, - {file = "Django-3.2.18.tar.gz", hash = "sha256:08208dfe892eb64fff073ca743b3b952311104f939e7f6dae954fe72dcc533ba"}, + {file = "Django-3.2.25-py3-none-any.whl", hash = "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38"}, + {file = "Django-3.2.25.tar.gz", hash = "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777"}, ] [package.dependencies] @@ -388,23 +472,23 @@ bcrypt = ["bcrypt"] [[package]] name = "django-ajax-selects" -version = "2.2.0" +version = "2.2.1" description = "Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete." optional = false python-versions = "*" files = [ - {file = "django-ajax-selects-2.2.0.tar.gz", hash = "sha256:539298874b2d26ce9e778a5173d312f55340c887a126c7e2d3460b9a5b4593a2"}, + {file = "django-ajax-selects-2.2.1.tar.gz", hash = "sha256:996ffb38dff1a621b358613afdf2681dbf261e5976da3c30a75e9b08fd81a887"}, ] [[package]] name = "django-countries" -version = "7.5.1" +version = "7.6.1" description = "Provides a country field for Django models." optional = false python-versions = "*" files = [ - {file = "django-countries-7.5.1.tar.gz", hash = "sha256:22915d9b9403932b731622619940a54894a3eb0da9a374e7249c8fc453c122d7"}, - {file = "django_countries-7.5.1-py3-none-any.whl", hash = "sha256:2df707aca7a5e677254bed116cf6021a136ebaccd5c2f46860abd6452bb45521"}, + {file = "django-countries-7.6.1.tar.gz", hash = "sha256:c772d4e3e54afcc5f97a018544e96f246c6d9f1db51898ab0c15cd57e19437cf"}, + {file = "django_countries-7.6.1-py3-none-any.whl", hash = "sha256:1ed20842fe0f6194f91faca21076649513846a8787c9eb5aeec3cbe1656b8acc"}, ] [package.dependencies] @@ -412,20 +496,20 @@ asgiref = "*" typing-extensions = "*" [package.extras] -dev = ["black", "django", "djangorestframework", "graphene-django", "pytest", "pytest-django", "tox"] -maintainer = ["django", "transifex-client", "zest.releaser[recommended]"] +dev = ["black", "django", "djangorestframework", "graphene-django", "pytest", "pytest-django", "tox (==4.*)"] +maintainer = ["django", "zest.releaser[recommended]"] pyuca = ["pyuca"] test = ["djangorestframework", "graphene-django", "pytest", "pytest-cov", "pytest-django"] [[package]] name = "django-debug-toolbar" -version = "4.0.0" +version = "4.3.0" description = "A configurable set of panels that display various debug information about the current request/response." optional = false python-versions = ">=3.8" files = [ - {file = "django_debug_toolbar-4.0.0-py3-none-any.whl", hash = "sha256:bad339d68520652ddc1580c76f136fcbc3e020fd5ed96510a89a02ec81bb3fb1"}, - {file = "django_debug_toolbar-4.0.0.tar.gz", hash = "sha256:89619f6e0ea1057dca47bfc429ed99b237ef70074dabc065a7faa5f00e1459cf"}, + {file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"}, + {file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"}, ] [package.dependencies] @@ -434,33 +518,35 @@ sqlparse = ">=0.2" [[package]] name = "django-haystack" -version = "3.2.1" +version = "3.3.0" description = "Pluggable search for Django." optional = false python-versions = "*" files = [ - {file = "django-haystack-3.2.1.tar.gz", hash = "sha256:97e3197aefc225fe405b6f17600a2534bf827cb4d6743130c20bc1a06f7293a4"}, + {file = "django_haystack-3.3.0.tar.gz", hash = "sha256:e3ceed6b8000625da14d409eb4dac69894905e2ac8ac18f9bfdb59323ca02eab"}, ] [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" +packaging = "*" [package.extras] elasticsearch = ["elasticsearch (>=5,<8)"] +testing = ["coverage", "geopy (==2)", "pysolr (>=3.7)", "python-dateutil", "requests", "whoosh (>=2.5.4,<3.0)"] [[package]] name = "django-jinja" -version = "2.10.2" +version = "2.11.0" description = "Jinja2 templating language integrated in Django." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "django-jinja-2.10.2.tar.gz", hash = "sha256:bfdfbb55c1f5a679d69ad575d550c4707d386634009152efe014089f3c4d1412"}, - {file = "django_jinja-2.10.2-py3-none-any.whl", hash = "sha256:dd003ec1c95c0989eb28a538831bced62b1b61da551cb44a5dfd708fcf75589f"}, + {file = "django-jinja-2.11.0.tar.gz", hash = "sha256:47c06d3271e6b2f27d3596278af517bfe2e19c1eb36ae1c0b1cc302d7f0259af"}, + {file = "django_jinja-2.11.0-py3-none-any.whl", hash = "sha256:cc4c72246a6e346aa0574e0c56c3e534c1a20ef47b8476f05d7287781f69a0a9"}, ] [package.dependencies] -django = ">=2.2" +django = ">=3.2" jinja2 = ">=3" [[package]] @@ -507,17 +593,17 @@ django = "*" [[package]] name = "django-simple-captcha" -version = "0.5.17" +version = "0.5.20" description = "A very simple, yet powerful, Django captcha application" optional = false python-versions = "*" files = [ - {file = "django-simple-captcha-0.5.17.tar.gz", hash = "sha256:9649e66dab4e71efacbfef02f48b83b91684898352a1ab56f1686ce71033b328"}, - {file = "django_simple_captcha-0.5.17-py2.py3-none-any.whl", hash = "sha256:f9a07e5e9de264ba4039c9eaad66bc48188a21ceda5fcdc2fa13c5512141c2c9"}, + {file = "django-simple-captcha-0.5.20.tar.gz", hash = "sha256:20273009a7beb44297e9f6c7a6bd21ada3d2fa93c314d2f6bf5e394ceeb6a297"}, + {file = "django_simple_captcha-0.5.20-py2.py3-none-any.whl", hash = "sha256:3359cb033c489eae6544a80ad92517db3d35b3b328b3b427393399c3d7f55275"}, ] [package.dependencies] -Django = ">=2.2" +Django = ">=3.2" django-ranged-response = "0.2.0" Pillow = ">=6.2.0" @@ -526,39 +612,54 @@ test = ["testfixtures"] [[package]] name = "djangorestframework" -version = "3.14.0" +version = "3.15.1" description = "Web APIs for Django, made easy." optional = false python-versions = ">=3.6" files = [ - {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"}, - {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"}, + {file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"}, + {file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"}, ] [package.dependencies] django = ">=3.0" -pytz = "*" [[package]] name = "docutils" -version = "0.17.1" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, - {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] name = "freezegun" -version = "1.2.2" +version = "1.5.1" description = "Let your Python tests travel through time" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "freezegun-1.2.2-py3-none-any.whl", hash = "sha256:ea1b963b993cb9ea195adbd893a48d573fda951b0da64f60883d7e988b606c9f"}, - {file = "freezegun-1.2.2.tar.gz", hash = "sha256:cd22d1ba06941384410cd967d8a99d5ae2442f57dfafeff2fda5de8dc5c05446"}, + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, ] [package.dependencies] @@ -566,45 +667,26 @@ python-dateutil = ">=2.7" [[package]] name = "idna" -version = "3.4" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" -optional = true +optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] -[[package]] -name = "importlib-metadata" -version = "6.0.0" -description = "Read metadata from Python packages" -optional = true -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, - {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - [[package]] name = "ipython" version = "7.34.0" @@ -643,32 +725,32 @@ test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments" [[package]] name = "jedi" -version = "0.18.2" +version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ - {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, - {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, ] [package.dependencies] -parso = ">=0.8.0,<0.9.0" +parso = ">=0.8.3,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -688,67 +770,88 @@ files = [ {file = "libsass-0.22.0-cp37-abi3-macosx_10_15_x86_64.whl", hash = "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a"}, {file = "libsass-0.22.0-cp37-abi3-win32.whl", hash = "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f"}, {file = "libsass-0.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf"}, + {file = "libsass-0.22.0-cp38-abi3-macosx_14_0_arm64.whl", hash = "sha256:5fb2297a4754a6c8e25cfe5c015a3b51a2b6b9021b333f989bb8ce9d60eb5828"}, {file = "libsass-0.22.0.tar.gz", hash = "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425"}, ] [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "matplotlib-inline" -version = "0.1.6" +version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, - {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, ] [package.dependencies] @@ -767,61 +870,61 @@ files = [ [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "packaging" -version = "23.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "parso" -version = "0.8.3" +version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" files = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, ] [package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] [[package]] name = "pathspec" -version = "0.10.3" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "pexpect" -version = "4.8.0" +version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" files = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] @@ -829,13 +932,13 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.4" +version = "8.13.39" 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.4-py2.py3-none-any.whl", hash = "sha256:a577a46c069ad889c7b7cf4dd978751d059edeab28b97acead4775d2ea1fc70a"}, - {file = "phonenumbers-8.13.4.tar.gz", hash = "sha256:6d63455012fc9431105ffc7739befca61c3efc551b287dca58d2be2e745475a9"}, + {file = "phonenumbers-8.13.39-py2.py3-none-any.whl", hash = "sha256:3ad2d086fa71e7eef409001b9195ac54bebb0c6e3e752209b558ca192c9229a0"}, + {file = "phonenumbers-8.13.39.tar.gz", hash = "sha256:db7ca4970d206b2056231105300753b1a5b229f43416f8c2b3010e63fbb68d77"}, ] [[package]] @@ -851,118 +954,108 @@ files = [ [[package]] name = "pillow" -version = "9.4.0" +version = "9.5.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.7" files = [ - {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, - {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, - {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, - {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, - {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, - {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, - {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, - {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, - {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, - {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, - {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, - {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, - {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, - {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, - {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, - {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, - {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, - {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, - {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, - {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, - {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, - {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, - {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, - {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, - {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, - {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, - {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, - {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, - {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, - {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, - {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, - {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, - {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, - {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, - {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, - {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, - {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, - {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, - {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, - {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, - {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, - {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, - {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, - {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, - {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, - {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, - {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, - {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, - {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "platformdirs" -version = "2.6.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-2.6.2-py3-none-any.whl", hash = "sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490"}, - {file = "platformdirs-2.6.2.tar.gz", hash = "sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "prompt-toolkit" -version = "3.0.36" +version = "3.0.47" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.36-py3-none-any.whl", hash = "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305"}, - {file = "prompt_toolkit-3.0.36.tar.gz", hash = "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63"}, + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, ] [package.dependencies] @@ -970,70 +1063,83 @@ wcwidth = "*" [[package]] name = "psycopg2-binary" -version = "2.9.3" +version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f2534ab7dc7e776a263b463a16e189eb30e85ec9bbe1bff9e78dae802608932"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"}, - {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"}, - {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, - {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e6aa71ae45f952a2205377773e76f4e3f27951df38e69a4c95440c779e013560"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, - {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b3a24a1982ae56461cc24f6680604fffa2c1b818e9dc55680da038792e004d18"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"}, - {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"}, + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] [[package]] @@ -1049,52 +1155,52 @@ files = [ [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "pygments" -version = "2.14.0" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.6" -files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, -] - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pygraphviz" -version = "1.10" -description = "Python interface to Graphviz" -optional = false python-versions = ">=3.8" files = [ - {file = "pygraphviz-1.10.zip", hash = "sha256:457e093a888128903251a266a8cc16b4ba93f3f6334b3ebfed92c7471a74d867"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pygraphviz" +version = "1.13" +description = "Python interface to Graphviz" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pygraphviz-1.13.tar.gz", hash = "sha256:6ad8aa2f26768830a5a1cfc8a14f022d13df170a8f6fdfd68fd1aa1267000964"}, ] [[package]] name = "pyopenssl" -version = "23.1.1" +version = "23.2.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.6" files = [ - {file = "pyOpenSSL-23.1.1-py3-none-any.whl", hash = "sha256:9e0c526404a210df9d2b18cd33364beadb0dc858a739b885677bc65e105d4a4c"}, - {file = "pyOpenSSL-23.1.1.tar.gz", hash = "sha256:841498b9bec61623b1b6c47ebbc02367c07d60e0e195f19790817f10cc8db0b7"}, + {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, + {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, ] [package.dependencies] -cryptography = ">=38.0.0,<41" +cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" [package.extras] docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] @@ -1102,13 +1208,13 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -1127,81 +1233,40 @@ files = [ [[package]] name = "reportlab" -version = "3.6.12" +version = "4.2.0" description = "The Reportlab Toolkit" optional = false -python-versions = ">=3.7,<4" +python-versions = "<4,>=3.7" files = [ - {file = "reportlab-3.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dfcf7bd6db5d80711cbbd0996b6e7a79cc414ca81457960367df11d2860f92a"}, - {file = "reportlab-3.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a0bc7a1d64fe754b62e175ba0cf47a630b529c0488ec9ac4e4c7655e295ea4d"}, - {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:adf78ccb2defad5b6ecb2e2e9f2a672719b0a8e2278592a7d77f6c220a042388"}, - {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c84afd5bef6e407c80ba9f99b6abbe3ea78e8243b0f19897a871a7bcad1f749d"}, - {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fa3cdf490f3828b055381e8c7dc7819b3e5f7a442d7af7a8f90e9806a7fff51"}, - {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07fdd968df7941c2bfb67b9bb4532f424992dfafc71b72a4e4b291ff707e6b0e"}, - {file = "reportlab-3.6.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce85a204f46c871c8af6fa64b9bbed165456935c1d0bfb2f570a3194f6723ddb"}, - {file = "reportlab-3.6.12-cp310-cp310-win32.whl", hash = "sha256:090ea99ff829d918f7b6140594373b1340a34e1e6876eddae5aa06662ec10d64"}, - {file = "reportlab-3.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:4c599645af9b5b2241a23e977a82c965a59c24cd94b2600b8d34373c66cad763"}, - {file = "reportlab-3.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:236a6483210049205f6180d7a7595d0ca2e4ce343d83cc94ca719a4145809c6f"}, - {file = "reportlab-3.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:69f41295d696c822224334f0994f1f107df7efed72211d45a1118696f1427c84"}, - {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f51dcb39e910a853749250c0f82aced80bca3f7315e9c4ee14349eb7cab6a3f8"}, - {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8dddc52e0e486291be0ad39184da0607fae9cc665fdba1881211de9cfc0b332"}, - {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4863c49602722237e35cbce5aa91af4539cc63a671f59504d2b3f3767d898cf"}, - {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b1215facead57cc5325aef4229ef886e85d270b2ba02080fb5809ce9d2b81b4"}, - {file = "reportlab-3.6.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12049314497d872f6788f811e2b331654db207937f8a2fb34ff3e3cd9897faa"}, - {file = "reportlab-3.6.12-cp311-cp311-win32.whl", hash = "sha256:759495c2b8c15cb0d6b539c246896029e4cde42a896c3956f77e311c5f6b0807"}, - {file = "reportlab-3.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:666bdba4958b348460a765c48b8c0640e7085540846ed9494f47d8651604b33c"}, - {file = "reportlab-3.6.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a7c3369fa618eca79f9554ce06c618a5e738e592d61d96aa09b2457ca3ea410"}, - {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9b0861d8f40d7a24b094b8834f6a489b9e8c70bceaa7fa98237eed229671ce"}, - {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26c25ea4afa8b92a2c14f4edc41c8fc30505745ce84cae86538e80cacadd7ae2"}, - {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55a070206580e161b6bbe1a96abf816c18d4c2c225d49916654714c93d842835"}, - {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c40e108072379ff83dd7442159ebc249d12eb8eec15b70614953fecd2c403792"}, - {file = "reportlab-3.6.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39e92fa4ab2a8f0f2cc051d9c1e3acb881340c07ef59c0c8b627861343d653c0"}, - {file = "reportlab-3.6.12-cp37-cp37m-win32.whl", hash = "sha256:3fd1ffdd5204301eb4c290a5752ac62f44d2d0b262e02e35a1e5234c13e14662"}, - {file = "reportlab-3.6.12-cp37-cp37m-win_amd64.whl", hash = "sha256:d4cecfb48a6cfbfe2caf0fc280cecea999699e63bc98cb02254bd87b39eff677"}, - {file = "reportlab-3.6.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b6a1b685da0b9a8000bb980e02d9d5be202d0cc539af113b661c76c051fca6f1"}, - {file = "reportlab-3.6.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f5808e1dac6b66c109d6205ce2aebf84bb89e1a1493b7e6df38932df5ebfb9cf"}, - {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb83df8f7840321d34cb5b24c972c617a8c1716c8a36e5050fff56adf5891b8c"}, - {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72ec333f089b4fce5a6d740ed0a1963a3994146be195722da0d8e14d4a7e1600"}, - {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71cf73f9907c444ef663ea653dbac24af07c307079572c3ff8f20ad1463af3b7"}, - {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cee3b6ebef5e4a8654ec5f0effeb1a2bb157ad87b0ac856871d25a805c0f2f90"}, - {file = "reportlab-3.6.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db62bed0774778fdf82c609cb9efd0062f2fdcd285be527d01f6be9fd9755888"}, - {file = "reportlab-3.6.12-cp38-cp38-win32.whl", hash = "sha256:b777ddc57b2d3366cbc540616034cdc1089ca0a31fefc907028e1dd62a6bf16c"}, - {file = "reportlab-3.6.12-cp38-cp38-win_amd64.whl", hash = "sha256:c07ec796a2a5d44bf787f2b623b6e668a389b0cafb78af34cf74554ff3bc532b"}, - {file = "reportlab-3.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cdd206883e999278d2af656f988dfcc89eb0c175ce6d75e87b713cf1e792c0c4"}, - {file = "reportlab-3.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a62e51a4a47616896bd0f1e9cc3fbfb174b713794a5031a34b84f69dbe01775"}, - {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dd0307b2b13b0482ac8314fd793fbbce263a428b189371addf0466784e1d597"}, - {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56d701f7dc662e1d3d7fe364e66fa1339eafce54a488c2d16ec0ea49dc213c2"}, - {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:109009b02fc225882ea766a5ed8be0ef473fa1356e252a3f651a6aa89b4a195f"}, - {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b3648f3c340b6b6aabf9352341478c708cee6f00c5cd5c902311fcf4ce870f3c"}, - {file = "reportlab-3.6.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:907f7cd4832bb295d0c1573de15cc5aab5988282caf2ee7a2b1276fb6cdf502b"}, - {file = "reportlab-3.6.12-cp39-cp39-win32.whl", hash = "sha256:93e229519d046491b798f2c12dbbf2f3e237e89589aa5cbb5e1d8c1a978816db"}, - {file = "reportlab-3.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:498b4ec7e73426de64c6bf6ec03c5b3f10dedf5db8a9e13fdf195f95a3d065aa"}, - {file = "reportlab-3.6.12.tar.gz", hash = "sha256:b13cebf4e397bba14542bcd023338b6ff2c151a3a12aabca89eecbf972cb361a"}, + {file = "reportlab-4.2.0-py3-none-any.whl", hash = "sha256:53630f9d25a7938def3e6a93d723b72a7a5921d34d23cf7a0930adeb2cb0e6c1"}, + {file = "reportlab-4.2.0.tar.gz", hash = "sha256:474fb28d63431a5d47d75c90d580393050df7d491a09c7877df3291a2e9f6d0a"}, ] [package.dependencies] +chardet = "*" pillow = ">=9.0.0" [package.extras] -fttextpath = ["freetype-py (>=2.3.0,<2.4)"] -rlpycairo = ["rlPyCairo (>=0.1.0)"] +accel = ["rl-accel (>=0.9.0,<1.1)"] +pycairo = ["freetype-py (>=2.3.0,<2.4)", "rlPyCairo (>=0.2.0,<1)"] +renderpm = ["rl-renderPM (>=4.0.3,<4.1)"] [[package]] name = "requests" -version = "2.28.1" +version = "2.32.3" description = "Python HTTP for Humans." -optional = true -python-versions = ">=3.7, <4" +optional = false +python-versions = ">=3.8" files = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -1209,13 +1274,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sentry-sdk" -version = "1.21.0" +version = "1.45.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.21.0.tar.gz", hash = "sha256:36a1ca082a3065a8a05aafa4b1e3d74e9459d41fbb0bea8a5364caca68626341"}, - {file = "sentry_sdk-1.21.0-py2.py3-none-any.whl", hash = "sha256:82faf9e2c9eb77401a7a187094b126ca25c2a3a478de6704612f48b3346f7a84"}, + {file = "sentry-sdk-1.45.0.tar.gz", hash = "sha256:509aa9678c0512344ca886281766c2e538682f8acfa50fd8d405f8c417ad0625"}, + {file = "sentry_sdk-1.45.0-py2.py3-none-any.whl", hash = "sha256:1ce29e30240cc289a027011103a8c83885b15ef2f316a60bcc7c5300afa144f1"}, ] [package.dependencies] @@ -1225,18 +1290,24 @@ urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] +celery-redbeat = ["celery-redbeat (>=2)"] chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] -flask = ["blinker (>=1.1)", "flask (>=0.11)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] grpcio = ["grpcio (>=1.21.1)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +loguru = ["loguru (>=0.5)"] +openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] @@ -1250,19 +1321,18 @@ tornado = ["tornado (>=5)"] [[package]] name = "setuptools" -version = "65.6.3" +version = "70.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, - {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, + {file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"}, + {file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1279,7 +1349,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = true +optional = false python-versions = "*" files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, @@ -1288,27 +1358,26 @@ files = [ [[package]] name = "sphinx" -version = "4.5.0" +version = "5.3.0" description = "Python documentation generator" -optional = true +optional = false python-versions = ">=3.6" files = [ - {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, - {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.18" -imagesize = "*" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" requests = ">=2.5.0" -snowballstemmer = ">=1.1" +snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" @@ -1318,8 +1387,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] [[package]] name = "sphinx-copybutton" @@ -1341,72 +1410,90 @@ rtd = ["ipython", "sphinx", "sphinx-book-theme"] [[package]] name = "sphinx-rtd-theme" -version = "1.1.1" +version = "1.3.0" description = "Read the Docs theme for Sphinx" optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "sphinx_rtd_theme-1.1.1-py2.py3-none-any.whl", hash = "sha256:31faa07d3e97c8955637fc3f1423a5ab2c44b74b8cc558a51498c202ce5cbda7"}, - {file = "sphinx_rtd_theme-1.1.1.tar.gz", hash = "sha256:6146c845f1e1947b3c3dd4432c28998a1693ccc742b4f9ad7c63129f0757c103"}, + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] -docutils = "<0.18" -sphinx = ">=1.6,<6" +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" [package.extras] dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.3" +version = "1.0.8" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = true -python-versions = ">=3.8" +optional = false +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib.applehelp-1.0.3-py3-none-any.whl", hash = "sha256:ba0f2a22e6eeada8da6428d0d520215ee8864253f32facf958cca81e426f661d"}, - {file = "sphinxcontrib.applehelp-1.0.3.tar.gz", hash = "sha256:83749f09f6ac843b8cb685277dbc818a8bf2d76cc19602699094fe9a74db529e"}, + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -optional = true -python-versions = ">=3.5" +version = "1.0.6" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.0" +version = "2.0.5" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = true -python-versions = ">=3.6" +optional = false +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = true +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" + [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = true +optional = false python-versions = ">=3.5" files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, @@ -1418,49 +1505,50 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -optional = true -python-versions = ">=3.5" +version = "1.0.7" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -optional = true -python-versions = ">=3.5" +version = "1.1.10" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, ] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sqlparse" -version = "0.4.4" +version = "0.5.0" description = "A non-validating SQL parser." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" files = [ - {file = "sqlparse-0.4.4-py3-none-any.whl", hash = "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3"}, - {file = "sqlparse-0.4.4.tar.gz", hash = "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"}, + {file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"}, + {file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"}, ] [package.extras] -dev = ["build", "flake8"] +dev = ["build", "hatch"] doc = ["sphinx"] -test = ["pytest", "pytest-cov"] [[package]] name = "tomli" @@ -1475,95 +1563,72 @@ files = [ [[package]] name = "traitlets" -version = "5.8.1" +version = "5.14.3" description = "Traitlets Python configuration system" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "traitlets-5.8.1-py3-none-any.whl", hash = "sha256:a1ca5df6414f8b5760f7c5f256e326ee21b581742114545b462b35ffe3f04861"}, - {file = "traitlets-5.8.1.tar.gz", hash = "sha256:32500888f5ff7bbf3b9267ea31748fa657aaf34d56d85e60f91dda7dc7f5785b"}, + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "urllib3" -version = "1.26.13" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.8" files = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "wcwidth" -version = "0.2.5" +version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] - -[[package]] -name = "xapian-bindings" -version = "0.1.0" -description = "Meta-package to build and install xapian-bindings extension." -optional = false -python-versions = "*" -files = [ - {file = "xapian-bindings-0.1.0.tar.gz", hash = "sha256:f2b0396082ebf4f6681ab43d6d8fd1f63b6964b18c32c91236ed067c6f62ad14"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [[package]] name = "xapian-haystack" -version = "3.0.1" +version = "3.1.0" description = "A Xapian backend for Haystack" optional = false python-versions = "*" files = [ - {file = "xapian-haystack-3.0.1.tar.gz", hash = "sha256:a5c0e1262b95008df4dfeb58d093c654acee3f2b27ea3f7d366900895cdc70f9"}, + {file = "xapian-haystack-3.1.0.tar.gz", hash = "sha256:9f9ab90bf450bf6699d164594d569243aafb6c9f0990a16855f55a1d16bc09c6"}, ] [package.dependencies] -django = ">=2.2" +django = ">=3.2" django-haystack = ">=2.8.0" - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = true -python-versions = ">=3.7" -files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +filelock = ">=3.4" [extras] docs = ["Sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] @@ -1571,5 +1636,5 @@ testing = ["coverage"] [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "62519616aff5a472dac3dd8071a6404b1ee8eab12a197af717a0520f7ded0331" +python-versions = "^3.10,<3.12" +content-hash = "1b87ea58b2796016b7f16421fd0d4276d88fc6be217e3e5934f3399b834af1f6" diff --git a/pyproject.toml b/pyproject.toml index cb1a6aaf..9a8b7d24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ authors = [ "Skia ", "klmp200 ", "Krophil ", + "Maréchal ", "Och ", "tleb ", "Soldat ", @@ -19,7 +20,7 @@ homepage = "https://ae.utbm.fr/" license = "GPL-3.0-only" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.10,<3.12" Django = "^3.2" Pillow = "^9.2" mistune = "^0.8.4" @@ -31,29 +32,32 @@ djangorestframework = "^3.13" django-phonenumber-field = "^6.3" phonenumbers = "^8.12" django-ajax-selects = "^2.1.0" -reportlab = "^3.6" +reportlab = "^4.2" django-haystack = "^3.2.1" xapian-haystack = "^3.0.1" -xapian-bindings = "^0.1.0" libsass = "^0.22" django-ordered-model = "^3.7" django-simple-captcha = "^0.5.17" python-dateutil = "^2.8.2" -psycopg2-binary = "2.9.3" +psycopg2-binary = "^2.9" sentry-sdk = "^1.21.0" -pygraphviz = "^1.9" +pygraphviz = "^1.1" Jinja2 = "^3.1" django-countries = "^7.5.1" dict2xml = "^1.7.3" +Sphinx = "^5" # Needed for building xapian +tomli = "^2.0.1" # Extra optional dependencies coverage = {version = "^5.5", optional = true} # Docs extra dependencies -Sphinx = {version = "^4.2.0", optional = true} sphinx-rtd-theme = {version = "^1.0.0", optional = true} sphinx-copybutton = {version = "^0.4.0", optional = true} +[tool.xapian] +version = "1.4.25" + [tool.poetry.extras] testing = ["coverage"] docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] From e681c17a0f9fec6d70d2b6bd5ae5d0532eabbd97 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 23 Jun 2024 21:04:52 +0200 Subject: [PATCH 72/95] Adapt CI to new xapian install process --- .github/actions/setup_project/action.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup_project/action.yml b/.github/actions/setup_project/action.yml index b1eb6e2a..beb091e2 100644 --- a/.github/actions/setup_project/action.yml +++ b/.github/actions/setup_project/action.yml @@ -6,13 +6,13 @@ runs: - name: Install apt packages uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: gettext libxapian-dev libgraphviz-dev + packages: gettext libgraphviz-dev version: 1.0 # increment to reset cache - name: Install dependencies run: | sudo apt update - sudo apt install gettext libxapian-dev libgraphviz-dev + sudo apt install gettext libgraphviz-dev shell: bash - name: Set up python @@ -48,6 +48,10 @@ runs: run: poetry install -E testing -E docs shell: bash + - name: Install xapian + run: poetry run ./manage.py install_xapian + shell: bash + - name: Compile gettext messages run: poetry run ./manage.py compilemessages shell: bash From e1bf7caa9afc8359f7791a7f874482d3b4f559e3 Mon Sep 17 00:00:00 2001 From: Sli Date: Sat, 22 Jun 2024 21:16:42 +0200 Subject: [PATCH 73/95] Fix CVE-2023-31047 --- core/views/files.py | 29 +++++++++++++++++++++++++++-- pyproject.toml | 2 +- sas/views.py | 5 ++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/core/views/files.py b/core/views/files.py index 1047f381..986477ab 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -79,12 +79,37 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"): return response +class MultipleFileInput(forms.ClearableFileInput): + allow_multiple_selected = True + + +class _MultipleFieldMixin: + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = [single_file_clean(data, initial)] + return result + + +class MultipleFileField(_MultipleFieldMixin, forms.FileField): + ... + + +class MultipleImageField(_MultipleFieldMixin, forms.ImageField): + ... + + class AddFilesForm(forms.Form): folder_name = forms.CharField( label=_("Add a new folder"), max_length=30, required=False ) - file_field = forms.FileField( - widget=forms.ClearableFileInput(attrs={"multiple": True}), + file_field = MultipleFileField( label=_("Files"), required=False, ) diff --git a/pyproject.toml b/pyproject.toml index 9a8b7d24..a27426eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ homepage = "https://ae.utbm.fr/" license = "GPL-3.0-only" [tool.poetry.dependencies] -python = "^3.10,<3.12" +python = "^3.10,<3.12" # Version is held back by mistune Django = "^3.2" Pillow = "^9.2" mistune = "^0.8.4" diff --git a/sas/views.py b/sas/views.py index ff51fe37..052f7a4e 100644 --- a/sas/views.py +++ b/sas/views.py @@ -30,7 +30,7 @@ from ajax_select import make_ajax_field from ajax_select.fields import AutoCompleteSelectMultipleField from core.views import CanViewMixin, CanEditMixin -from core.views.files import send_file, FileView +from core.views.files import send_file, FileView, MultipleImageField from core.models import SithFile, User, Notification, RealGroup from sas.models import Picture, Album, PeoplePictureRelation @@ -40,8 +40,7 @@ class SASForm(forms.Form): album_name = forms.CharField( label=_("Add a new album"), max_length=30, required=False ) - images = forms.ImageField( - widget=forms.ClearableFileInput(attrs={"multiple": True}), + images = MultipleImageField( label=_("Upload images"), required=False, ) From ca27b89a8b53f347a1f7eb41f88fd1621f96c143 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 26 Jun 2024 11:31:39 +0200 Subject: [PATCH 74/95] Apply shellcheck on install_xapian.sh --- core/management/commands/install_xapian.sh | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/core/management/commands/install_xapian.sh b/core/management/commands/install_xapian.sh index c517fda6..ae183f6e 100755 --- a/core/management/commands/install_xapian.sh +++ b/core/management/commands/install_xapian.sh @@ -13,37 +13,35 @@ export CXXFLAGS= export CPPFLAGS= # prepare -rm -rf $VIRTUAL_ENV/packages -mkdir -p $VIRTUAL_ENV/packages && cd $VIRTUAL_ENV/packages +rm -rf "$VIRTUAL_ENV/packages" +mkdir -p "$VIRTUAL_ENV/packages" && cd "$VIRTUAL_ENV/packages" || exit 1 CORE=xapian-core-$VERSION BINDINGS=xapian-bindings-$VERSION # download echo "Downloading source..." -curl -O https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz -curl -O https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz +curl -O "https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz" +curl -O "https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz" # extract echo "Extracting source..." -tar xf ${CORE}.tar.xz -tar xf ${BINDINGS}.tar.xz +tar xf "${CORE}.tar.xz" +tar xf "${BINDINGS}.tar.xz" # install echo "Installing Xapian-core..." -cd $VIRTUAL_ENV/packages/${CORE} -./configure --prefix=$VIRTUAL_ENV && make && make install - -PYV=`python -c "import sys;t='{v[0]}'.format(v=list(sys.version_info[:1]));sys.stdout.write(t)";` +cd "$VIRTUAL_ENV/packages/${CORE}" || exit 1 +./configure --prefix="$VIRTUAL_ENV" && make && make install PYTHON_FLAG=--with-python3 echo "Installing Xapian-bindings..." -cd $VIRTUAL_ENV/packages/${BINDINGS} -./configure --prefix=$VIRTUAL_ENV $PYTHON_FLAG XAPIAN_CONFIG=$VIRTUAL_ENV/bin/xapian-config && make && make install +cd "$VIRTUAL_ENV/packages/${BINDINGS}" || exit 1 +./configure --prefix="$VIRTUAL_ENV" $PYTHON_FLAG XAPIAN_CONFIG="$VIRTUAL_ENV/bin/xapian-config" && make && make install # clean -rm -rf $VIRTUAL_ENV/packages +rm -rf "$VIRTUAL_ENV/packages" # test python -c "import xapian" From e06bc7dba30a4e397b9ae18e060d9ac61263857a Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 24 Jun 2024 11:24:08 +0200 Subject: [PATCH 75/95] reorganize pyproject.toml --- .github/actions/setup_project/action.yml | 2 +- doc/start/install.rst | 2 +- poetry.lock | 124 +++++++++++------------ pyproject.toml | 32 +++--- 4 files changed, 79 insertions(+), 81 deletions(-) diff --git a/.github/actions/setup_project/action.yml b/.github/actions/setup_project/action.yml index beb091e2..975dbde9 100644 --- a/.github/actions/setup_project/action.yml +++ b/.github/actions/setup_project/action.yml @@ -45,7 +45,7 @@ runs: ${{ runner.os }}-poetry- - name: Install dependencies - run: poetry install -E testing -E docs + run: poetry install --with docs,tests shell: bash - name: Install xapian diff --git a/doc/start/install.rst b/doc/start/install.rst index d07e2ac4..db1a9749 100644 --- a/doc/start/install.rst +++ b/doc/start/install.rst @@ -179,7 +179,7 @@ Cette commande génère la documentation à chacune de ses modifications, inutil Les dépendances pour la documentation sont optionnelles. Avant de commencer à travailler sur la doc, il faut donc les installer - avec la commande :code:`poetry install -E docs` + avec la commande :code:`poetry install --with docs` .. sourcecode:: bash diff --git a/poetry.lock b/poetry.lock index 00ba5bb5..8bf30dcd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -322,67 +322,67 @@ files = [ [[package]] name = "coverage" -version = "5.5" +version = "7.5.4" description = "Code coverage measurement for Python" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +optional = false +python-versions = ">=3.8" files = [ - {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"}, - {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"}, - {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"}, - {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"}, - {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"}, - {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"}, - {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"}, - {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"}, - {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"}, - {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"}, - {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"}, - {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"}, - {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"}, - {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"}, - {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"}, - {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"}, - {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"}, - {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"}, - {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"}, - {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"}, - {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"}, - {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"}, - {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"}, - {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"}, - {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"}, - {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"}, - {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"}, - {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"}, - {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"}, - {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"}, - {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"}, - {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"}, - {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"}, - {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"}, - {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"}, - {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, - {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, + {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, + {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, + {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, + {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, + {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, + {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, + {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, + {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, + {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, + {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, + {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, + {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, ] [package.extras] -toml = ["toml"] +toml = ["tomli"] [[package]] name = "cryptography" @@ -1394,7 +1394,7 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] name = "sphinx-copybutton" version = "0.4.0" description = "Add a copy button to each of your code cells." -optional = true +optional = false python-versions = ">=3.6" files = [ {file = "sphinx-copybutton-0.4.0.tar.gz", hash = "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386"}, @@ -1412,7 +1412,7 @@ rtd = ["ipython", "sphinx", "sphinx-book-theme"] name = "sphinx-rtd-theme" version = "1.3.0" description = "Read the Docs theme for Sphinx" -optional = true +optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, @@ -1479,7 +1479,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" -optional = true +optional = false python-versions = ">=2.7" files = [ {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, @@ -1630,11 +1630,7 @@ django = ">=3.2" django-haystack = ">=2.8.0" filelock = ">=3.4" -[extras] -docs = ["Sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -testing = ["coverage"] - [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "1b87ea58b2796016b7f16421fd0d4276d88fc6be217e3e5934f3399b834af1f6" +content-hash = "fe98c5784d71478c6da090c2adae73fbbff3ef47e0cd5fa8893811e4b357949b" diff --git a/pyproject.toml b/pyproject.toml index a27426eb..d1157762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,26 +48,28 @@ dict2xml = "^1.7.3" Sphinx = "^5" # Needed for building xapian tomli = "^2.0.1" -# Extra optional dependencies -coverage = {version = "^5.5", optional = true} - -# Docs extra dependencies -sphinx-rtd-theme = {version = "^1.0.0", optional = true} -sphinx-copybutton = {version = "^0.4.0", optional = true} - -[tool.xapian] -version = "1.4.25" - -[tool.poetry.extras] -testing = ["coverage"] -docs = ["Sphinx", "sphinx-rtd-theme", "sphinx-copybutton"] - -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] freezegun = "^1.2.2" # used to test time-dependent code django-debug-toolbar = "^4.0.0" ipython = "^7.28.0" black = "^23.3.0" +[tool.poetry.group.tests.dependencies] +coverage = "^7.5.4" + +[tool.poetry.group.tests] +optional = true + +[tool.poetry.group.docs.dependencies] +sphinx-rtd-theme = "^1.0.0" +sphinx-copybutton = "^0.4.0" + +[tool.poetry.group.docs] +optional = true + +[tool.xapian] +version = "1.4.25" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" From 9bdf3fc4ac0e3980f0f2228d6dc276cf377eb408 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 24 Jun 2024 11:56:38 +0200 Subject: [PATCH 76/95] use ruff for formating Co-authored-by: Bartuccio Antoine --- .github/workflows/ci.yml | 16 ++--- accounting/views.py | 2 +- club/tests.py | 3 +- core/models.py | 8 ++- core/operations.py | 6 +- core/views/__init__.py | 5 +- core/views/files.py | 6 +- core/views/group.py | 2 +- core/views/site.py | 5 +- counter/views.py | 32 ++++++---- doc/start/devtools.rst | 54 ++++++++--------- eboutic/tests.py | 16 ++--- poetry.lock | 126 +++++++++------------------------------ pyproject.toml | 2 +- sith/settings.py | 2 +- sith/urls.py | 1 + 16 files changed, 105 insertions(+), 181 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cd386d5..74f18dd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,15 +8,17 @@ on: workflow_dispatch: jobs: - black: - name: Black format + ruff: + name: Ruff lint & format runs-on: ubuntu-latest steps: - - name: Check out repository - uses: actions/checkout@v3 - - name: Setup Project - uses: ./.github/actions/setup_project - - run: poetry run black --check . + - uses: actions/checkout@v4 + - name: ruff format + uses: chartboost/ruff-action@v1 # format + with: + args: format --diff + - name: ruff check + uses: chartboost/ruff-action@v1 # lint tests: name: Run tests and generate coverage report diff --git a/accounting/views.py b/accounting/views.py index 7182ec83..32ebc1c5 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -599,7 +599,7 @@ class OperationPDFView(CanViewMixin, DetailView): payment_mode = "" for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD: if m[0] == mode: - payment_mode += "[\u00D7]" + payment_mode += "[\u00d7]" else: payment_mode += "[ ]" payment_mode += " %s\n" % (m[1]) diff --git a/club/tests.py b/club/tests.py index 0cc05e89..b333cc48 100644 --- a/club/tests.py +++ b/club/tests.py @@ -861,8 +861,7 @@ class MailingFormTest(TestCase): reverse("club:mailing", kwargs={"club_id": self.bdf.id}), { "action": MailingForm.ACTION_REMOVE_SUBSCRIPTION, - "removal_%d" - % mde.id: [ + "removal_%d" % mde.id: [ user.id for user in mde.subscriptions.filter( user__in=[self.rbatsbak, self.comunity] diff --git a/core/models.py b/core/models.py index c8a38426..c804d30b 100644 --- a/core/models.py +++ b/core/models.py @@ -698,9 +698,11 @@ class User(AbstractBaseUser): %s """ % ( - self.profile_pict.get_download_url() - if self.profile_pict - else staticfiles_storage.url("core/img/unknown.jpg"), + ( + self.profile_pict.get_download_url() + if self.profile_pict + else staticfiles_storage.url("core/img/unknown.jpg") + ), _("Profile"), escape(self.get_display_name()), ) diff --git a/core/operations.py b/core/operations.py index 29740292..10882f22 100644 --- a/core/operations.py +++ b/core/operations.py @@ -23,9 +23,9 @@ # """ - This page is useful for custom migration tricks. - Sometimes, when you need to have a migration hack and you think it can be - useful again, put it there, we never know if we might need the hack again. +This page is useful for custom migration tricks. +Sometimes, when you need to have a migration hack and you think it can be +useful again, put it there, we never know if we might need the hack again. """ from django.db import connection, migrations diff --git a/core/views/__init__.py b/core/views/__init__.py index fc807663..a36147b4 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -314,9 +314,8 @@ class QuickNotifMixin: quick_notif_list = [] def dispatch(self, request, *arg, **kwargs): - self.quick_notif_list = ( - [] - ) # In some cases, the class can stay instanciated, so we need to reset the list + # In some cases, the class can stay instanciated, so we need to reset the list + self.quick_notif_list = [] return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs) def get_success_url(self): diff --git a/core/views/files.py b/core/views/files.py index 986477ab..61d3aec9 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -97,12 +97,10 @@ class _MultipleFieldMixin: return result -class MultipleFileField(_MultipleFieldMixin, forms.FileField): - ... +class MultipleFileField(_MultipleFieldMixin, forms.FileField): ... -class MultipleImageField(_MultipleFieldMixin, forms.ImageField): - ... +class MultipleImageField(_MultipleFieldMixin, forms.ImageField): ... class AddFilesForm(forms.Form): diff --git a/core/views/group.py b/core/views/group.py index a6b61866..419d699d 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -15,7 +15,7 @@ # """ - This module contains views to manage Groups +This module contains views to manage Groups """ from django.views.generic.edit import UpdateView, CreateView, DeleteView diff --git a/core/views/site.py b/core/views/site.py index c34cf2c4..c7163488 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -100,9 +100,8 @@ def search_club(query, as_json=False): if query: clubs = Club.objects.filter(name__icontains=query).all() clubs = clubs[:5] - if ( - as_json - ): # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers + if as_json: + # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers clubs = json.loads(serializers.serialize("json", clubs, fields=("name"))) else: clubs = list(clubs) diff --git a/counter/views.py b/counter/views.py index 6bbc819d..4c0b167b 100644 --- a/counter/views.py +++ b/counter/views.py @@ -141,9 +141,11 @@ class CounterTabsMixin(TabedViewMixin): "url": reverse_lazy( "counter:details", kwargs={ - "counter_id": self.object.stock_owner.counter.id - if hasattr(self.object, "stock_owner") - else self.object.id + "counter_id": ( + self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + ) }, ), "slug": "counter", @@ -160,9 +162,11 @@ class CounterTabsMixin(TabedViewMixin): "url": reverse_lazy( "counter:cash_summary", kwargs={ - "counter_id": self.object.stock_owner.counter.id - if hasattr(self.object, "stock_owner") - else self.object.id + "counter_id": ( + self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + ) }, ), "slug": "cash_summary", @@ -174,9 +178,11 @@ class CounterTabsMixin(TabedViewMixin): "url": reverse_lazy( "counter:last_ops", kwargs={ - "counter_id": self.object.stock_owner.counter.id - if hasattr(self.object, "stock_owner") - else self.object.id + "counter_id": ( + self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + ) }, ), "slug": "last_ops", @@ -189,9 +195,11 @@ class CounterTabsMixin(TabedViewMixin): "url": reverse_lazy( "stock:take_items", kwargs={ - "stock_id": self.object.stock.id - if hasattr(self.object, "stock") - else self.object.stock_owner.id + "stock_id": ( + self.object.stock.id + if hasattr(self.object, "stock") + else self.object.stock_owner.id + ) }, ), "slug": "take_items_from_stock", diff --git a/doc/start/devtools.rst b/doc/start/devtools.rst index 9256bfb4..80d56a13 100644 --- a/doc/start/devtools.rst +++ b/doc/start/devtools.rst @@ -1,70 +1,66 @@ Configurer son environnement de développement ============================================= -Le projet n'est en aucun cas lié à un quelconque environnement de développement. Il est possible pour chacun de travailler avec les outils dont il a envie et d'utiliser l'éditeur de code avec lequel il est le plus à l'aise. +Le projet n'est en aucun cas lié à un quelconque environnement de développement. +Il est possible pour chacun de travailler avec les outils dont il a envie et d'utiliser l'éditeur de code avec lequel il est le plus à l'aise. Pour donner une idée, Skia a écrit une énorme partie de projet avec l'éditeur *Vim* sur du GNU/Linux alors que Sli a utilisé *Sublime Text* sur MacOS et que Maréchal travaille avec PyCharm -sur Windows muni de WSL. +sur ~~Windows muni de WSL~~ Arch Linux btw. -Configurer Black pour son éditeur +Configurer Ruff pour son éditeur --------------------------------- .. note:: - Black est inclus dans les dépendances du projet. + Ruff est inclus dans les dépendances du projet. Si vous avez réussi à terminer l'installation, vous n'avez donc pas de configuration supplémentaire à effectuer. -Pour utiliser Black, placez-vous à la racine du projet et lancez la commande suivante : +Pour utiliser Ruff, placez-vous à la racine du projet et lancez la commande suivante : .. code-block:: - black . + ruff format # pour formatter le code + ruff check # pour linter le code -Black va alors faire son travail sur l'ensemble du projet puis vous dire quels documents -ont été reformatés. +Ruff va alors faire son travail sur l'ensemble du projet puis vous dire +si des documents ont été reformatés (si vous avez fait `ruff format`) +ou bien s'il y a des erreurs à réparer (si vous avez faire `ruff check`). -Appeler Black en ligne de commandes avant de pousser votre code sur Github +Appeler Ruff en ligne de commandes avant de pousser votre code sur Github est une technique qui marche très bien. Cependant, vous risquez de souvent l'oublier. -Or, lorsque le code est mal formaté, la pipeline bloque les PR sur les branches protégées. +Or, lorsque le code ne respecte pas les standards de qualité, +la pipeline bloque les PR sur les branches protégées. -Pour éviter de vous faire régulièrement blacked, vous pouvez configurer -votre éditeur pour que Black fasse son travail automatiquement à chaque édition d'un fichier. +Pour éviter de vous faire régulièrement avoir, vous pouvez configurer +votre éditeur pour que Ruff fasse son travail automatiquement à chaque édition d'un fichier. Nous tenterons de vous faire ici un résumé pour deux éditeurs de textes populaires que sont VsCode et Sublime Text. VsCode ~~~~~~ -.. warning:: - - Il faut installer black dans son environement virtuel pour cet éditeur - -Black est directement pris en charge par l'extension pour le Python de VsCode, il suffit de rentrer la configuration suivante : +Installez l'extension Ruff pour VsCode. +Ensuite, ajoutez ceci dans votre configuration : .. sourcecode:: json { - "python.formatting.provider": "black", - "editor.formatOnSave": true + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "charliermarsh.ruff" + } } Sublime Text ~~~~~~~~~~~~ -Il est tout d'abord nécessaire d'installer ce plugin : https://packagecontrol.io/packages/sublack. +Vous devez installer ce plugin : https://packagecontrol.io/packages/LSP-ruff. +Suivez ensuite les instructions données dans la description du plugin. -Il suffit ensuite d'ajouter dans les settings du projet (ou directement dans les settings globales) : - -.. sourcecode:: json - - { - "sublack.black_on_save": true - } - -Si vous utilisez le plugin `anaconda `__, pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de black comme ceci : +Si vous utilisez le plugin `anaconda `__, pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de ruff comme ceci : .. sourcecode:: json diff --git a/eboutic/tests.py b/eboutic/tests.py index af0e5850..b37b4a7b 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -114,9 +114,7 @@ class EbouticTest(TestCase): def test_submit_basket(self): self.client.login(username="subscriber", password="plop") - self.client.cookies[ - "basket_items" - ] = """[ + self.client.cookies["basket_items"] = """[ {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28}, {"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7} ]""" @@ -150,9 +148,7 @@ class EbouticTest(TestCase): def test_submit_invalid_basket(self): self.client.login(username="subscriber", password="plop") max_id = Product.objects.aggregate(res=Max("id"))["res"] - self.client.cookies[ - "basket_items" - ] = f"""[ + self.client.cookies["basket_items"] = f"""[ {{"id": {max_id + 1}, "name": "", "quantity": 1, "unit_price": 28}} ]""" response = self.client.get(reverse("eboutic:command")) @@ -168,9 +164,7 @@ class EbouticTest(TestCase): def test_submit_basket_illegal_quantity(self): self.client.login(username="subscriber", password="plop") - self.client.cookies[ - "basket_items" - ] = """[ + self.client.cookies["basket_items"] = """[ {"id": 4, "name": "Barbar", "quantity": -1, "unit_price": 1.7} ]""" response = self.client.get(reverse("eboutic:command")) @@ -182,9 +176,7 @@ class EbouticTest(TestCase): reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}) ) self.assertTrue("Non cotisant" in str(response.content)) - self.client.cookies[ - "basket_items" - ] = """[ + self.client.cookies["basket_items"] = """[ {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28} ]""" response = self.client.get(reverse("eboutic:command")) diff --git a/poetry.lock b/poetry.lock index 8bf30dcd..f43c9663 100644 --- a/poetry.lock +++ b/poetry.lock @@ -64,52 +64,6 @@ files = [ {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] -[[package]] -name = "black" -version = "23.12.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, - {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, - {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, - {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, - {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, - {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, - {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, - {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, - {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, - {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, - {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, - {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, - {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, - {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, - {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, - {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, - {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, - {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, - {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, - {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, - {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, - {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "certifi" version = "2024.6.2" @@ -295,20 +249,6 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - [[package]] name = "colorama" version = "0.4.6" @@ -868,17 +808,6 @@ files = [ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "packaging" version = "24.1" @@ -905,17 +834,6 @@ files = [ qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["docopt", "pytest"] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "pexpect" version = "4.9.0" @@ -1031,22 +949,6 @@ files = [ docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - [[package]] name = "prompt-toolkit" version = "3.0.47" @@ -1272,6 +1174,32 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "ruff" +version = "0.4.10" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.4.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c2c4d0859305ac5a16310eec40e4e9a9dec5dcdfbe92697acd99624e8638dac"}, + {file = "ruff-0.4.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a79489607d1495685cdd911a323a35871abfb7a95d4f98fc6f85e799227ac46e"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1dd1681dfa90a41b8376a61af05cc4dc5ff32c8f14f5fe20dba9ff5deb80cd6"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c75c53bb79d71310dc79fb69eb4902fba804a81f374bc86a9b117a8d077a1784"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18238c80ee3d9100d3535d8eb15a59c4a0753b45cc55f8bf38f38d6a597b9739"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d8f71885bce242da344989cae08e263de29752f094233f932d4f5cfb4ef36a81"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:330421543bd3222cdfec481e8ff3460e8702ed1e58b494cf9d9e4bf90db52b9d"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e9b6fb3a37b772628415b00c4fc892f97954275394ed611056a4b8a2631365e"}, + {file = "ruff-0.4.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f54c481b39a762d48f64d97351048e842861c6662d63ec599f67d515cb417f6"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:67fe086b433b965c22de0b4259ddfe6fa541c95bf418499bedb9ad5fb8d1c631"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:acfaaab59543382085f9eb51f8e87bac26bf96b164839955f244d07125a982ef"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3cea07079962b2941244191569cf3a05541477286f5cafea638cd3aa94b56815"}, + {file = "ruff-0.4.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:338a64ef0748f8c3a80d7f05785930f7965d71ca260904a9321d13be24b79695"}, + {file = "ruff-0.4.10-py3-none-win32.whl", hash = "sha256:ffe3cd2f89cb54561c62e5fa20e8f182c0a444934bf430515a4b422f1ab7b7ca"}, + {file = "ruff-0.4.10-py3-none-win_amd64.whl", hash = "sha256:67f67cef43c55ffc8cc59e8e0b97e9e60b4837c8f21e8ab5ffd5d66e196e25f7"}, + {file = "ruff-0.4.10-py3-none-win_arm64.whl", hash = "sha256:dd1fcee327c20addac7916ca4e2653fbbf2e8388d8a6477ce5b4e986b68ae6c0"}, + {file = "ruff-0.4.10.tar.gz", hash = "sha256:3aa4f2bc388a30d346c56524f7cacca85945ba124945fe489952aadb6b5cd804"}, +] + [[package]] name = "sentry-sdk" version = "1.45.0" @@ -1633,4 +1561,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "fe98c5784d71478c6da090c2adae73fbbff3ef47e0cd5fa8893811e4b357949b" +content-hash = "78f859d93ec1f207dbdebd5b608abfac44f87bb254a37ebeafaaf1823f605a71" diff --git a/pyproject.toml b/pyproject.toml index d1157762..0bb1a27f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ tomli = "^2.0.1" freezegun = "^1.2.2" # used to test time-dependent code django-debug-toolbar = "^4.0.0" ipython = "^7.28.0" -black = "^23.3.0" +ruff = "^0.4.10" [tool.poetry.group.tests.dependencies] coverage = "^7.5.4" diff --git a/sith/settings.py b/sith/settings.py index 5ed279af..df2b4ced 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -567,7 +567,7 @@ SITH_SUBSCRIPTIONS = { "name": _("One year for free(CA offer)"), "price": 0, "duration": 2, - } + }, # To be completed.... } diff --git a/sith/urls.py b/sith/urls.py index 6a098b5e..08e25b3e 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -29,6 +29,7 @@ Including another URLconf 1. Add an import: from blog import urls as blog_urls 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) """ + from django.urls import include, path from django.contrib import admin from django.conf import settings From 3143d3d91a5bd9a1859e00615aff74130d21beaf Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 24 Jun 2024 13:07:36 +0200 Subject: [PATCH 77/95] reorganize imports with ruff --- accounting/admin.py | 1 - accounting/migrations/0001_initial.py | 5 +- .../migrations/0002_auto_20160824_2152.py | 2 +- .../migrations/0003_auto_20160824_2203.py | 2 +- .../migrations/0004_auto_20161005_1505.py | 2 +- accounting/models.py | 18 ++-- accounting/tests.py | 14 +-- accounting/views.py | 58 ++++++------- api/admin.py | 2 - api/models.py | 2 - api/tests.py | 2 - api/urls.py | 4 +- api/views/__init__.py | 14 +-- api/views/api.py | 2 +- api/views/club.py | 10 +-- api/views/counter.py | 5 +- api/views/group.py | 3 +- api/views/launderette.py | 5 +- api/views/sas.py | 3 +- api/views/user.py | 5 +- api/views/uv.py | 13 +-- club/forms.py | 11 +-- club/migrations/0001_initial.py | 2 +- club/migrations/0002_auto_20160824_2152.py | 4 +- club/migrations/0004_auto_20160915_1057.py | 4 +- club/migrations/0005_auto_20161120_1149.py | 2 +- club/migrations/0006_auto_20161229_0040.py | 2 +- club/migrations/0007_auto_20170324_0917.py | 2 +- club/migrations/0009_auto_20170822_2232.py | 5 +- club/migrations/0010_auto_20170912_2028.py | 2 +- club/migrations/0011_auto_20180426_2013.py | 5 +- club/models.py | 16 ++-- club/tests.py | 11 ++- club/views.py | 58 ++++++------- com/migrations/0002_news_newsdate.py | 4 +- com/migrations/0003_auto_20170115_2300.py | 4 +- com/migrations/0004_auto_20171221_1614.py | 4 +- com/models.py | 18 ++-- com/tests.py | 7 +- com/views.py | 45 +++++----- core/admin.py | 4 +- core/apps.py | 2 +- core/converters.py | 3 - core/lookups.py | 11 ++- core/management/commands/check_fs.py | 2 +- core/management/commands/compilemessages.py | 1 + core/management/commands/compilestatic.py | 3 +- core/management/commands/documentation.py | 5 +- core/management/commands/install_xapian.py | 5 +- core/management/commands/markdown.py | 1 + core/management/commands/populate.py | 33 ++++--- core/management/commands/repair_fs.py | 2 +- core/management/commands/setup.py | 3 +- core/markdown.py | 3 +- core/middleware.py | 3 +- core/migrations/0001_initial.py | 8 +- core/migrations/0003_auto_20160902_1914.py | 2 +- core/migrations/0004_user_godfathers.py | 2 +- core/migrations/0005_auto_20161105_1035.py | 4 +- core/migrations/0009_auto_20161120_1155.py | 3 +- core/migrations/0011_auto_20161124_0848.py | 3 +- core/migrations/0012_notification.py | 6 +- core/migrations/0015_sithfile_moderator.py | 4 +- core/migrations/0016_auto_20161212_1922.py | 4 +- core/migrations/0020_auto_20170324_0917.py | 2 +- core/migrations/0023_auto_20170902_1226.py | 4 +- core/migrations/0025_auto_20170919_1521.py | 2 +- core/migrations/0027_gift.py | 6 +- core/migrations/0029_auto_20180426_2013.py | 5 +- core/migrations/0034_operationlog.py | 2 +- core/models.py | 43 +++++----- core/scss/finder.py | 1 + core/scss/processor.py | 8 +- core/search_indexes.py | 1 - core/signals.py | 6 +- core/templatetags/renderer.py | 4 +- core/templatetags/search_helpers.py | 2 +- core/urls.py | 4 +- core/utils.py | 2 +- core/views/__init__.py | 27 +++--- core/views/files.py | 31 ++++--- core/views/forms.py | 39 ++++----- core/views/group.py | 13 ++- core/views/page.py | 10 +-- core/views/site.py | 23 +++-- core/views/user.py | 56 ++++++------ counter/app.py | 2 +- counter/forms.py | 10 +-- counter/migrations/0001_initial.py | 6 +- counter/migrations/0002_auto_20160826_1342.py | 7 +- .../migrations/0003_permanency_activity.py | 3 +- counter/migrations/0005_auto_20160826_2330.py | 2 +- counter/migrations/0009_eticket.py | 2 +- .../0013_customer_recorded_products.py | 6 +- counter/migrations/0015_merge.py | 2 +- counter/migrations/0017_studentcard.py | 2 +- counter/migrations/0019_billinginfo.py | 2 +- counter/migrations/0020_auto_20221215_1709.py | 3 +- counter/models.py | 42 +++++---- counter/signals.py | 4 +- counter/tests.py | 4 +- counter/views.py | 85 +++++++++---------- doc/conf.py | 1 + eboutic/forms.py | 2 +- eboutic/migrations/0001_initial.py | 7 +- eboutic/models.py | 9 +- eboutic/tests.py | 5 +- eboutic/tests/test.py | 1 + eboutic/urls.py | 2 +- eboutic/views.py | 14 +-- election/admin.py | 2 +- election/migrations/0001_initial.py | 4 +- election/models.py | 6 +- election/tests.py | 5 +- election/views.py | 29 +++---- forum/admin.py | 1 - forum/migrations/0001_initial.py | 9 +- forum/migrations/0004_auto_20170531_1949.py | 2 +- .../0005_forumtopic_subscribed_users.py | 2 +- forum/migrations/0006_auto_20180426_2013.py | 1 + forum/models.py | 14 +-- forum/tests.py | 2 - forum/views.py | 31 ++++--- .../commands/generate_galaxy_test_data.py | 11 +-- galaxy/management/commands/rule_galaxy.py | 3 +- galaxy/migrations/0001_initial.py | 2 +- galaxy/migrations/0002_auto_20230412_1130.py | 2 +- galaxy/models.py | 9 +- galaxy/tests.py | 1 - galaxy/views.py | 10 +-- launderette/migrations/0001_initial.py | 2 +- launderette/models.py | 8 +- launderette/tests.py | 2 - launderette/views.py | 27 +++--- matmat/admin.py | 2 - matmat/models.py | 2 - matmat/tests.py | 2 - matmat/views.py | 15 ++-- pedagogy/forms.py | 5 +- pedagogy/migrations/0001_initial.py | 4 +- pedagogy/models.py | 11 ++- pedagogy/search_indexes.py | 1 - pedagogy/tests.py | 5 +- pedagogy/views.py | 29 +++---- pyproject.toml | 3 + rootplace/admin.py | 2 - rootplace/models.py | 2 - rootplace/tests.py | 6 +- rootplace/views.py | 2 +- sas/admin.py | 1 - sas/migrations/0001_initial.py | 2 +- sas/migrations/0002_auto_20161119_1241.py | 4 +- sas/models.py | 14 +-- sas/tests.py | 2 - sas/views.py | 32 ++++--- sith/urls.py | 8 +- stock/admin.py | 2 +- stock/migrations/0001_initial.py | 2 +- stock/models.py | 7 +- stock/tests.py | 2 - stock/views.py | 35 +++----- subscription/migrations/0001_initial.py | 2 +- subscription/models.py | 15 ++-- subscription/tests.py | 9 +- subscription/views.py | 22 +++-- trombi/migrations/0001_initial.py | 5 +- .../migrations/0004_trombiclubmembership.py | 2 +- trombi/models.py | 14 +-- trombi/tests.py | 2 - trombi/views.py | 39 +++++---- 170 files changed, 702 insertions(+), 804 deletions(-) diff --git a/accounting/admin.py b/accounting/admin.py index e485392d..95216e59 100644 --- a/accounting/admin.py +++ b/accounting/admin.py @@ -18,7 +18,6 @@ from django.contrib import admin from accounting.models import * - admin.site.register(BankAccount) admin.site.register(ClubAccount) admin.site.register(GeneralJournal) diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py index 51add331..b5112cdd 100644 --- a/accounting/migrations/0001_initial.py +++ b/accounting/migrations/0001_initial.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.core.validators -import accounting.models import django.db.models.deletion +from django.db import migrations, models + +import accounting.models class Migration(migrations.Migration): diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py index b0cc051f..d331dd5c 100644 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ b/accounting/migrations/0002_auto_20160824_2152.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/accounting/migrations/0003_auto_20160824_2203.py b/accounting/migrations/0003_auto_20160824_2203.py index 597f582f..cf53223f 100644 --- a/accounting/migrations/0003_auto_20160824_2203.py +++ b/accounting/migrations/0003_auto_20160824_2203.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import phonenumber_field.modelfields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py index 993754bd..6e122f7d 100644 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ b/accounting/migrations/0004_auto_20161005_1505.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/accounting/models.py b/accounting/models.py index e10a029d..55bdda46 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -14,19 +14,19 @@ # # -from django.urls import reverse -from django.core.exceptions import ValidationError -from django.core import validators -from django.db import models -from django.conf import settings -from django.utils.translation import gettext_lazy as _ -from django.template import defaultfilters +from decimal import Decimal +from django.conf import settings +from django.core import validators +from django.core.exceptions import ValidationError +from django.db import models +from django.template import defaultfilters +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField -from decimal import Decimal -from core.models import User, SithFile from club.models import Club +from core.models import SithFile, User class CurrencyField(models.DecimalField): diff --git a/accounting/tests.py b/accounting/tests.py index 6a4ae2b3..28c7a420 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -14,19 +14,19 @@ # # -from django.test import TestCase -from django.urls import reverse -from django.core.management import call_command from datetime import date, timedelta -from core.models import User +from django.test import TestCase +from django.urls import reverse + from accounting.models import ( - GeneralJournal, - Operation, - Label, AccountingType, + GeneralJournal, + Label, + Operation, SimplifiedAccountingType, ) +from core.models import User class RefoundAccountTest(TestCase): diff --git a/accounting/views.py b/accounting/views.py index 32ebc1c5..f9618beb 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -14,41 +14,41 @@ # # -from django.views.generic import ListView, DetailView -from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView -from django.urls import reverse_lazy, reverse -from django.utils.translation import gettext_lazy as _ -from django.forms.models import modelform_factory -from django.core.exceptions import PermissionDenied, ValidationError -from django.forms import HiddenInput -from django.db import transaction -from django.db.models import Sum -from django.conf import settings -from django import forms -from django.http import HttpResponse import collections from ajax_select.fields import AutoCompleteSelectField +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied, ValidationError +from django.db import transaction +from django.db.models import Sum +from django.forms import HiddenInput +from django.forms.models import modelform_factory +from django.http import HttpResponse +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 CreateView, DeleteView, FormView, UpdateView -from core.views import ( - CanViewMixin, - CanEditMixin, - CanEditPropMixin, - CanCreateMixin, - TabedViewMixin, -) -from core.views.forms import SelectFile, SelectDate from accounting.models import ( + AccountingType, BankAccount, ClubAccount, - GeneralJournal, - Operation, - AccountingType, Company, - SimplifiedAccountingType, + GeneralJournal, Label, + Operation, + SimplifiedAccountingType, ) -from counter.models import Counter, Selling, Product +from core.views import ( + CanCreateMixin, + CanEditMixin, + CanEditPropMixin, + CanViewMixin, + TabedViewMixin, +) +from core.views.forms import SelectDate, SelectFile +from counter.models import Counter, Product, Selling # Main accounting view @@ -521,14 +521,14 @@ class OperationPDFView(CanViewMixin, DetailView): pk_url_kwarg = "op_id" def get(self, request, *args, **kwargs): - from reportlab.pdfgen import canvas - from reportlab.lib.units import cm - from reportlab.platypus import Table, TableStyle from reportlab.lib import colors from reportlab.lib.pagesizes import letter + from reportlab.lib.units import cm from reportlab.lib.utils import ImageReader - from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + from reportlab.pdfgen import canvas + from reportlab.platypus import Table, TableStyle pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf")) diff --git a/api/admin.py b/api/admin.py index 362a5c4f..5531f2a2 100644 --- a/api/admin.py +++ b/api/admin.py @@ -14,6 +14,4 @@ # # -from django.contrib import admin - # Register your models here. diff --git a/api/models.py b/api/models.py index 5672eba4..084dfa73 100644 --- a/api/models.py +++ b/api/models.py @@ -14,6 +14,4 @@ # # -from django.db import models - # Create your models here. diff --git a/api/tests.py b/api/tests.py index 46a200c2..d888e761 100644 --- a/api/tests.py +++ b/api/tests.py @@ -14,6 +14,4 @@ # # -from django.test import TestCase - # Create your tests here. diff --git a/api/urls.py b/api/urls.py index ca267eee..4dde736c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -14,10 +14,10 @@ # # -from django.urls import re_path, path, include +from django.urls import include, path, re_path +from rest_framework import routers from api.views import * -from rest_framework import routers # Router config router = routers.DefaultRouter() diff --git a/api/views/__init__.py b/api/views/__init__.py index ae83fbe5..b0157985 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -14,13 +14,13 @@ # # -from rest_framework.response import Response -from rest_framework import viewsets from django.core.exceptions import PermissionDenied -from rest_framework.decorators import action from django.db.models.query import QuerySet +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response -from core.views import can_view, can_edit +from core.views import can_edit, can_view def check_if(obj, user, test): @@ -64,10 +64,10 @@ class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet): from .api import * -from .counter import * -from .user import * from .club import * +from .counter import * from .group import * from .launderette import * -from .uv import * from .sas import * +from .user import * +from .uv import * diff --git a/api/views/api.py b/api/views/api.py index 732ee654..4329a98b 100644 --- a/api/views/api.py +++ b/api/views/api.py @@ -14,9 +14,9 @@ # # -from rest_framework.response import Response from rest_framework.decorators import api_view, renderer_classes from rest_framework.renderers import StaticHTMLRenderer +from rest_framework.response import Response from core.templatetags.renderer import markdown diff --git a/api/views/club.py b/api/views/club.py index 24377073..a08d6c4f 100644 --- a/api/views/club.py +++ b/api/views/club.py @@ -14,17 +14,15 @@ # # -from rest_framework.response import Response +from django.conf import settings +from django.core.exceptions import PermissionDenied from rest_framework import serializers from rest_framework.decorators import api_view, renderer_classes from rest_framework.renderers import StaticHTMLRenderer - -from django.conf import settings -from django.core.exceptions import PermissionDenied - -from club.models import Club, Mailing +from rest_framework.response import Response from api.views import RightModelViewSet +from club.models import Club, Mailing class ClubSerializer(serializers.ModelSerializer): diff --git a/api/views/counter.py b/api/views/counter.py index 604cd986..2e633cae 100644 --- a/api/views/counter.py +++ b/api/views/counter.py @@ -15,12 +15,11 @@ # from rest_framework import serializers -from rest_framework.response import Response from rest_framework.decorators import action - -from counter.models import Counter +from rest_framework.response import Response from api.views import RightModelViewSet +from counter.models import Counter class CounterSerializer(serializers.ModelSerializer): diff --git a/api/views/group.py b/api/views/group.py index f6fd7594..a6aa7e2b 100644 --- a/api/views/group.py +++ b/api/views/group.py @@ -16,9 +16,8 @@ from rest_framework import serializers -from core.models import RealGroup - from api.views import RightModelViewSet +from core.models import RealGroup class GroupSerializer(serializers.ModelSerializer): diff --git a/api/views/launderette.py b/api/views/launderette.py index ab7fcf66..a1225274 100644 --- a/api/views/launderette.py +++ b/api/views/launderette.py @@ -15,12 +15,11 @@ # from rest_framework import serializers -from rest_framework.response import Response from rest_framework.decorators import action - -from launderette.models import Launderette, Machine, Token +from rest_framework.response import Response from api.views import RightModelViewSet +from launderette.models import Launderette, Machine, Token class LaunderettePlaceSerializer(serializers.ModelSerializer): diff --git a/api/views/sas.py b/api/views/sas.py index 063b9eab..455edf09 100644 --- a/api/views/sas.py +++ b/api/views/sas.py @@ -1,4 +1,5 @@ from typing import List + from rest_framework.decorators import api_view, renderer_classes from rest_framework.exceptions import PermissionDenied from rest_framework.generics import get_object_or_404 @@ -6,8 +7,8 @@ from rest_framework.renderers import JSONRenderer from rest_framework.request import Request from rest_framework.response import Response -from core.views import can_edit from core.models import User +from core.views import can_edit from sas.models import Picture diff --git a/api/views/user.py b/api/views/user.py index ed3b6b1a..a9ad19a6 100644 --- a/api/views/user.py +++ b/api/views/user.py @@ -17,12 +17,11 @@ import datetime from rest_framework import serializers -from rest_framework.response import Response from rest_framework.decorators import action - -from core.models import User +from rest_framework.response import Response from api.views import RightModelViewSet +from core.models import User class UserSerializer(serializers.ModelSerializer): diff --git a/api/views/uv.py b/api/views/uv.py index e9790e0b..a83a8936 100644 --- a/api/views/uv.py +++ b/api/views/uv.py @@ -1,11 +1,12 @@ -from rest_framework.response import Response +import json +import urllib.request + +from django.conf import settings +from django.core.exceptions import PermissionDenied +from rest_framework import serializers from rest_framework.decorators import api_view, renderer_classes from rest_framework.renderers import JSONRenderer -from django.core.exceptions import PermissionDenied -from django.conf import settings -from rest_framework import serializers -import urllib.request -import json +from rest_framework.response import Response from pedagogy.views import CanCreateUVFunctionMixin diff --git a/club/forms.py b/club/forms.py index dcf88ff5..ca6cb324 100644 --- a/club/forms.py +++ b/club/forms.py @@ -23,18 +23,15 @@ # # -from django.conf import settings +from ajax_select.fields import AutoCompleteSelectMultipleField from django import forms +from django.conf import settings from django.utils.translation import gettext_lazy as _ -from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField - -from club.models import Mailing, MailingSubscription, Club, Membership - +from club.models import Club, Mailing, MailingSubscription, Membership from core.models import User -from core.views.forms import SelectDate, SelectDateTime +from core.views.forms import SelectDate, TzAwareDateTimeField from counter.models import Counter -from core.views.forms import TzAwareDateTimeField class ClubEditForm(forms.ModelForm): diff --git a/club/migrations/0001_initial.py b/club/migrations/0001_initial.py index 55922e03..4a26270e 100644 --- a/club/migrations/0001_initial.py +++ b/club/migrations/0001_initial.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.core.validators import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/club/migrations/0002_auto_20160824_2152.py b/club/migrations/0002_auto_20160824_2152.py index 6b69c1b7..ffc57443 100644 --- a/club/migrations/0002_auto_20160824_2152.py +++ b/club/migrations/0002_auto_20160824_2152.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/club/migrations/0004_auto_20160915_1057.py b/club/migrations/0004_auto_20160915_1057.py index 3f08d9dd..8e0dc244 100644 --- a/club/migrations/0004_auto_20160915_1057.py +++ b/club/migrations/0004_auto_20160915_1057.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/club/migrations/0005_auto_20161120_1149.py b/club/migrations/0005_auto_20161120_1149.py index 5fb949a7..b9eda617 100644 --- a/club/migrations/0005_auto_20161120_1149.py +++ b/club/migrations/0005_auto_20161120_1149.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/club/migrations/0006_auto_20161229_0040.py b/club/migrations/0006_auto_20161229_0040.py index 290ecf5b..fec86868 100644 --- a/club/migrations/0006_auto_20161229_0040.py +++ b/club/migrations/0006_auto_20161229_0040.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/club/migrations/0007_auto_20170324_0917.py b/club/migrations/0007_auto_20170324_0917.py index eb3c03cc..e356bac2 100644 --- a/club/migrations/0007_auto_20170324_0917.py +++ b/club/migrations/0007_auto_20170324_0917.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): diff --git a/club/migrations/0009_auto_20170822_2232.py b/club/migrations/0009_auto_20170822_2232.py index ce7dc337..4e679d09 100644 --- a/club/migrations/0009_auto_20170822_2232.py +++ b/club/migrations/0009_auto_20170822_2232.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import re + import django.core.validators import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/club/migrations/0010_auto_20170912_2028.py b/club/migrations/0010_auto_20170912_2028.py index a9b32d1b..0dbf796a 100644 --- a/club/migrations/0010_auto_20170912_2028.py +++ b/club/migrations/0010_auto_20170912_2028.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import django.db.models.deletion from django.db import migrations, models from club.models import Club from core.operations import PsqlRunOnly -import django.db.models.deletion def generate_club_pages(apps, schema_editor): diff --git a/club/migrations/0011_auto_20180426_2013.py b/club/migrations/0011_auto_20180426_2013.py index dc3234df..b1b1d362 100644 --- a/club/migrations/0011_auto_20180426_2013.py +++ b/club/migrations/0011_auto_20180426_2013.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -import club.models import django.db.models.deletion +from django.db import migrations, models + +import club.models class Migration(migrations.Migration): diff --git a/club/models.py b/club/models.py index 4d22b8e5..252dc085 100644 --- a/club/models.py +++ b/club/models.py @@ -24,21 +24,19 @@ # from typing import Optional -from django.core.cache import cache -from django.db import models -from django.core import validators from django.conf import settings +from django.core import validators +from django.core.cache import cache +from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.core.validators import RegexValidator, validate_email +from django.db import models, transaction from django.db.models import Q -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ -from django.core.exceptions import ValidationError, ObjectDoesNotExist -from django.db import transaction from django.urls import reverse from django.utils import timezone -from django.core.validators import RegexValidator, validate_email from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ -from core.models import User, MetaGroup, Group, SithFile, RealGroup, Notification, Page +from core.models import Group, MetaGroup, Notification, Page, RealGroup, SithFile, User # Create your models here. diff --git a/club/tests.py b/club/tests.py index b333cc48..73480979 100644 --- a/club/tests.py +++ b/club/tests.py @@ -18,15 +18,14 @@ from datetime import timedelta from django.conf import settings from django.core.cache import cache from django.test import TestCase -from django.utils import timezone, html -from django.utils.timezone import now, localtime -from django.utils.translation import gettext as _ from django.urls import reverse -from django.core.management import call_command +from django.utils import html, timezone +from django.utils.timezone import localtime, now +from django.utils.translation import gettext as _ -from core.models import User, AnonymousUser -from club.models import Club, Membership, Mailing from club.forms import MailingForm +from club.models import Club, Mailing, Membership +from core.models import AnonymousUser, User from sith.settings import SITH_BAR_MANAGER, SITH_MAIN_CLUB_ID diff --git a/club/views.py b/club/views.py index 2470cbb4..dce1c86f 100644 --- a/club/views.py +++ b/club/views.py @@ -26,50 +26,42 @@ import csv from django.conf import settings -from django import forms -from django.views.generic import ListView, DetailView, TemplateView, View -from django.views.generic.edit import DeleteView -from django.views.generic.detail import SingleObjectMixin -from django.views.generic.edit import UpdateView, CreateView +from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError +from django.core.paginator import InvalidPage, Paginator +from django.db.models import Sum from django.http import ( - HttpResponseRedirect, - HttpResponse, Http404, + HttpResponseRedirect, StreamingHttpResponse, ) +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.utils.translation import gettext as _t -from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS -from django.core.paginator import Paginator, InvalidPage -from django.shortcuts import get_object_or_404, redirect -from django.db.models import Sum +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, ListView, TemplateView, View +from django.views.generic.edit import CreateView, DeleteView, UpdateView - -from core.views import ( - CanCreateMixin, - CanViewMixin, - CanEditMixin, - CanEditPropMixin, - UserIsRootMixin, - TabedViewMixin, - PageEditViewBase, - DetailFormView, +from club.forms import ClubEditForm, ClubMemberForm, MailingForm, SellingsForm +from club.models import Club, Mailing, MailingSubscription, Membership +from com.views import ( + PosterCreateBaseView, + PosterDeleteBaseView, + PosterEditBaseView, + PosterListBaseView, ) from core.models import PageRev - -from counter.models import Selling - -from com.views import ( - PosterListBaseView, - PosterCreateBaseView, - PosterEditBaseView, - PosterDeleteBaseView, +from core.views import ( + CanCreateMixin, + CanEditMixin, + CanEditPropMixin, + CanViewMixin, + DetailFormView, + PageEditViewBase, + TabedViewMixin, + UserIsRootMixin, ) - -from club.models import Club, Membership, Mailing, MailingSubscription -from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm +from counter.models import Selling class ClubTabsMixin(TabedViewMixin): diff --git a/com/migrations/0002_news_newsdate.py b/com/migrations/0002_news_newsdate.py index da548fdc..51a378f3 100644 --- a/com/migrations/0002_news_newsdate.py +++ b/com/migrations/0002_news_newsdate.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/com/migrations/0003_auto_20170115_2300.py b/com/migrations/0003_auto_20170115_2300.py index 2087f40f..a21196b3 100644 --- a/com/migrations/0003_auto_20170115_2300.py +++ b/com/migrations/0003_auto_20170115_2300.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/com/migrations/0004_auto_20171221_1614.py b/com/migrations/0004_auto_20171221_1614.py index fe9539dc..6b4d4d9d 100644 --- a/com/migrations/0004_auto_20171221_1614.py +++ b/com/migrations/0004_auto_20171221_1614.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models +import django.db.models.deletion import django.utils.timezone from django.conf import settings -import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/com/models.py b/com/models.py index 71da8941..d7a31596 100644 --- a/com/models.py +++ b/com/models.py @@ -23,22 +23,20 @@ # # -from django.shortcuts import render +from django.conf import settings +from django.core.exceptions import ValidationError +from django.core.mail import EmailMultiAlternatives from django.db import models, transaction from django.db.models import Q -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from django.urls import reverse -from django.conf import settings +from django.shortcuts import render from django.templatetags.static import static -from django.core.mail import EmailMultiAlternatives -from django.core.exceptions import ValidationError - +from django.urls import reverse from django.utils import timezone +from django.utils.translation import gettext_lazy as _ -from core import utils -from core.models import User, Preferences, RealGroup, Notification, SithFile from club.models import Club +from core import utils +from core.models import Notification, Preferences, RealGroup, User class Sith(models.Model): diff --git a/com/tests.py b/com/tests.py index 6dde46db..0cd01a1d 100644 --- a/com/tests.py +++ b/com/tests.py @@ -13,18 +13,17 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase -from django.conf import settings from django.urls import reverse -from django.core.management import call_command from django.utils import html from django.utils.timezone import localtime, now from django.utils.translation import gettext as _ from club.models import Club, Membership -from com.models import Sith, News, Weekmail, WeekmailArticle, Poster -from core.models import User, RealGroup, AnonymousUser +from com.models import News, Poster, Sith, Weekmail, WeekmailArticle +from core.models import AnonymousUser, RealGroup, User class ComAlertTest(TestCase): diff --git a/com/views.py b/com/views.py index f8d0119b..774d1c5f 100644 --- a/com/views.py +++ b/com/views.py @@ -23,38 +23,35 @@ # # -from django.shortcuts import redirect, get_object_or_404 -from django.http import HttpResponseRedirect -from django.views.generic import ListView, DetailView, View -from django.views.generic.edit import UpdateView, CreateView, DeleteView -from django.views.generic.detail import SingleObjectMixin -from django.utils.translation import gettext_lazy as _ -from django.urls import reverse, reverse_lazy -from django.core.exceptions import ValidationError -from django.utils import timezone -from django.conf import settings -from django.db.models import Max -from django.forms.models import modelform_factory -from django.core.exceptions import PermissionDenied -from django import forms - from datetime import timedelta from smtplib import SMTPRecipientsRefused -from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied, ValidationError +from django.db.models import Max +from django.forms.models import modelform_factory +from django.http import HttpResponseRedirect +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.generic import DetailView, ListView, View +from django.views.generic.detail import SingleObjectMixin +from django.views.generic.edit import CreateView, DeleteView, UpdateView + +from club.models import Club, Mailing +from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle +from core.models import Notification, RealGroup, User from core.views import ( - CanViewMixin, + CanCreateMixin, CanEditMixin, CanEditPropMixin, - TabedViewMixin, - CanCreateMixin, + CanViewMixin, QuickNotifMixin, + TabedViewMixin, ) -from core.views.forms import SelectDateTime, MarkdownInput -from core.models import Notification, RealGroup, User -from club.models import Club, Mailing -from core.views.forms import TzAwareDateTimeField - +from core.views.forms import MarkdownInput, TzAwareDateTimeField # Sith object diff --git a/core/admin.py b/core/admin.py index 33ce50e4..8b202a30 100644 --- a/core/admin.py +++ b/core/admin.py @@ -14,12 +14,12 @@ # # -from django.contrib import admin from ajax_select import make_ajax_form -from core.models import User, Page, RealGroup, MetaGroup, SithFile +from django.contrib import admin from django.contrib.auth.models import Group as AuthGroup from haystack.admin import SearchModelAdmin +from core.models import MetaGroup, Page, RealGroup, SithFile, User admin.site.unregister(AuthGroup) admin.site.register(MetaGroup) diff --git a/core/apps.py b/core/apps.py index cd131f57..872f34ae 100644 --- a/core/apps.py +++ b/core/apps.py @@ -34,8 +34,8 @@ class SithConfig(AppConfig): verbose_name = "Core app of the Sith" def ready(self): + import core.signals # noqa F401 from forum.models import Forum - import core.signals cache.clear() diff --git a/core/converters.py b/core/converters.py index cb7bc95b..b681564d 100644 --- a/core/converters.py +++ b/core/converters.py @@ -1,6 +1,3 @@ -from core.models import Page - - class FourDigitYearConverter: regex = "[0-9]{4}" diff --git a/core/lookups.py b/core/lookups.py index f245442b..15205194 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -14,15 +14,14 @@ # # +from ajax_select import LookupChannel, register from django.core.exceptions import PermissionDenied -from ajax_select import register, LookupChannel -from core.views.site import search_user -from core.models import User, Group, SithFile -from club.models import Club -from counter.models import Product, Counter, Customer from accounting.models import ClubAccount, Company -from eboutic.models import BasketItem +from club.models import Club +from core.models import Group, SithFile, User +from core.views.site import search_user +from counter.models import Counter, Customer, Product def check_token(request): diff --git a/core/management/commands/check_fs.py b/core/management/commands/check_fs.py index 7ebc076d..a6fc9597 100644 --- a/core/management/commands/check_fs.py +++ b/core/management/commands/check_fs.py @@ -23,8 +23,8 @@ # import os + from django.core.management.base import BaseCommand -from django.core.management import call_command from core.models import SithFile diff --git a/core/management/commands/compilemessages.py b/core/management/commands/compilemessages.py index b3c336bc..87f1b2de 100644 --- a/core/management/commands/compilemessages.py +++ b/core/management/commands/compilemessages.py @@ -25,6 +25,7 @@ import os + from django.core.management.commands import compilemessages diff --git a/core/management/commands/compilestatic.py b/core/management/commands/compilestatic.py index 31e5c13e..f1268c23 100644 --- a/core/management/commands/compilestatic.py +++ b/core/management/commands/compilestatic.py @@ -24,9 +24,10 @@ # import os + import sass -from django.core.management.base import BaseCommand from django.conf import settings +from django.core.management.base import BaseCommand class Command(BaseCommand): diff --git a/core/management/commands/documentation.py b/core/management/commands/documentation.py index 79f2e0d8..bcaa0664 100644 --- a/core/management/commands/documentation.py +++ b/core/management/commands/documentation.py @@ -24,10 +24,9 @@ # import os -import sys import signal - -from http.server import test, CGIHTTPRequestHandler +import sys +from http.server import CGIHTTPRequestHandler, test from django.core.management.base import BaseCommand from django.utils import autoreload diff --git a/core/management/commands/install_xapian.py b/core/management/commands/install_xapian.py index b2009cd4..9181c151 100644 --- a/core/management/commands/install_xapian.py +++ b/core/management/commands/install_xapian.py @@ -15,11 +15,12 @@ # import os -import tomli import subprocess -from django.core.management.base import BaseCommand, CommandParser from pathlib import Path +import tomli +from django.core.management.base import BaseCommand, CommandParser + class Command(BaseCommand): help = "Install xapian" diff --git a/core/management/commands/markdown.py b/core/management/commands/markdown.py index 1b5a6855..35941ec4 100644 --- a/core/management/commands/markdown.py +++ b/core/management/commands/markdown.py @@ -23,6 +23,7 @@ # import os + from django.core.management.base import BaseCommand from core.markdown import markdown diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index c78de401..a9285653 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -24,38 +24,37 @@ import os from datetime import date, datetime, timedelta -from io import StringIO, BytesIO +from io import BytesIO, StringIO from pathlib import Path -from django.contrib.auth.models import Permission -from django.core.management.base import BaseCommand -from django.core.management import call_command from django.conf import settings -from django.db import connection +from django.contrib.auth.models import Permission from django.contrib.sites.models import Site +from django.core.management import call_command +from django.core.management.base import BaseCommand +from django.db import connection from django.utils import timezone - from PIL import Image -from core.models import Group, User, Page, PageRev, SithFile from accounting.models import ( - GeneralJournal, + AccountingType, BankAccount, ClubAccount, - Operation, - AccountingType, - SimplifiedAccountingType, Company, + GeneralJournal, + Operation, + SimplifiedAccountingType, ) -from core.utils import resize_image from club.models import Club, Membership -from subscription.models import Subscription -from counter.models import Customer, ProductType, Product, Counter, Selling, StudentCard -from com.models import Sith, Weekmail, News, NewsDate -from election.models import Election, Role, Candidature, ElectionList +from com.models import News, NewsDate, Sith, Weekmail +from core.models import Group, Page, PageRev, SithFile, User +from core.utils import resize_image +from counter.models import Counter, Customer, Product, ProductType, Selling, StudentCard +from election.models import Candidature, Election, ElectionList, Role from forum.models import Forum, ForumTopic from pedagogy.models import UV -from sas.models import Album, Picture, PeoplePictureRelation +from sas.models import Album, PeoplePictureRelation, Picture +from subscription.models import Subscription class Command(BaseCommand): diff --git a/core/management/commands/repair_fs.py b/core/management/commands/repair_fs.py index 055dbc9b..21408084 100644 --- a/core/management/commands/repair_fs.py +++ b/core/management/commands/repair_fs.py @@ -23,8 +23,8 @@ # import os + from django.core.management.base import BaseCommand -from django.core.management import call_command from core.models import SithFile diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index cc0ee1ca..5c91e1e6 100644 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -15,8 +15,9 @@ # import os -from django.core.management.base import BaseCommand + from django.core.management import call_command +from django.core.management.base import BaseCommand class Command(BaseCommand): diff --git a/core/markdown.py b/core/markdown.py index 72b1cd02..0abe1954 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -16,8 +16,9 @@ import os import re -from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link + from django.urls import reverse +from mistune import InlineGrammar, InlineLexer, Markdown, Renderer, escape, escape_link class SithRenderer(Renderer): diff --git a/core/middleware.py b/core/middleware.py index 39afa266..ddc6dea3 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -16,12 +16,13 @@ import importlib import threading + from django.conf import settings -from django.utils.functional import SimpleLazyObject from django.contrib.auth import get_user from django.contrib.auth.middleware import ( AuthenticationMiddleware as DjangoAuthenticationMiddleware, ) +from django.utils.functional import SimpleLazyObject module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) AnonymousUser = getattr(importlib.import_module(module), klass) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 7474e93a..57050f26 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.contrib.auth.models -import django.db.models.deletion import django.core.validators -import core.models +import django.db.models.deletion import phonenumber_field.modelfields from django.conf import settings -import django.db.models.deletion +from django.db import migrations, models + +import core.models class Migration(migrations.Migration): diff --git a/core/migrations/0003_auto_20160902_1914.py b/core/migrations/0003_auto_20160902_1914.py index b39d9838..65f11d3a 100644 --- a/core/migrations/0003_auto_20160902_1914.py +++ b/core/migrations/0003_auto_20160902_1914.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.core.validators +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0004_user_godfathers.py b/core/migrations/0004_user_godfathers.py index d068cfc7..d4066cc5 100644 --- a/core/migrations/0004_user_godfathers.py +++ b/core/migrations/0004_user_godfathers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0005_auto_20161105_1035.py b/core/migrations/0005_auto_20161105_1035.py index 7d40b163..6f7c487f 100644 --- a/core/migrations/0005_auto_20161105_1035.py +++ b/core/migrations/0005_auto_20161105_1035.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0009_auto_20161120_1155.py b/core/migrations/0009_auto_20161120_1155.py index c017706a..aafb2c54 100644 --- a/core/migrations/0009_auto_20161120_1155.py +++ b/core/migrations/0009_auto_20161120_1155.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models + import core.models diff --git a/core/migrations/0011_auto_20161124_0848.py b/core/migrations/0011_auto_20161124_0848.py index b3ea7f4a..6475189e 100644 --- a/core/migrations/0011_auto_20161124_0848.py +++ b/core/migrations/0011_auto_20161124_0848.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models + import core.models diff --git a/core/migrations/0012_notification.py b/core/migrations/0012_notification.py index 360fbcb8..245a38e3 100644 --- a/core/migrations/0012_notification.py +++ b/core/migrations/0012_notification.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings -import django.utils.timezone import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0015_sithfile_moderator.py b/core/migrations/0015_sithfile_moderator.py index 20d1512f..4e2a438d 100644 --- a/core/migrations/0015_sithfile_moderator.py +++ b/core/migrations/0015_sithfile_moderator.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0016_auto_20161212_1922.py b/core/migrations/0016_auto_20161212_1922.py index bde89b82..72432467 100644 --- a/core/migrations/0016_auto_20161212_1922.py +++ b/core/migrations/0016_auto_20161212_1922.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0020_auto_20170324_0917.py b/core/migrations/0020_auto_20170324_0917.py index c026483a..219be979 100644 --- a/core/migrations/0020_auto_20170324_0917.py +++ b/core/migrations/0020_auto_20170324_0917.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.core.validators +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0023_auto_20170902_1226.py b/core/migrations/0023_auto_20170902_1226.py index 2cdc4c85..9bfd01ed 100644 --- a/core/migrations/0023_auto_20170902_1226.py +++ b/core/migrations/0023_auto_20170902_1226.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0025_auto_20170919_1521.py b/core/migrations/0025_auto_20170919_1521.py index f37a829b..f7bd278c 100644 --- a/core/migrations/0025_auto_20170919_1521.py +++ b/core/migrations/0025_auto_20170919_1521.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.core.validators +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0027_gift.py b/core/migrations/0027_gift.py index 21bf8442..ca8ce67b 100644 --- a/core/migrations/0027_gift.py +++ b/core/migrations/0027_gift.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings -import django.utils.timezone import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0029_auto_20180426_2013.py b/core/migrations/0029_auto_20180426_2013.py index eadfd558..c591f47b 100644 --- a/core/migrations/0029_auto_20180426_2013.py +++ b/core/migrations/0029_auto_20180426_2013.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -import core.models import django.db.models.deletion +from django.db import migrations, models + +import core.models class Migration(migrations.Migration): diff --git a/core/migrations/0034_operationlog.py b/core/migrations/0034_operationlog.py index df59cd6a..505e2332 100644 --- a/core/migrations/0034_operationlog.py +++ b/core/migrations/0034_operationlog.py @@ -1,8 +1,8 @@ # Generated by Django 2.2.6 on 2019-11-14 15:10 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/core/models.py b/core/models.py index c804d30b..4fba43c3 100644 --- a/core/models.py +++ b/core/models.py @@ -23,36 +23,39 @@ # # import importlib -from typing import Union, Optional, List +import os +import unicodedata +from datetime import date, timedelta +from typing import List, Optional, Union -from django.core.cache import cache -from django.core.mail import send_mail +from django.conf import settings from django.contrib.auth.models import ( AbstractBaseUser, UserManager, - Group as AuthGroup, - GroupManager as AuthGroupManager, +) +from django.contrib.auth.models import ( AnonymousUser as AuthAnonymousUser, ) -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from django.core import validators -from django.core.exceptions import ValidationError, PermissionDenied -from django.urls import reverse -from django.conf import settings -from django.db import models, transaction +from django.contrib.auth.models import ( + Group as AuthGroup, +) +from django.contrib.auth.models import ( + GroupManager as AuthGroupManager, +) from django.contrib.staticfiles.storage import staticfiles_storage -from django.utils.html import escape +from django.core import validators +from django.core.cache import cache +from django.core.exceptions import PermissionDenied, ValidationError +from django.core.mail import send_mail +from django.db import models, transaction +from django.urls import reverse +from django.utils import timezone from django.utils.functional import cached_property - -import os -from core import utils - +from django.utils.html import escape +from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField -from datetime import timedelta, date - -import unicodedata +from core import utils class RealGroupManager(AuthGroupManager): diff --git a/core/scss/finder.py b/core/scss/finder.py index 3e25279f..0b62fab3 100644 --- a/core/scss/finder.py +++ b/core/scss/finder.py @@ -25,6 +25,7 @@ import os from collections import OrderedDict + from django.conf import settings from django.contrib.staticfiles.finders import FileSystemFinder from django.core.files.storage import FileSystemStorage diff --git a/core/scss/processor.py b/core/scss/processor.py index 6aa334f2..add5e042 100644 --- a/core/scss/processor.py +++ b/core/scss/processor.py @@ -24,12 +24,14 @@ # import os -import sass from urllib.parse import urljoin -from django.utils.encoding import force_bytes, iri_to_uri + +import sass +from django.conf import settings from django.core.files.base import ContentFile from django.templatetags.static import static -from django.conf import settings +from django.utils.encoding import force_bytes, iri_to_uri + from core.scss.storage import ScssFileStorage, find_file diff --git a/core/search_indexes.py b/core/search_indexes.py index b98dc67b..f2448adb 100644 --- a/core/search_indexes.py +++ b/core/search_indexes.py @@ -24,7 +24,6 @@ # from django.db import models - from haystack import indexes, signals from core.models import User diff --git a/core/signals.py b/core/signals.py index 7cda36c7..23e38599 100644 --- a/core/signals.py +++ b/core/signals.py @@ -8,10 +8,10 @@ from core.models import User @receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed") def user_groups_changed(sender, instance: User, **kwargs): """ - Clear the cached clubs of the user + Clear the cached groups of the user """ # As a m2m relationship doesn't live within the model # but rather on an intermediary table, there is no # model method to override, meaning we must use - # a signal to invalidate the cache when a user is removed from a club - cache.delete(f"user_{instance.id}_groups") + # a signal to invalidate the cache when a user is removed from a group + cache.delete(f"user_{instance.pk}_groups") diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 2ee19a45..86bd6791 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -24,15 +24,15 @@ # import datetime -import phonenumbers +import phonenumbers from django import template from django.template.defaultfilters import stringfilter from django.utils.safestring import mark_safe from django.utils.translation import ngettext -from core.scss.processor import ScssProcessor from core.markdown import markdown as md +from core.scss.processor import ScssProcessor register = template.Library() diff --git a/core/templatetags/search_helpers.py b/core/templatetags/search_helpers.py index 537ff357..d38574df 100644 --- a/core/templatetags/search_helpers.py +++ b/core/templatetags/search_helpers.py @@ -1,6 +1,6 @@ -from django.template.exceptions import TemplateSyntaxError from django import template from django.template.defaultfilters import stringfilter +from django.template.exceptions import TemplateSyntaxError register = template.Library() diff --git a/core/urls.py b/core/urls.py index ec42f880..3f6398e8 100644 --- a/core/urls.py +++ b/core/urls.py @@ -25,12 +25,12 @@ from django.urls import path, re_path, register_converter -from core.views import * from core.converters import ( + BooleanStringConverter, FourDigitYearConverter, TwoDigitMonthConverter, - BooleanStringConverter, ) +from core.views import * register_converter(FourDigitYearConverter, "yyyy") register_converter(TwoDigitMonthConverter, "mm") diff --git a/core/utils.py b/core/utils.py index d30e3ebf..62cf04bb 100644 --- a/core/utils.py +++ b/core/utils.py @@ -26,8 +26,8 @@ from typing import Optional import PIL from django.conf import settings from django.core.files.base import ContentFile -from PIL import ExifTags from django.utils import timezone +from PIL import ExifTags def get_git_revision_short_hash() -> str: diff --git a/core/views/__init__.py b/core/views/__init__.py index a36147b4..ad86fe2d 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -25,26 +25,21 @@ import types -from sentry_sdk import last_event_id -from django.shortcuts import render +from django.core.exceptions import ( + ImproperlyConfigured, + PermissionDenied, +) from django.http import ( HttpResponseForbidden, HttpResponseNotFound, HttpResponseServerError, ) -from django.template import RequestContext -from django.core.exceptions import ( - PermissionDenied, - ObjectDoesNotExist, - ImproperlyConfigured, -) -from django.views.generic.base import View -from django.views.generic.edit import FormView -from django.views.generic.detail import SingleObjectMixin from django.utils.functional import cached_property -from django.db.models import Count +from django.views.generic.base import View +from django.views.generic.detail import SingleObjectMixin +from django.views.generic.edit import FormView +from sentry_sdk import last_event_id -from core.models import Group from core.views.forms import LoginForm @@ -361,8 +356,8 @@ class DetailFormView(SingleObjectMixin, FormView): return super(DetailFormView, self).get_object() -from .user import * -from .page import * from .files import * -from .site import * from .group import * +from .page import * +from .site import * +from .user import * diff --git a/core/views/files.py b/core/views/files.py index 61d3aec9..7cce06e7 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -15,29 +15,28 @@ # # This file contains all the views that concern the page model -from django.shortcuts import redirect, get_object_or_404 -from django.utils.http import http_date -from django.views.generic import ListView, DetailView, TemplateView -from django.views.generic.edit import UpdateView, FormMixin, DeleteView -from django.views.generic.detail import SingleObjectMixin -from django.forms.models import modelform_factory -from django.conf import settings -from django.utils.translation import gettext_lazy as _ -from django.http import Http404, HttpResponse -from wsgiref.util import FileWrapper -from django.urls import reverse -from django.core.exceptions import PermissionDenied -from django import forms - import os +from wsgiref.util import FileWrapper from ajax_select import make_ajax_field +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.forms.models import modelform_factory +from django.http import Http404, HttpResponse +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse +from django.utils.http import http_date +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, ListView, TemplateView +from django.views.generic.detail import SingleObjectMixin +from django.views.generic.edit import DeleteView, FormMixin, UpdateView -from core.models import SithFile, RealGroup, Notification +from core.models import Notification, RealGroup, SithFile from core.views import ( - CanViewMixin, CanEditMixin, CanEditPropMixin, + CanViewMixin, can_view, ) from counter.models import Counter diff --git a/core/views/forms.py b/core/views/forms.py index 936abb26..5426ef14 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -21,40 +21,37 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +import datetime +import re +from io import BytesIO + +from ajax_select import make_ajax_field +from ajax_select.fields import AutoCompleteSelectField from captcha.fields import CaptchaField -from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django import forms from django.conf import settings -from django.db import transaction -from django.templatetags.static import static -from django.urls import reverse +from django.contrib.auth.forms import AuthenticationForm, UserCreationForm from django.core.exceptions import ValidationError +from django.db import transaction from django.forms import ( CheckboxSelectMultiple, - Select, DateInput, - TextInput, DateTimeInput, Textarea, + TextInput, ) -from django.utils.translation import gettext_lazy as _ -from django.utils.translation import gettext -from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget -from ajax_select.fields import AutoCompleteSelectField -from ajax_select import make_ajax_field -from django.utils.dateparse import parse_datetime -from django.utils import timezone -import datetime from django.forms.utils import to_current_timezone - -import re - -from core.models import User, Page, SithFile, Gift - -from core.utils import resize_image -from io import BytesIO +from django.templatetags.static import static +from django.urls import reverse +from django.utils import timezone +from django.utils.dateparse import parse_datetime +from django.utils.translation import gettext +from django.utils.translation import gettext_lazy as _ +from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget from PIL import Image +from core.models import Gift, Page, SithFile, User +from core.utils import resize_image # Widgets diff --git a/core/views/group.py b/core/views/group.py index 419d699d..1c598832 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -18,15 +18,12 @@ This module contains views to manage Groups """ -from django.views.generic.edit import UpdateView, CreateView, DeleteView -from django.views.generic import ListView -from django.views.generic.edit import FormView -from django.urls import reverse_lazy -from django.shortcuts import get_object_or_404 -from django.utils.translation import gettext_lazy as _ -from django import forms - from ajax_select.fields import AutoCompleteSelectMultipleField +from django import forms +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import ListView +from django.views.generic.edit import CreateView, DeleteView, UpdateView from core.models import RealGroup, User from core.views import CanCreateMixin, CanEditMixin, DetailFormView diff --git a/core/views/page.py b/core/views/page.py index c2c7dbce..5f148235 100644 --- a/core/views/page.py +++ b/core/views/page.py @@ -15,16 +15,16 @@ # # This file contains all the views that concern the page model -from django.urls import reverse_lazy -from django.views.generic import ListView, DetailView -from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.forms.models import modelform_factory from django.http import Http404 from django.shortcuts import redirect +from django.urls import reverse_lazy +from django.views.generic import DetailView, ListView +from django.views.generic.edit import CreateView, DeleteView, UpdateView -from core.models import Page, PageRev, LockError +from core.models import LockError, Page, PageRev +from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin from core.views.forms import MarkdownInput, PageForm, PagePropForm -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin class CanEditPagePropMixin(CanEditPropMixin): diff --git a/core/views/site.py b/core/views/site.py index c7163488..bdd575f4 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -23,23 +23,22 @@ # # -from django.shortcuts import render, redirect -from django.http import JsonResponse -from django.core import serializers -from django.contrib.auth.decorators import login_required -from django.utils import html -from django.views.generic import ListView, TemplateView -from django.conf import settings -from django.utils.text import slugify -from django.db.models.query import QuerySet - import json +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.core import serializers +from django.db.models.query import QuerySet +from django.http import JsonResponse +from django.shortcuts import redirect, render +from django.utils import html +from django.utils.text import slugify +from django.views.generic import ListView, TemplateView from haystack.query import SearchQuerySet -from core.models import User, Notification -from core.utils import doku_to_markdown, bbcode_to_markdown from club.models import Club +from core.models import Notification, User +from core.utils import bbcode_to_markdown, doku_to_markdown def index(request, context=None): diff --git a/core/views/user.py b/core/views/user.py index dbd60f13..d8d8d909 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -24,50 +24,49 @@ # # This file contains all the views that concern the user model -from django.shortcuts import render, redirect, get_object_or_404 +import logging +from datetime import date, timedelta + +from django.conf import settings from django.contrib.auth import views from django.contrib.auth.forms import PasswordChangeForm -from django.utils.translation import gettext as _ -from django.urls import reverse from django.core.exceptions import PermissionDenied, ValidationError +from django.forms import CheckboxSelectMultiple +from django.forms.models import modelform_factory from django.http import Http404, HttpResponse -from django.views.generic.edit import UpdateView +from django.shortcuts import get_object_or_404, redirect, render +from django.template.response import TemplateResponse +from django.urls import reverse, reverse_lazy +from django.utils.translation import gettext as _ from django.views.generic import ( - ListView, - DetailView, - TemplateView, CreateView, DeleteView, + DetailView, + ListView, + TemplateView, ) -from django.forms.models import modelform_factory -from django.forms import CheckboxSelectMultiple -from django.urls import reverse_lazy -from django.template.response import TemplateResponse -from django.conf import settings -from django.views.generic.dates import YearMixin, MonthMixin +from django.views.generic.dates import MonthMixin, YearMixin +from django.views.generic.edit import UpdateView -from datetime import timedelta, date -import logging from api.views.sas import all_pictures_of_user - +from core.models import Gift, Preferences, SithFile, User from core.views import ( - CanViewMixin, CanEditMixin, CanEditPropMixin, - UserIsLoggedMixin, - TabedViewMixin, + CanViewMixin, QuickNotifMixin, + TabedViewMixin, + UserIsLoggedMixin, ) from core.views.forms import ( - RegisteringForm, - UserProfileForm, - LoginForm, - UserGodfathersForm, GiftForm, + LoginForm, + RegisteringForm, + UserGodfathersForm, + UserProfileForm, ) -from core.models import User, SithFile, Preferences, Gift -from subscription.models import Subscription from counter.forms import StudentCardForm +from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -501,9 +500,10 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): kwargs = super(UserStatsView, self).get_context_data(**kwargs) - from counter.models import Counter from django.db.models import Sum + from counter.models import Counter + foyer = Counter.objects.filter(name="Foyer").first() mde = Counter.objects.filter(name="MDE").first() gommette = Counter.objects.filter(name="La Gommette").first() @@ -601,10 +601,12 @@ class UserUploadProfilePictView(CanEditMixin, DetailView): template_name = "core/user_edit.jinja" def post(self, request, *args, **kwargs): - from core.utils import resize_image from io import BytesIO + from PIL import Image + from core.utils import resize_image + self.object = self.get_object() if self.object.profile_pict: raise ValidationError(_("User already has a profile picture")) diff --git a/counter/app.py b/counter/app.py index c1feefe2..a1165d7c 100644 --- a/counter/app.py +++ b/counter/app.py @@ -31,4 +31,4 @@ class CounterConfig(AppConfig): verbose_name = _("counter") def ready(self): - import counter.signals + import counter.signals # noqa F401 diff --git a/counter/forms.py b/counter/forms.py index a09236e7..7c282f57 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -3,15 +3,15 @@ from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultip from django import forms from django.utils.translation import gettext_lazy as _ -from core.views.forms import TzAwareDateTimeField, SelectDate +from core.views.forms import SelectDate, TzAwareDateTimeField from counter.models import ( BillingInfo, - StudentCard, - Customer, - Refilling, Counter, - Product, + Customer, Eticket, + Product, + Refilling, + StudentCard, ) diff --git a/counter/migrations/0001_initial.py b/counter/migrations/0001_initial.py index e9635e5d..aa84e936 100644 --- a/counter/migrations/0001_initial.py +++ b/counter/migrations/0001_initial.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -import accounting.models import django.db.models.deletion from django.conf import settings -import django.db.models.deletion +from django.db import migrations, models + +import accounting.models class Migration(migrations.Migration): diff --git a/counter/migrations/0002_auto_20160826_1342.py b/counter/migrations/0002_auto_20160826_1342.py index ccc4288c..83e6a83a 100644 --- a/counter/migrations/0002_auto_20160826_1342.py +++ b/counter/migrations/0002_auto_20160826_1342.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings -import accounting.models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +import accounting.models class Migration(migrations.Migration): diff --git a/counter/migrations/0003_permanency_activity.py b/counter/migrations/0003_permanency_activity.py index be4e9962..929d8308 100644 --- a/counter/migrations/0003_permanency_activity.py +++ b/counter/migrations/0003_permanency_activity.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import datetime + +from django.db import migrations, models from django.utils.timezone import utc diff --git a/counter/migrations/0005_auto_20160826_2330.py b/counter/migrations/0005_auto_20160826_2330.py index 8e0df744..9dc3fad9 100644 --- a/counter/migrations/0005_auto_20160826_2330.py +++ b/counter/migrations/0005_auto_20160826_2330.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/counter/migrations/0009_eticket.py b/counter/migrations/0009_eticket.py index 68b675ef..96d82e30 100644 --- a/counter/migrations/0009_eticket.py +++ b/counter/migrations/0009_eticket.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/counter/migrations/0013_customer_recorded_products.py b/counter/migrations/0013_customer_recorded_products.py index d6b3cfa8..271491eb 100644 --- a/counter/migrations/0013_customer_recorded_products.py +++ b/counter/migrations/0013_customer_recorded_products.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.utils.translation import gettext_lazy as _ -from django.db import migrations, models from django.conf import settings +from django.db import migrations, models +from django.utils.translation import gettext_lazy as _ from core.models import User -from counter.models import Customer, Product, Selling, Counter +from counter.models import Counter, Customer, Product, Selling def balance_ecocups(apps, schema_editor): diff --git a/counter/migrations/0015_merge.py b/counter/migrations/0015_merge.py index 6dcff021..eba94807 100644 --- a/counter/migrations/0015_merge.py +++ b/counter/migrations/0015_merge.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): diff --git a/counter/migrations/0017_studentcard.py b/counter/migrations/0017_studentcard.py index a2f62222..267cef25 100644 --- a/counter/migrations/0017_studentcard.py +++ b/counter/migrations/0017_studentcard.py @@ -3,8 +3,8 @@ from __future__ import unicode_literals import django.core.validators -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/counter/migrations/0019_billinginfo.py b/counter/migrations/0019_billinginfo.py index c4c74c39..b348c86a 100644 --- a/counter/migrations/0019_billinginfo.py +++ b/counter/migrations/0019_billinginfo.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.16 on 2023-01-08 12:49 -from django.db import migrations, models import django.db.models.deletion import django_countries.fields +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/counter/migrations/0020_auto_20221215_1709.py b/counter/migrations/0020_auto_20221215_1709.py index 52010133..22db406b 100644 --- a/counter/migrations/0020_auto_20221215_1709.py +++ b/counter/migrations/0020_auto_20221215_1709.py @@ -1,8 +1,9 @@ # Generated by Django 3.2.16 on 2022-12-15 16:09 -import accounting.models from django.db import migrations +import accounting.models + class Migration(migrations.Migration): dependencies = [ diff --git a/counter/models.py b/counter/models.py index 476aaf13..c9972f10 100644 --- a/counter/models.py +++ b/counter/models.py @@ -15,35 +15,33 @@ # from __future__ import annotations -from typing import Tuple, Optional - -from django.db import models -from django.db.models import F, Value, Sum, QuerySet, OuterRef, Exists -from django.db.models.functions import Concat, Length -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from django.conf import settings -from django.urls import reverse -from django.core.validators import MinLengthValidator -from django.forms import ValidationError -from django.utils.functional import cached_property - -from datetime import timedelta, date, datetime +import base64 +import os import random import string -import os -import base64 -from dict2xml import dict2xml +from datetime import date, datetime, timedelta +from typing import Optional, Tuple +from dict2xml import dict2xml +from django.conf import settings +from django.core.validators import MinLengthValidator +from django.db import models +from django.db.models import Exists, F, OuterRef, QuerySet, Sum, Value +from django.db.models.functions import Concat, Length +from django.forms import ValidationError +from django.urls import reverse +from django.utils import timezone +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ +from django_countries.fields import CountryField + +from accounting.models import CurrencyField +from club.models import Club +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 club.models import Club, Membership -from accounting.models import CurrencyField -from core.models import Group, User, Notification from subscription.models import Subscription -from django_countries.fields import CountryField - class Customer(models.Model): """ diff --git a/counter/signals.py b/counter/signals.py index 9c1f4b78..9221494d 100644 --- a/counter/signals.py +++ b/counter/signals.py @@ -24,12 +24,10 @@ from django.db.models.signals import pre_delete from django.dispatch import receiver -from django.conf import settings from core.middleware import get_signal_request from core.models import OperationLog - -from counter.models import Selling, Refilling, Counter +from counter.models import Counter, Refilling, Selling def write_log(instance, operation_type): diff --git a/counter/tests.py b/counter/tests.py index 6079099a..f921a5d4 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -13,20 +13,18 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # -from datetime import date, timedelta import json import re import string from django.test import TestCase from django.urls import reverse -from django.core.management import call_command from django.utils import timezone from django.utils.timezone import timedelta from club.models import Club from core.models import User -from counter.models import Counter, Customer, BillingInfo, Permanency, Selling, Product +from counter.models import BillingInfo, Counter, Customer, Permanency, Product, Selling from sith.settings import SITH_MAIN_CLUB diff --git a/counter/views.py b/counter/views.py index 4c0b167b..fe10d0a1 100644 --- a/counter/views.py +++ b/counter/views.py @@ -14,68 +14,65 @@ # # import json +import re +from datetime import datetime, timedelta +from http import HTTPStatus from urllib.parse import parse_qs +import pytz +from django import forms +from django.conf import settings from django.contrib.auth.decorators import login_required -from django.db.models import F -from django.shortcuts import get_object_or_404 -from django.http import Http404 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, HttpResponse, HttpResponseRedirect, JsonResponse +from django.shortcuts import get_object_or_404 +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 ListView, DetailView, RedirectView, TemplateView +from django.views.generic import DetailView, ListView, RedirectView, TemplateView from django.views.generic.base import View from django.views.generic.edit import ( - UpdateView, CreateView, DeleteView, - ProcessFormView, FormMixin, FormView, + ProcessFormView, + UpdateView, ) -from django.forms.models import modelform_factory -from django.forms import CheckboxSelectMultiple -from django.urls import reverse_lazy, reverse -from django.http import HttpResponseRedirect, HttpResponse, JsonResponse -from django.utils import timezone -from django import forms -from django.utils.translation import gettext_lazy as _ -from django.conf import settings -from django.db import DataError, transaction -import json -import re -import pytz -from datetime import timedelta, datetime -from http import HTTPStatus - -from core.utils import get_start_of_semester, get_semester_code -from core.views import CanViewMixin, TabedViewMixin, CanEditMixin -from core.views.forms import LoginForm +from accounting.models import CurrencyField from core.models import User +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 ( BillingInfoForm, - StudentCardForm, - GetUserForm, - RefillForm, - CounterEditForm, - ProductEditForm, CashSummaryFormBase, + CounterEditForm, EticketForm, + GetUserForm, + ProductEditForm, + RefillForm, + StudentCardForm, ) from counter.models import ( - Counter, - Customer, - StudentCard, - Product, - Selling, - Refilling, - ProductType, + BillingInfo, CashRegisterSummary, CashRegisterSummaryItem, + Counter, + Customer, Eticket, - BillingInfo, + Product, + ProductType, + Refilling, + Selling, + StudentCard, ) -from accounting.models import CurrencyField class CounterAdminMixin(View): @@ -1486,7 +1483,7 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): end_date = (start_date + timedelta(days=32)).replace( day=1, hour=0, minute=0, microsecond=0 ) - from django.db.models import Sum, Case, When, F + from django.db.models import Case, F, Sum, When kwargs["sum_cb"] = sum( [ @@ -1574,12 +1571,12 @@ class EticketPDFView(CanViewMixin, DetailView): pk_url_kwarg = "selling_id" def get(self, request, *args, **kwargs): - from reportlab.pdfgen import canvas - from reportlab.lib.utils import ImageReader - from reportlab.lib.units import cm - from reportlab.graphics.shapes import Drawing - from reportlab.graphics.barcode.qr import QrCodeWidget 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") diff --git a/doc/conf.py b/doc/conf.py index 2a103e0b..d9f219c9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,6 +12,7 @@ # import os import sys + import django sys.path.insert(0, os.path.abspath("..")) diff --git a/eboutic/forms.py b/eboutic/forms.py index c5842700..3189e330 100644 --- a/eboutic/forms.py +++ b/eboutic/forms.py @@ -25,8 +25,8 @@ import json import re import typing - from urllib.parse import unquote + from django.http import HttpRequest from django.utils.translation import gettext as _ from sentry_sdk import capture_message diff --git a/eboutic/migrations/0001_initial.py b/eboutic/migrations/0001_initial.py index b642642f..f698999c 100644 --- a/eboutic/migrations/0001_initial.py +++ b/eboutic/migrations/0001_initial.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -import accounting.models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + +import accounting.models class Migration(migrations.Migration): diff --git a/eboutic/models.py b/eboutic/models.py index 556eb8ab..da048b63 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -14,21 +14,20 @@ # # import hmac -import html import typing from datetime import datetime from typing import List from dict2xml import dict2xml from django.conf import settings -from django.db import models, DataError -from django.db.models import Sum, F +from django.db import DataError, models +from django.db.models import F, Sum from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from accounting.models import CurrencyField -from core.models import Group, User -from counter.models import Counter, Product, Selling, Refilling, BillingInfo, Customer +from core.models import User +from counter.models import BillingInfo, Counter, Customer, Product, Refilling, Selling def get_eboutic_products(user: User) -> List[Product]: diff --git a/eboutic/tests.py b/eboutic/tests.py index b37b4a7b..3435bfc5 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -26,15 +26,14 @@ import base64 import json import urllib -from OpenSSL import crypto from django.conf import settings -from django.core.management import call_command from django.db.models import Max from django.test import TestCase from django.urls import reverse +from OpenSSL import crypto from core.models import User -from counter.models import Product, Counter, Customer, Selling +from counter.models import Counter, Customer, Product, Selling from eboutic.models import Basket diff --git a/eboutic/tests/test.py b/eboutic/tests/test.py index 2a4e6cfb..359db7ff 100755 --- a/eboutic/tests/test.py +++ b/eboutic/tests/test.py @@ -7,6 +7,7 @@ # import base64 + from OpenSSL import crypto with open("./private_key.pem") as f: diff --git a/eboutic/urls.py b/eboutic/urls.py index a1bd6ecd..22602aeb 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -25,8 +25,8 @@ from django.urls import path, register_converter -from eboutic.views import * from eboutic.converters import PaymentResultConverter +from eboutic.views import * register_converter(PaymentResultConverter, "res") diff --git a/eboutic/views.py b/eboutic/views.py index 885edf43..c240d64d 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -16,23 +16,23 @@ import base64 import json -import sentry_sdk - from datetime import datetime from urllib.parse import unquote -from OpenSSL import crypto + +import sentry_sdk from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.exceptions import SuspiciousOperation -from django.db import transaction, DatabaseError -from django.http import HttpResponse, HttpRequest -from django.shortcuts import render, redirect +from django.db import DatabaseError, transaction +from django.http import HttpRequest, HttpResponse +from django.shortcuts import redirect, render from django.utils.decorators import method_decorator from django.views.decorators.http import require_GET, require_POST from django.views.generic import TemplateView, View +from OpenSSL import crypto from counter.forms import BillingInfoForm -from counter.models import Customer, Counter, Product +from counter.models import Counter, Customer, Product from eboutic.forms import BasketForm from eboutic.models import Basket, Invoice, InvoiceItem, get_eboutic_products diff --git a/election/admin.py b/election/admin.py index 3cd3ce37..fadc9a52 100644 --- a/election/admin.py +++ b/election/admin.py @@ -1,7 +1,7 @@ from ajax_select import make_ajax_form from django.contrib import admin -from election.models import Election, Role, ElectionList, Candidature +from election.models import Candidature, Election, ElectionList, Role @admin.register(Election) diff --git a/election/migrations/0001_initial.py b/election/migrations/0001_initial.py index d3c4c449..04deb469 100644 --- a/election/migrations/0001_initial.py +++ b/election/migrations/0001_initial.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/election/models.py b/election/models.py index f100a1d6..3bcf761f 100644 --- a/election/models.py +++ b/election/models.py @@ -1,9 +1,9 @@ from django.db import models -from ordered_model.models import OrderedModel -from django.utils.translation import gettext_lazy as _ from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from ordered_model.models import OrderedModel -from core.models import User, Group +from core.models import Group, User class Election(models.Model): diff --git a/election/tests.py b/election/tests.py index 03b46481..3a3de97a 100644 --- a/election/tests.py +++ b/election/tests.py @@ -1,9 +1,8 @@ +from django.conf import settings from django.test import TestCase from django.urls import reverse -from django.core.management import call_command -from django.conf import settings -from core.models import User, Group +from core.models import Group, User from election.models import Election diff --git a/election/views.py b/election/views.py index a223c78c..71be8bfe 100644 --- a/election/views.py +++ b/election/views.py @@ -1,24 +1,19 @@ -from django.shortcuts import get_object_or_404 -from django.views.generic import ListView, DetailView -from django.views.generic.edit import UpdateView, CreateView -from django.views.generic.edit import DeleteView, FormView -from django.urls import reverse_lazy, reverse -from django.utils.translation import gettext_lazy as _ +from ajax_select import make_ajax_field +from ajax_select.fields import AutoCompleteSelectField +from django import forms from django.core.exceptions import PermissionDenied from django.db import transaction -from django.shortcuts import redirect -from django import forms +from django.db.models.query import QuerySet +from django.shortcuts import get_object_or_404, redirect +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 CreateView, DeleteView, FormView, UpdateView from core.models import User -from core.views import CanViewMixin, CanEditMixin, CanCreateMixin -from django.db.models.query import QuerySet -from core.views.forms import SelectDateTime, MarkdownInput -from election.models import Election, Role, Candidature, ElectionList, Vote -from core.views.forms import TzAwareDateTimeField - -from ajax_select.fields import AutoCompleteSelectField -from ajax_select import make_ajax_field - +from core.views import CanCreateMixin, CanEditMixin, CanViewMixin +from core.views.forms import MarkdownInput, TzAwareDateTimeField +from election.models import Candidature, Election, ElectionList, Role, Vote # Custom form field diff --git a/forum/admin.py b/forum/admin.py index 6d0c088a..eff7d401 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -17,7 +17,6 @@ from django.contrib import admin from haystack.admin import SearchModelAdmin - from forum.models import * diff --git a/forum/migrations/0001_initial.py b/forum/migrations/0001_initial.py index 6c06b0a6..4ad137d6 100644 --- a/forum/migrations/0001_initial.py +++ b/forum/migrations/0001_initial.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import datetime + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings from django.db import migrations, models from django.utils.timezone import utc -from django.conf import settings -import django.utils.timezone -import datetime -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/forum/migrations/0004_auto_20170531_1949.py b/forum/migrations/0004_auto_20170531_1949.py index 152bdaea..ef466a9b 100644 --- a/forum/migrations/0004_auto_20170531_1949.py +++ b/forum/migrations/0004_auto_20170531_1949.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/forum/migrations/0005_forumtopic_subscribed_users.py b/forum/migrations/0005_forumtopic_subscribed_users.py index b1f6d629..b977aadf 100644 --- a/forum/migrations/0005_forumtopic_subscribed_users.py +++ b/forum/migrations/0005_forumtopic_subscribed_users.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/forum/migrations/0006_auto_20180426_2013.py b/forum/migrations/0006_auto_20180426_2013.py index 30248dec..974b9b8e 100644 --- a/forum/migrations/0006_auto_20180426_2013.py +++ b/forum/migrations/0006_auto_20180426_2013.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.db import migrations, models + import forum.models diff --git a/forum/models.py b/forum/models.py index 3894caa7..a7d77c4d 100644 --- a/forum/models.py +++ b/forum/models.py @@ -22,20 +22,20 @@ # # -from django.db import models +from datetime import datetime +from itertools import chain + +import pytz from django.conf import settings -from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError +from django.db import models from django.urls import reverse from django.utils import timezone from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ -from datetime import datetime -from itertools import chain -import pytz - -from core.models import User, Group from club.models import Club +from core.models import Group, User class Forum(models.Model): diff --git a/forum/tests.py b/forum/tests.py index 46a200c2..d888e761 100644 --- a/forum/tests.py +++ b/forum/tests.py @@ -14,6 +14,4 @@ # # -from django.test import TestCase - # Create your tests here. diff --git a/forum/views.py b/forum/views.py index 9b75794b..74e6bff4 100644 --- a/forum/views.py +++ b/forum/views.py @@ -23,31 +23,30 @@ # # -from django.shortcuts import get_object_or_404 -from django.views.generic import ListView, DetailView, RedirectView -from django.views.generic.edit import UpdateView, CreateView, DeleteView -from django.views.generic.detail import SingleObjectMixin -from django.utils.translation import gettext_lazy as _ -from django.urls import reverse_lazy -from django.utils import timezone, html -from django.conf import settings -from django import forms -from django.core.exceptions import PermissionDenied -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger - from ajax_select import make_ajax_field +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator +from django.shortcuts import get_object_or_404 +from django.urls import reverse_lazy +from django.utils import html, timezone +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, ListView, RedirectView +from django.views.generic.detail import SingleObjectMixin +from django.views.generic.edit import CreateView, DeleteView, UpdateView +from haystack.query import RelatedSearchQuerySet from core.views import ( - CanViewMixin, + CanCreateMixin, CanEditMixin, CanEditPropMixin, - CanCreateMixin, + CanViewMixin, UserIsLoggedMixin, can_view, ) from core.views.forms import MarkdownInput -from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta -from haystack.query import RelatedSearchQuerySet +from forum.models import Forum, ForumMessage, ForumMessageMeta, ForumTopic class ForumSearchView(ListView): diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py index f6442487..191198f7 100644 --- a/galaxy/management/commands/generate_galaxy_test_data.py +++ b/galaxy/management/commands/generate_galaxy_test_data.py @@ -21,7 +21,9 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +import logging import warnings +from datetime import timedelta from typing import Final, Optional from django.conf import settings @@ -29,15 +31,10 @@ from django.core.files.base import ContentFile from django.core.management.base import BaseCommand from django.utils import timezone -from datetime import timedelta - -import logging - from club.models import Club, Membership -from core.models import User, Group, Page, SithFile +from core.models import Group, Page, SithFile, User +from sas.models import Album, PeoplePictureRelation, Picture from subscription.models import Subscription -from sas.models import Album, Picture, PeoplePictureRelation - RED_PIXEL_PNG: Final[bytes] = ( b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" diff --git a/galaxy/management/commands/rule_galaxy.py b/galaxy/management/commands/rule_galaxy.py index 55cb9ae9..1743fadc 100644 --- a/galaxy/management/commands/rule_galaxy.py +++ b/galaxy/management/commands/rule_galaxy.py @@ -21,6 +21,7 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # +import logging import warnings from django.core.management.base import BaseCommand @@ -28,8 +29,6 @@ from django.db import connection from galaxy.models import Galaxy -import logging - class Command(BaseCommand): help = ( diff --git a/galaxy/migrations/0001_initial.py b/galaxy/migrations/0001_initial.py index ab9a6a06..7c8e8521 100644 --- a/galaxy/migrations/0001_initial.py +++ b/galaxy/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.16 on 2023-03-02 10:07 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/galaxy/migrations/0002_auto_20230412_1130.py b/galaxy/migrations/0002_auto_20230412_1130.py index be01af99..00a69d00 100644 --- a/galaxy/migrations/0002_auto_20230412_1130.py +++ b/galaxy/migrations/0002_auto_20230412_1130.py @@ -1,8 +1,8 @@ # Generated by Django 3.2.16 on 2023-04-12 09:30 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/galaxy/models.py b/galaxy/models.py index 744cca79..cca7397d 100644 --- a/galaxy/models.py +++ b/galaxy/models.py @@ -24,20 +24,19 @@ from __future__ import annotations -import math import logging +import math import time - -from typing import List, TypedDict, NamedTuple, Union, Optional +from typing import List, NamedTuple, Optional, TypedDict, Union from django.db import models -from django.db.models import Q, Case, F, Value, When, Count +from django.db.models import Case, Count, F, Q, Value, When from django.db.models.functions import Concat from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from core.models import User from club.models import Club +from core.models import User from sas.models import Picture diff --git a/galaxy/tests.py b/galaxy/tests.py index 70314574..2a523af3 100644 --- a/galaxy/tests.py +++ b/galaxy/tests.py @@ -23,7 +23,6 @@ # import json - from pathlib import Path from django.core.management import call_command diff --git a/galaxy/views.py b/galaxy/views.py index 3dda003e..3e0b33da 100644 --- a/galaxy/views.py +++ b/galaxy/views.py @@ -22,18 +22,18 @@ # # -from django.views.generic import DetailView, View -from django.http import JsonResponse, Http404 -from django.db.models import Q, Case, F, When, Value +from django.db.models import Case, F, Q, Value, When from django.db.models.functions import Concat +from django.http import Http404, JsonResponse from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, View +from core.models import User from core.views import ( CanViewMixin, FormerSubscriberMixin, + UserTabsMixin, ) -from core.models import User -from core.views import UserTabsMixin from galaxy.models import Galaxy, GalaxyLane diff --git a/launderette/migrations/0001_initial.py b/launderette/migrations/0001_initial.py index 79f011a7..f1548cd2 100644 --- a/launderette/migrations/0001_initial.py +++ b/launderette/migrations/0001_initial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/launderette/models.py b/launderette/models.py index 3ca12b2d..c2f344bb 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -14,14 +14,14 @@ # # -from django.db import models, DataError -from django.utils.translation import gettext_lazy as _ from django.conf import settings +from django.db import DataError, models from django.urls import reverse +from django.utils.translation import gettext_lazy as _ -from counter.models import Counter -from core.models import User from club.models import Club +from core.models import User +from counter.models import Counter # Create your models here. diff --git a/launderette/tests.py b/launderette/tests.py index 46a200c2..d888e761 100644 --- a/launderette/tests.py +++ b/launderette/tests.py @@ -14,6 +14,4 @@ # # -from django.test import TestCase - # Create your tests here. diff --git a/launderette/views.py b/launderette/views.py index 716b41f4..c0b16eed 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -14,27 +14,26 @@ # # -from datetime import datetime, timedelta from collections import OrderedDict +from datetime import datetime, timedelta + import pytz - -from django.views.generic import ListView, DetailView, TemplateView -from django.views.generic.edit import UpdateView, CreateView, DeleteView, BaseFormView -from django.utils.translation import gettext as _ -from django.utils import dateparse, timezone -from django.urls import reverse_lazy -from django.conf import settings -from django.db import transaction, DataError from django import forms +from django.conf import settings +from django.db import DataError, transaction from django.template import defaultfilters +from django.urls import reverse_lazy +from django.utils import dateparse, timezone +from django.utils.translation import gettext as _ +from django.views.generic import DetailView, ListView, TemplateView +from django.views.generic.edit import BaseFormView, CreateView, DeleteView, UpdateView -from core.models import Page, User from club.models import Club -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin -from launderette.models import Launderette, Token, Machine, Slot -from counter.models import Counter, Customer, Selling +from core.models import Page, User +from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin from counter.forms import GetUserForm - +from counter.models import Counter, Customer, Selling +from launderette.models import Launderette, Machine, Slot, Token # For users diff --git a/matmat/admin.py b/matmat/admin.py index 8c38f3f3..846f6b40 100644 --- a/matmat/admin.py +++ b/matmat/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - # Register your models here. diff --git a/matmat/models.py b/matmat/models.py index 71a83623..6b202199 100644 --- a/matmat/models.py +++ b/matmat/models.py @@ -1,3 +1 @@ -from django.db import models - # Create your models here. diff --git a/matmat/tests.py b/matmat/tests.py index 7ce503c2..a39b155a 100644 --- a/matmat/tests.py +++ b/matmat/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/matmat/views.py b/matmat/views.py index 10b2d4f5..eb769c62 100644 --- a/matmat/views.py +++ b/matmat/views.py @@ -24,19 +24,18 @@ from ast import literal_eval from enum import Enum -from django.views.generic import ListView, View -from django.views.generic.edit import FormView -from django.utils.translation import gettext_lazy as _ -from django.views.generic.detail import SingleObjectMixin +from django import forms from django.http.response import HttpResponseRedirect from django.urls import reverse -from django import forms +from django.utils.translation import gettext_lazy as _ +from django.views.generic import ListView, View +from django.views.generic.detail import SingleObjectMixin +from django.views.generic.edit import FormView +from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget from core.models import User -from core.views import FormerSubscriberMixin +from core.views import FormerSubscriberMixin, search_user from core.views.forms import SelectDate -from core.views import search_user -from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget # Enum to select search type diff --git a/pedagogy/forms.py b/pedagogy/forms.py index 6a3d29ce..28810e64 100644 --- a/pedagogy/forms.py +++ b/pedagogy/forms.py @@ -24,12 +24,9 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from django.forms.widgets import Widget -from django.templatetags.static import static -from core.views.forms import MarkdownInput from core.models import User - +from core.views.forms import MarkdownInput from pedagogy.models import UV, UVComment, UVCommentReport diff --git a/pedagogy/migrations/0001_initial.py b/pedagogy/migrations/0001_initial.py index e4285277..782e65c4 100644 --- a/pedagogy/migrations/0001_initial.py +++ b/pedagogy/migrations/0001_initial.py @@ -2,10 +2,10 @@ # Generated by Django 1.11.20 on 2019-07-05 14:32 from __future__ import unicode_literals -from django.conf import settings import django.core.validators -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/pedagogy/models.py b/pedagogy/models.py index 2278dda6..0de5c230 100644 --- a/pedagogy/models.py +++ b/pedagogy/models.py @@ -22,14 +22,13 @@ # # -from django.db import models -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from django.core import validators from django.conf import settings -from django.utils.functional import cached_property +from django.core import validators +from django.db import models from django.urls import reverse - +from django.utils import timezone +from django.utils.functional import cached_property +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from core.models import User diff --git a/pedagogy/search_indexes.py b/pedagogy/search_indexes.py index 3ea75343..c6bc74af 100644 --- a/pedagogy/search_indexes.py +++ b/pedagogy/search_indexes.py @@ -23,7 +23,6 @@ # from django.db import models - from haystack import indexes, signals from core.search_indexes import BigCharFieldIndex diff --git a/pedagogy/tests.py b/pedagogy/tests.py index baee59bf..c52ee2ae 100644 --- a/pedagogy/tests.py +++ b/pedagogy/tests.py @@ -23,13 +23,12 @@ # from django.conf import settings +from django.core.management import call_command from django.test import TestCase from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.core.management import call_command - -from core.models import User, Notification +from core.models import Notification, User from pedagogy.models import UV, UVComment, UVCommentReport diff --git a/pedagogy/views.py b/pedagogy/views.py index 13151c60..89187ca5 100644 --- a/pedagogy/views.py +++ b/pedagogy/views.py @@ -22,38 +22,35 @@ # # +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django.http import HttpResponse +from django.shortcuts import get_object_or_404 +from django.urls import reverse, reverse_lazy +from django.utils import html from django.views.generic import ( CreateView, DeleteView, - UpdateView, - ListView, FormView, + ListView, + UpdateView, View, ) -from django.utils import html -from django.http import HttpResponse -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist -from django.urls import reverse_lazy, reverse -from django.shortcuts import get_object_or_404 -from django.conf import settings - from haystack.query import SearchQuerySet from rest_framework.renderers import JSONRenderer +from core.models import Notification, RealGroup from core.views import ( - DetailFormView, CanCreateMixin, - CanEditMixin, - CanViewMixin, CanEditPropMixin, + CanViewMixin, + DetailFormView, ) -from core.models import RealGroup, Notification - from pedagogy.forms import ( - UVForm, UVCommentForm, - UVCommentReportForm, UVCommentModerationForm, + UVCommentReportForm, + UVForm, ) from pedagogy.models import UV, UVComment, UVCommentReport, UVSerializer diff --git a/pyproject.toml b/pyproject.toml index 0bb1a27f..e9dfe6d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,9 @@ optional = true [tool.xapian] version = "1.4.25" +[tool.ruff.lint] +select = ["I", "F401"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/rootplace/admin.py b/rootplace/admin.py index 362a5c4f..5531f2a2 100644 --- a/rootplace/admin.py +++ b/rootplace/admin.py @@ -14,6 +14,4 @@ # # -from django.contrib import admin - # Register your models here. diff --git a/rootplace/models.py b/rootplace/models.py index 5672eba4..084dfa73 100644 --- a/rootplace/models.py +++ b/rootplace/models.py @@ -14,6 +14,4 @@ # # -from django.db import models - # Create your models here. diff --git a/rootplace/tests.py b/rootplace/tests.py index f1bb174f..80119edf 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -15,14 +15,12 @@ # from datetime import date, timedelta -from django.core.management import call_command from django.test import TestCase from django.urls import reverse -from django.utils.timezone import localtime, now from club.models import Club -from core.models import User, RealGroup -from counter.models import Customer, Product, Selling, Counter, Refilling +from core.models import RealGroup, User +from counter.models import Counter, Customer, Product, Refilling, Selling from subscription.models import Subscription diff --git a/rootplace/views.py b/rootplace/views.py index fbb04e79..7a045b73 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -32,7 +32,7 @@ from django.utils.translation import gettext as _ from django.views.generic import ListView from django.views.generic.edit import FormView -from core.models import User, OperationLog, SithFile +from core.models import OperationLog, SithFile, User from core.views import CanEditPropMixin from counter.models import Customer from forum.models import ForumMessageMeta diff --git a/sas/admin.py b/sas/admin.py index d1001ef8..3f6c6f42 100644 --- a/sas/admin.py +++ b/sas/admin.py @@ -18,7 +18,6 @@ from django.contrib import admin from sas.models import * - admin.site.register(Album) # admin.site.register(Picture) admin.site.register(PeoplePictureRelation) diff --git a/sas/migrations/0001_initial.py b/sas/migrations/0001_initial.py index 41ed4dc9..7ef55737 100644 --- a/sas/migrations/0001_initial.py +++ b/sas/migrations/0001_initial.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): diff --git a/sas/migrations/0002_auto_20161119_1241.py b/sas/migrations/0002_auto_20161119_1241.py index 21f44323..15ba12ad 100644 --- a/sas/migrations/0002_auto_20161119_1241.py +++ b/sas/migrations/0002_auto_20161119_1241.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/sas/models.py b/sas/models.py index e9fe4f97..3342facf 100644 --- a/sas/models.py +++ b/sas/models.py @@ -14,19 +14,19 @@ # # +import os +from io import BytesIO + +from django.conf import settings +from django.core.cache import cache from django.db import models from django.urls import reverse -from django.core.cache import cache -from django.conf import settings -from django.utils.translation import gettext_lazy as _ from django.utils import timezone - +from django.utils.translation import gettext_lazy as _ from PIL import Image -from io import BytesIO -import os from core.models import SithFile, User -from core.utils import resize_image, exif_auto_rotate +from core.utils import exif_auto_rotate, resize_image class SASPictureManager(models.Manager): diff --git a/sas/tests.py b/sas/tests.py index 46a200c2..d888e761 100644 --- a/sas/tests.py +++ b/sas/tests.py @@ -14,6 +14,4 @@ # # -from django.test import TestCase - # Create your tests here. diff --git a/sas/views.py b/sas/views.py index 052f7a4e..c2821bac 100644 --- a/sas/views.py +++ b/sas/views.py @@ -14,26 +14,24 @@ # # -from django.shortcuts import redirect -from django.http import HttpResponse, Http404 -from django.urls import reverse_lazy, reverse -from core.views.forms import SelectDate -from django.views.generic import DetailView, TemplateView -from django.views.generic.edit import UpdateView, FormMixin, FormView -from django.utils.translation import gettext_lazy as _ -from django.conf import settings -from django import forms -from django.core.exceptions import PermissionDenied -from django.core.paginator import Paginator, InvalidPage - from ajax_select import make_ajax_field from ajax_select.fields import AutoCompleteSelectMultipleField +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.core.paginator import InvalidPage, Paginator +from django.http import Http404, HttpResponse +from django.shortcuts import redirect +from django.urls import reverse, reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, TemplateView +from django.views.generic.edit import FormMixin, FormView, UpdateView -from core.views import CanViewMixin, CanEditMixin -from core.views.files import send_file, FileView, MultipleImageField -from core.models import SithFile, User, Notification, RealGroup - -from sas.models import Picture, Album, PeoplePictureRelation +from core.models import Notification, SithFile, User +from core.views import CanEditMixin, CanViewMixin +from core.views.files import FileView, MultipleImageField, send_file +from core.views.forms import SelectDate +from sas.models import Album, PeoplePictureRelation, Picture class SASForm(forms.Form): diff --git a/sith/urls.py b/sith/urls.py index 08e25b3e..106c2851 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -30,14 +30,12 @@ Including another URLconf 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) """ -from django.urls import include, path -from django.contrib import admin +from ajax_select import urls as ajax_select_urls from django.conf import settings from django.conf.urls.static import static +from django.contrib import admin +from django.urls import include, path from django.views.i18n import JavaScriptCatalog -from ajax_select import urls as ajax_select_urls - -import core.urls js_info_dict = {"packages": ("sith",)} diff --git a/stock/admin.py b/stock/admin.py index 46567a3b..28985c8c 100644 --- a/stock/admin.py +++ b/stock/admin.py @@ -25,7 +25,7 @@ from django.contrib import admin -from stock.models import Stock, StockItem, ShoppingList, ShoppingListItem +from stock.models import ShoppingList, ShoppingListItem, Stock, StockItem # Register your models here. admin.site.register(Stock) diff --git a/stock/migrations/0001_initial.py b/stock/migrations/0001_initial.py index 43b658ff..e203981a 100644 --- a/stock/migrations/0001_initial.py +++ b/stock/migrations/0001_initial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/stock/models.py b/stock/models.py index 6c9c1ae7..f93a1544 100644 --- a/stock/models.py +++ b/stock/models.py @@ -23,11 +23,10 @@ # # -from django.db import models -from django.utils.translation import gettext_lazy as _ -from django.urls import reverse from django.conf import settings - +from django.db import models +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from counter.models import Counter, ProductType diff --git a/stock/tests.py b/stock/tests.py index ad602a5c..884e1b09 100644 --- a/stock/tests.py +++ b/stock/tests.py @@ -23,6 +23,4 @@ # # -from django.test import TestCase - # Create your tests here. diff --git a/stock/views.py b/stock/views.py index 6b4776aa..72a9ab56 100644 --- a/stock/views.py +++ b/stock/views.py @@ -24,36 +24,21 @@ # from collections import OrderedDict -from datetime import datetime, timedelta -from django.utils import timezone -from django.shortcuts import render, get_object_or_404 -from django.views.generic import ListView, DetailView, RedirectView, TemplateView -from django.views.generic.edit import ( - UpdateView, - CreateView, - DeleteView, - ProcessFormView, - FormMixin, - BaseFormView, -) -from django.utils.translation import gettext_lazy as _ from django import forms -from django.http import HttpResponseRedirect, HttpResponse +from django.db import transaction from django.forms.models import modelform_factory -from django.urls import reverse_lazy, reverse -from django.db import transaction, DataError +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, ListView +from django.views.generic.edit import BaseFormView, CreateView, DeleteView, UpdateView -from core.views import ( - CanViewMixin, - CanEditMixin, - CanEditPropMixin, - CanCreateMixin, - TabedViewMixin, -) +from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin +from counter.models import ProductType from counter.views import CounterAdminTabsMixin, CounterTabsMixin -from counter.models import Counter, ProductType -from stock.models import Stock, StockItem, ShoppingList, ShoppingListItem +from stock.models import ShoppingList, ShoppingListItem, Stock, StockItem class StockItemList(CounterAdminTabsMixin, CanCreateMixin, ListView): diff --git a/subscription/migrations/0001_initial.py b/subscription/migrations/0001_initial.py index 3ca942f0..932fafaf 100644 --- a/subscription/migrations/0001_initial.py +++ b/subscription/migrations/0001_initial.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.contrib.auth.models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/subscription/models.py b/subscription/models.py index f1c2b2d5..5e47ab75 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -14,17 +14,16 @@ # # +import math from datetime import date, timedelta -from django.db import models -from django.utils.translation import gettext_lazy as _ -from django.conf import settings -from django.core.exceptions import ValidationError -from django.urls import reverse -from django.contrib.auth.forms import PasswordResetForm from dateutil.relativedelta import relativedelta - -import math +from django.conf import settings +from django.contrib.auth.forms import PasswordResetForm +from django.core.exceptions import ValidationError +from django.db import models +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from core.models import User from core.utils import get_start_of_semester diff --git a/subscription/tests.py b/subscription/tests.py index 4e8ec5a1..1b9924e8 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -16,12 +16,11 @@ from datetime import date from unittest import mock -from django.test import TestCase -from subscription.models import Subscription -from core.models import User from django.conf import settings -from datetime import datetime -from django.core.management import call_command +from django.test import TestCase + +from core.models import User +from subscription.models import Subscription class FakeDate(date): diff --git a/subscription/views.py b/subscription/views.py index bd10ac2c..ccffd58f 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -14,21 +14,19 @@ # # -from django.views.generic.edit import CreateView, FormView -from django.utils.translation import gettext_lazy as _ -from django.core.exceptions import PermissionDenied, ValidationError -from django.urls import reverse_lazy -from django import forms -from django.conf import settings - -from ajax_select.fields import AutoCompleteSelectField import random -from subscription.models import Subscription -from core.views.forms import SelectDateTime +from ajax_select.fields import AutoCompleteSelectField +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied, ValidationError +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic.edit import CreateView, FormView + from core.models import User -from core.views.forms import SelectDate -from core.views.forms import TzAwareDateTimeField +from core.views.forms import SelectDate, TzAwareDateTimeField +from subscription.models import Subscription class SelectionDateForm(forms.Form): diff --git a/trombi/migrations/0001_initial.py b/trombi/migrations/0001_initial.py index 0943bad1..3c609c63 100644 --- a/trombi/migrations/0001_initial.py +++ b/trombi/migrations/0001_initial.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models -from django.conf import settings import datetime + import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/trombi/migrations/0004_trombiclubmembership.py b/trombi/migrations/0004_trombiclubmembership.py index 5bb017b4..eb036839 100644 --- a/trombi/migrations/0004_trombiclubmembership.py +++ b/trombi/migrations/0004_trombiclubmembership.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/trombi/models.py b/trombi/models.py index 18f36526..b5e7d1a8 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -22,17 +22,17 @@ # # -from django.db import models -from django.utils.translation import gettext_lazy as _ -from django.urls import reverse +from datetime import date + from django.conf import settings from django.core.exceptions import ValidationError +from django.db import models +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ -from datetime import timedelta, date - -from core.models import User -from core.utils import get_start_of_semester, get_semester_code from club.models import Club +from core.models import User +from core.utils import get_semester_code class TrombiManager(models.Manager): diff --git a/trombi/tests.py b/trombi/tests.py index de62cd38..087c75b4 100644 --- a/trombi/tests.py +++ b/trombi/tests.py @@ -22,6 +22,4 @@ # # -from django.test import TestCase - # Create your tests here. diff --git a/trombi/views.py b/trombi/views.py index 98bf5fc2..d2bf3231 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -23,34 +23,33 @@ # # -from django.http import Http404, HttpResponseRedirect -from django.shortcuts import get_object_or_404, redirect -from django.urls import reverse_lazy, reverse -from django.views.generic import DetailView, RedirectView, TemplateView, View -from django.views.generic.edit import UpdateView, CreateView, DeleteView -from django.utils.translation import gettext_lazy as _ -from django import forms -from django.conf import settings -from django.forms.models import modelform_factory -from django.core.exceptions import PermissionDenied - -from ajax_select.fields import AutoCompleteSelectField - from datetime import date -from trombi.models import Trombi, TrombiUser, TrombiComment, TrombiClubMembership -from core.views.forms import SelectDate +from ajax_select.fields import AutoCompleteSelectField +from django import forms +from django.conf import settings +from django.core.exceptions import PermissionDenied +from django.forms.models import modelform_factory +from django.http import Http404, HttpResponseRedirect +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse, reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, RedirectView, TemplateView, View +from django.views.generic.edit import CreateView, DeleteView, UpdateView + +from club.models import Club +from core.models import User from core.views import ( - CanViewMixin, + CanCreateMixin, CanEditMixin, CanEditPropMixin, - TabedViewMixin, - CanCreateMixin, + CanViewMixin, QuickNotifMixin, + TabedViewMixin, UserIsLoggedMixin, ) -from core.models import User -from club.models import Club +from core.views.forms import SelectDate +from trombi.models import Trombi, TrombiClubMembership, TrombiComment, TrombiUser class TrombiTabsMixin(TabedViewMixin): From d97602e60b56c85a905f75046ba949c70bf2d5e3 Mon Sep 17 00:00:00 2001 From: thomas girod <56346771+imperosol@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:10:24 +0200 Subject: [PATCH 78/95] Use pytest for tests (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use pytest for tests Eh ouais, il y a que la config qui change. Pytest est implémentable par étapes. Et ça c'est beau. * rework tests with pytest * remove unittest custom TestRunner * Edit doc and CI --- .github/workflows/ci.yml | 4 +- accounting/tests.py | 94 +++--- club/tests.py | 451 +++++++++++++-------------- club/views.py | 4 +- com/tests.py | 95 +++--- conftest.py | 14 + core/tests.py | 491 +++++++++-------------------- counter/tests.py | 365 +++++++++------------- doc/start/install.rst | 8 +- eboutic/tests.py | 126 ++++---- election/tests.py | 60 ++-- galaxy/tests.py | 49 +-- pedagogy/tests.py | 659 ++++++++++++++++++--------------------- poetry.lock | 103 +++++- pyproject.toml | 11 +- rootplace/tests.py | 35 ++- sith/settings.py | 5 +- sith/testrunner.py | 9 - subscription/tests.py | 200 ++++++------ 19 files changed, 1268 insertions(+), 1515 deletions(-) create mode 100644 conftest.py delete mode 100644 sith/testrunner.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74f18dd6..5e3f8cd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: ./.github/actions/setup_project - uses: ./.github/actions/setup_xapian - uses: ./.github/actions/compile_messages - name: Run tests - run: poetry run coverage run ./manage.py test + run: poetry run coverage run -m pytest - name: Generate coverage report run: | poetry run coverage report diff --git a/accounting/tests.py b/accounting/tests.py index 28c7a420..0ea7a29e 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -30,71 +30,66 @@ from core.models import User class RefoundAccountTest(TestCase): - def setUp(self): - self.skia = User.objects.filter(username="skia").first() + @classmethod + def setUpTestData(cls): + cls.skia = User.objects.get(username="skia") # reffil skia's account - self.skia.customer.amount = 800 - self.skia.customer.save() + cls.skia.customer.amount = 800 + cls.skia.customer.save() + cls.refound_account_url = reverse("accounting:refound_account") def test_permission_denied(self): - self.client.login(username="guy", password="plop") + self.client.force_login(User.objects.get(username="guy")) response_post = self.client.post( - reverse("accounting:refound_account"), {"user": self.skia.id} + self.refound_account_url, {"user": self.skia.id} ) - response_get = self.client.get(reverse("accounting:refound_account")) - self.assertTrue(response_get.status_code == 403) - self.assertTrue(response_post.status_code == 403) + response_get = self.client.get(self.refound_account_url) + assert response_get.status_code == 403 + assert response_post.status_code == 403 def test_root_granteed(self): - self.client.login(username="root", password="plop") - response_post = self.client.post( - reverse("accounting:refound_account"), {"user": self.skia.id} - ) - self.skia = User.objects.filter(username="skia").first() - response_get = self.client.get(reverse("accounting:refound_account")) - self.assertFalse(response_get.status_code == 403) - self.assertTrue('' in str(response_get.content)) - self.assertFalse(response_post.status_code == 403) - self.assertTrue(self.skia.customer.amount == 0) + self.client.force_login(User.objects.get(username="root")) + response = self.client.post(self.refound_account_url, {"user": self.skia.id}) + self.assertRedirects(response, self.refound_account_url) + self.skia.refresh_from_db() + response = self.client.get(self.refound_account_url) + assert response.status_code == 200 + assert '' in str(response.content) + assert self.skia.customer.amount == 0 def test_comptable_granteed(self): - self.client.login(username="comptable", password="plop") - response_post = self.client.post( - reverse("accounting:refound_account"), {"user": self.skia.id} - ) - self.skia = User.objects.filter(username="skia").first() - response_get = self.client.get(reverse("accounting:refound_account")) - self.assertFalse(response_get.status_code == 403) - self.assertTrue('' in str(response_get.content)) - self.assertFalse(response_post.status_code == 403) - self.assertTrue(self.skia.customer.amount == 0) + self.client.force_login(User.objects.get(username="comptable")) + response = self.client.post(self.refound_account_url, {"user": self.skia.id}) + self.assertRedirects(response, self.refound_account_url) + self.skia.refresh_from_db() + response = self.client.get(self.refound_account_url) + assert response.status_code == 200 + assert '' in str(response.content) + assert self.skia.customer.amount == 0 class JournalTest(TestCase): - def setUp(self): - self.journal = GeneralJournal.objects.filter(id=1).first() + @classmethod + def setUpTestData(cls): + cls.journal = GeneralJournal.objects.get(id=1) def test_permission_granted(self): - self.client.login(username="comptable", password="plop") + self.client.force_login(User.objects.get(username="comptable")) response_get = self.client.get( reverse("accounting:journal_details", args=[self.journal.id]) ) - self.assertTrue(response_get.status_code == 200) - self.assertTrue( - "
    " in str(response_get.content) - ) + assert response_get.status_code == 200 + assert "" in str(response_get.content) def test_permission_not_granted(self): - self.client.login(username="skia", password="plop") + self.client.force_login(User.objects.get(username="skia")) response_get = self.client.get( reverse("accounting:journal_details", args=[self.journal.id]) ) - self.assertTrue(response_get.status_code == 403) - self.assertFalse( - "" in str(response_get.content) - ) + assert response_get.status_code == 403 + assert "" not in str(response_get.content) class OperationTest(TestCase): @@ -108,9 +103,8 @@ class OperationTest(TestCase): code="443", label="Ce code n'existe pas", movement_type="CREDIT" ) at.save() - l = Label(club_account=self.journal.club_account, name="bob") - l.save() - self.client.login(username="comptable", password="plop") + l = Label.objects.create(club_account=self.journal.club_account, name="bob") + self.client.force_login(User.objects.get(username="comptable")) self.op1 = Operation( journal=self.journal, date=date.today(), @@ -139,8 +133,7 @@ class OperationTest(TestCase): self.op2.save() def test_new_operation(self): - self.client.login(username="comptable", password="plop") - at = AccountingType.objects.filter(code="604").first() + at = AccountingType.objects.get(code="604") response = self.client.post( reverse("accounting:op_new", args=[self.journal.id]), { @@ -172,8 +165,7 @@ class OperationTest(TestCase): self.assertTrue("" in str(response_get.content)) def test_bad_new_operation(self): - self.client.login(username="comptable", password="plop") - AccountingType.objects.filter(code="604").first() + AccountingType.objects.get(code="604") response = self.client.post( reverse("accounting:op_new", args=[self.journal.id]), { @@ -199,7 +191,7 @@ class OperationTest(TestCase): ) def test_new_operation_not_authorized(self): - self.client.login(username="skia", password="plop") + self.client.force_login(self.skia) at = AccountingType.objects.filter(code="604").first() response = self.client.post( reverse("accounting:op_new", args=[self.journal.id]), @@ -226,7 +218,6 @@ class OperationTest(TestCase): ) def test__operation_simple_accounting(self): - self.client.login(username="comptable", password="plop") sat = SimplifiedAccountingType.objects.all().first() response = self.client.post( reverse("accounting:op_new", args=[self.journal.id]), @@ -263,14 +254,12 @@ class OperationTest(TestCase): ) def test_nature_statement(self): - self.client.login(username="comptable", password="plop") response = self.client.get( reverse("accounting:journal_nature_statement", args=[self.journal.id]) ) self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200) def test_person_statement(self): - self.client.login(username="comptable", password="plop") response = self.client.get( reverse("accounting:journal_person_statement", args=[self.journal.id]) ) @@ -292,7 +281,6 @@ class OperationTest(TestCase): ) def test_accounting_statement(self): - self.client.login(username="comptable", password="plop") response = self.client.get( reverse("accounting:journal_accounting_statement", args=[self.journal.id]) ) diff --git a/club/tests.py b/club/tests.py index 73480979..21285ade 100644 --- a/club/tests.py +++ b/club/tests.py @@ -19,7 +19,7 @@ from django.conf import settings from django.core.cache import cache from django.test import TestCase from django.urls import reverse -from django.utils import html, timezone +from django.utils import timezone from django.utils.timezone import localtime, now from django.utils.translation import gettext as _ @@ -49,6 +49,7 @@ class ClubTest(TestCase): cls.richard = User.objects.get(username="rbatsbak") cls.comptable = User.objects.get(username="comptable") cls.sli = User.objects.get(username="sli") + cls.root = User.objects.get(username="root") # subscribed users - not initial members cls.krophil = User.objects.get(username="krophil") @@ -102,45 +103,42 @@ class MembershipQuerySetTest(ClubTest): Test that the ongoing queryset method returns the memberships that are not ended. """ - current_members = self.club.members.ongoing() + current_members = list(self.club.members.ongoing().order_by("id")) expected = [ self.skia.memberships.get(club=self.club), self.comptable.memberships.get(club=self.club), self.richard.memberships.get(club=self.club), ] - self.assertEqual(len(current_members), len(expected)) - for member in current_members: - self.assertIn(member, expected) + expected.sort(key=lambda i: i.id) + assert current_members == expected def test_board(self): """ Test that the board queryset method returns the memberships of user in the club board """ - board_members = list(self.club.members.board()) + board_members = list(self.club.members.board().order_by("id")) expected = [ self.skia.memberships.get(club=self.club), self.comptable.memberships.get(club=self.club), # sli is no more member, but he was in the board self.sli.memberships.get(club=self.club), ] - self.assertEqual(len(board_members), len(expected)) - for member in board_members: - self.assertIn(member, expected) + expected.sort(key=lambda i: i.id) + assert board_members == expected def test_ongoing_board(self): """ Test that combining ongoing and board returns users who are currently board members of the club """ - members = list(self.club.members.ongoing().board()) + members = list(self.club.members.ongoing().board().order_by("id")) expected = [ self.skia.memberships.get(club=self.club), self.comptable.memberships.get(club=self.club), ] - self.assertEqual(len(members), len(expected)) - for member in members: - self.assertIn(member, expected) + expected.sort(key=lambda i: i.id) + assert members == expected def test_update_invalidate_cache(self): """ @@ -149,8 +147,9 @@ class MembershipQuerySetTest(ClubTest): mem_skia = self.skia.memberships.get(club=self.club) cache.set(f"membership_{mem_skia.club_id}_{mem_skia.user_id}", mem_skia) self.skia.memberships.update(end_date=localtime(now()).date()) - self.assertEqual( - cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}"), "not_member" + assert ( + cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}") + == "not_member" ) mem_richard = self.richard.memberships.get(club=self.club) @@ -159,8 +158,8 @@ class MembershipQuerySetTest(ClubTest): ) self.richard.memberships.update(role=5) new_mem = self.richard.memberships.get(club=self.club) - self.assertNotEqual(new_mem, "not_member") - self.assertEqual(new_mem.role, 5) + assert new_mem != "not_member" + assert new_mem.role == 5 def test_delete_invalidate_cache(self): """ @@ -177,40 +176,39 @@ class MembershipQuerySetTest(ClubTest): # should delete the subscriptions of skia and comptable self.club.members.ongoing().board().delete() - self.assertEqual( - cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}"), "not_member" + assert ( + cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}") + == "not_member" ) - self.assertEqual( - cache.get(f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}"), - "not_member", + assert ( + cache.get(f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}") + == "not_member", ) class ClubModelTest(ClubTest): - def assert_membership_just_started(self, user: User, role: int): + def assert_membership_started_today(self, user: User, role: int): """ Assert that the given membership is active and started today """ membership = user.memberships.ongoing().filter(club=self.club).first() - self.assertIsNotNone(membership) - self.assertEqual(localtime(now()).date(), membership.start_date) - self.assertIsNone(membership.end_date) - self.assertEqual(membership.role, role) - self.assertEqual(membership.club.get_membership_for(user), membership) + assert membership is not None + assert localtime(now()).date() == membership.start_date + assert membership.end_date is None + assert membership.role == role + assert membership.club.get_membership_for(user) == membership member_group = self.club.unix_name + settings.SITH_MEMBER_SUFFIX board_group = self.club.unix_name + settings.SITH_BOARD_SUFFIX - self.assertTrue(user.is_in_group(name=member_group)) - self.assertTrue(user.is_in_group(name=board_group)) + assert user.is_in_group(name=member_group) + assert user.is_in_group(name=board_group) - def assert_membership_just_ended(self, user: User): + def assert_membership_ended_today(self, user: User): """ Assert that the given user have a membership which ended today """ today = localtime(now()).date() - self.assertIsNotNone( - user.memberships.filter(club=self.club, end_date=today).first() - ) - self.assertIsNone(self.club.get_membership_for(user)) + assert user.memberships.filter(club=self.club, end_date=today).exists() + assert self.club.get_membership_for(user) is None def test_access_unauthorized(self): """ @@ -218,20 +216,20 @@ class ClubModelTest(ClubTest): cannot see the page """ response = self.client.post(self.members_url) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 - self.client.login(username="public", password="plop") + self.client.force_login(self.public) response = self.client.post(self.members_url) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 def test_display(self): """ Test that a GET request return a page where the requested information are displayed. """ - self.client.login(username=self.skia.username, password="plop") + self.client.force_login(self.skia) response = self.client.get(self.members_url) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 expected_html = ( "
    M\\xc3\\xa9thode de paiementM\\xc3\\xa9thode de paiementM\xc3\xa9thode de paiementM\xc3\xa9thode de paiementLe fantome de la nuit
    " "" @@ -264,20 +262,20 @@ class ClubModelTest(ClubTest): """ Test that root users can add members to clubs, one at a time """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( self.members_url, {"users": self.subscriber.id, "role": 3}, ) self.assertRedirects(response, self.members_url) self.subscriber.refresh_from_db() - self.assert_membership_just_started(self.subscriber, role=3) + self.assert_membership_started_today(self.subscriber, role=3) def test_root_add_multiple_club_member(self): """ Test that root users can add multiple members at once to clubs """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( self.members_url, { @@ -287,36 +285,36 @@ class ClubModelTest(ClubTest): ) self.assertRedirects(response, self.members_url) self.subscriber.refresh_from_db() - self.assert_membership_just_started(self.subscriber, role=3) - self.assert_membership_just_started(self.krophil, role=3) + self.assert_membership_started_today(self.subscriber, role=3) + self.assert_membership_started_today(self.krophil, role=3) def test_add_unauthorized_members(self): """ Test that users who are not currently subscribed cannot be members of clubs. """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( self.members_url, {"users": self.public.id, "role": 1}, ) - self.assertIsNone(self.public.memberships.filter(club=self.club).first()) - self.assertTrue('
    • ' in str(response.content)) + assert not self.public.memberships.filter(club=self.club).exists() + assert '
      • ' in response.content.decode() response = self.client.post( self.members_url, {"users": self.old_subscriber.id, "role": 1}, ) - self.assertIsNone(self.public.memberships.filter(club=self.club).first()) - self.assertIsNone(self.club.get_membership_for(self.public)) - self.assertTrue('
        • ' in str(response.content)) + assert not self.public.memberships.filter(club=self.club).exists() + assert self.club.get_membership_for(self.public) is None + assert '
          • ' in response.content.decode() def test_add_members_already_members(self): """ Test that users who are already members of a club cannot be added again to this club """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) current_membership = self.skia.memberships.ongoing().get(club=self.club) nb_memberships = self.skia.memberships.count() self.client.post( @@ -324,10 +322,10 @@ class ClubModelTest(ClubTest): {"users": self.skia.id, "role": current_membership.role + 1}, ) self.skia.refresh_from_db() - self.assertEqual(nb_memberships, self.skia.memberships.count()) + assert nb_memberships == self.skia.memberships.count() new_membership = self.skia.memberships.ongoing().get(club=self.club) - self.assertEqual(current_membership, new_membership) - self.assertEqual(self.club.get_membership_for(self.skia), new_membership) + assert current_membership == new_membership + assert self.club.get_membership_for(self.skia) == new_membership def test_add_not_existing_users(self): """ @@ -335,15 +333,16 @@ class ClubModelTest(ClubTest): If one user in the request is invalid, no membership creation at all can take place. """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) nb_memberships = self.club.members.count() response = self.client.post( self.members_url, {"users": [9999], "role": 1}, ) - self.assertContains(response, '
            • ') + assert response.status_code == 200 + assert '
              • ' in response.content.decode() self.club.refresh_from_db() - self.assertEqual(self.club.members.count(), nb_memberships) + assert self.club.members.count() == nb_memberships response = self.client.post( self.members_url, { @@ -352,9 +351,10 @@ class ClubModelTest(ClubTest): "role": 3, }, ) - self.assertContains(response, '
                • ') + assert response.status_code == 200 + assert '
                  • ' in response.content.decode() self.club.refresh_from_db() - self.assertEqual(self.club.members.count(), nb_memberships) + assert self.club.members.count() == nb_memberships def test_president_add_members(self): """ @@ -363,7 +363,7 @@ class ClubModelTest(ClubTest): president = self.club.members.get(role=10).user nb_club_membership = self.club.members.count() nb_subscriber_memberships = self.subscriber.memberships.count() - self.client.login(username=president.username, password="plop") + self.client.force_login(president) response = self.client.post( self.members_url, {"users": self.subscriber.id, "role": 9}, @@ -371,56 +371,55 @@ class ClubModelTest(ClubTest): self.assertRedirects(response, self.members_url) self.club.refresh_from_db() self.subscriber.refresh_from_db() - self.assertEqual(self.club.members.count(), nb_club_membership + 1) - self.assertEqual( - self.subscriber.memberships.count(), nb_subscriber_memberships + 1 - ) - self.assert_membership_just_started(self.subscriber, role=9) + assert self.club.members.count() == nb_club_membership + 1 + assert self.subscriber.memberships.count() == nb_subscriber_memberships + 1 + self.assert_membership_started_today(self.subscriber, role=9) def test_add_member_greater_role(self): """ Test that a member of the club member cannot create a membership with a greater role than its own. """ - self.client.login(username=self.skia.username, password="plop") + self.client.force_login(self.skia) nb_memberships = self.club.members.count() response = self.client.post( self.members_url, {"users": self.subscriber.id, "role": 10}, ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertInHTML( "
                  • Vous n'avez pas la permission de faire cela
                  • ", response.content.decode(), ) self.club.refresh_from_db() - self.assertEqual(nb_memberships, self.club.members.count()) - self.assertIsNone(self.subscriber.memberships.filter(club=self.club).first()) + assert nb_memberships == self.club.members.count() + assert not self.subscriber.memberships.filter(club=self.club).exists() def test_add_member_without_role(self): """ Test that trying to add members without specifying their role fails """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( self.members_url, {"users": self.subscriber.id, "start_date": "12/06/2016"}, ) - self.assertTrue( - '
                    • Vous devez choisir un r' in str(response.content) + assert ( + '
                      • Vous devez choisir un r' + in response.content.decode() ) def test_end_membership_self(self): """ Test that a member can end its own membership """ - self.client.login(username="skia", password="plop") + self.client.force_login(self.skia) self.client.post( self.members_url, {"users_old": self.skia.id}, ) self.skia.refresh_from_db() - self.assert_membership_just_ended(self.skia) + self.assert_membership_ended_today(self.skia) def test_end_membership_lower_role(self): """ @@ -428,14 +427,14 @@ class ClubModelTest(ClubTest): of users with lower roles """ # remainder : skia has role 3, comptable has role 10, richard has role 1 - self.client.login(username=self.skia.username, password="plop") + self.client.force_login(self.skia) response = self.client.post( self.members_url, {"users_old": self.richard.id}, ) self.assertRedirects(response, self.members_url) self.club.refresh_from_db() - self.assert_membership_just_ended(self.richard) + self.assert_membership_ended_today(self.richard) def test_end_membership_higher_role(self): """ @@ -443,18 +442,18 @@ class ClubModelTest(ClubTest): of users with higher roles """ membership = self.comptable.memberships.filter(club=self.club).first() - self.client.login(username=self.skia.username, password="plop") + self.client.force_login(self.skia) self.client.post( self.members_url, {"users_old": self.comptable.id}, ) self.club.refresh_from_db() new_membership = self.club.get_membership_for(self.comptable) - self.assertIsNotNone(new_membership) - self.assertEqual(new_membership, membership) + assert new_membership is not None + assert new_membership == membership membership = self.comptable.memberships.filter(club=self.club).first() - self.assertIsNone(membership.end_date) + assert membership.end_date is None def test_end_membership_as_main_club_board(self): """ @@ -466,29 +465,29 @@ class ClubModelTest(ClubTest): Membership.objects.create(club=self.ae, user=self.subscriber, role=3) nb_memberships = self.club.members.count() - self.client.login(username=self.subscriber.username, password="plop") + self.client.force_login(self.subscriber) response = self.client.post( self.members_url, {"users_old": self.comptable.id}, ) self.assertRedirects(response, self.members_url) - self.assert_membership_just_ended(self.comptable) - self.assertEqual(self.club.members.ongoing().count(), nb_memberships - 1) + self.assert_membership_ended_today(self.comptable) + assert self.club.members.ongoing().count() == nb_memberships - 1 def test_end_membership_as_root(self): """ Test that root users can end the membership of anyone """ nb_memberships = self.club.members.count() - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( self.members_url, {"users_old": [self.comptable.id]}, ) self.assertRedirects(response, self.members_url) - self.assert_membership_just_ended(self.comptable) - self.assertEqual(self.club.members.ongoing().count(), nb_memberships - 1) - self.assertEqual(self.club.members.count(), nb_memberships) + self.assert_membership_ended_today(self.comptable) + assert self.club.members.ongoing().count() == nb_memberships - 1 + assert self.club.members.count() == nb_memberships def test_end_membership_as_foreigner(self): """ @@ -496,16 +495,15 @@ class ClubModelTest(ClubTest): """ nb_memberships = self.club.members.count() membership = self.richard.memberships.filter(club=self.club).first() - self.client.login(username="subscriber", password="root") + self.client.force_login(self.subscriber) self.client.post( self.members_url, {"users_old": [self.richard.id]}, ) # nothing should have changed new_mem = self.club.get_membership_for(self.richard) - self.assertIsNotNone(new_mem) - self.assertEqual(self.club.members.count(), nb_memberships) - self.assertEqual(membership, new_mem) + assert self.club.members.count() == nb_memberships + assert membership == new_mem def test_delete_remove_from_meta_group(self): """ @@ -518,7 +516,7 @@ class ClubModelTest(ClubTest): self.club.delete() for user in users: - self.assertFalse(user.is_in_group(name=meta_group)) + assert not user.is_in_group(name=meta_group) def test_add_to_meta_group(self): """ @@ -526,11 +524,11 @@ class ClubModelTest(ClubTest): """ group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX - self.assertFalse(self.subscriber.is_in_group(name=group_members)) - self.assertFalse(self.subscriber.is_in_group(name=board_members)) + assert not self.subscriber.is_in_group(name=group_members) + assert not self.subscriber.is_in_group(name=board_members) Membership.objects.create(club=self.club, user=self.subscriber, role=3) - self.assertTrue(self.subscriber.is_in_group(name=group_members)) - self.assertTrue(self.subscriber.is_in_group(name=board_members)) + assert self.subscriber.is_in_group(name=group_members) + assert self.subscriber.is_in_group(name=board_members) def test_remove_from_meta_group(self): """ @@ -538,24 +536,24 @@ class ClubModelTest(ClubTest): """ group_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX board_members = self.club.unix_name + settings.SITH_BOARD_SUFFIX - self.assertTrue(self.comptable.is_in_group(name=group_members)) - self.assertTrue(self.comptable.is_in_group(name=board_members)) + assert self.comptable.is_in_group(name=group_members) + assert self.comptable.is_in_group(name=board_members) self.comptable.memberships.update(end_date=localtime(now())) - self.assertFalse(self.comptable.is_in_group(name=group_members)) - self.assertFalse(self.comptable.is_in_group(name=board_members)) + assert not self.comptable.is_in_group(name=group_members) + assert not self.comptable.is_in_group(name=board_members) def test_club_owner(self): """ Test that a club is owned only by board members of the main club """ anonymous = AnonymousUser() - self.assertFalse(self.club.is_owned_by(anonymous)) - self.assertFalse(self.club.is_owned_by(self.subscriber)) + assert not self.club.is_owned_by(anonymous) + assert not self.club.is_owned_by(self.subscriber) # make sli a board member self.sli.memberships.all().delete() Membership(club=self.ae, user=self.sli, role=3).save() - self.assertTrue(self.club.is_owned_by(self.sli)) + assert self.club.is_owned_by(self.sli) class MailingFormTest(TestCase): @@ -563,11 +561,13 @@ class MailingFormTest(TestCase): @classmethod def setUpTestData(cls): - cls.skia = User.objects.filter(username="skia").first() - cls.rbatsbak = User.objects.filter(username="rbatsbak").first() - cls.krophil = User.objects.filter(username="krophil").first() - cls.comunity = User.objects.filter(username="comunity").first() - cls.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first() + cls.skia = User.objects.get(username="skia") + cls.rbatsbak = User.objects.get(username="rbatsbak") + cls.krophil = User.objects.get(username="krophil") + cls.comunity = User.objects.get(username="comunity") + cls.root = User.objects.get(username="root") + cls.bdf = Club.objects.get(unix_name=SITH_BAR_MANAGER["unix_name"]) + cls.mail_url = reverse("club:mailing", kwargs={"club_id": cls.bdf.id}) def setUp(self): Membership( @@ -579,134 +579,125 @@ class MailingFormTest(TestCase): def test_mailing_list_add_no_moderation(self): # Test with Communication admin - self.client.login(username="comunity", password="plop") - self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.client.force_login(self.comunity) + response = self.client.post( + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "foyer"}, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, text="Liste de diffusion foyer@utbm.fr") + self.assertRedirects(response, self.mail_url) + response = self.client.get(self.mail_url) + assert response.status_code == 200 + assert "Liste de diffusion foyer@utbm.fr" in response.content.decode() # Test with Root - self.client.login(username="root", password="plop") + self.client.force_login(self.root) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, text="Liste de diffusion mde@utbm.fr") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + assert "Liste de diffusion mde@utbm.fr" in response.content.decode() def test_mailing_list_add_moderation(self): - self.client.login(username="rbatsbak", password="plop") + self.client.force_login(self.rbatsbak) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertNotContains(response, text="Liste de diffusion mde@utbm.fr") - self.assertContains( - response, text="

                        Listes de diffusions en attente de modération

                        " - ) - self.assertContains(response, "
                      • mde@utbm.fr") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() + assert "Liste de diffusion mde@utbm.fr" not in content + assert "

                        Listes de diffusions en attente de modération

                        " in content + assert "
                      • mde@utbm.fr" in content def test_mailing_list_forbidden(self): # With anonymous user - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) + response = self.client.get(self.mail_url) self.assertContains(response, "", status_code=403) # With user not in club - self.client.login(username="krophil", password="plop") - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, "", status_code=403) + self.client.force_login(self.krophil) + response = self.client.get(self.mail_url) + assert response.status_code == 403 def test_add_new_subscription_fail_not_moderated(self): - self.client.login(username="rbatsbak", password="plop") + self.client.force_login(self.rbatsbak) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, ) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": self.skia.id, "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertNotContains(response, "skia@git.an") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + assert "skia@git.an" not in response.content.decode() def test_add_new_subscription_success(self): # Prepare mailing list - self.client.login(username="comunity", password="plop") + self.client.force_login(self.comunity) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, ) # Add single user self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": self.skia.id, "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, "skia@git.an") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + assert "skia@git.an" in response.content.decode() # Add multiple users self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": "|%s|%s|" % (self.comunity.id, self.rbatsbak.id), "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, "richard@git.an") - self.assertContains(response, "comunity@git.an") - self.assertContains(response, "skia@git.an") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() + assert "richard@git.an" in content + assert "comunity@git.an" in content + assert "skia@git.an" in content # Add arbitrary email self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_email": "arbitrary@git.an", "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, "richard@git.an") - self.assertContains(response, "comunity@git.an") - self.assertContains(response, "skia@git.an") - self.assertContains(response, "arbitrary@git.an") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() + assert "richard@git.an" in content + assert "comunity@git.an" in content + assert "skia@git.an" in content + assert "arbitrary@git.an" in content # Add user and arbitrary email self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_email": "more.arbitrary@git.an", @@ -714,57 +705,61 @@ class MailingFormTest(TestCase): "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) - self.assertContains(response, "richard@git.an") - self.assertContains(response, "comunity@git.an") - self.assertContains(response, "skia@git.an") - self.assertContains(response, "arbitrary@git.an") - self.assertContains(response, "more.arbitrary@git.an") - self.assertContains(response, "krophil@git.an") + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() + assert "richard@git.an" in content + assert "comunity@git.an" in content + assert "skia@git.an" in content + assert "arbitrary@git.an" in content + assert "more.arbitrary@git.an" in content + assert "krophil@git.an" in content def test_add_new_subscription_fail_form_errors(self): # Prepare mailing list - self.client.login(username="comunity", password="plop") + self.client.force_login(self.comunity) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, ) # Neither email or email is specified response = self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - self.assertContains( - response, text=_("You must specify at least an user or an email address") + assert response.status_code + self.assertInHTML( + _("You must specify at least an user or an email address"), + response.content.decode(), ) # No mailing specified response = self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": self.krophil.id, }, ) - self.assertContains(response, text=_("This field is required")) + assert response.status_code == 200 + assert _("This field is required") in response.content.decode() # One of the selected users doesn't exist response = self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": "|789|", "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - self.assertContains( - response, text=html.escape(_("One of the selected users doesn't exist")) + assert response.status_code == 200 + self.assertInHTML( + _("One of the selected users doesn't exist"), response.content.decode() ) # An user has no email adress @@ -772,18 +767,17 @@ class MailingFormTest(TestCase): self.krophil.save() response = self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": self.krophil.id, "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - self.assertContains( - response, - text=html.escape( - _("One of the selected users doesn't have an email address") - ), + assert response.status_code == 200 + self.assertInHTML( + _("One of the selected users doesn't have an email address"), + response.content.decode(), ) self.krophil.email = "krophil@git.an" @@ -792,7 +786,7 @@ class MailingFormTest(TestCase): # An user is added twice self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": self.krophil.id, @@ -801,28 +795,29 @@ class MailingFormTest(TestCase): ) response = self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": self.krophil.id, "subscription_mailing": Mailing.objects.get(email="mde").id, }, ) - self.assertContains( - response, - text=html.escape(_("This email is already suscribed in this mailing")), + assert response.status_code == 200 + self.assertInHTML( + _("This email is already suscribed in this mailing"), + response.content.decode(), ) def test_remove_subscription_success(self): # Prepare mailing list - self.client.login(username="comunity", password="plop") + self.client.force_login(self.comunity) self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, ) mde = Mailing.objects.get(email="mde") self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_NEW_SUBSCRIPTION, "subscription_users": "|%s|%s|%s|" @@ -831,33 +826,33 @@ class MailingFormTest(TestCase): }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() - self.assertContains(response, "comunity@git.an") - self.assertContains(response, "richard@git.an") - self.assertContains(response, "krophil@git.an") + assert "comunity@git.an" in content + assert "richard@git.an" in content + assert "krophil@git.an" in content # Delete one user self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_REMOVE_SUBSCRIPTION, "removal_%d" % mde.id: mde.subscriptions.get(user=self.krophil).id, }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() - self.assertContains(response, "comunity@git.an") - self.assertContains(response, "richard@git.an") - self.assertNotContains(response, "krophil@git.an") + assert "comunity@git.an" in content + assert "richard@git.an" in content + assert "krophil@git.an" not in content # Delete multiple users self.client.post( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + self.mail_url, { "action": MailingForm.ACTION_REMOVE_SUBSCRIPTION, "removal_%d" % mde.id: [ @@ -868,13 +863,13 @@ class MailingFormTest(TestCase): ], }, ) - response = self.client.get( - reverse("club:mailing", kwargs={"club_id": self.bdf.id}) - ) + response = self.client.get(self.mail_url) + assert response.status_code == 200 + content = response.content.decode() - self.assertNotContains(response, "comunity@git.an") - self.assertNotContains(response, "richard@git.an") - self.assertNotContains(response, "krophil@git.an") + assert "comunity@git.an" not in content + assert "richard@git.an" not in content + assert "krophil@git.an" not in content class ClubSellingViewTest(TestCase): @@ -882,15 +877,17 @@ class ClubSellingViewTest(TestCase): Perform basics tests to ensure that the page is available """ - def setUp(self): - self.ae = Club.objects.filter(unix_name="ae").first() + @classmethod + def setUpTestData(cls): + cls.ae = Club.objects.get(unix_name="ae") + cls.skia = User.objects.get(username="skia") def test_page_not_internal_error(self): """ Test that the page does not return and internal error """ - self.client.login(username="skia", password="plop") + self.client.force_login(self.skia) response = self.client.get( reverse("club:club_sellings", kwargs={"club_id": self.ae.id}) ) - self.assertFalse(response.status_code == 500) + assert response.status_code == 200 diff --git a/club/views.py b/club/views.py index dce1c86f..1d738b8e 100644 --- a/club/views.py +++ b/club/views.py @@ -603,7 +603,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): } return kwargs - def add_new_mailing(self, cleaned_data) -> ValidationError: + def add_new_mailing(self, cleaned_data) -> ValidationError | None: """ Create a new mailing list from the form """ @@ -620,7 +620,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): mailing.save() return None - def add_new_subscription(self, cleaned_data) -> ValidationError: + def add_new_subscription(self, cleaned_data) -> ValidationError | None: """ Add mailing subscriptions for each user given and/or for the specified email in form """ diff --git a/com/tests.py b/com/tests.py index 0cd01a1d..df374161 100644 --- a/com/tests.py +++ b/com/tests.py @@ -13,6 +13,7 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # +import pytest from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase @@ -26,37 +27,39 @@ from com.models import News, Poster, Sith, Weekmail, WeekmailArticle from core.models import AnonymousUser, RealGroup, User -class ComAlertTest(TestCase): - def test_page_is_working(self): - self.client.login(username="comunity", password="plop") - response = self.client.get(reverse("com:alert_edit")) - self.assertNotEqual(response.status_code, 500) - self.assertEqual(response.status_code, 200) +@pytest.fixture() +def user_community(): + return User.objects.get(username="comunity") -class ComInfoTest(TestCase): - def test_page_is_working(self): - self.client.login(username="comunity", password="plop") - response = self.client.get(reverse("com:info_edit")) - self.assertNotEqual(response.status_code, 500) - self.assertEqual(response.status_code, 200) +@pytest.mark.django_db +@pytest.mark.parametrize( + "url", + [ + reverse("com:alert_edit"), + reverse("com:info_edit"), + ], +) +def test_com_page_is_working(client, url, user_community): + client.force_login(user_community) + response = client.get(url) + assert response.status_code == 200 class ComTest(TestCase): @classmethod def setUpTestData(cls): - cls.skia = User.objects.filter(username="skia").first() + cls.skia = User.objects.get(username="skia") cls.com_group = RealGroup.objects.filter( id=settings.SITH_GROUP_COM_ADMIN_ID ).first() cls.skia.groups.set([cls.com_group]) - cls.skia.save() def setUp(self): - self.client.login(username=self.skia.username, password="plop") + self.client.force_login(self.skia) def test_alert_msg(self): - response = self.client.post( + self.client.post( reverse("com:alert_edit"), { "alert_msg": """ @@ -67,7 +70,6 @@ class ComTest(TestCase): }, ) r = self.client.get(reverse("core:index")) - self.assertTrue(r.status_code == 200) self.assertContains( r, """
                        @@ -76,7 +78,7 @@ class ComTest(TestCase): ) def test_info_msg(self): - response = self.client.post( + self.client.post( reverse("com:info_edit"), { "info_msg": """ @@ -85,7 +87,6 @@ class ComTest(TestCase): }, ) r = self.client.get(reverse("core:index")) - self.assertTrue(r.status_code == 200) self.assertContains( r, """
                        @@ -93,7 +94,7 @@ class ComTest(TestCase): ) def test_birthday_non_subscribed_user(self): - self.client.login(username="guy", password="plop") + self.client.force_login(User.objects.get(username="guy")) response = self.client.get(reverse("core:index")) self.assertContains( response, @@ -122,13 +123,13 @@ class SithTest(TestCase): sith: Sith = Sith.objects.first() com_admin = User.objects.get(username="comunity") - self.assertTrue(sith.is_owned_by(com_admin)) + assert sith.is_owned_by(com_admin) anonymous = AnonymousUser() - self.assertFalse(sith.is_owned_by(anonymous)) + assert not sith.is_owned_by(anonymous) sli = User.objects.get(username="sli") - self.assertFalse(sith.is_owned_by(sli)) + assert not sith.is_owned_by(sli) class NewsTest(TestCase): @@ -153,10 +154,10 @@ class NewsTest(TestCase): or by their author but nobody else """ - self.assertTrue(self.new.is_owned_by(self.com_admin)) - self.assertTrue(self.new.is_owned_by(self.author)) - self.assertFalse(self.new.is_owned_by(self.anonymous)) - self.assertFalse(self.new.is_owned_by(self.sli)) + assert self.new.is_owned_by(self.com_admin) + assert self.new.is_owned_by(self.author) + assert not self.new.is_owned_by(self.anonymous) + assert not self.new.is_owned_by(self.sli) def test_news_viewer(self): """ @@ -164,26 +165,26 @@ class NewsTest(TestCase): and not moderated news only by com admins """ # by default a news isn't moderated - self.assertTrue(self.new.can_be_viewed_by(self.com_admin)) - self.assertFalse(self.new.can_be_viewed_by(self.sli)) - self.assertFalse(self.new.can_be_viewed_by(self.anonymous)) - self.assertFalse(self.new.can_be_viewed_by(self.author)) + assert self.new.can_be_viewed_by(self.com_admin) + assert not self.new.can_be_viewed_by(self.sli) + assert not self.new.can_be_viewed_by(self.anonymous) + assert not self.new.can_be_viewed_by(self.author) self.new.is_moderated = True self.new.save() - self.assertTrue(self.new.can_be_viewed_by(self.com_admin)) - self.assertTrue(self.new.can_be_viewed_by(self.sli)) - self.assertTrue(self.new.can_be_viewed_by(self.anonymous)) - self.assertTrue(self.new.can_be_viewed_by(self.author)) + assert self.new.can_be_viewed_by(self.com_admin) + assert self.new.can_be_viewed_by(self.sli) + assert self.new.can_be_viewed_by(self.anonymous) + assert self.new.can_be_viewed_by(self.author) def test_news_editor(self): """ Test that only com admins can edit news """ - self.assertTrue(self.new.can_be_edited_by(self.com_admin)) - self.assertFalse(self.new.can_be_edited_by(self.sli)) - self.assertFalse(self.new.can_be_edited_by(self.anonymous)) - self.assertFalse(self.new.can_be_edited_by(self.author)) + assert self.new.can_be_edited_by(self.com_admin) + assert not self.new.can_be_edited_by(self.sli) + assert not self.new.can_be_edited_by(self.anonymous) + assert not self.new.can_be_edited_by(self.author) class WeekmailArticleTest(TestCase): @@ -206,10 +207,10 @@ class WeekmailArticleTest(TestCase): """ Test that weekmails are owned only by com admins """ - self.assertTrue(self.article.is_owned_by(self.com_admin)) - self.assertFalse(self.article.is_owned_by(self.author)) - self.assertFalse(self.article.is_owned_by(self.anonymous)) - self.assertFalse(self.article.is_owned_by(self.sli)) + assert self.article.is_owned_by(self.com_admin) + assert not self.article.is_owned_by(self.author) + assert not self.article.is_owned_by(self.anonymous) + assert not self.article.is_owned_by(self.sli) class PosterTest(TestCase): @@ -232,8 +233,8 @@ class PosterTest(TestCase): """ Test that poster are owned by com admins and board members in clubs """ - self.assertTrue(self.poster.is_owned_by(self.com_admin)) - self.assertFalse(self.poster.is_owned_by(self.anonymous)) + assert self.poster.is_owned_by(self.com_admin) + assert not self.poster.is_owned_by(self.anonymous) - self.assertFalse(self.poster.is_owned_by(self.susbcriber)) - self.assertTrue(self.poster.is_owned_by(self.sli)) + assert not self.poster.is_owned_by(self.susbcriber) + assert self.poster.is_owned_by(self.sli) diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..fb67017d --- /dev/null +++ b/conftest.py @@ -0,0 +1,14 @@ +import pytest +from django.core.management import call_command +from django.utils.translation import activate + + +@pytest.fixture(scope="session") +def django_db_setup(django_db_setup, django_db_blocker): + with django_db_blocker.unblock(): + call_command("setup") + + +@pytest.fixture(scope="session", autouse=True) +def set_default_language(): + activate("fr") diff --git a/core/tests.py b/core/tests.py index 61d497a5..733c0c38 100644 --- a/core/tests.py +++ b/core/tests.py @@ -18,10 +18,12 @@ import os from datetime import date, timedelta import freezegun +import pytest from django.core.cache import cache -from django.test import Client, TestCase +from django.test import TestCase from django.urls import reverse from django.utils.timezone import now +from pytest_django.asserts import assertRedirects from club.models import Membership from core.markdown import markdown @@ -29,270 +31,105 @@ from core.models import AnonymousUser, Group, Page, User from core.utils import get_semester_code, get_start_of_semester from sith import settings -""" -to run these tests : - python3 manage.py test -""" + +@pytest.mark.django_db +class TestUserRegistration: + @pytest.fixture() + def valid_payload(self): + return { + "first_name": "this user does not exist (yet)", + "last_name": "this user does not exist (yet)", + "email": "i-dont-exist-yet@git.an", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + } + + def test_register_user_form_ok(self, client, valid_payload): + """Should register a user correctly.""" + response = client.post(reverse("core:register"), valid_payload) + assert response.status_code == 200 + assert "TEST_REGISTER_USER_FORM_OK" in str(response.content) + + @pytest.mark.parametrize( + "payload_edit", + [ + {"password2": "not the same as password1"}, + {"email": "not-an-email"}, + {"first_name": ""}, + {"last_name": ""}, + {"captcha_1": "WRONG_CAPTCHA"}, + ], + ) + def test_register_user_form_fail(self, client, valid_payload, payload_edit): + """Should not register a user correctly.""" + payload = valid_payload | payload_edit + response = client.post(reverse("core:register"), payload) + assert response.status_code == 200 + assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) + + def test_register_user_form_fail_already_exists(self, client, valid_payload): + """Should not register a user correctly if it already exists.""" + # create the user, then try to create it again + client.post(reverse("core:register"), valid_payload) + response = client.post(reverse("core:register"), valid_payload) + assert response.status_code == 200 + assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content) -class UserRegistrationTest(TestCase): - @classmethod - def setUpTestData(cls): - User.objects.all().delete() +@pytest.mark.django_db +class TestUserLogin: + @pytest.fixture() + def user(self) -> User: + return User.objects.first() - def test_register_user_form_ok(self): - """ - Should register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "Carlier", - "email": "guy@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_OK" in str(response.content)) - - def test_register_user_form_fail_password(self): - """ - Should not register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop2", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_register_user_form_fail_email(self): - """ - Should not register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "Carlier", - "email": "bibou.git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_register_user_form_fail_missing_name(self): - """ - Should not register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_register_user_form_fail_missing_date_of_birth(self): - """ - Should not register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_register_user_form_fail_missing_first_name(self): - """ - Should not register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_register_user_form_fail_wrong_captcha(self): - """ - Should not register a user correctly - """ - c = Client() - response = c.post( - reverse("core:register"), - { - "first_name": "Bibou", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "WRONG_CAPTCHA", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_register_user_form_fail_already_exists(self): - """ - Should not register a user correctly - """ - c = Client() - c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - response = c.post( - reverse("core:register"), - { - "first_name": "Bibou", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - self.assertTrue(response.status_code == 200) - self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) - - def test_login_success(self): - """ - Should login a user correctly - """ - c = Client() - c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, - ) - response = c.post( - reverse("core:login"), {"username": "gcarlier", "password": "plop"} - ) - self.assertTrue(response.status_code == 302) - # self.assertTrue('Hello, world' in str(response.content)) - - def test_login_fail(self): + def test_login_fail(self, client, user): """ Should not login a user correctly """ - c = Client() - c.post( - reverse("core:register"), - { - "first_name": "Guy", - "last_name": "Carlier", - "email": "bibou@git.an", - "date_of_birth": "12/6/1942", - "password1": "plop", - "password2": "plop", - "captcha_0": "dummy-value", - "captcha_1": "PASSED", - }, + + response = client.post( + reverse("core:login"), + {"username": user.username, "password": "wrong-password"}, ) - response = c.post( - reverse("core:login"), {"username": "gcarlier", "password": "guy"} - ) - self.assertTrue(response.status_code == 200) - self.assertTrue( - """

                        Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

                        """ - in str(response.content) + assert response.status_code == 200 + assert ( + '

                        Votre nom d\'utilisateur ' + "et votre mot de passe ne correspondent pas. Merci de réessayer.

                        " + ) in str(response.content.decode()) + + def test_login_success(self, client, user): + """ + Should login a user correctly + """ + response = client.post( + reverse("core:login"), {"username": user.username, "password": "plop"} ) + assertRedirects(response, reverse("core:index")) -class MarkdownTest(TestCase): - def test_full_markdown_syntax(self): - root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file: - md = md_file.read() - with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file: - html = html_file.read() - result = markdown(md) - self.assertTrue(result == html) +def test_full_markdown_syntax(): + root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file: + md = md_file.read() + with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file: + html = html_file.read() + result = markdown(md) + assert result == html class PageHandlingTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.root = User.objects.get(username="root") + cls.root_group = Group.objects.get(name="Root") + def setUp(self): - self.client.login(username="root", password="plop") - self.root_group = Group.objects.get(name="Root") + self.client.force_login(self.root) def test_create_page_ok(self): - """ - Should create a page correctly - """ + """Should create a page correctly.""" response = self.client.post( reverse("core:page_new"), @@ -301,19 +138,17 @@ class PageHandlingTest(TestCase): self.assertRedirects( response, reverse("core:page", kwargs={"page_name": "guy"}) ) - self.assertTrue(Page.objects.filter(name="guy").exists()) + assert Page.objects.filter(name="guy").exists() response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 html = response.content.decode() - self.assertIn('', html) - self.assertIn('', html) - self.assertIn('', html) + assert '' in html + assert '' in html + assert '' in html def test_create_child_page_ok(self): - """ - Should create a page correctly - """ + """Should create a page correctly.""" # remove all other pages to make sure there is no side effect Page.objects.all().delete() self.client.post( @@ -321,7 +156,7 @@ class PageHandlingTest(TestCase): {"parent": "", "name": "guy", "owner_group": str(self.root_group.id)}, ) page = Page.objects.first() - response = self.client.post( + self.client.post( reverse("core:page_new"), { "parent": str(page.id), @@ -332,8 +167,8 @@ class PageHandlingTest(TestCase): response = self.client.get( reverse("core:page", kwargs={"page_name": "guy/bibou"}) ) - self.assertTrue(response.status_code == 200) - self.assertTrue('' in str(response.content)) + assert response.status_code == 200 + assert '' in str(response.content) def test_access_child_page_ok(self): """ @@ -346,7 +181,7 @@ class PageHandlingTest(TestCase): response = self.client.get( reverse("core:page", kwargs={"page_name": "guy/bibou"}) ) - self.assertTrue(response.status_code == 200) + assert response.status_code == 200 html = response.content.decode() self.assertIn('', html) @@ -355,7 +190,7 @@ class PageHandlingTest(TestCase): Should not display a page correctly """ response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"})) - self.assertTrue(response.status_code == 200) + assert response.status_code == 200 html = response.content.decode() self.assertIn('', html) @@ -383,8 +218,8 @@ http://git.an }, ) response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) - self.assertTrue(response.status_code == 200) - self.assertTrue( + assert response.status_code == 200 + assert ( '

                        Guy bibou

                        \\n

                        http://git.an

                        \\n' + "

                        Swag

                        \\n<guy>Bibou</guy>" + "<script>alert(\\'Guy\\');</script>" @@ -392,35 +227,19 @@ http://git.an ) -class UserToolsTest(TestCase): - def test_anonymous_user_unauthorized(self): - response = self.client.get(reverse("core:user_tools")) - self.assertEqual(response.status_code, 403) +class UserToolsTest: + def test_anonymous_user_unauthorized(self, client): + """An anonymous user shouldn't have access to the tools page""" + response = client.get(reverse("core:user_tools")) + assert response.status_code == 403 - def test_page_is_working(self): + @pytest.mark.parametrize("username", ["guy", "root", "skia", "comunity"]) + def test_page_is_working(self, client, username): + """All existing users should be able to see the test page""" # Test for simple user - self.client.login(username="guy", password="plop") - response = self.client.get(reverse("core:user_tools")) - self.assertNotEqual(response.status_code, 500) - self.assertEqual(response.status_code, 200) - - # Test for root - self.client.login(username="root", password="plop") - response = self.client.get(reverse("core:user_tools")) - self.assertNotEqual(response.status_code, 500) - self.assertEqual(response.status_code, 200) - - # Test for skia - self.client.login(username="skia", password="plop") - response = self.client.get(reverse("core:user_tools")) - self.assertNotEqual(response.status_code, 500) - self.assertEqual(response.status_code, 200) - - # Test for comunity - self.client.login(username="comunity", password="plop") - response = self.client.get(reverse("core:user_tools")) - self.assertNotEqual(response.status_code, 500) - self.assertEqual(response.status_code, 200) + client.force_login(User.objects.get(username=username)) + response = client.get(reverse("core:user_tools")) + assert response.status_code == 200 # TODO: many tests on the pages: @@ -442,12 +261,12 @@ class FileHandlingTest(TestCase): reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), {"folder_name": "GUY_folder_test"}, ) - self.assertTrue(response.status_code == 302) + assert response.status_code == 302 response = self.client.get( reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) ) - self.assertTrue(response.status_code == 200) - self.assertTrue("GUY_folder_test" in str(response.content)) + assert response.status_code == 200 + assert "GUY_folder_test" in str(response.content) def test_upload_file_home(self): with open("/bin/ls", "rb") as f: @@ -457,12 +276,12 @@ class FileHandlingTest(TestCase): ), {"file_field": f}, ) - self.assertTrue(response.status_code == 302) + assert response.status_code == 302 response = self.client.get( reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) ) - self.assertTrue(response.status_code == 200) - self.assertTrue("ls" in str(response.content)) + assert response.status_code == 200 + assert "ls" in str(response.content) class UserIsInGroupTest(TestCase): @@ -477,6 +296,10 @@ class UserIsInGroupTest(TestCase): cls.root_group = Group.objects.get(name="Root") cls.public = Group.objects.get(name="Public") + cls.skia = User.objects.get(username="skia") + cls.toto = User.objects.create( + username="toto", first_name="a", last_name="b", email="a.b@toto.fr" + ) cls.subscribers = Group.objects.get(name="Subscribers") cls.old_subscribers = Group.objects.get(name="Old subscribers") cls.accounting_admin = Group.objects.get(name="Accounting admin") @@ -493,21 +316,15 @@ class UserIsInGroupTest(TestCase): ) cls.main_club = Club.objects.get(id=1) - def setUp(self) -> None: - self.toto = User.objects.create( - username="toto", first_name="a", last_name="b", email="a.b@toto.fr" - ) - self.skia = User.objects.get(username="skia") - def assert_in_public_group(self, user): - self.assertTrue(user.is_in_group(pk=self.public.id)) - self.assertTrue(user.is_in_group(name=self.public.name)) + assert user.is_in_group(pk=self.public.id) + assert user.is_in_group(name=self.public.name) def assert_in_club_metagroups(self, user, club): meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX - self.assertFalse(user.is_in_group(name=meta_groups_board)) - self.assertFalse(user.is_in_group(name=meta_groups_members)) + assert user.is_in_group(name=meta_groups_board) is False + assert user.is_in_group(name=meta_groups_members) is False def assert_only_in_public_group(self, user): self.assert_in_public_group(user) @@ -519,12 +336,12 @@ class UserIsInGroupTest(TestCase): self.subscribers, self.old_subscribers, ): - self.assertFalse(user.is_in_group(pk=group.pk)) - self.assertFalse(user.is_in_group(name=group.name)) + assert not user.is_in_group(pk=group.pk) + assert not user.is_in_group(name=group.name) meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX - self.assertFalse(user.is_in_group(name=meta_groups_board)) - self.assertFalse(user.is_in_group(name=meta_groups_members)) + assert user.is_in_group(name=meta_groups_board) is False + assert user.is_in_group(name=meta_groups_members) is False def test_anonymous_user(self): """ @@ -583,15 +400,13 @@ class UserIsInGroupTest(TestCase): ) meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX cache.clear() - self.assertTrue(self.toto.is_in_group(name=meta_groups_members)) - self.assertEqual( - membership, cache.get(f"membership_{self.club.id}_{self.toto.id}") - ) + assert self.toto.is_in_group(name=meta_groups_members) is True + assert membership == cache.get(f"membership_{self.club.id}_{self.toto.id}") membership.end_date = now() - timedelta(minutes=5) membership.save() cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}") - self.assertEqual(cached_membership, "not_member") - self.assertFalse(self.toto.is_in_group(name=meta_groups_members)) + assert cached_membership == "not_member" + assert self.toto.is_in_group(name=meta_groups_members) is False def test_cache_properly_cleared_group(self): """ @@ -600,24 +415,24 @@ class UserIsInGroupTest(TestCase): """ # testing with pk self.toto.groups.add(self.com_admin.pk) - self.assertTrue(self.toto.is_in_group(pk=self.com_admin.pk)) + assert self.toto.is_in_group(pk=self.com_admin.pk) is True self.toto.groups.remove(self.com_admin.pk) - self.assertFalse(self.toto.is_in_group(pk=self.com_admin.pk)) + assert self.toto.is_in_group(pk=self.com_admin.pk) is False # testing with name self.toto.groups.add(self.sas_admin.pk) - self.assertTrue(self.toto.is_in_group(name="SAS admin")) + assert self.toto.is_in_group(name="SAS admin") is True self.toto.groups.remove(self.sas_admin.pk) - self.assertFalse(self.toto.is_in_group(name="SAS admin")) + assert self.toto.is_in_group(name="SAS admin") is False def test_not_existing_group(self): """ Test that searching for a not existing group returns False """ - self.assertFalse(self.skia.is_in_group(name="This doesn't exist")) + assert self.skia.is_in_group(name="This doesn't exist") is False class DateUtilsTest(TestCase): @@ -639,29 +454,25 @@ class DateUtilsTest(TestCase): """ Test that the get_semester function returns the correct semester string """ - self.assertEqual(get_semester_code(self.autumn_semester_january), "A24") - self.assertEqual(get_semester_code(self.autumn_semester_september), "A24") - self.assertEqual(get_semester_code(self.autumn_first_day), "A24") + assert get_semester_code(self.autumn_semester_january) == "A24" + assert get_semester_code(self.autumn_semester_september) == "A24" + assert get_semester_code(self.autumn_first_day) == "A24" - self.assertEqual(get_semester_code(self.spring_semester_march), "P23") - self.assertEqual(get_semester_code(self.spring_first_day), "P23") + assert get_semester_code(self.spring_semester_march) == "P23" + assert get_semester_code(self.spring_first_day) == "P23" def test_get_start_of_semester_fixed_date(self): """ Test that the get_start_of_semester correctly the starting date of the semester. """ automn_2024 = date(2024, self.autumn_month, self.autumn_day) - self.assertEqual( - get_start_of_semester(self.autumn_semester_january), automn_2024 - ) - self.assertEqual( - get_start_of_semester(self.autumn_semester_september), automn_2024 - ) - self.assertEqual(get_start_of_semester(self.autumn_first_day), automn_2024) + assert get_start_of_semester(self.autumn_semester_january) == automn_2024 + assert get_start_of_semester(self.autumn_semester_september) == automn_2024 + assert get_start_of_semester(self.autumn_first_day) == automn_2024 spring_2023 = date(2023, self.spring_month, self.spring_day) - self.assertEqual(get_start_of_semester(self.spring_semester_march), spring_2023) - self.assertEqual(get_start_of_semester(self.spring_first_day), spring_2023) + assert get_start_of_semester(self.spring_semester_march) == spring_2023 + assert get_start_of_semester(self.spring_first_day) == spring_2023 def test_get_start_of_semester_today(self): """ @@ -669,10 +480,10 @@ class DateUtilsTest(TestCase): when no date is given """ with freezegun.freeze_time(self.autumn_semester_september): - self.assertEqual(get_start_of_semester(), self.autumn_first_day) + assert get_start_of_semester() == self.autumn_first_day with freezegun.freeze_time(self.spring_semester_march): - self.assertEqual(get_start_of_semester(), self.spring_first_day) + assert get_start_of_semester() == self.spring_first_day def test_get_start_of_semester_changing_date(self): """ @@ -685,8 +496,8 @@ class DateUtilsTest(TestCase): mid_autumn = autumn_2023 + timedelta(days=45) with freezegun.freeze_time(mid_spring) as frozen_time: - self.assertEqual(get_start_of_semester(), spring_2023) + assert get_start_of_semester() == spring_2023 # forward time to the middle of the next semester frozen_time.move_to(mid_autumn) - self.assertEqual(get_start_of_semester(), autumn_2023) + assert get_start_of_semester() == autumn_2023 diff --git a/counter/tests.py b/counter/tests.py index f921a5d4..ddfde22c 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -46,9 +46,7 @@ class CounterTest(TestCase): reverse("counter:details", kwargs={"counter_id": self.mde.id}) ) - self.assertTrue( - 'class="link-button">S' Kia' in str(response.content) - ) + assert 'class="link-button">S' Kia' in str(response.content) counter_token = re.search( r'name="counter_token" value="([^"]*)"', str(response.content) @@ -60,7 +58,7 @@ class CounterTest(TestCase): ) counter_url = response.get("location") response = self.client.get(response.get("location")) - self.assertTrue(">Richard BatsbakRichard BatsbakClient : Richard Batsbak - Nouveau montant : 3.60" - in str(response_content) + assert "2 x Barbar" in str(response_content) + assert "2 x Déconsigne Eco-cup" in str(response_content) + assert "

                        Client : Richard Batsbak - Nouveau montant : 3.60" in str( + response_content ) self.client.post( @@ -111,7 +108,7 @@ class CounterTest(TestCase): "bank": "OTHER", }, ) - self.assertTrue(response.status_code == 200) + assert response.status_code == 200 self.client.post( reverse("counter:login", kwargs={"counter_id": self.foyer.id}), @@ -141,27 +138,26 @@ class CounterTest(TestCase): "bank": "OTHER", }, ) - self.assertTrue(response.status_code == 200) + assert response.status_code == 200 def test_annotate_has_barman_queryset(self): """ Test if the custom queryset method ``annotate_has_barman`` works as intended """ - self.sli.counters.clear() - self.sli.counters.add(self.foyer, self.mde) + self.sli.counters.set([self.foyer, self.mde]) counters = Counter.objects.annotate_has_barman(self.sli) for counter in counters: if counter.name in ("Foyer", "MDE"): - self.assertTrue(counter.has_annotated_barman) + assert counter.has_annotated_barman else: - self.assertFalse(counter.has_annotated_barman) + assert not counter.has_annotated_barman class CounterStatsTest(TestCase): @classmethod def setUpTestData(cls): - cls.counter = Counter.objects.filter(id=2).first() + cls.counter = Counter.objects.get(id=2) cls.krophil = User.objects.get(username="krophil") cls.skia = User.objects.get(username="skia") cls.sli = User.objects.get(username="sli") @@ -262,110 +258,57 @@ class CounterStatsTest(TestCase): def test_not_authenticated_user_fail(self): # Test with not login user response = self.client.get(reverse("counter:stats", args=[self.counter.id])) - self.assertTrue(response.status_code == 403) + assert response.status_code == 403 def test_unauthorized_user_fails(self): - user = User.objects.get(username="public") - self.client.login(username=user.username, password="plop") + self.client.force_login(User.objects.get(username="public")) response = self.client.get(reverse("counter:stats", args=[self.counter.id])) - self.assertTrue(response.status_code == 403) + assert response.status_code == 403 def test_get_total_sales(self): """ Test the result of the Counter.get_total_sales() method """ - total = self.counter.get_total_sales() - self.assertEqual(total, 3102) + assert self.counter.get_total_sales() == 3102 def test_top_barmen(self): """ Test the result of Counter.get_top_barmen() is correct """ - top = iter(self.counter.get_top_barmen()) - self.assertEqual( - next(top), + users = [self.skia, self.root, self.sli] + perm_times = [ + timedelta(days=16, hours=2, minutes=35, seconds=54), + timedelta(hours=21), + timedelta(hours=5), + ] + assert list(self.counter.get_top_barmen()) == [ { - "user": self.skia.id, - "name": f"{self.skia.first_name} {self.skia.last_name}", - "promo": self.skia.promo, - "nickname": self.skia.nick_name, - "perm_sum": timedelta(days=16, hours=2, minutes=35, seconds=54), - }, - ) - self.assertEqual( - next(top), - { - "user": self.root.id, - "name": f"{self.root.first_name} {self.root.last_name}", - "promo": self.root.promo, - "nickname": self.root.nick_name, - "perm_sum": timedelta(hours=21), - }, - ) - self.assertEqual( - next(top), - { - "user": self.sli.id, - "name": f"{self.sli.first_name} {self.sli.last_name}", - "promo": self.sli.promo, - "nickname": self.sli.nick_name, - "perm_sum": timedelta(hours=5), - }, - ) - self.assertIsNone( - next(top, None), msg="barmen with no office hours should not be in the top" - ) + "user": user.id, + "name": f"{user.first_name} {user.last_name}", + "promo": user.promo, + "nickname": user.nick_name, + "perm_sum": perm_time, + } + for user, perm_time in zip(users, perm_times) + ] def test_top_customer(self): """ Test the result of Counter.get_top_customers() is correct """ - top = iter(self.counter.get_top_customers()) - expected_results = [ + users = [self.sli, self.skia, self.krophil, self.root] + sale_amounts = [2000, 1000, 100, 2] + assert list(self.counter.get_top_customers()) == [ { - "user": self.sli.id, - "name": f"{self.sli.first_name} {self.sli.last_name}", - "promo": self.sli.promo, - "nickname": self.sli.nick_name, - "selling_sum": 2000, - }, - { - "user": self.skia.id, - "name": f"{self.skia.first_name} {self.skia.last_name}", - "promo": self.skia.promo, - "nickname": self.skia.nick_name, - "selling_sum": 1000, - }, - { - "user": self.krophil.id, - "name": f"{self.krophil.first_name} {self.krophil.last_name}", - "promo": self.krophil.promo, - "nickname": self.krophil.nick_name, - "selling_sum": 100, - }, - { - "user": self.root.id, - "name": f"{self.root.first_name} {self.root.last_name}", - "promo": self.root.promo, - "nickname": self.root.nick_name, - "selling_sum": 2, - }, + "user": user.id, + "name": f"{user.first_name} {user.last_name}", + "promo": user.promo, + "nickname": user.nick_name, + "selling_sum": sale_amount, + } + for user, sale_amount in zip(users, sale_amounts) ] - for result in expected_results: - self.assertEqual( - next(top), - { - "user": result["user"], - "name": result["name"], - "promo": result["promo"], - "nickname": result["nickname"], - "selling_sum": result["selling_sum"], - }, - ) - - self.assertIsNone(next(top, None)) - class BillingInfoTest(TestCase): @classmethod @@ -386,13 +329,15 @@ class BillingInfoTest(TestCase): "city": "Sète", "country": "FR", } + cls.root = User.objects.get(username="root") + cls.subscriber = User.objects.get(username="subscriber") def test_edit_infos(self): - user = User.objects.get(username="subscriber") + user = self.subscriber BillingInfo.objects.get_or_create( customer=user.customer, defaults=self.payload_1 ) - self.client.login(username=user.username, password="plop") + self.client.force_login(user) response = self.client.post( reverse("counter:edit_billing_info", args=[user.id]), json.dumps(self.payload_2), @@ -400,23 +345,23 @@ class BillingInfoTest(TestCase): ) user = User.objects.get(username="subscriber") infos = BillingInfo.objects.get(customer__user=user) - self.assertEqual(200, response.status_code) + assert response.status_code == 200 self.assertJSONEqual(response.content, {"errors": None}) - self.assertTrue(hasattr(user.customer, "billing_infos")) - self.assertEqual(user.customer, infos.customer) - self.assertEqual("Subscribed", infos.first_name) - self.assertEqual("User", infos.last_name) - self.assertEqual("3, rue de Troyes", infos.address_1) - self.assertEqual(None, infos.address_2) - self.assertEqual("34301", infos.zip_code) - self.assertEqual("Sète", infos.city) - self.assertEqual("FR", infos.country) + assert hasattr(user.customer, "billing_infos") + assert infos.customer == user.customer + assert infos.first_name == "Subscribed" + assert infos.last_name == "User" + assert infos.address_1 == "3, rue de Troyes" + assert infos.address_2 is None + assert infos.zip_code == "34301" + assert infos.city == "Sète" + assert infos.country == "FR" def test_create_infos_for_user_with_account(self): user = User.objects.get(username="subscriber") if hasattr(user.customer, "billing_infos"): user.customer.billing_infos.delete() - self.client.login(username=user.username, password="plop") + self.client.force_login(user) response = self.client.post( reverse("counter:create_billing_info", args=[user.id]), json.dumps(self.payload_1), @@ -424,48 +369,48 @@ class BillingInfoTest(TestCase): ) user = User.objects.get(username="subscriber") infos = BillingInfo.objects.get(customer__user=user) - self.assertEqual(200, response.status_code) + assert response.status_code == 200 self.assertJSONEqual(response.content, {"errors": None}) - self.assertTrue(hasattr(user.customer, "billing_infos")) - self.assertEqual(user.customer, infos.customer) - self.assertEqual("Subscribed", infos.first_name) - self.assertEqual("User", infos.last_name) - self.assertEqual("1 rue des Huns", infos.address_1) - self.assertEqual(None, infos.address_2) - self.assertEqual("90000", infos.zip_code) - self.assertEqual("Belfort", infos.city) - self.assertEqual("FR", infos.country) + assert hasattr(user.customer, "billing_infos") + assert infos.customer == user.customer + assert infos.first_name == "Subscribed" + assert infos.last_name == "User" + assert infos.address_1 == "1 rue des Huns" + assert infos.address_2 is None + assert infos.zip_code == "90000" + assert infos.city == "Belfort" + assert infos.country == "FR" def test_create_infos_for_user_without_account(self): user = User.objects.get(username="subscriber") if hasattr(user, "customer"): user.customer.delete() - self.client.login(username=user.username, password="plop") + self.client.force_login(user) response = self.client.post( reverse("counter:create_billing_info", args=[user.id]), json.dumps(self.payload_1), content_type="application/json", ) user = User.objects.get(username="subscriber") - self.assertTrue(hasattr(user, "customer")) - self.assertTrue(hasattr(user.customer, "billing_infos")) - self.assertEqual(200, response.status_code) + assert hasattr(user, "customer") + assert hasattr(user.customer, "billing_infos") + assert response.status_code == 200 self.assertJSONEqual(response.content, {"errors": None}) infos = BillingInfo.objects.get(customer__user=user) self.assertEqual(user.customer, infos.customer) - self.assertEqual("Subscribed", infos.first_name) - self.assertEqual("User", infos.last_name) - self.assertEqual("1 rue des Huns", infos.address_1) - self.assertEqual(None, infos.address_2) - self.assertEqual("90000", infos.zip_code) - self.assertEqual("Belfort", infos.city) - self.assertEqual("FR", infos.country) + assert infos.first_name == "Subscribed" + assert infos.last_name == "User" + assert infos.address_1 == "1 rue des Huns" + assert infos.address_2 is None + assert infos.zip_code == "90000" + assert infos.city == "Belfort" + assert infos.country == "FR" def test_create_invalid(self): user = User.objects.get(username="subscriber") if hasattr(user.customer, "billing_infos"): user.customer.billing_infos.delete() - self.client.login(username=user.username, password="plop") + self.client.force_login(user) # address_1, zip_code and country are missing payload = { "first_name": user.first_name, @@ -479,7 +424,7 @@ class BillingInfoTest(TestCase): ) user = User.objects.get(username="subscriber") self.assertEqual(400, response.status_code) - self.assertFalse(hasattr(user.customer, "billing_infos")) + assert not hasattr(user.customer, "billing_infos") expected_errors = { "errors": [ {"field": "Adresse 1", "messages": ["Ce champ est obligatoire."]}, @@ -494,7 +439,7 @@ class BillingInfoTest(TestCase): BillingInfo.objects.get_or_create( customer=user.customer, defaults=self.payload_1 ) - self.client.login(username=user.username, password="plop") + self.client.force_login(user) # address_1, zip_code and country are missing payload = { "first_name": user.first_name, @@ -508,7 +453,7 @@ class BillingInfoTest(TestCase): ) user = User.objects.get(username="subscriber") self.assertEqual(400, response.status_code) - self.assertTrue(hasattr(user.customer, "billing_infos")) + assert hasattr(user.customer, "billing_infos") expected_errors = { "errors": [ {"field": "Adresse 1", "messages": ["Ce champ est obligatoire."]}, @@ -535,7 +480,7 @@ class BillingInfoTest(TestCase): user = User.objects.get(username="subscriber") if hasattr(user.customer, "billing_infos"): user.customer.billing_infos.delete() - self.client.login(username=user.username, password="plop") + self.client.force_login(user) response = self.client.post( reverse("counter:edit_billing_info", args=[user.id]), json.dumps(self.payload_2), @@ -548,17 +493,17 @@ class BillingInfoTest(TestCase): BillingInfo.objects.get_or_create( customer=user.customer, defaults=self.payload_1 ) - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( reverse("counter:edit_billing_info", args=[user.id]), json.dumps(self.payload_2), content_type="application/json", ) - self.assertEqual(200, response.status_code) + assert response.status_code == 200 user = User.objects.get(username="subscriber") infos = BillingInfo.objects.get(customer__user=user) self.assertJSONEqual(response.content, {"errors": None}) - self.assertTrue(hasattr(user.customer, "billing_infos")) + assert hasattr(user.customer, "billing_infos") self.assertEqual(user.customer, infos.customer) self.assertEqual("Subscribed", infos.first_name) self.assertEqual("User", infos.last_name) @@ -572,61 +517,55 @@ class BillingInfoTest(TestCase): user = User.objects.get(username="subscriber") if hasattr(user.customer, "billing_infos"): user.customer.billing_infos.delete() - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.post( reverse("counter:create_billing_info", args=[user.id]), json.dumps(self.payload_2), content_type="application/json", ) - self.assertEqual(200, response.status_code) + assert response.status_code == 200 user = User.objects.get(username="subscriber") infos = BillingInfo.objects.get(customer__user=user) self.assertJSONEqual(response.content, {"errors": None}) - self.assertTrue(hasattr(user.customer, "billing_infos")) - self.assertEqual(user.customer, infos.customer) - self.assertEqual("Subscribed", infos.first_name) - self.assertEqual("User", infos.last_name) - self.assertEqual("3, rue de Troyes", infos.address_1) - self.assertEqual(None, infos.address_2) - self.assertEqual("34301", infos.zip_code) - self.assertEqual("Sète", infos.city) - self.assertEqual("FR", infos.country) + assert hasattr(user.customer, "billing_infos") + assert infos.customer == user.customer + assert infos.first_name == "Subscribed" + assert infos.last_name == "User" + assert infos.address_1 == "3, rue de Troyes" + assert infos.address_2 is None + assert infos.zip_code == "34301" + assert infos.city == "Sète" + assert infos.country == "FR" class BarmanConnectionTest(TestCase): - def setUp(self): - self.krophil = User.objects.get(username="krophil") - self.skia = User.objects.get(username="skia") - self.skia.customer.account = 800 - self.krophil.customer.save() - self.skia.customer.save() + @classmethod + def setUpTestData(cls): + cls.krophil = User.objects.get(username="krophil") + cls.skia = User.objects.get(username="skia") + cls.skia.customer.account = 800 + cls.krophil.customer.save() + cls.skia.customer.save() - self.counter = Counter.objects.filter(id=2).first() + cls.counter = Counter.objects.get(id=2) def test_barman_granted(self): self.client.post( reverse("counter:login", args=[self.counter.id]), {"username": "krophil", "password": "plop"}, ) - response_get = self.client.get( - reverse("counter:details", args=[self.counter.id]) - ) + response = self.client.get(reverse("counter:details", args=[self.counter.id])) - self.assertTrue("

                        Entrez un code client :

                        " in str(response_get.content)) + assert "

                        Entrez un code client :

                        " in str(response.content) def test_counters_list_barmen(self): self.client.post( reverse("counter:login", args=[self.counter.id]), {"username": "krophil", "password": "plop"}, ) - response_get = self.client.get( - reverse("counter:activity", args=[self.counter.id]) - ) + response = self.client.get(reverse("counter:activity", args=[self.counter.id])) - self.assertTrue( - '
                      • Kro Phil'
                      • ' - in str(response_get.content) - ) + assert '
                      • Kro Phil'
                      • ' in str(response.content) def test_barman_denied(self): self.client.post( @@ -637,20 +576,16 @@ class BarmanConnectionTest(TestCase): reverse("counter:details", args=[self.counter.id]) ) - self.assertTrue("

                        Merci de vous identifier

                        " in str(response_get.content)) + assert "

                        Merci de vous identifier

                        " in str(response_get.content) def test_counters_list_no_barmen(self): self.client.post( reverse("counter:login", args=[self.counter.id]), {"username": "krophil", "password": "plop"}, ) - response_get = self.client.get( - reverse("counter:activity", args=[self.counter.id]) - ) + response = self.client.get(reverse("counter:activity", args=[self.counter.id])) - self.assertFalse( - '
                      • S' Kia
                      • ' in str(response_get.content) - ) + assert not '
                      • S' Kia
                      • ' in str(response.content) class StudentCardTest(TestCase): @@ -659,12 +594,16 @@ class StudentCardTest(TestCase): Test that an user can be found with it's student card """ + @classmethod + def setUpTestData(cls): + cls.krophil = User.objects.get(username="krophil") + cls.sli = User.objects.get(username="sli") + cls.skia = User.objects.get(username="skia") + cls.root = User.objects.get(username="root") + + cls.counter = Counter.objects.get(id=2) + def setUp(self): - self.krophil = User.objects.get(username="krophil") - self.sli = User.objects.get(username="sli") - - self.counter = Counter.objects.filter(id=2).first() - # Auto login on counter self.client.post( reverse("counter:login", args=[self.counter.id]), @@ -677,12 +616,9 @@ class StudentCardTest(TestCase): {"code": "9A89B82018B0A0"}, ) - self.assertEqual( - response.url, - reverse( - "counter:click", - kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, - ), + assert response.url == reverse( + "counter:click", + kwargs={"counter_id": self.counter.id, "user_id": self.sli.id}, ) def test_add_student_card_from_counter(self): @@ -778,7 +714,7 @@ class StudentCardTest(TestCase): ) def test_delete_student_card_with_owner(self): - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) self.client.post( reverse( "counter:delete_student_card", @@ -788,10 +724,10 @@ class StudentCardTest(TestCase): }, ) ) - self.assertFalse(self.sli.customer.student_cards.exists()) + assert not self.sli.customer.student_cards.exists() def test_delete_student_card_with_board_member(self): - self.client.login(username="skia", password="plop") + self.client.force_login(self.skia) self.client.post( reverse( "counter:delete_student_card", @@ -801,10 +737,10 @@ class StudentCardTest(TestCase): }, ) ) - self.assertFalse(self.sli.customer.student_cards.exists()) + assert not self.sli.customer.student_cards.exists() def test_delete_student_card_with_root(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.root) self.client.post( reverse( "counter:delete_student_card", @@ -814,10 +750,10 @@ class StudentCardTest(TestCase): }, ) ) - self.assertFalse(self.sli.customer.student_cards.exists()) + assert not self.sli.customer.student_cards.exists() def test_delete_student_card_fail(self): - self.client.login(username="krophil", password="plop") + self.client.force_login(self.krophil) response = self.client.post( reverse( "counter:delete_student_card", @@ -827,12 +763,12 @@ class StudentCardTest(TestCase): }, ) ) - self.assertEqual(response.status_code, 403) - self.assertTrue(self.sli.customer.student_cards.exists()) + assert response.status_code == 403 + assert self.sli.customer.student_cards.exists() def test_add_student_card_from_user_preferences(self): # Test with owner of the card - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) self.client.post( reverse( "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} @@ -846,7 +782,7 @@ class StudentCardTest(TestCase): self.assertContains(response, text="8B90734A802A8F") # Test with board member - self.client.login(username="skia", password="plop") + self.client.force_login(self.skia) self.client.post( reverse( "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} @@ -884,7 +820,7 @@ class StudentCardTest(TestCase): self.assertContains(response, text="ABCAAAFAAFAAAB") # Test with root - self.client.login(username="root", password="plop") + self.client.force_login(self.root) self.client.post( reverse( "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} @@ -898,7 +834,7 @@ class StudentCardTest(TestCase): self.assertContains(response, text="8B90734A802A8B") def test_add_student_card_from_user_preferences_fail(self): - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) # UID too short response = self.client.post( reverse( @@ -943,29 +879,30 @@ class StudentCardTest(TestCase): reverse( "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} ), - {"uid": " "}, + {"uid": " " * 14}, ) self.assertContains(response, text="Cet UID est invalide") # Test with unauthorized user - self.client.login(username="krophil", password="plop") + self.client.force_login(self.krophil) response = self.client.post( reverse( "counter:add_student_card", kwargs={"customer_id": self.sli.customer.pk} ), {"uid": "8B90734A802A8F"}, ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 class CustomerAccountIdTest(TestCase): - def setUp(self): - self.user_a = User.objects.create( + @classmethod + def setUpTestData(cls): + cls.user_a = User.objects.create( username="a", password="plop", email="a.a@a.fr" ) user_b = User.objects.create(username="b", password="plop", email="b.b@b.fr") user_c = User.objects.create(username="c", password="plop", email="c.c@c.fr") - Customer.objects.create(user=self.user_a, amount=10, account_id="1111a") + Customer.objects.create(user=cls.user_a, amount=10, account_id="1111a") Customer.objects.create(user=user_b, amount=0, account_id="9999z") Customer.objects.create(user=user_c, amount=0, account_id="12345f") @@ -974,14 +911,14 @@ class CustomerAccountIdTest(TestCase): customer, created = Customer.get_or_create(user_d) account_id = customer.account_id number = account_id[:-1] - self.assertTrue(created) - self.assertEqual(number, "12346") - self.assertEqual(6, len(account_id)) - self.assertIn(account_id[-1], string.ascii_lowercase) - self.assertEqual(0, customer.amount) + assert created is True + assert number == "12346" + assert 6 == len(account_id) + assert account_id[-1] in string.ascii_lowercase + assert customer.amount == 0 def test_get_existing_account(self): account, created = Customer.get_or_create(self.user_a) - self.assertFalse(created) - self.assertEqual(account.account_id, "1111a") - self.assertEqual(10, account.amount) + assert created is False + assert account.account_id == "1111a" + assert account.amount == 10 diff --git a/doc/start/install.rst b/doc/start/install.rst index db1a9749..fdb639ce 100644 --- a/doc/start/install.rst +++ b/doc/start/install.rst @@ -196,16 +196,16 @@ Pour lancer les tests il suffit d'utiliser la commande intégrée à django. .. code-block:: bash # Lancer tous les tests - python manage.py test + pytest # Lancer les tests de l'application core - python manage.py test core + pytest core # Lancer les tests de la classe UserRegistrationTest de core - python manage.py test core.tests.UserRegistrationTest + pytest core.tests.UserRegistrationTest # Lancer une méthode en particulier de cette même classe - python manage.py test core.tests.UserRegistrationTest.test_register_user_form_ok + pytest core.tests.UserRegistrationTest.test_register_user_form_ok Vérifier les dépendances Javascript ----------------------------------- diff --git a/eboutic/tests.py b/eboutic/tests.py index 3435bfc5..b5e82e8e 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -40,14 +40,14 @@ from eboutic.models import Basket class EbouticTest(TestCase): @classmethod def setUpTestData(cls): - cls.barbar = Product.objects.filter(code="BARB").first() - cls.refill = Product.objects.filter(code="15REFILL").first() - cls.cotis = Product.objects.filter(code="1SCOTIZ").first() - cls.eboutic = Counter.objects.filter(name="Eboutic").first() - cls.skia = User.objects.filter(username="skia").first() - cls.subscriber = User.objects.filter(username="subscriber").first() - cls.old_subscriber = User.objects.filter(username="old_subscriber").first() - cls.public = User.objects.filter(username="public").first() + cls.barbar = Product.objects.get(code="BARB") + cls.refill = Product.objects.get(code="15REFILL") + cls.cotis = Product.objects.get(code="1SCOTIZ") + cls.eboutic = Counter.objects.get(name="Eboutic") + cls.skia = User.objects.get(username="skia") + cls.subscriber = User.objects.get(username="subscriber") + cls.old_subscriber = User.objects.get(username="old_subscriber") + cls.public = User.objects.get(username="public") def get_busy_basket(self, user) -> Basket: """ @@ -82,7 +82,7 @@ class EbouticTest(TestCase): return url def test_buy_with_sith_account(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) self.subscriber.customer.amount = 100 # give money before test self.subscriber.customer.save() basket = self.get_busy_basket(self.subscriber) @@ -90,14 +90,12 @@ class EbouticTest(TestCase): response = self.client.post(reverse("eboutic:pay_with_sith")) self.assertRedirects(response, "/eboutic/pay/success/") new_balance = Customer.objects.get(user=self.subscriber).amount - self.assertEqual(float(new_balance), 100 - amount) - self.assertEqual( - 'basket_items=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/eboutic', - self.client.cookies["basket_items"].OutputString(), - ) + assert float(new_balance) == 100 - amount + expected = 'basket_items=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/eboutic' + assert expected == self.client.cookies["basket_items"].OutputString() def test_buy_with_sith_account_no_money(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) basket = self.get_busy_basket(self.subscriber) initial = basket.get_total() - 1 # just not enough to complete the sale self.subscriber.customer.amount = initial @@ -105,20 +103,19 @@ class EbouticTest(TestCase): response = self.client.post(reverse("eboutic:pay_with_sith")) self.assertRedirects(response, "/eboutic/pay/failure/") new_balance = Customer.objects.get(user=self.subscriber).amount - self.assertEqual(float(new_balance), initial) - self.assertEqual( - 'basket_items=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/eboutic', - self.client.cookies["basket_items"].OutputString(), - ) # this cookie should be removed after payment + assert float(new_balance) == initial + # this cookie should be removed after payment + expected = 'basket_items=""; expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/eboutic' + assert expected == self.client.cookies["basket_items"].OutputString() def test_submit_basket(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) self.client.cookies["basket_items"] = """[ {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28}, {"id": 4, "name": "Barbar", "quantity": 3, "unit_price": 1.7} ]""" response = self.client.get(reverse("eboutic:command")) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertInHTML( "
    ", response.content.decode(), @@ -127,42 +124,37 @@ class EbouticTest(TestCase): "", response.content.decode(), ) - self.assertIn("basket_id", self.client.session) + assert "basket_id" in self.client.session basket = Basket.objects.get(id=self.client.session["basket_id"]) - self.assertEqual(basket.items.count(), 2) + assert basket.items.count() == 2 barbar = basket.items.filter(product_name="Barbar").first() - self.assertIsNotNone(barbar) - self.assertEqual(barbar.quantity, 3) + assert barbar is not None + assert barbar.quantity == 3 cotis = basket.items.filter(product_name="Cotis 2 semestres").first() - self.assertIsNotNone(cotis) - self.assertEqual(cotis.quantity, 1) - self.assertEqual(basket.get_total(), 3 * 1.7 + 28) + assert cotis is not None + assert cotis.quantity == 1 + assert basket.get_total() == 3 * 1.7 + 28 def test_submit_empty_basket(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) self.client.cookies["basket_items"] = "[]" response = self.client.get(reverse("eboutic:command")) self.assertRedirects(response, "/eboutic/") def test_submit_invalid_basket(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) max_id = Product.objects.aggregate(res=Max("id"))["res"] self.client.cookies["basket_items"] = f"""[ {{"id": {max_id + 1}, "name": "", "quantity": 1, "unit_price": 28}} ]""" response = self.client.get(reverse("eboutic:command")) - self.assertIn( - 'basket_items=""', - self.client.cookies["basket_items"].OutputString(), - ) - self.assertIn( - "Path=/eboutic", - self.client.cookies["basket_items"].OutputString(), - ) + cookie = self.client.cookies["basket_items"].OutputString() + assert 'basket_items=""' in cookie + assert "Path=/eboutic" in cookie self.assertRedirects(response, "/eboutic/") def test_submit_basket_illegal_quantity(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) self.client.cookies["basket_items"] = """[ {"id": 4, "name": "Barbar", "quantity": -1, "unit_price": 1.7} ]""" @@ -170,11 +162,11 @@ class EbouticTest(TestCase): self.assertRedirects(response, "/eboutic/") def test_buy_subscribe_product_with_credit_card(self): - self.client.login(username="old_subscriber", password="plop") + self.client.force_login(self.old_subscriber) response = self.client.get( reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}) ) - self.assertTrue("Non cotisant" in str(response.content)) + assert "Non cotisant" in str(response.content) self.client.cookies["basket_items"] = """[ {"id": 2, "name": "Cotis 2 semestres", "quantity": 1, "unit_price": 28} ]""" @@ -184,21 +176,21 @@ class EbouticTest(TestCase): response.content.decode(), ) basket = Basket.objects.get(id=self.client.session["basket_id"]) - self.assertEqual(basket.items.count(), 1) + assert basket.items.count() == 1 response = self.client.get(self.generate_bank_valid_answer()) - self.assertTrue(response.status_code == 200) - self.assertTrue(response.content.decode("utf-8") == "Payment successful") + assert response.status_code == 200 + assert response.content.decode("utf-8") == "Payment successful" subscriber = User.objects.get(id=self.old_subscriber.id) - self.assertEqual(subscriber.subscriptions.count(), 2) + assert subscriber.subscriptions.count() == 2 sub = subscriber.subscriptions.order_by("-subscription_end").first() - self.assertTrue(sub.is_valid_now()) - self.assertEqual(sub.member, subscriber) - self.assertEqual(sub.subscription_type, "deux-semestres") - self.assertEqual(sub.location, "EBOUTIC") + assert sub.is_valid_now() + assert sub.member == subscriber + assert sub.subscription_type == "deux-semestres" + assert sub.location == "EBOUTIC" def test_buy_refill_product_with_credit_card(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) # basket contains 1 refill item worth 15€ self.client.cookies["basket_items"] = json.dumps( [{"id": 3, "name": "Rechargement 15 €", "quantity": 1, "unit_price": 15}] @@ -208,13 +200,13 @@ class EbouticTest(TestCase): url = self.generate_bank_valid_answer() response = self.client.get(url) - self.assertTrue(response.status_code == 200) - self.assertTrue(response.content.decode() == "Payment successful") + assert response.status_code == 200 + assert response.content.decode() == "Payment successful" new_balance = Customer.objects.get(user=self.subscriber).amount - self.assertEqual(new_balance, initial_balance + 15) + assert new_balance == initial_balance + 15 def test_alter_basket_after_submission(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) self.client.cookies["basket_items"] = json.dumps( [{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}] ) @@ -227,30 +219,30 @@ class EbouticTest(TestCase): ) self.client.get(reverse("eboutic:command")) response = self.client.get(et_answer_url) - self.assertEqual(response.status_code, 500) - self.assertIn( - "Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'", - response.content.decode("utf-8"), + assert response.status_code == 500 + assert ( + "Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'" + in response.content.decode("utf-8"), ) def test_buy_simple_product_with_credit_card(self): - self.client.login(username="subscriber", password="plop") + self.client.force_login(self.subscriber) self.client.cookies["basket_items"] = json.dumps( [{"id": 4, "name": "Barbar", "quantity": 1, "unit_price": 1.7}] ) self.client.get(reverse("eboutic:command")) et_answer_url = self.generate_bank_valid_answer() response = self.client.get(et_answer_url) - self.assertTrue(response.status_code == 200) - self.assertTrue(response.content.decode("utf-8") == "Payment successful") + assert response.status_code == 200 + assert response.content.decode("utf-8") == "Payment successful" selling = ( Selling.objects.filter(customer=self.subscriber.customer) .order_by("-date") .first() ) - self.assertEqual(selling.payment_method, "CARD") - self.assertEqual(selling.quantity, 1) - self.assertEqual(selling.unit_price, self.barbar.selling_price) - self.assertEqual(selling.counter.type, "EBOUTIC") - self.assertEqual(selling.product, self.barbar) + assert selling.payment_method == "CARD" + assert selling.quantity == 1 + assert selling.unit_price == self.barbar.selling_price + assert selling.counter.type == "EBOUTIC" + assert selling.product == self.barbar diff --git a/election/tests.py b/election/tests.py index 3a3de97a..9ea534b8 100644 --- a/election/tests.py +++ b/election/tests.py @@ -6,56 +6,44 @@ from core.models import Group, User from election.models import Election -class MainElection(TestCase): - def setUp(self): - self.election = Election.objects.all().first() - self.public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) - self.subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) - self.ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) - self.sli = User.objects.get(username="sli") - self.subscriber = User.objects.get(username="subscriber") - self.public = User.objects.get(username="public") +class ElectionTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.election = Election.objects.first() + cls.public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) + cls.subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) + cls.ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) + cls.sli = User.objects.get(username="sli") + cls.subscriber = User.objects.get(username="subscriber") + cls.public = User.objects.get(username="public") -class ElectionDetailTest(MainElection): +class ElectionDetailTest(ElectionTest): def test_permission_denied(self): self.election.view_groups.remove(self.public_group) - self.election.view_groups.add(self.subscriber_group) - self.election.save() - self.client.login(username=self.public.username, password="plop") - response_get = self.client.get( + self.client.force_login(self.public) + response = self.client.get( reverse("election:detail", args=str(self.election.id)) ) - response_post = self.client.get( - reverse("election:detail", args=str(self.election.id)) - ) - self.assertTrue(response_get.status_code == 403) - self.assertTrue(response_post.status_code == 403) - self.election.view_groups.remove(self.subscriber_group) - self.election.view_groups.add(self.public_group) - self.election.save() + assert response.status_code == 403 def test_permisson_granted(self): - self.client.login(username=self.public.username, password="plop") - response_get = self.client.get( + self.client.force_login(self.public) + response = self.client.get( reverse("election:detail", args=str(self.election.id)) ) - response_post = self.client.post( - reverse("election:detail", args=str(self.election.id)) - ) - self.assertFalse(response_get.status_code == 403) - self.assertFalse(response_post.status_code == 403) - self.assertTrue("La roue tourne" in str(response_get.content)) + assert response.status_code == 200 + assert "La roue tourne" in str(response.content) -class ElectionUpdateView(MainElection): +class ElectionUpdateView(ElectionTest): def test_permission_denied(self): - self.client.login(username=self.subscriber.username, password="plop") - response_get = self.client.get( + self.client.force_login(self.subscriber) + response = self.client.get( reverse("election:update", args=str(self.election.id)) ) - response_post = self.client.post( + assert response.status_code == 403 + response = self.client.post( reverse("election:update", args=str(self.election.id)) ) - self.assertTrue(response_get.status_code == 403) - self.assertTrue(response_post.status_code == 403) + assert response.status_code == 403 diff --git a/galaxy/tests.py b/galaxy/tests.py index 2a523af3..f1a3f092 100644 --- a/galaxy/tests.py +++ b/galaxy/tests.py @@ -34,29 +34,30 @@ from galaxy.models import Galaxy class GalaxyTestModel(TestCase): - def setUp(self): - self.root = User.objects.get(username="root") - self.skia = User.objects.get(username="skia") - self.sli = User.objects.get(username="sli") - self.krophil = User.objects.get(username="krophil") - self.richard = User.objects.get(username="rbatsbak") - self.subscriber = User.objects.get(username="subscriber") - self.public = User.objects.get(username="public") - self.com = User.objects.get(username="comunity") + @classmethod + def setUpTestData(cls): + cls.root = User.objects.get(username="root") + cls.skia = User.objects.get(username="skia") + cls.sli = User.objects.get(username="sli") + cls.krophil = User.objects.get(username="krophil") + cls.richard = User.objects.get(username="rbatsbak") + cls.subscriber = User.objects.get(username="subscriber") + cls.public = User.objects.get(username="public") + cls.com = User.objects.get(username="comunity") def test_user_self_score(self): """ Test that individual user scores are correct """ with self.assertNumQueries(8): - self.assertEqual(Galaxy.compute_user_score(self.root), 9) - self.assertEqual(Galaxy.compute_user_score(self.skia), 10) - self.assertEqual(Galaxy.compute_user_score(self.sli), 8) - self.assertEqual(Galaxy.compute_user_score(self.krophil), 2) - self.assertEqual(Galaxy.compute_user_score(self.richard), 10) - self.assertEqual(Galaxy.compute_user_score(self.subscriber), 8) - self.assertEqual(Galaxy.compute_user_score(self.public), 8) - self.assertEqual(Galaxy.compute_user_score(self.com), 1) + assert Galaxy.compute_user_score(self.root) == 9 + assert Galaxy.compute_user_score(self.skia) == 10 + assert Galaxy.compute_user_score(self.sli) == 8 + assert Galaxy.compute_user_score(self.krophil) == 2 + assert Galaxy.compute_user_score(self.richard) == 10 + assert Galaxy.compute_user_score(self.subscriber) == 8 + assert Galaxy.compute_user_score(self.public) == 8 + assert Galaxy.compute_user_score(self.com) == 1 def test_users_score(self): """ @@ -155,12 +156,13 @@ class GalaxyTestView(TestCase): call_command("generate_galaxy_test_data", "-v", "0") galaxy = Galaxy.objects.create() galaxy.rule(26) # We want a fast test + cls.root = User.objects.get(username="root") def test_page_is_citizen(self): """ Test that users can access the galaxy page of users who are citizens """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) user = User.objects.get(last_name="n°500") response = self.client.get(reverse("galaxy:user", args=[user.id])) self.assertContains( @@ -174,10 +176,10 @@ class GalaxyTestView(TestCase): Test that trying to access the galaxy page of a user who is not citizens return a 404 """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) user = User.objects.get(last_name="n°1") response = self.client.get(reverse("galaxy:user", args=[user.id])) - self.assertEquals(response.status_code, 404) + assert response.status_code == 404 def test_full_galaxy_state(self): """ @@ -185,7 +187,7 @@ class GalaxyTestView(TestCase): command that the relation scores are correct, and that the view exposes the right data. """ - self.client.login(username="root", password="plop") + self.client.force_login(self.root) response = self.client.get(reverse("galaxy:data")) state = response.json() @@ -194,7 +196,6 @@ class GalaxyTestView(TestCase): # Dump computed state, either for easier debugging, or to copy as new reference if changes are legit (galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state)) - self.assertEqual( - state, - json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()), + assert ( + state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()), ) diff --git a/pedagogy/tests.py b/pedagogy/tests.py index c52ee2ae..2c61facf 100644 --- a/pedagogy/tests.py +++ b/pedagogy/tests.py @@ -32,10 +32,12 @@ from core.models import Notification, User from pedagogy.models import UV, UVComment, UVCommentReport -def create_uv_template(user_id, code="IFC1", exclude_list=[]): +def create_uv_template(user_id, code="IFC1", exclude_list=None): """ Factory to help UV creation/update in post requests """ + if exclude_list is None: + exclude_list = [] uv = { "code": code, "author": user_id, @@ -83,235 +85,218 @@ class UVCreation(TestCase): """ @classmethod - def setUp(cls): + def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") + cls.create_uv_url = reverse("pedagogy:uv_create") + + def test_create_uv_admin_success(self): + self.client.force_login(self.bibou) + response = self.client.post( + self.create_uv_url, create_uv_template(self.bibou.id) + ) + assert response.status_code == 302 + assert UV.objects.filter(code="IFC1").exists() + + def test_create_uv_pedagogy_admin_success(self): + self.client.force_login(self.tutu) + response = self.client.post( + self.create_uv_url, create_uv_template(self.tutu.id) + ) + assert response.status_code == 302 + assert UV.objects.filter(code="IFC1").exists() + + def test_create_uv_unauthorized_fail(self): + # Test with anonymous user + response = self.client.post(self.create_uv_url, create_uv_template(0)) + assert response.status_code == 403 + + # Test with subscribed user + self.client.force_login(self.sli) + response = self.client.post(self.create_uv_url, create_uv_template(self.sli.id)) + assert response.status_code == 403 + + # Test with non subscribed user + self.client.force_login(self.guy) + response = self.client.post(self.create_uv_url, create_uv_template(self.guy.id)) + assert response.status_code == 403 + + # Check that the UV has never been created + assert not UV.objects.filter(code="IFC1").exists() + + def test_create_uv_bad_request_fail(self): + self.client.force_login(self.tutu) + + # Test with wrong user id (if someone cheats on the hidden input) + response = self.client.post( + self.create_uv_url, create_uv_template(self.bibou.id) + ) + assert response.status_code == 200 + + # Remove a required field + response = self.client.post( + self.create_uv_url, + create_uv_template(self.tutu.id, exclude_list=["title"]), + ) + assert response.status_code == 200 + + # Check that the UV hase never been created + assert not UV.objects.filter(code="IFC1").exists() + + +class UVListTest(TestCase): + """Test guide display rights.""" + + @classmethod + def setUpTestData(cls): cls.bibou = User.objects.get(username="root") cls.tutu = User.objects.get(username="tutu") cls.sli = User.objects.get(username="sli") cls.guy = User.objects.get(username="guy") - def test_create_uv_admin_success(self): - self.client.login(username="root", password="plop") - response = self.client.post( - reverse("pedagogy:uv_create"), create_uv_template(self.bibou.id) - ) - self.assertEqual(response.status_code, 302) - self.assertTrue(UV.objects.filter(code="IFC1").exists()) - - def test_create_uv_pedagogy_admin_success(self): - self.client.login(username="tutu", password="plop") - response = self.client.post( - reverse("pedagogy:uv_create"), create_uv_template(self.tutu.id) - ) - self.assertEqual(response.status_code, 302) - self.assertTrue(UV.objects.filter(code="IFC1").exists()) - - def test_create_uv_unauthorized_fail(self): - # Test with anonymous user - response = self.client.post( - reverse("pedagogy:uv_create"), create_uv_template(0) - ) - self.assertEqual(response.status_code, 403) - - # Test with subscribed user - self.client.login(username="sli", password="plop") - response = self.client.post( - reverse("pedagogy:uv_create"), create_uv_template(self.sli.id) - ) - self.assertEqual(response.status_code, 403) - - # Test with non subscribed user - self.client.login(username="guy", password="plop") - response = self.client.post( - reverse("pedagogy:uv_create"), create_uv_template(self.guy.id) - ) - self.assertEqual(response.status_code, 403) - - # Check that the UV has never been created - self.assertFalse(UV.objects.filter(code="IFC1").exists()) - - def test_create_uv_bad_request_fail(self): - self.client.login(username="tutu", password="plop") - - # Test with wrong user id (if someone cheats on the hidden input) - response = self.client.post( - reverse("pedagogy:uv_create"), create_uv_template(self.bibou.id) - ) - self.assertNotEqual(response.status_code, 302) - self.assertEqual(response.status_code, 200) - - # Remove a required field - response = self.client.post( - reverse("pedagogy:uv_create"), - create_uv_template(self.tutu.id, exclude_list=["title"]), - ) - self.assertNotEqual(response.status_code, 302) - self.assertEqual(response.status_code, 200) - - # Check that the UV hase never been created - self.assertFalse(UV.objects.filter(code="IFC1").exists()) - - -class UVListTest(TestCase): - """ - Test guide display rights - """ - def test_uv_list_display_success(self): # Display for root - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.get(reverse("pedagogy:guide")) self.assertContains(response, text="PA00") # Display for pedagogy admin - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) response = self.client.get(reverse("pedagogy:guide")) self.assertContains(response, text="PA00") # Display for simple subscriber - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) response = self.client.get(reverse("pedagogy:guide")) self.assertContains(response, text="PA00") def test_uv_list_display_fail(self): # Don't display for anonymous user response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Don't display for none subscribed users - self.client.login(username="guy", password="plop") + self.client.force_login(self.guy) response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 class UVDeleteTest(TestCase): - """ - Test UV deletion rights - """ + """Test UV deletion rights.""" + + @classmethod + def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") + cls.uv = UV.objects.get(code="PA00") + cls.delete_uv_url = reverse("pedagogy:uv_delete", kwargs={"uv_id": cls.uv.id}) def test_uv_delete_root_success(self): - self.client.login(username="root", password="plop") - self.client.post( - reverse( - "pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id} - ) - ) - self.assertFalse(UV.objects.filter(code="PA00").exists()) + self.client.force_login(self.bibou) + self.client.post(self.delete_uv_url) + assert not UV.objects.filter(pk=self.uv.pk).exists() def test_uv_delete_pedagogy_admin_success(self): - self.client.login(username="tutu", password="plop") - self.client.post( - reverse( - "pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id} - ) - ) - self.assertFalse(UV.objects.filter(code="PA00").exists()) + self.client.force_login(self.tutu) + self.client.post(self.delete_uv_url) + assert not UV.objects.filter(pk=self.uv.pk).exists() def test_uv_delete_pedagogy_unauthorized_fail(self): # Anonymous user - response = self.client.post( - reverse( - "pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id} - ) - ) - self.assertEqual(response.status_code, 403) + response = self.client.post(self.delete_uv_url) + assert response.status_code == 403 + assert UV.objects.filter(pk=self.uv.pk).exists() # Not subscribed user - self.client.login(username="guy", password="plop") - response = self.client.post( - reverse( - "pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id} - ) - ) - self.assertEqual(response.status_code, 403) + self.client.force_login(self.guy) + response = self.client.post(self.delete_uv_url) + assert response.status_code == 403 + assert UV.objects.filter(pk=self.uv.pk).exists() # Simply subscribed user - self.client.login(username="sli", password="plop") - response = self.client.post( - reverse( - "pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id} - ) - ) - self.assertEqual(response.status_code, 403) - - # Check that the UV still exists - self.assertTrue(UV.objects.filter(code="PA00").exists()) + self.client.force_login(self.sli) + response = self.client.post(self.delete_uv_url) + assert response.status_code == 403 + assert UV.objects.filter(pk=self.uv.pk).exists() class UVUpdateTest(TestCase): - """ - Test UV update rights - """ + """Test UV update rights.""" - def setUp(self): - self.bibou = User.objects.filter(username="root").first() - self.tutu = User.objects.filter(username="tutu").first() - self.uv = UV.objects.get(code="PA00") + @classmethod + def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") + cls.uv = UV.objects.get(code="PA00") + cls.update_uv_url = reverse("pedagogy:uv_update", kwargs={"uv_id": cls.uv.id}) def test_uv_update_root_success(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) self.client.post( - reverse("pedagogy:uv_update", kwargs={"uv_id": self.uv.id}), - create_uv_template(self.bibou.id, code="PA00"), + self.update_uv_url, create_uv_template(self.bibou.id, code="PA00") ) self.uv.refresh_from_db() - self.assertEqual(self.uv.credit_type, "TM") + assert self.uv.credit_type == "TM" def test_uv_update_pedagogy_admin_success(self): - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) self.client.post( - reverse("pedagogy:uv_update", kwargs={"uv_id": self.uv.id}), - create_uv_template(self.bibou.id, code="PA00"), + self.update_uv_url, create_uv_template(self.bibou.id, code="PA00") ) self.uv.refresh_from_db() - self.assertEqual(self.uv.credit_type, "TM") + assert self.uv.credit_type == "TM" def test_uv_update_original_author_does_not_change(self): - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) response = self.client.post( - reverse("pedagogy:uv_update", kwargs={"uv_id": self.uv.id}), + self.update_uv_url, create_uv_template(self.tutu.id, code="PA00"), ) - + assert response.status_code == 200 self.uv.refresh_from_db() - self.assertEqual(response.status_code, 200) - self.assertEqual(self.uv.author, self.bibou) + assert self.uv.author == self.bibou def test_uv_update_pedagogy_unauthorized_fail(self): # Anonymous user response = self.client.post( - reverse("pedagogy:uv_update", kwargs={"uv_id": self.uv.id}), - create_uv_template(self.bibou.id, code="PA00"), + self.update_uv_url, create_uv_template(self.bibou.id, code="PA00") ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Not subscribed user - self.client.login(username="guy", password="plop") + self.client.force_login(self.guy) response = self.client.post( - reverse("pedagogy:uv_update", kwargs={"uv_id": self.uv.id}), - create_uv_template(self.bibou.id, code="PA00"), + self.update_uv_url, create_uv_template(self.bibou.id, code="PA00") ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Simply subscribed user - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) response = self.client.post( - reverse("pedagogy:uv_update", kwargs={"uv_id": self.uv.id}), - create_uv_template(self.bibou.id, code="PA00"), + self.update_uv_url, create_uv_template(self.bibou.id, code="PA00") ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Check that the UV has not changed self.uv.refresh_from_db() - self.assertEqual(self.uv.credit_type, "OM") + assert self.uv.credit_type == "OM" # UVComment class tests -def create_uv_comment_template(user_id, uv_code="PA00", exclude_list=[]): +def create_uv_comment_template(user_id, uv_code="PA00", exclude_list=None): """ Factory to help UVComment creation/update in post requests """ + if exclude_list is None: + exclude_list = [] comment = { "author": user_id, "uv": UV.objects.get(code=uv_code).id, @@ -340,105 +325,80 @@ class UVCommentCreationAndDisplay(TestCase): cls.sli = User.objects.get(username="sli") cls.guy = User.objects.get(username="guy") cls.uv = UV.objects.get(code="PA00") + cls.uv_url = reverse("pedagogy:uv_detail", kwargs={"uv_id": cls.uv.id}) def test_create_uv_comment_admin_success(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(self.bibou.id), - ) - self.assertEqual(response.status_code, 302) - response = self.client.get( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}) + self.uv_url, create_uv_comment_template(self.bibou.id) ) + self.assertRedirects(response, self.uv_url) + response = self.client.get(self.uv_url) self.assertContains(response, text="Superbe UV") def test_create_uv_comment_pedagogy_admin_success(self): - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(self.tutu.id), - ) - self.assertEqual(response.status_code, 302) - response = self.client.get( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}) + self.uv_url, create_uv_comment_template(self.tutu.id) ) + self.assertRedirects(response, self.uv_url) + response = self.client.get(self.uv_url) self.assertContains(response, text="Superbe UV") def test_create_uv_comment_subscriber_success(self): - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(self.sli.id), - ) - self.assertEqual(response.status_code, 302) - response = self.client.get( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}) + self.uv_url, create_uv_comment_template(self.sli.id) ) + self.assertRedirects(response, self.uv_url) + response = self.client.get(self.uv_url) self.assertContains(response, text="Superbe UV") def test_create_uv_comment_unauthorized_fail(self): + nb_comments = self.uv.comments.count() # Test with anonymous user - response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(0), - ) - self.assertEqual(response.status_code, 403) + response = self.client.post(self.uv_url, create_uv_comment_template(0)) + assert response.status_code == 403 # Test with non subscribed user - self.client.login(username="guy", password="plop") + self.client.force_login(self.guy) response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(self.guy.id), + self.uv_url, create_uv_comment_template(self.guy.id) ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 - # Check that the comment has never been created - self.client.login(username="root", password="plop") - response = self.client.get( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}) - ) - self.assertNotContains(response, text="Superbe UV") + # Check that no comment has been created + assert self.uv.comments.count() == nb_comments def test_create_uv_comment_bad_form_fail(self): - self.client.login(username="root", password="plop") + nb_comments = self.uv.comments.count() + self.client.force_login(self.bibou) response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), + self.uv_url, create_uv_comment_template(self.bibou.id, exclude_list=["grade_global"]), ) - self.assertEqual(response.status_code, 200) - - response = self.client.get( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}) - ) - self.assertNotContains(response, text="Superbe UV") + assert response.status_code == 200 + assert self.uv.comments.count() == nb_comments def test_create_uv_comment_twice_fail(self): # Checks that the has_user_already_commented method works proprely - self.assertFalse(self.uv.has_user_already_commented(self.bibou)) + assert not self.uv.has_user_already_commented(self.bibou) # Create a first comment - self.client.login(username="root", password="plop") - self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(self.bibou.id), - ) + self.client.force_login(self.bibou) + self.client.post(self.uv_url, create_uv_comment_template(self.bibou.id)) # Checks that the has_user_already_commented method works proprely - self.assertTrue(self.uv.has_user_already_commented(self.bibou)) + assert self.uv.has_user_already_commented(self.bibou) # Create the second comment comment = create_uv_comment_template(self.bibou.id) comment["comment"] = "Twice" - response = self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), comment - ) - self.assertEqual(response.status_code, 200) - self.assertTrue( - UVComment.objects.filter(comment__contains="Superbe UV").exists() - ) - self.assertFalse(UVComment.objects.filter(comment__contains="Twice").exists()) + response = self.client.post(self.uv_url, comment) + assert response.status_code == 200 + assert UVComment.objects.filter(comment__contains="Superbe UV").exists() + assert not UVComment.objects.filter(comment__contains="Twice").exists() self.assertContains( response, _( @@ -448,21 +408,26 @@ class UVCommentCreationAndDisplay(TestCase): # Ensure that there is no crash when no uv or no author is given self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), - create_uv_comment_template(self.bibou.id, exclude_list=["uv"]), + self.uv_url, create_uv_comment_template(self.bibou.id, exclude_list=["uv"]) ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.client.post( - reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}), + self.uv_url, create_uv_comment_template(self.bibou.id, exclude_list=["author"]), ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 class UVCommentDeleteTest(TestCase): - """ - Test UVComment deletion rights - """ + """Test UVComment deletion rights.""" + + @classmethod + def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") + cls.krophil = User.objects.get(username="krophil") def setUp(self): comment_kwargs = create_uv_comment_template( @@ -474,58 +439,60 @@ class UVCommentDeleteTest(TestCase): self.comment.save() def test_uv_comment_delete_root_success(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) self.client.post( reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) ) - self.assertFalse(UVComment.objects.filter(id=self.comment.id).exists()) + assert not UVComment.objects.filter(id=self.comment.id).exists() def test_uv_comment_delete_pedagogy_admin_success(self): - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) self.client.post( reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) ) - self.assertFalse(UVComment.objects.filter(id=self.comment.id).exists()) + assert not UVComment.objects.filter(id=self.comment.id).exists() def test_uv_comment_delete_author_success(self): - self.client.login(username="krophil", password="plop") + self.client.force_login(self.krophil) self.client.post( reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) ) - self.assertFalse(UVComment.objects.filter(id=self.comment.id).exists()) + assert not UVComment.objects.filter(id=self.comment.id).exists() def test_uv_comment_delete_unauthorized_fail(self): # Anonymous user response = self.client.post( reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Unsbscribed user - self.client.login(username="guy", password="plop") + self.client.force_login(self.guy) response = self.client.post( reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Subscribed user (not author of the comment) - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) response = self.client.post( reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Check that the comment still exists - self.assertTrue(UVComment.objects.filter(id=self.comment.id).exists()) + assert UVComment.objects.filter(id=self.comment.id).exists() class UVCommentUpdateTest(TestCase): - """ - Test UVComment update rights - """ + """Test UVComment update rights.""" @classmethod def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") cls.krophil = User.objects.get(username="krophil") def setUp(self): @@ -541,32 +508,32 @@ class UVCommentUpdateTest(TestCase): self.comment_edit["comment"] = "Edited" def test_uv_comment_update_root_success(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 self.comment.refresh_from_db() self.assertEqual(self.comment.comment, self.comment_edit["comment"]) def test_uv_comment_update_pedagogy_admin_success(self): - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) response = self.client.post( reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 self.comment.refresh_from_db() self.assertEqual(self.comment.comment, self.comment_edit["comment"]) def test_uv_comment_update_author_success(self): - self.client.login(username="krophil", password="plop") + self.client.force_login(self.krophil) response = self.client.post( reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 self.comment.refresh_from_db() self.assertEqual(self.comment.comment, self.comment_edit["comment"]) @@ -576,35 +543,35 @@ class UVCommentUpdateTest(TestCase): reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Unsbscribed user response = self.client.post( reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Subscribed user (not author of the comment) response = self.client.post( reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Check that the comment hasn't change self.comment.refresh_from_db() self.assertNotEqual(self.comment.comment, self.comment_edit["comment"]) def test_uv_comment_update_original_author_does_not_change(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) self.comment_edit["author"] = User.objects.get(username="root").id response = self.client.post( reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), self.comment_edit, ) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 self.assertEqual(self.comment.author, self.krophil) @@ -614,37 +581,44 @@ class UVSearchTest(TestCase): Test that the API is working well """ + @classmethod + def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") + def setUp(self): call_command("update_index", "pedagogy") def test_get_page_authorized_success(self): # Test with root user - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 # Test with pedagogy admin - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 # Test with subscribed user - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 def test_get_page_unauthorized_fail(self): # Test with anonymous user response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Test with not subscribed user - self.client.login(username="guy", password="plop") + self.client.force_login(self.guy) response = self.client.get(reverse("pedagogy:guide")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 def test_search_pa00_success(self): - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) # Search with UV code response = self.client.get(reverse("pedagogy:guide"), {"search": "PA00"}) @@ -783,9 +757,15 @@ class UVModerationFormTest(TestCase): Assert access rights and if the form works well """ - def setUp(self): - self.krophil = User.objects.get(username="krophil") + @classmethod + def setUpTestData(cls): + cls.bibou = User.objects.get(username="root") + cls.tutu = User.objects.get(username="tutu") + cls.sli = User.objects.get(username="sli") + cls.guy = User.objects.get(username="guy") + cls.krophil = User.objects.get(username="krophil") + def setUp(self): # Prepare a comment comment_kwargs = create_uv_comment_template(self.krophil.id) comment_kwargs["author"] = self.krophil @@ -818,119 +798,109 @@ class UVModerationFormTest(TestCase): def test_access_authorized_success(self): # Test with root - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.get(reverse("pedagogy:moderation")) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 # Test with pedagogy admin - self.client.login(username="tutu", password="plop") + self.client.force_login(self.tutu) response = self.client.get(reverse("pedagogy:moderation")) - self.assertEqual(response.status_code, 200) + assert response.status_code == 200 def test_access_unauthorized_fail(self): # Test with anonymous user response = self.client.get(reverse("pedagogy:moderation")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Test with unsubscribed user - self.client.login(username="guy", password="plop") + self.client.force_login(self.guy) response = self.client.get(reverse("pedagogy:moderation")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 # Test with subscribed user - self.client.login(username="sli", password="plop") + self.client.force_login(self.sli) response = self.client.get(reverse("pedagogy:moderation")) - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 def test_do_nothing(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post(reverse("pedagogy:moderation")) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that nothing has changed - self.assertTrue(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists()) - self.assertTrue( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) - self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert UVComment.objects.filter(id=self.comment_1.id).exists() + assert UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() + assert UVCommentReport.objects.filter(id=self.report_2.id).exists() + assert UVComment.objects.filter(id=self.comment_2.id).exists() def test_delete_comment(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), {"accepted_reports": [self.report_1.id]} ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that the comment and it's associated report has been deleted - self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists()) + assert not UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert not UVComment.objects.filter(id=self.comment_1.id).exists() # Test that the bis report has been deleted - self.assertFalse( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) + assert not UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() # Test that the other comment and report still exists - self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert UVCommentReport.objects.filter(id=self.report_2.id).exists() + assert UVComment.objects.filter(id=self.comment_2.id).exists() def test_delete_comment_bulk(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), {"accepted_reports": [self.report_1.id, self.report_2.id]}, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that comments and their associated reports has been deleted - self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists()) - self.assertFalse(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - self.assertFalse(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert not UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert not UVComment.objects.filter(id=self.comment_1.id).exists() + assert not UVCommentReport.objects.filter(id=self.report_2.id).exists() + assert not UVComment.objects.filter(id=self.comment_2.id).exists() # Test that the bis report has been deleted - self.assertFalse( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) + assert not UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() def test_delete_comment_with_bis(self): # Test case if two reports targets the same comment and are both deleted - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), {"accepted_reports": [self.report_1.id, self.report_1_bis.id]}, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that the comment and it's associated report has been deleted - self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists()) + assert not UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert not UVComment.objects.filter(id=self.comment_1.id).exists() # Test that the bis report has been deleted - self.assertFalse( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) + assert not UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() def test_delete_report(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), {"denied_reports": [self.report_1.id]} ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that the report has been deleted and that the comment still exists - self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists()) + assert not UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert UVComment.objects.filter(id=self.comment_1.id).exists() # Test that the bis report is still there - self.assertTrue( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) + assert UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() # Test that the other comment and report still exists - self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert UVCommentReport.objects.filter(id=self.report_2.id).exists() + assert UVComment.objects.filter(id=self.comment_2.id).exists() def test_delete_report_bulk(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), { @@ -941,21 +911,18 @@ class UVModerationFormTest(TestCase): ] }, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that every reports has been deleted - self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertFalse( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) - self.assertFalse(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - + assert not UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert not UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() + assert not UVCommentReport.objects.filter(id=self.report_2.id).exists() # Test that comments still exists - self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert UVComment.objects.filter(id=self.comment_1.id).exists() + assert UVComment.objects.filter(id=self.comment_2.id).exists() def test_delete_mixed(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), { @@ -963,23 +930,21 @@ class UVModerationFormTest(TestCase): "denied_reports": [self.report_1.id], }, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that report 2 and his comment has been deleted - self.assertFalse(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - self.assertFalse(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert not UVCommentReport.objects.filter(id=self.report_2.id).exists() + assert not UVComment.objects.filter(id=self.comment_2.id).exists() # Test that report 1 has been deleted and it's comment still exists - self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists()) + assert not UVCommentReport.objects.filter(id=self.report_1.id).exists() + assert UVComment.objects.filter(id=self.comment_1.id).exists() # Test that report 1 bis is still there - self.assertTrue( - UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() - ) + assert UVCommentReport.objects.filter(id=self.report_1_bis.id).exists() def test_delete_mixed_with_bis(self): - self.client.login(username="root", password="plop") + self.client.force_login(self.bibou) response = self.client.post( reverse("pedagogy:moderation"), { @@ -987,21 +952,19 @@ class UVModerationFormTest(TestCase): "denied_reports": [self.report_1_bis.id], }, ) - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 # Test that report 1 and 1 bis has been deleted - self.assertFalse( - UVCommentReport.objects.filter( - id__in=[self.report_1.id, self.report_1_bis.id] - ).exists() - ) + assert not UVCommentReport.objects.filter( + id__in=[self.report_1.id, self.report_1_bis.id] + ).exists() # Test that comment 1 has been deleted - self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists()) + assert not UVComment.objects.filter(id=self.comment_1.id).exists() # Test that report and comment 2 still exists - self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists()) - self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists()) + assert UVCommentReport.objects.filter(id=self.report_2.id).exists() + assert UVComment.objects.filter(id=self.comment_2.id).exists() class UVCommentReportCreateTest(TestCase): @@ -1032,9 +995,9 @@ class UVCommentReportCreateTest(TestCase): }, ) if success: - self.assertEqual(response.status_code, 302) + assert response.status_code == 302 else: - self.assertEqual(response.status_code, 403) + assert response.status_code == 403 self.assertEqual(UVCommentReport.objects.all().exists(), success) def test_create_report_root_success(self): @@ -1054,32 +1017,24 @@ class UVCommentReportCreateTest(TestCase): reverse("pedagogy:comment_report", kwargs={"comment_id": self.comment.id}), {"comment": self.comment.id, "reporter": 0, "reason": "C'est moche"}, ) - self.assertEqual(response.status_code, 403) - self.assertFalse(UVCommentReport.objects.all().exists()) + assert response.status_code == 403 + assert not UVCommentReport.objects.all().exists() def test_notifications(self): - self.assertFalse( - self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists() - ) + assert not self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists() # Create a comment report self.create_report_test("tutu", True) # Check that a notification has been created for pedagogy admins - self.assertTrue( - self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists() - ) + assert self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists() # Check that only pedagogy admins recieves this notification for notif in Notification.objects.filter(type="PEDAGOGY_MODERATION").all(): - self.assertTrue( - notif.user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) - ) + assert notif.user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) # Check that notifications are not duplicated if not viewed self.create_report_test("tutu", True) - self.assertEqual( - self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count(), 1 - ) + assert self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count() == 1 # Check that a new notification is created when the old one has been viewed notif = self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").first() @@ -1088,6 +1043,4 @@ class UVCommentReportCreateTest(TestCase): self.create_report_test("tutu", True) - self.assertEqual( - self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count(), 2 - ) + assert self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count() == 2 diff --git a/poetry.lock b/poetry.lock index f43c9663..937b4ad7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -321,6 +321,9 @@ files = [ {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, ] +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + [package.extras] toml = ["tomli"] @@ -575,6 +578,20 @@ files = [ {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] +[[package]] +name = "exceptiongroup" +version = "1.2.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "filelock" version = "3.15.4" @@ -627,6 +644,17 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "ipython" version = "7.34.0" @@ -949,6 +977,21 @@ files = [ docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "prompt-toolkit" version = "3.0.47" @@ -1108,6 +1151,64 @@ cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] +[[package]] +name = "pytest" +version = "8.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +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"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-django" +version = "4.8.0" +description = "A Django plugin for pytest." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-django-4.8.0.tar.gz", hash = "sha256:5d054fe011c56f3b10f978f41a8efb2e5adfc7e680ef36fb571ada1f24779d90"}, + {file = "pytest_django-4.8.0-py3-none-any.whl", hash = "sha256:ca1ddd1e0e4c227cf9e3e40a6afc6d106b3e70868fd2ac5798a22501271cd0c7"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +testing = ["Django", "django-configurations (>=2.0)"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1561,4 +1662,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "78f859d93ec1f207dbdebd5b608abfac44f87bb254a37ebeafaaf1823f605a71" +content-hash = "76711479953fc14d7761c3bdcc27088d301df318ebda1a6d8c4a4ab930e341f3" diff --git a/pyproject.toml b/pyproject.toml index e9dfe6d4..dbba8ce7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,10 +55,9 @@ ipython = "^7.28.0" ruff = "^0.4.10" [tool.poetry.group.tests.dependencies] -coverage = "^7.5.4" - -[tool.poetry.group.tests] -optional = true +pytest = "^8.2.2" +pytest-cov = "^5.0.0" +pytest-django = "^4.8.0" [tool.poetry.group.docs.dependencies] sphinx-rtd-theme = "^1.0.0" @@ -73,6 +72,10 @@ version = "1.4.25" [tool.ruff.lint] select = ["I", "F401"] +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "sith.settings" +python_files = ["tests.py", "test_*.py", "*_tests.py"] + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/rootplace/tests.py b/rootplace/tests.py index 80119edf..81d0b616 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -13,10 +13,11 @@ # OR WITHIN THE LOCAL FILE "LICENSE" # # -from datetime import date, timedelta +from datetime import timedelta from django.test import TestCase from django.urls import reverse +from django.utils.timezone import localtime, now from club.models import Club from core.models import RealGroup, User @@ -41,7 +42,7 @@ class MergeUserTest(TestCase): ) def setUp(self) -> None: - self.client.login(username="root", password="plop") + self.client.force_login(self.root) def test_simple(self): self.to_delete.first_name = "Biggus" @@ -66,15 +67,15 @@ class MergeUserTest(TestCase): self.to_keep = User.objects.get(pk=self.to_keep.pk) # fields of to_delete should be assigned to to_keep # if they were not set beforehand - self.assertEqual("Biggus", self.to_keep.first_name) - self.assertEqual("Dickus", self.to_keep.last_name) - self.assertEqual("B'ian", self.to_keep.nick_name) - self.assertEqual("Jerusalem", self.to_keep.address) - self.assertEqual("Rome", self.to_keep.parent_address) - self.assertEqual(3, self.to_keep.groups.count()) - groups = list(self.to_keep.groups.all()) - expected = [subscribers, mde_admin, sas_admin] - self.assertCountEqual(groups, expected) + assert "Biggus" == self.to_keep.first_name + assert "Dickus" == self.to_keep.last_name + assert "B'ian" == self.to_keep.nick_name + assert "Jerusalem" == self.to_keep.address + assert "Rome" == self.to_keep.parent_address + assert (3, self.to_keep.groups.count()) + groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id) + expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id) + assert groups == expected def test_both_subscribers_and_with_account(self): Customer(user=self.to_keep, account_id="11000l", amount=0).save() @@ -113,7 +114,7 @@ class MergeUserTest(TestCase): quantity=4, payment_method="SITH_ACCOUNT", ).save() - today = date.today() + today = localtime(now()).date() # both subscriptions began last month and shall end in 5 months Subscription( member=self.to_keep, @@ -137,9 +138,9 @@ class MergeUserTest(TestCase): # to_delete had 20€ and bought 4 barbar # total should be 10 - 4 + 20 - 8 = 18 self.assertAlmostEqual(18, self.to_keep.customer.amount, delta=0.0001) - self.assertEqual(2, self.to_keep.customer.buyings.count()) - self.assertEqual(2, self.to_keep.customer.refillings.count()) - self.assertTrue(self.to_keep.is_subscribed) + assert self.to_keep.customer.buyings.count() == 2 + assert self.to_keep.customer.refillings.count() == 2 + assert self.to_keep.is_subscribed # to_keep had 5 months of subscription remaining and received # 5 more months from to_delete, so he should be subscribed for 10 months self.assertAlmostEqual( @@ -191,7 +192,7 @@ class MergeUserTest(TestCase): self.assertRedirects(res, self.to_keep.get_absolute_url()) # to_delete had 20€ and bought 4 barbar worth 2€ each # total should be 20 - 8 = 12 - self.assertTrue(hasattr(self.to_keep, "customer")) + assert hasattr(self.to_keep, "customer") self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001) def test_delete_has_no_account(self): @@ -219,5 +220,5 @@ class MergeUserTest(TestCase): self.assertRedirects(res, self.to_keep.get_absolute_url()) # to_keep had 20€ and bought 4 barbar worth 2€ each # total should be 20 - 8 = 12 - self.assertTrue(hasattr(self.to_keep, "customer")) + assert hasattr(self.to_keep, "customer") self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001) diff --git a/sith/settings.py b/sith/settings.py index df2b4ced..8680be2a 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -57,6 +57,7 @@ SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False +TESTING = "pytest" in sys.modules INTERNAL_IPS = ["127.0.0.1"] ALLOWED_HOSTS = ["*"] @@ -113,8 +114,6 @@ MIDDLEWARE = ( "core.middleware.SignalRequestMiddleware", ) -TEST_RUNNER = "sith.testrunner.SithTestRunner" - ROOT_URLCONF = "sith.urls" TEMPLATES = [ @@ -697,7 +696,7 @@ if DEBUG: SASS_INCLUDE_FOLDERS = ["core/static/"] SENTRY_ENV = "development" -if "test" in sys.argv: +if TESTING: CAPTCHA_TEST_MODE = True if SENTRY_DSN: diff --git a/sith/testrunner.py b/sith/testrunner.py deleted file mode 100644 index 1112cf89..00000000 --- a/sith/testrunner.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.core.management import call_command -from django.test.runner import DiscoverRunner - - -class SithTestRunner(DiscoverRunner): - def setup_databases(self, **kwargs): - res = super().setup_databases(**kwargs) - call_command("populate") - return res diff --git a/subscription/tests.py b/subscription/tests.py index 1b9924e8..c347ab65 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -14,8 +14,9 @@ # # from datetime import date -from unittest import mock +import freezegun +import pytest from django.conf import settings from django.test import TestCase @@ -23,113 +24,92 @@ from core.models import User from subscription.models import Subscription -class FakeDate(date): - """A fake replacement for date that can be mocked for testing.""" - - def __new__(cls, *args, **kwargs): - return date.__new__(date, *args, **kwargs) +@pytest.mark.parametrize( + ("today", "duration", "expected_start"), + [ + (date(2020, 9, 18), 1, date(2020, 9, 18)), + (date(2020, 9, 18), 2, date(2020, 9, 18)), + (date(2020, 5, 17), 3, date(2020, 2, 15)), + (date(2021, 1, 18), 4, date(2020, 8, 15)), + (date(2020, 9, 18), 4, date(2020, 8, 15)), + ], +) +def test_subscription_compute_start_from_today(today, duration, expected_start): + with freezegun.freeze_time(today): + assert Subscription.compute_start(duration=duration) == expected_start -def date_mock_today(year, month, day): - FakeDate.today = classmethod(lambda cls: date(year, month, day)) +@pytest.mark.parametrize( + ("start_date", "duration", "expected_start"), + [ + (date(2020, 5, 17), 1, date(2020, 5, 17)), + (date(2020, 5, 17), 2, date(2020, 5, 17)), + (date(2020, 5, 17), 3, date(2020, 2, 15)), + (date(2020, 1, 11), 3, date(2019, 8, 15)), + ], +) +def test_subscription_compute_start_explicit(start_date, duration, expected_start): + assert Subscription.compute_start(start_date, duration=duration) == expected_start -class SubscriptionUnitTest(TestCase): - @mock.patch("subscription.models.date", FakeDate) - def test_start_dates_sliding_without_start(self): - date_mock_today(2015, 9, 18) - d = Subscription.compute_start(duration=1) - self.assertTrue(d == date(2015, 9, 18)) - self.assertTrue(Subscription.compute_start(duration=2) == date(2015, 9, 18)) +@pytest.mark.parametrize( + ("today", "duration", "expected_end"), + [ + (date(2020, 9, 18), 1, date(2021, 3, 18)), + (date(2020, 9, 18), 2, date(2021, 9, 18)), + (date(2020, 9, 18), 3, date(2022, 2, 15)), + (date(2020, 5, 17), 4, date(2022, 8, 15)), + (date(2020, 9, 18), 0.33, date(2020, 11, 18)), + (date(2020, 9, 18), 0.67, date(2021, 1, 19)), + (date(2020, 9, 18), 0.5, date(2020, 12, 18)), + ], +) +def test_subscription_compute_end_from_today(today, duration, expected_end): + with freezegun.freeze_time(today): + assert Subscription.compute_end(duration=duration) == expected_end - def test_start_dates_sliding_with_start(self): - self.assertTrue( - Subscription.compute_start(date(2015, 5, 17), 1) == date(2015, 5, 17) - ) - self.assertTrue( - Subscription.compute_start(date(2015, 5, 17), 2) == date(2015, 5, 17) - ) - @mock.patch("subscription.models.date", FakeDate) - def test_start_dates_not_sliding_without_start(self): - date_mock_today(2015, 5, 17) - self.assertTrue(Subscription.compute_start(duration=3) == date(2015, 2, 15)) - date_mock_today(2016, 1, 18) - self.assertTrue(Subscription.compute_start(duration=4) == date(2015, 8, 15)) - date_mock_today(2015, 9, 18) - self.assertTrue(Subscription.compute_start(duration=4) == date(2015, 8, 15)) - - def test_start_dates_not_sliding_with_start(self): - self.assertTrue( - Subscription.compute_start(date(2015, 5, 17), 3) == date(2015, 2, 15) - ) - self.assertTrue( - Subscription.compute_start(date(2015, 1, 11), 3) == date(2014, 8, 15) - ) - - @mock.patch("subscription.models.date", FakeDate) - def test_end_dates_sliding(self): - date_mock_today(2015, 9, 18) - d = Subscription.compute_end(2) - self.assertTrue(d == date(2016, 9, 18)) - d = Subscription.compute_end(1) - self.assertTrue(d == date(2016, 3, 18)) - - @mock.patch("subscription.models.date", FakeDate) - def test_end_dates_not_sliding_without_start(self): - date_mock_today(2015, 9, 18) - d = Subscription.compute_end(duration=3) - self.assertTrue(d == date(2017, 2, 15)) - d = Subscription.compute_end(duration=4) - self.assertTrue(d == date(2017, 8, 15)) - - @mock.patch("subscription.models.date", FakeDate) - def test_end_dates_with_float(self): - date_mock_today(2015, 9, 18) - d = Subscription.compute_end(duration=0.33) - self.assertTrue(d == date(2015, 11, 18)) - d = Subscription.compute_end(duration=0.67) - self.assertTrue(d == date(2016, 1, 19)) - d = Subscription.compute_end(duration=0.5) - self.assertTrue(d == date(2015, 12, 18)) - - def test_end_dates_not_sliding_with_start(self): - d = Subscription.compute_end(duration=3, start=date(2015, 9, 18)) - self.assertTrue(d == date(2017, 3, 18)) - d = Subscription.compute_end(duration=4, start=date(2015, 9, 18)) - self.assertTrue(d == date(2017, 9, 18)) +@pytest.mark.parametrize( + ("start_date", "duration", "expected_end"), + [ + (date(2020, 9, 18), 3, date(2022, 3, 18)), + (date(2020, 9, 18), 4, date(2022, 9, 18)), + ], +) +def test_subscription_compute_end_from_today(start_date, duration, expected_end): + assert Subscription.compute_end(duration, start_date) == expected_end class SubscriptionIntegrationTest(TestCase): @classmethod - def setUp(cls): - cls.user = User.objects.filter(username="public").first() + def setUpTestData(cls): + cls.user = User.objects.get(username="public") def test_duration_one_month(self): s = Subscription( - member=User.objects.filter(pk=self.user.pk).first(), + member=self.user, subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], ) s.subscription_start = date(2017, 8, 29) s.subscription_end = s.compute_end(duration=0.166, start=s.subscription_start) s.save() - self.assertTrue(s.subscription_end == date(2017, 9, 29)) + assert s.subscription_end == date(2017, 9, 29) def test_duration_two_months(self): s = Subscription( - member=User.objects.filter(pk=self.user.pk).first(), + member=self.user, subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], ) s.subscription_start = date(2017, 8, 29) s.subscription_end = s.compute_end(duration=0.333, start=s.subscription_start) s.save() - self.assertTrue(s.subscription_end == date(2017, 10, 29)) + assert s.subscription_end == date(2017, 10, 29) def test_duration_one_day(self): s = Subscription( - member=User.objects.filter(pk=self.user.pk).first(), + member=self.user, subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], ) @@ -139,44 +119,43 @@ class SubscriptionIntegrationTest(TestCase): start=s.subscription_start, ) s.save() - self.assertTrue(s.subscription_end == date(2017, 8, 30)) + assert s.subscription_end == date(2017, 8, 30) def test_duration_three_months(self): s = Subscription( - member=User.objects.filter(pk=self.user.pk).first(), + member=self.user, subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], ) s.subscription_start = date(2017, 8, 29) s.subscription_end = s.compute_end(duration=0.5, start=s.subscription_start) s.save() - self.assertTrue(s.subscription_end == date(2017, 11, 29)) + assert s.subscription_end == date(2017, 11, 29) def test_duration_four_months(self): s = Subscription( - member=User.objects.filter(pk=self.user.pk).first(), + member=self.user, subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], ) s.subscription_start = date(2017, 8, 29) s.subscription_end = s.compute_end(duration=0.67, start=s.subscription_start) s.save() - self.assertTrue(s.subscription_end == date(2017, 12, 30)) + assert s.subscription_end == date(2017, 12, 30) def test_duration_six_weeks(self): s = Subscription( - member=User.objects.filter(pk=self.user.pk).first(), + member=self.user, subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], ) s.subscription_start = date(2018, 9, 1) s.subscription_end = s.compute_end(duration=0.23, start=s.subscription_start) s.save() - self.assertTrue(s.subscription_end == date(2018, 10, 13)) + assert s.subscription_end == date(2018, 10, 13) - @mock.patch("subscription.models.date", FakeDate) def test_dates_sliding_with_subscribed_user(self): - user = User.objects.filter(pk=self.user.pk).first() + user = self.user s = Subscription( member=user, subscription_type="deux-semestres", @@ -188,18 +167,16 @@ class SubscriptionIntegrationTest(TestCase): start=s.subscription_start, ) s.save() - self.assertTrue(s.subscription_end == date(2016, 8, 29)) - date_mock_today(2016, 8, 25) - d = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], - user=user, - ) + assert s.subscription_end == date(2016, 8, 29) + with freezegun.freeze_time("2016-08-25"): + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) + assert d == date(2017, 8, 29) - self.assertTrue(d == date(2017, 8, 29)) - - @mock.patch("subscription.models.date", FakeDate) def test_dates_renewal_sliding_during_two_free_monthes(self): - user = User.objects.filter(pk=self.user.pk).first() + user = self.user s = Subscription( member=user, subscription_type="deux-mois-essai", @@ -211,17 +188,16 @@ class SubscriptionIntegrationTest(TestCase): start=s.subscription_start, ) s.save() - self.assertTrue(s.subscription_end == date(2015, 10, 29)) - date_mock_today(2015, 9, 25) - d = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], - user=user, - ) - self.assertTrue(d == date(2016, 10, 29)) + assert s.subscription_end == date(2015, 10, 29) + with freezegun.freeze_time("2015-09-25"): + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) + assert d == date(2016, 10, 29) - @mock.patch("subscription.models.date", FakeDate) def test_dates_renewal_sliding_after_two_free_monthes(self): - user = User.objects.filter(pk=self.user.pk).first() + user = self.user s = Subscription( member=user, subscription_type="deux-mois-essai", @@ -233,10 +209,10 @@ class SubscriptionIntegrationTest(TestCase): start=s.subscription_start, ) s.save() - self.assertTrue(s.subscription_end == date(2015, 10, 29)) - date_mock_today(2015, 11, 5) - d = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], - user=user, - ) - self.assertTrue(d == date(2016, 11, 5)) + assert s.subscription_end == date(2015, 10, 29) + with freezegun.freeze_time("2015-11-05"): + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) + assert d == date(2016, 11, 5) From e29e1101cd02dec0be5390939c29e7f36fb87ba6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 08:59:52 +0000 Subject: [PATCH 79/95] [UPDATE] Bump ipython from 7.34.0 to 8.26.0 Bumps [ipython](https://github.com/ipython/ipython) from 7.34.0 to 8.26.0. - [Release notes](https://github.com/ipython/ipython/releases) - [Commits](https://github.com/ipython/ipython/compare/7.34.0...8.26.0) --- updated-dependencies: - dependency-name: ipython dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- poetry.lock | 149 ++++++++++++++++++++++++++++--------------------- pyproject.toml | 2 +- 2 files changed, 85 insertions(+), 66 deletions(-) diff --git a/poetry.lock b/poetry.lock index 937b4ad7..e7307095 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -11,17 +11,6 @@ files = [ {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] -[[package]] -name = "appnope" -version = "0.1.4" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = ">=3.6" -files = [ - {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, - {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, -] - [[package]] name = "asgiref" version = "3.8.1" @@ -39,6 +28,24 @@ typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""} [package.extras] tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + [[package]] name = "babel" version = "2.15.0" @@ -53,17 +60,6 @@ files = [ [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] -[[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" -optional = false -python-versions = "*" -files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] - [[package]] name = "certifi" version = "2024.6.2" @@ -592,6 +588,20 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + [[package]] name = "filelock" version = "3.15.4" @@ -657,39 +667,41 @@ files = [ [[package]] name = "ipython" -version = "7.34.0" +version = "8.26.0" description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" files = [ - {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, - {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, + {file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"}, + {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"}, ] [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = "*" -setuptools = ">=18.5" -traitlets = ">=4.2" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" +typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] -doc = ["Sphinx (>=1.3)"] +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"] kernel = ["ipykernel"] +matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "jedi" @@ -887,17 +899,6 @@ files = [ {file = "phonenumbers-8.13.39.tar.gz", hash = "sha256:db7ca4970d206b2056231105300753b1a5b229f43416f8c2b3010e63fbb68d77"}, ] -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -optional = false -python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] - [[package]] name = "pillow" version = "9.5.0" @@ -1098,6 +1099,20 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pycparser" version = "2.22" @@ -1348,21 +1363,6 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] -[[package]] -name = "setuptools" -version = "70.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"}, - {file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -1579,6 +1579,25 @@ files = [ dev = ["build", "hatch"] doc = ["sphinx"] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "tomli" version = "2.0.1" @@ -1662,4 +1681,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "76711479953fc14d7761c3bdcc27088d301df318ebda1a6d8c4a4ab930e341f3" +content-hash = "0fd9231aa25ed4b54b2f8f7fdf1e77a075c436cdc37e77eab7684fece1fc0e4f" diff --git a/pyproject.toml b/pyproject.toml index dbba8ce7..441e29fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ tomli = "^2.0.1" [tool.poetry.group.dev.dependencies] freezegun = "^1.2.2" # used to test time-dependent code django-debug-toolbar = "^4.0.0" -ipython = "^7.28.0" +ipython = "^8.26.0" ruff = "^0.4.10" [tool.poetry.group.tests.dependencies] From c7135875b892aaef996d2d006db941fd51e67717 Mon Sep 17 00:00:00 2001 From: Sli Date: Wed, 26 Jun 2024 19:00:41 +0200 Subject: [PATCH 80/95] Use pre-commits hooks instead of ruff directly --- .github/workflows/ci.yml | 16 ++-- .pre-commit-config.yaml | 10 +++ doc/start/devtools.rst | 46 ++++++++++ poetry.lock | 177 +++++++++++++++++++++++++++++++++++++-- pyproject.toml | 5 +- 5 files changed, 235 insertions(+), 19 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e3f8cd9..fea0549d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,17 +8,15 @@ on: workflow_dispatch: jobs: - ruff: - name: Ruff lint & format + pre-commit: + name: Launch pre-commits checks (ruff) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: ruff format - uses: chartboost/ruff-action@v1 # format - with: - args: format --diff - - name: ruff check - uses: chartboost/ruff-action@v1 # lint + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files --show-diff-on-failure tests: name: Run tests and generate coverage report diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..19dc1151 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.4.10 + hooks: + # Run the linter. + - id: ruff + args: ["--fix"] + # Run the formatter. + - id: ruff-format diff --git a/doc/start/devtools.rst b/doc/start/devtools.rst index 80d56a13..1cc9dc3a 100644 --- a/doc/start/devtools.rst +++ b/doc/start/devtools.rst @@ -8,6 +8,36 @@ Pour donner une idée, Skia a écrit une énorme partie de projet avec l'éditeu alors que Sli a utilisé *Sublime Text* sur MacOS et que Maréchal travaille avec PyCharm sur ~~Windows muni de WSL~~ Arch Linux btw. +Configurer les pre-commit hooks +-------------------------------- + +La procédure habituelle pour contribuer au projet consiste à commit des modifications, puis à les push sur le dépôt distant et à ouvrir une pull request. Cette PR va faire tourner les outils de vérification de la qualité de code. Si la vérification échoue, la PR est bloquée, et il faut réparer le problème (ce qui implique de push un micro-commit ou de push force sur la branche). + +Dans l'idéal, on aimerait donc qu'il soit impossible d'oublier de faire tourner ces vérification. Pour ça, il existe un mécanisme : les pre-commits hooks. Ce sont des actions qui tournent automatiquement lorsque vous effectuez un `git commit`. Ces dernières vont analyser et éventuellement modifier le code, avant que Git n'ajoute effectivement le commit sur l'arbre git. Voyez ça comme une micro-CI qui tourne en local. + +Les git hooks sont une fonctionnalité par défaut de Git. Cependant, leur configuration peut-être un peu embêtante si vous le faites manuellement. Pour gérer ça plus simplement, nous utilisons le logiciel python [pre-commit](https://pre-commit.com/) qui permet de contrôler leur installation via un seul fichier de configuration, placé à la racine du projet (plus précisément, il s'agit du fichier `.pre-commit-config.yaml`). + +.. note:: + + Les pre-commits sont également utilisés dans la CI. Si ces derniers fonctionnent localement, vous avez la garantie que la pipeline ne sera pas fachée. ;) + +C'est une fonctionnalité de git lui même mais c'est assez embêtant à gérer manuellement. Pour gérer ça plus simplement, nous utilisons le logiciel python [pre-commit](https://pre-commit.com/) qui permet de contrôller leur installation via un fichier yaml. + +Les pre-commits sont également utilisés dans la CI, si les pre-commits fonctionnent localement, vous avez la garantie que la pipeline ne sera pas fachée ;). + +Le logiciel est installé par défaut par poetry. Il suffit ensuite de lancer : + +.. code-block:: + + pre-commit install + +Tout se fait ensuite automatiquement lorsqu'on utilise git normalement pour commit. Pour appliquer les pre-commits manuellement, il est possible d'appeler soi même les pre-commits + +.. code-block:: + + pre-commit run --all-files + + Configurer Ruff pour son éditeur --------------------------------- @@ -60,6 +90,22 @@ Sublime Text Vous devez installer ce plugin : https://packagecontrol.io/packages/LSP-ruff. Suivez ensuite les instructions données dans la description du plugin. +Dans la configuration de votre projet, ajoutez ceci: + +.. sourcecode:: json + + { + "settings": { + "lsp_format_on_save": true, + "LSP": { + "LSP-ruff": { + "enabled": true, + } + } + } + } + + Si vous utilisez le plugin `anaconda `__, pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de ruff comme ceci : .. sourcecode:: json diff --git a/poetry.lock b/poetry.lock index e7307095..97d9036b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -135,6 +135,17 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "chardet" version = "5.2.0" @@ -389,6 +400,17 @@ files = [ [package.extras] tests = ["noseofyeti[black] (==2.4.4)", "pytest (==7.4.4)"] +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + [[package]] name = "django" version = "3.2.25" @@ -632,6 +654,20 @@ files = [ [package.dependencies] python-dateutil = ">=2.7" +[[package]] +name = "identify" +version = "2.5.36" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.7" @@ -848,6 +884,17 @@ files = [ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + [[package]] name = "packaging" version = "24.1" @@ -890,13 +937,13 @@ ptyprocess = ">=0.5" [[package]] name = "phonenumbers" -version = "8.13.39" +version = "8.13.40" 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.39-py2.py3-none-any.whl", hash = "sha256:3ad2d086fa71e7eef409001b9195ac54bebb0c6e3e752209b558ca192c9229a0"}, - {file = "phonenumbers-8.13.39.tar.gz", hash = "sha256:db7ca4970d206b2056231105300753b1a5b229f43416f8c2b3010e63fbb68d77"}, + {file = "phonenumbers-8.13.40-py2.py3-none-any.whl", hash = "sha256:9582752c20a1da5ec4449f7f97542bf8a793c8e2fec0ab57f767177bb8fc0b1d"}, + {file = "phonenumbers-8.13.40.tar.gz", hash = "sha256:f137c2848b8e83dd064b71881b65680584417efa202177fd330e2f7ff6c68113"}, ] [[package]] @@ -978,6 +1025,22 @@ files = [ docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + [[package]] name = "pluggy" version = "1.5.0" @@ -993,6 +1056,24 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.7.1" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.1-py2.py3-none-any.whl", hash = "sha256:fae36fd1d7ad7d6a5a1c0b0d5adb2ed1a3bda5a21bf6c3e5372073d7a11cd4c5"}, + {file = "pre_commit-3.7.1.tar.gz", hash = "sha256:8ca3ad567bc78a4972a3f1a477e94a79d4597e8140a6e0b651c5e33899c3654a"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "prompt-toolkit" version = "3.0.47" @@ -1249,15 +1330,75 @@ files = [ {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, ] +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + [[package]] name = "reportlab" -version = "4.2.0" +version = "4.2.2" description = "The Reportlab Toolkit" optional = false python-versions = "<4,>=3.7" files = [ - {file = "reportlab-4.2.0-py3-none-any.whl", hash = "sha256:53630f9d25a7938def3e6a93d723b72a7a5921d34d23cf7a0930adeb2cb0e6c1"}, - {file = "reportlab-4.2.0.tar.gz", hash = "sha256:474fb28d63431a5d47d75c90d580393050df7d491a09c7877df3291a2e9f6d0a"}, + {file = "reportlab-4.2.2-py3-none-any.whl", hash = "sha256:927616931637e2f13e2ee3b3b6316d7a07803170e258621cff7d138bde17fbb5"}, + {file = "reportlab-4.2.2.tar.gz", hash = "sha256:765eecbdd68491c56947e29c38b8b69b834ee5dbbdd2fb7409f08ebdebf04428"}, ] [package.dependencies] @@ -1652,6 +1793,26 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "wcwidth" version = "0.2.13" @@ -1681,4 +1842,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "0fd9231aa25ed4b54b2f8f7fdf1e77a075c436cdc37e77eab7684fece1fc0e4f" +content-hash = "7f807b6216c032932ac20e2bdac462c7a2c4e8db328cdd3536281d6fbc009776" diff --git a/pyproject.toml b/pyproject.toml index 441e29fa..baaee06f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,12 +49,13 @@ Sphinx = "^5" # Needed for building xapian tomli = "^2.0.1" [tool.poetry.group.dev.dependencies] -freezegun = "^1.2.2" # used to test time-dependent code django-debug-toolbar = "^4.0.0" ipython = "^8.26.0" -ruff = "^0.4.10" +pre-commit = "^3.7.1" +ruff = "^0.4.10" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml [tool.poetry.group.tests.dependencies] +freezegun = "^1.2.2" # used to test time-dependent code pytest = "^8.2.2" pytest-cov = "^5.0.0" pytest-django = "^4.8.0" From 6dfd43a8da6d022a1a6d5f102c94d8052f035f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 08:37:14 +0000 Subject: [PATCH 81/95] [UPDATE] Bump reportlab from 4.2.0 to 4.2.2 Bumps [reportlab](https://www.reportlab.com/) from 4.2.0 to 4.2.2. --- updated-dependencies: - dependency-name: reportlab dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index e7307095..e1c637ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1251,13 +1251,13 @@ files = [ [[package]] name = "reportlab" -version = "4.2.0" +version = "4.2.2" description = "The Reportlab Toolkit" optional = false python-versions = "<4,>=3.7" files = [ - {file = "reportlab-4.2.0-py3-none-any.whl", hash = "sha256:53630f9d25a7938def3e6a93d723b72a7a5921d34d23cf7a0930adeb2cb0e6c1"}, - {file = "reportlab-4.2.0.tar.gz", hash = "sha256:474fb28d63431a5d47d75c90d580393050df7d491a09c7877df3291a2e9f6d0a"}, + {file = "reportlab-4.2.2-py3-none-any.whl", hash = "sha256:927616931637e2f13e2ee3b3b6316d7a07803170e258621cff7d138bde17fbb5"}, + {file = "reportlab-4.2.2.tar.gz", hash = "sha256:765eecbdd68491c56947e29c38b8b69b834ee5dbbdd2fb7409f08ebdebf04428"}, ] [package.dependencies] From 99605b98d40c1f89ea3d250ab2dc413073aced74 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 2 Jul 2024 20:16:02 +0200 Subject: [PATCH 82/95] Two steps pre-commit and better workflow output --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fea0549d..b5bbaded 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.1 with: - extra_args: --all-files --show-diff-on-failure + extra_args: --all-files tests: name: Run tests and generate coverage report diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19dc1151..9548a31d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,8 +3,8 @@ repos: # Ruff version. rev: v0.4.10 hooks: - # Run the linter. - - id: ruff - args: ["--fix"] + - id: ruff # just check the code, and print the errors + - id: ruff # actually fix the fixable errors, but print nothing + args: ["--fix", "--silent"] # Run the formatter. - - id: ruff-format + - id: ruff-format \ No newline at end of file From 507080f75eced704ecfcbd9495ba0ef6fc7c1641 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 26 Jun 2024 11:24:09 +0200 Subject: [PATCH 83/95] update django to 4.2 --- poetry.lock | 29 ++++++++++++++++++++--------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index 97d9036b..06085a2a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -413,19 +413,19 @@ files = [ [[package]] name = "django" -version = "3.2.25" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +version = "4.2.13" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "Django-3.2.25-py3-none-any.whl", hash = "sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38"}, - {file = "Django-3.2.25.tar.gz", hash = "sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777"}, + {file = "Django-4.2.13-py3-none-any.whl", hash = "sha256:a17fcba2aad3fc7d46fdb23215095dbbd64e6174bf4589171e732b18b07e426a"}, + {file = "Django-4.2.13.tar.gz", hash = "sha256:837e3cf1f6c31347a1396a3f6b65688f2b4bb4a11c580dcb628b5afe527b68a5"}, ] [package.dependencies] -asgiref = ">=3.3.2,<4" -pytz = "*" -sqlparse = ">=0.2.2" +asgiref = ">=3.6.0,<4" +sqlparse = ">=0.3.1" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] argon2 = ["argon2-cffi (>=19.1.0)"] @@ -1776,6 +1776,17 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + [[package]] name = "urllib3" version = "2.2.2" @@ -1842,4 +1853,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "7f807b6216c032932ac20e2bdac462c7a2c4e8db328cdd3536281d6fbc009776" +content-hash = "b090426042093af41cfbbced92a1a47bf0834ce88865dc7f51d0b7b04fda99a7" diff --git a/pyproject.toml b/pyproject.toml index baaee06f..a2b9fe90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ license = "GPL-3.0-only" [tool.poetry.dependencies] python = "^3.10,<3.12" # Version is held back by mistune -Django = "^3.2" +Django = "^4.2.13" Pillow = "^9.2" mistune = "^0.8.4" django-jinja = "^2.10" From ae1fcdb8c07f44dad69233f9377738198bcaaa3b Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 26 Jun 2024 11:24:56 +0200 Subject: [PATCH 84/95] fix: CashRegisterSummaryItem.check overriding a django method --- ...heck_cashregistersummaryitem_is_checked.py | 24 +++++++++++++++++ counter/models.py | 26 +++++++++++-------- counter/views.py | 10 +++---- 3 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 counter/migrations/0021_rename_check_cashregistersummaryitem_is_checked.py diff --git a/counter/migrations/0021_rename_check_cashregistersummaryitem_is_checked.py b/counter/migrations/0021_rename_check_cashregistersummaryitem_is_checked.py new file mode 100644 index 00000000..3ccc2744 --- /dev/null +++ b/counter/migrations/0021_rename_check_cashregistersummaryitem_is_checked.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2 on 2024-06-26 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [("counter", "0020_auto_20221215_1709")] + + operations = [ + migrations.RenameField( + model_name="cashregistersummaryitem", + old_name="check", + new_name="is_check", + ), + migrations.AlterField( + model_name="cashregistersummaryitem", + name="is_check", + field=models.BooleanField( + default=False, + help_text="True if this is a bank check, else False", + verbose_name="check", + ), + ), + ] diff --git a/counter/models.py b/counter/models.py index c9972f10..6c6dd27c 100644 --- a/counter/models.py +++ b/counter/models.py @@ -927,25 +927,25 @@ class CashRegisterSummary(models.Model): if name[:5] == "check": checks = self.items.filter(check=True).order_by("value").all() if name == "ten_cents": - return self.items.filter(value=0.1, check=False).first() + return self.items.filter(value=0.1, is_check=False).first() elif name == "twenty_cents": - return self.items.filter(value=0.2, check=False).first() + return self.items.filter(value=0.2, is_check=False).first() elif name == "fifty_cents": - return self.items.filter(value=0.5, check=False).first() + return self.items.filter(value=0.5, is_check=False).first() elif name == "one_euro": - return self.items.filter(value=1, check=False).first() + return self.items.filter(value=1, is_check=False).first() elif name == "two_euros": - return self.items.filter(value=2, check=False).first() + return self.items.filter(value=2, is_check=False).first() elif name == "five_euros": - return self.items.filter(value=5, check=False).first() + return self.items.filter(value=5, is_check=False).first() elif name == "ten_euros": - return self.items.filter(value=10, check=False).first() + return self.items.filter(value=10, is_check=False).first() elif name == "twenty_euros": - return self.items.filter(value=20, check=False).first() + return self.items.filter(value=20, is_check=False).first() elif name == "fifty_euros": - return self.items.filter(value=50, check=False).first() + return self.items.filter(value=50, is_check=False).first() elif name == "hundred_euros": - return self.items.filter(value=100, check=False).first() + return self.items.filter(value=100, is_check=False).first() elif name == "check_1": return checks[0] if 0 < len(checks) else None elif name == "check_2": @@ -993,7 +993,11 @@ class CashRegisterSummaryItem(models.Model): ) value = CurrencyField(_("value")) quantity = models.IntegerField(_("quantity"), default=0) - check = models.BooleanField(_("check"), default=False) + is_check = models.BooleanField( + _("check"), + default=False, + help_text=_("True if this is a bank check, else False"), + ) class Meta: verbose_name = _("cash register summary item") diff --git a/counter/views.py b/counter/views.py index fe10d0a1..e27547d2 100644 --- a/counter/views.py +++ b/counter/views.py @@ -1204,35 +1204,35 @@ class CashRegisterSummaryForm(forms.Form): cash_summary=summary, value=cd["check_1_value"], quantity=cd["check_1_quantity"], - check=True, + is_check=True, ).save() if cd["check_2_quantity"]: CashRegisterSummaryItem( cash_summary=summary, value=cd["check_2_value"], quantity=cd["check_2_quantity"], - check=True, + is_check=True, ).save() if cd["check_3_quantity"]: CashRegisterSummaryItem( cash_summary=summary, value=cd["check_3_value"], quantity=cd["check_3_quantity"], - check=True, + is_check=True, ).save() if cd["check_4_quantity"]: CashRegisterSummaryItem( cash_summary=summary, value=cd["check_4_value"], quantity=cd["check_4_quantity"], - check=True, + is_check=True, ).save() if cd["check_5_quantity"]: CashRegisterSummaryItem( cash_summary=summary, value=cd["check_5_value"], quantity=cd["check_5_quantity"], - check=True, + is_check=True, ).save() if summary.items.count() < 1: summary.delete() From 75bb3f992cdc651988eccf6a88574ff9b3e907b2 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 26 Jun 2024 11:39:07 +0200 Subject: [PATCH 85/95] fix: wrong logic in Club.delete() --- club/models.py | 2 +- club/tests.py | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/club/models.py b/club/models.py index 252dc085..10f1cd59 100644 --- a/club/models.py +++ b/club/models.py @@ -207,11 +207,11 @@ class Club(models.Model): cache.set(f"sith_club_{self.unix_name}", self) def delete(self, *args, **kwargs): - super().delete(*args, **kwargs) # Invalidate the cache of this club and of its memberships for membership in self.members.ongoing().select_related("user"): cache.delete(f"membership_{self.id}_{membership.user.id}") cache.delete(f"sith_club_{self.unix_name}") + super().delete(*args, **kwargs) def __str__(self): return self.name diff --git a/club/tests.py b/club/tests.py index 21285ade..99172a16 100644 --- a/club/tests.py +++ b/club/tests.py @@ -46,6 +46,8 @@ class ClubTest(TestCase): def setUpTestData(cls): # subscribed users - initial members cls.skia = User.objects.get(username="skia") + # by default, Skia is in the AE, which creates side effect + cls.skia.memberships.all().delete() cls.richard = User.objects.get(username="rbatsbak") cls.comptable = User.objects.get(username="comptable") cls.sli = User.objects.get(username="sli") @@ -62,38 +64,34 @@ class ClubTest(TestCase): cls.public = User.objects.get(username="public") cls.ae = Club.objects.filter(pk=SITH_MAIN_CLUB_ID)[0] - - def setUp(self): - # by default, Skia is in the AE, which creates side effect - self.skia.memberships.all().delete() - - # create a fake club - self.club = Club.objects.create( + cls.club = Club.objects.create( name="Fake Club", unix_name="fake-club", address="5 rue de la République, 90000 Belfort", ) - self.members_url = reverse( - "club:club_members", kwargs={"club_id": self.club.id} + cls.members_url = reverse( + "club:club_members", kwargs={"club_id": cls.club.id} ) a_month_ago = now() - timedelta(days=30) yesterday = now() - timedelta(days=1) Membership.objects.create( - club=self.club, user=self.skia, start_date=a_month_ago, role=3 + club=cls.club, user=cls.skia, start_date=a_month_ago, role=3 ) - Membership.objects.create(club=self.club, user=self.richard, role=1) + Membership.objects.create(club=cls.club, user=cls.richard, role=1) Membership.objects.create( - club=self.club, user=self.comptable, start_date=a_month_ago, role=10 + club=cls.club, user=cls.comptable, start_date=a_month_ago, role=10 ) # sli was a member but isn't anymore Membership.objects.create( - club=self.club, - user=self.sli, + club=cls.club, + user=cls.sli, start_date=a_month_ago, end_date=yesterday, role=2, ) + + def setUp(self): cache.clear() From cd58d5a357989c3468ddc263edf3bb0bc161f496 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 26 Jun 2024 12:28:00 +0200 Subject: [PATCH 86/95] resolve warnings --- club/tests.py | 17 ++++++----------- core/utils.py | 4 +++- counter/migrations/0003_permanency_activity.py | 6 ++++-- counter/models.py | 15 +++++++++------ forum/migrations/0001_initial.py | 6 ++++-- sith/settings.py | 4 +--- 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/club/tests.py b/club/tests.py index 99172a16..78fb8665 100644 --- a/club/tests.py +++ b/club/tests.py @@ -69,9 +69,7 @@ class ClubTest(TestCase): unix_name="fake-club", address="5 rue de la République, 90000 Belfort", ) - cls.members_url = reverse( - "club:club_members", kwargs={"club_id": cls.club.id} - ) + cls.members_url = reverse("club:club_members", kwargs={"club_id": cls.club.id}) a_month_ago = now() - timedelta(days=30) yesterday = now() - timedelta(days=1) Membership.objects.create( @@ -174,14 +172,11 @@ class MembershipQuerySetTest(ClubTest): # should delete the subscriptions of skia and comptable self.club.members.ongoing().board().delete() - assert ( - cache.get(f"membership_{mem_skia.club_id}_{mem_skia.user_id}") - == "not_member" - ) - assert ( - cache.get(f"membership_{mem_comptable.club_id}_{mem_comptable.user_id}") - == "not_member", - ) + for membership in (mem_skia, mem_comptable): + cached_mem = cache.get( + f"membership_{membership.club_id}_{membership.user_id}" + ) + assert cached_mem == "not_member" class ClubModelTest(ClubTest): diff --git a/core/utils.py b/core/utils.py index 62cf04bb..427786fe 100644 --- a/core/utils.py +++ b/core/utils.py @@ -28,6 +28,7 @@ from django.conf import settings from django.core.files.base import ContentFile from django.utils import timezone from PIL import ExifTags +from PIL.Image import Resampling def get_git_revision_short_hash() -> str: @@ -109,7 +110,8 @@ def resize_image(im, edge, format): (w, h) = im.size (width, height) = scale_dimension(w, h, long_edge=edge) content = BytesIO() - im = im.resize((width, height), PIL.Image.ANTIALIAS) + # use the lanczos filter for antialiasing + im = im.resize((width, height), Resampling.LANCZOS) try: im.save( fp=content, diff --git a/counter/migrations/0003_permanency_activity.py b/counter/migrations/0003_permanency_activity.py index 929d8308..915be8fc 100644 --- a/counter/migrations/0003_permanency_activity.py +++ b/counter/migrations/0003_permanency_activity.py @@ -2,9 +2,9 @@ from __future__ import unicode_literals import datetime +from datetime import timezone from django.db import migrations, models -from django.utils.timezone import utc class Migration(migrations.Migration): @@ -17,7 +17,9 @@ class Migration(migrations.Migration): field=models.DateTimeField( verbose_name="activity time", auto_now=True, - default=datetime.datetime(2016, 8, 26, 17, 5, 31, 202824, tzinfo=utc), + default=datetime.datetime( + 2016, 8, 26, 17, 5, 31, 202824, tzinfo=timezone.utc + ), ), preserve_default=False, ) diff --git a/counter/models.py b/counter/models.py index 6c6dd27c..29dfb92d 100644 --- a/counter/models.py +++ b/counter/models.py @@ -19,8 +19,8 @@ import base64 import os import random import string -from datetime import date, datetime, timedelta -from typing import Optional, Tuple +from datetime import date, datetime, timedelta, timezone +from typing import Tuple from dict2xml import dict2xml from django.conf import settings @@ -534,7 +534,7 @@ class Counter(models.Model): .order_by("-perm_sum") ) - def get_top_customers(self, since: Optional[date] = None) -> QuerySet: + def get_top_customers(self, since: datetime | date | None = None) -> QuerySet: """ Return a QuerySet querying the money spent by customers of this counter since the specified date, ordered by descending amount of money spent. @@ -543,9 +543,13 @@ class Counter(models.Model): - the full name (first name + last name) of the customer - the nickname of the customer - the amount of money spent by the customer + + :param since: timestamp from which to perform the calculation """ if since is None: since = get_start_of_semester() + if isinstance(since, date): + since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc) return ( self.sellings.filter(date__gte=since) .annotate( @@ -568,19 +572,18 @@ class Counter(models.Model): .order_by("-selling_sum") ) - def get_total_sales(self, since=None) -> CurrencyField: + def get_total_sales(self, since: datetime | date | None = None) -> CurrencyField: """ Compute and return the total turnover of this counter since the date specified in parameter (by default, since the start of the current semester) :param since: timestamp from which to perform the calculation - :type since: datetime | date | None :return: Total revenue earned at this counter """ if since is None: since = get_start_of_semester() if isinstance(since, date): - since = datetime.combine(since, datetime.min.time()) + since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc) total = self.sellings.filter(date__gte=since).aggregate( total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField()) )["total"] diff --git a/forum/migrations/0001_initial.py b/forum/migrations/0001_initial.py index 4ad137d6..3d3440db 100644 --- a/forum/migrations/0001_initial.py +++ b/forum/migrations/0001_initial.py @@ -2,12 +2,12 @@ from __future__ import unicode_literals import datetime +from datetime import timezone import django.db.models.deletion import django.utils.timezone from django.conf import settings from django.db import migrations, models -from django.utils.timezone import utc class Migration(migrations.Migration): @@ -226,7 +226,9 @@ class Migration(migrations.Migration): "last_read_date", models.DateTimeField( verbose_name="last read date", - default=datetime.datetime(1999, 1, 1, 0, 0, tzinfo=utc), + default=datetime.datetime( + 1999, 1, 1, 0, 0, tzinfo=timezone.utc + ), ), ), ( diff --git a/sith/settings.py b/sith/settings.py index 8680be2a..0489a8b1 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -187,6 +187,7 @@ TEMPLATES = [ }, }, ] +FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer" HAYSTACK_CONNECTIONS = { "default": { @@ -248,8 +249,6 @@ TIME_ZONE = "Europe/Paris" USE_I18N = True -USE_L10N = True - USE_TZ = True LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) @@ -690,7 +689,6 @@ if DEBUG: "sith.toolbar_debug.TemplatesPanel", "debug_toolbar.panels.cache.CachePanel", "debug_toolbar.panels.signals.SignalsPanel", - "debug_toolbar.panels.logging.LoggingPanel", "debug_toolbar.panels.redirects.RedirectsPanel", ] SASS_INCLUDE_FOLDERS = ["core/static/"] From bf18284450fc85d0ca7a990f863c3e2e45522015 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 26 Jun 2024 13:38:33 +0200 Subject: [PATCH 87/95] apply forgotten migrations --- ...0038_alter_preferences_receive_weekmail.py | 19 +++++++++++++ pedagogy/migrations/0003_alter_uv_language.py | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 core/migrations/0038_alter_preferences_receive_weekmail.py create mode 100644 pedagogy/migrations/0003_alter_uv_language.py diff --git a/core/migrations/0038_alter_preferences_receive_weekmail.py b/core/migrations/0038_alter_preferences_receive_weekmail.py new file mode 100644 index 00000000..4d029b9e --- /dev/null +++ b/core/migrations/0038_alter_preferences_receive_weekmail.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2 on 2024-06-26 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0037_auto_20211105_1708"), + ] + + operations = [ + migrations.AlterField( + model_name="preferences", + name="receive_weekmail", + field=models.BooleanField( + default=False, verbose_name="receive the Weekmail" + ), + ), + ] diff --git a/pedagogy/migrations/0003_alter_uv_language.py b/pedagogy/migrations/0003_alter_uv_language.py new file mode 100644 index 00000000..02f1e798 --- /dev/null +++ b/pedagogy/migrations/0003_alter_uv_language.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2 on 2024-06-26 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("pedagogy", "0002_auto_20190827_2251"), + ] + + operations = [ + migrations.AlterField( + model_name="uv", + name="language", + field=models.CharField( + choices=[ + ("FR", "French"), + ("EN", "English"), + ("DE", "German"), + ("SP", "Spanish"), + ], + default="FR", + max_length=10, + verbose_name="language", + ), + ), + ] From ea8247aa16b229c4aa7d36a1e9b365cd03c3d892 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 4 Jul 2024 11:18:45 +0200 Subject: [PATCH 88/95] fix broken translations --- core/templates/core/user_detail.jinja | 5 +- core/templates/core/user_edit.jinja | 5 +- locale/fr/LC_MESSAGES/django.po | 2108 +++++++++++++------------ 3 files changed, 1080 insertions(+), 1038 deletions(-) diff --git a/core/templates/core/user_detail.jinja b/core/templates/core/user_detail.jinja index 8a78ba48..b7651b88 100644 --- a/core/templates/core/user_detail.jinja +++ b/core/templates/core/user_detail.jinja @@ -162,8 +162,9 @@
    {% trans %}Not subscribed{% endtrans %} {% if user.is_board_member %} - {% trans %}New subscription{% endtrans - %} + + {% trans %}New subscription{% endtrans %} + {% endif %} {% endif %}
    diff --git a/core/templates/core/user_edit.jinja b/core/templates/core/user_edit.jinja index f189d339..41b9dfb8 100644 --- a/core/templates/core/user_edit.jinja +++ b/core/templates/core/user_edit.jinja @@ -133,8 +133,9 @@

    {%- elif user.is_root -%}

    - {%- trans -%}Change user password{%- - endtrans -%} + + {%- trans -%}Change user password{%- endtrans -%} +

    {%- endif -%} diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index fd44743c..6dfd598e 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: 2023-03-07 23:04+0100\n" +"POT-Creation-Date: 2024-07-04 10:44+0200\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Skia \n" "Language-Team: AE info \n" @@ -16,178 +16,178 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: accounting/models.py:61 accounting/models.py:110 accounting/models.py:143 -#: accounting/models.py:216 club/models.py:48 com/models.py:279 -#: com/models.py:296 counter/models.py:222 counter/models.py:255 -#: counter/models.py:369 forum/models.py:58 launderette/models.py:38 -#: launderette/models.py:93 launderette/models.py:131 stock/models.py:40 -#: stock/models.py:63 stock/models.py:105 stock/models.py:133 +#: accounting/models.py:53 accounting/models.py:102 accounting/models.py:137 +#: accounting/models.py:212 club/models.py:50 com/models.py:287 +#: com/models.py:306 counter/models.py:212 counter/models.py:247 +#: counter/models.py:363 forum/models.py:58 launderette/models.py:30 +#: launderette/models.py:87 launderette/models.py:127 stock/models.py:39 +#: stock/models.py:62 stock/models.py:104 stock/models.py:132 msgid "name" msgstr "nom" -#: accounting/models.py:62 +#: accounting/models.py:54 msgid "street" msgstr "rue" -#: accounting/models.py:63 +#: accounting/models.py:55 msgid "city" msgstr "ville" -#: accounting/models.py:64 +#: accounting/models.py:56 msgid "postcode" msgstr "code postal" -#: accounting/models.py:65 +#: accounting/models.py:57 msgid "country" msgstr "pays" -#: accounting/models.py:66 core/models.py:283 +#: accounting/models.py:58 core/models.py:361 msgid "phone" msgstr "téléphone" -#: accounting/models.py:67 +#: accounting/models.py:59 msgid "email" msgstr "email" -#: accounting/models.py:68 +#: accounting/models.py:60 msgid "website" msgstr "site internet" -#: accounting/models.py:71 +#: accounting/models.py:63 msgid "company" msgstr "entreprise" -#: accounting/models.py:111 +#: accounting/models.py:103 msgid "iban" msgstr "IBAN" -#: accounting/models.py:112 +#: accounting/models.py:104 msgid "account number" msgstr "numéro de compte" -#: accounting/models.py:116 accounting/models.py:147 club/models.py:275 -#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:273 -#: counter/models.py:371 trombi/models.py:217 +#: accounting/models.py:108 accounting/models.py:141 club/models.py:356 +#: com/models.py:77 com/models.py:272 com/models.py:312 counter/models.py:265 +#: counter/models.py:365 trombi/models.py:217 msgid "club" msgstr "club" -#: accounting/models.py:121 +#: accounting/models.py:113 msgid "Bank account" msgstr "Compte en banque" -#: accounting/models.py:153 +#: accounting/models.py:147 msgid "bank account" msgstr "compte en banque" -#: accounting/models.py:158 +#: accounting/models.py:152 msgid "Club account" msgstr "Compte club" -#: accounting/models.py:203 +#: accounting/models.py:199 #, python-format msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:214 club/models.py:281 counter/models.py:878 -#: election/models.py:18 launderette/models.py:194 +#: accounting/models.py:210 club/models.py:362 counter/models.py:883 +#: election/models.py:18 launderette/models.py:192 msgid "start date" msgstr "date de début" -#: accounting/models.py:215 club/models.py:282 counter/models.py:879 +#: accounting/models.py:211 club/models.py:363 counter/models.py:884 #: election/models.py:19 msgid "end date" msgstr "date de fin" -#: accounting/models.py:217 +#: accounting/models.py:213 msgid "is closed" msgstr "est fermé" -#: accounting/models.py:222 accounting/models.py:549 +#: accounting/models.py:218 accounting/models.py:551 msgid "club account" msgstr "compte club" -#: accounting/models.py:225 accounting/models.py:289 counter/models.py:64 -#: counter/models.py:600 +#: accounting/models.py:221 accounting/models.py:287 counter/models.py:54 +#: counter/models.py:601 msgid "amount" msgstr "montant" -#: accounting/models.py:226 +#: accounting/models.py:222 msgid "effective_amount" msgstr "montant effectif" -#: accounting/models.py:229 +#: accounting/models.py:225 msgid "General journal" msgstr "Classeur" -#: accounting/models.py:281 +#: accounting/models.py:279 msgid "number" msgstr "numéro" -#: accounting/models.py:286 +#: accounting/models.py:284 msgid "journal" msgstr "classeur" -#: accounting/models.py:290 core/models.py:862 core/models.py:1400 -#: core/models.py:1448 core/models.py:1477 core/models.py:1501 -#: counter/models.py:610 counter/models.py:703 counter/models.py:914 -#: eboutic/models.py:67 eboutic/models.py:236 forum/models.py:311 -#: forum/models.py:408 stock/models.py:104 +#: accounting/models.py:288 core/models.py:914 core/models.py:1477 +#: core/models.py:1525 core/models.py:1554 core/models.py:1580 +#: counter/models.py:611 counter/models.py:706 counter/models.py:919 +#: eboutic/models.py:58 eboutic/models.py:227 forum/models.py:314 +#: forum/models.py:414 stock/models.py:103 msgid "date" msgstr "date" -#: accounting/models.py:291 counter/models.py:224 counter/models.py:915 -#: pedagogy/models.py:219 stock/models.py:107 +#: accounting/models.py:289 counter/models.py:214 counter/models.py:920 +#: pedagogy/models.py:218 stock/models.py:106 msgid "comment" msgstr "commentaire" -#: accounting/models.py:293 counter/models.py:612 counter/models.py:705 -#: subscription/models.py:65 +#: accounting/models.py:291 counter/models.py:613 counter/models.py:708 +#: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" -#: accounting/models.py:298 +#: accounting/models.py:296 msgid "cheque number" msgstr "numéro de chèque" -#: accounting/models.py:303 eboutic/models.py:329 +#: accounting/models.py:301 eboutic/models.py:320 msgid "invoice" msgstr "facture" -#: accounting/models.py:308 +#: accounting/models.py:306 msgid "is done" msgstr "est fait" -#: accounting/models.py:312 +#: accounting/models.py:310 msgid "simple type" msgstr "type simplifié" -#: accounting/models.py:320 accounting/models.py:487 +#: accounting/models.py:318 accounting/models.py:487 msgid "accounting type" msgstr "type comptable" -#: accounting/models.py:328 accounting/models.py:475 accounting/models.py:510 -#: accounting/models.py:545 core/models.py:1476 core/models.py:1502 -#: counter/models.py:669 +#: accounting/models.py:326 accounting/models.py:475 accounting/models.py:512 +#: accounting/models.py:547 core/models.py:1553 core/models.py:1581 +#: counter/models.py:672 msgid "label" msgstr "étiquette" -#: accounting/models.py:334 +#: accounting/models.py:332 msgid "target type" msgstr "type de cible" -#: accounting/models.py:337 club/models.py:438 -#: club/templates/club/club_members.jinja:16 +#: accounting/models.py:335 club/models.py:528 +#: club/templates/club/club_members.jinja:17 #: club/templates/club/club_old_members.jinja:8 #: club/templates/club/mailing.jinja:41 #: counter/templates/counter/cash_summary_list.jinja:32 -#: counter/templates/counter/stats.jinja:19 -#: counter/templates/counter/stats.jinja:43 -#: counter/templates/counter/stats.jinja:65 +#: counter/templates/counter/stats.jinja:21 +#: counter/templates/counter/stats.jinja:47 +#: counter/templates/counter/stats.jinja:69 #: launderette/templates/launderette/launderette_admin.jinja:44 msgid "User" msgstr "Utilisateur" -#: accounting/models.py:338 club/models.py:334 +#: accounting/models.py:336 club/models.py:427 #: club/templates/club/club_detail.jinja:12 #: com/templates/com/mailing_admin.jinja:11 #: com/templates/com/news_admin_list.jinja:23 @@ -200,7 +200,7 @@ msgstr "Utilisateur" #: com/templates/com/news_admin_list.jinja:288 #: com/templates/com/weekmail.jinja:18 com/templates/com/weekmail.jinja:47 #: core/templates/core/user_clubs.jinja:15 -#: core/templates/core/user_clubs.jinja:44 +#: core/templates/core/user_clubs.jinja:46 #: counter/templates/counter/invoices_call.jinja:23 #: trombi/templates/trombi/edit_profile.jinja:15 #: trombi/templates/trombi/edit_profile.jinja:22 @@ -211,36 +211,36 @@ msgstr "Utilisateur" msgid "Club" msgstr "Club" -#: accounting/models.py:339 core/views/user.py:277 +#: accounting/models.py:337 core/views/user.py:277 msgid "Account" msgstr "Compte" -#: accounting/models.py:340 +#: accounting/models.py:338 msgid "Company" msgstr "Entreprise" -#: accounting/models.py:341 core/models.py:230 sith/settings.py:393 +#: accounting/models.py:339 core/models.py:308 sith/settings.py:398 #: stock/templates/stock/shopping_list_items.jinja:37 msgid "Other" msgstr "Autre" -#: accounting/models.py:344 +#: accounting/models.py:342 msgid "target id" msgstr "id de la cible" -#: accounting/models.py:346 +#: accounting/models.py:344 msgid "target label" msgstr "nom de la cible" -#: accounting/models.py:351 +#: accounting/models.py:349 msgid "linked operation" msgstr "opération liée" -#: accounting/models.py:371 +#: accounting/models.py:369 msgid "The date must be set." msgstr "La date doit être indiquée." -#: accounting/models.py:375 +#: accounting/models.py:373 #, python-format msgid "" "The date can not be before the start date of the journal, which is\n" @@ -249,16 +249,16 @@ msgstr "" "La date ne peut pas être avant la date de début du journal, qui est\n" "%(start_date)s." -#: accounting/models.py:385 +#: accounting/models.py:383 msgid "Target does not exists" msgstr "La cible n'existe pas." -#: accounting/models.py:388 +#: accounting/models.py:386 msgid "Please add a target label if you set no existing target" msgstr "" "Merci d'ajouter un nom de cible si vous ne spécifiez pas de cible existante" -#: accounting/models.py:393 +#: accounting/models.py:391 msgid "" "You need to provide ether a simplified accounting type or a standard " "accounting type" @@ -266,7 +266,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:467 counter/models.py:265 pedagogy/models.py:46 +#: accounting/models.py:467 counter/models.py:257 pedagogy/models.py:45 msgid "code" msgstr "code" @@ -281,14 +281,14 @@ msgstr "type de mouvement" #: accounting/models.py:479 #: accounting/templates/accounting/journal_statement_nature.jinja:9 #: accounting/templates/accounting/journal_statement_person.jinja:12 -#: accounting/views.py:602 +#: accounting/views.py:594 msgid "Credit" msgstr "Crédit" #: accounting/models.py:480 #: accounting/templates/accounting/journal_statement_nature.jinja:28 #: accounting/templates/accounting/journal_statement_person.jinja:40 -#: accounting/views.py:602 +#: accounting/views.py:594 msgid "Debit" msgstr "Débit" @@ -296,11 +296,11 @@ msgstr "Débit" msgid "Neutral" msgstr "Neutre" -#: accounting/models.py:514 +#: accounting/models.py:516 msgid "simplified accounting types" msgstr "type simplifié" -#: accounting/models.py:519 +#: accounting/models.py:521 msgid "simplified type" msgstr "type simplifié" @@ -317,7 +317,7 @@ msgstr "Liste des types comptable" #: accounting/templates/accounting/label_list.jinja:10 #: accounting/templates/accounting/operation_edit.jinja:10 #: accounting/templates/accounting/simplifiedaccountingtype_list.jinja:10 -#: core/templates/core/user_tools.jinja:58 +#: core/templates/core/user_tools.jinja:96 msgid "Accounting" msgstr "Comptabilité" @@ -336,7 +336,7 @@ msgstr "Il n'y a pas de types comptable dans ce site web." #: accounting/templates/accounting/bank_account_details.jinja:4 #: accounting/templates/accounting/bank_account_details.jinja:14 -#: core/templates/core/user_tools.jinja:67 +#: core/templates/core/user_tools.jinja:109 msgid "Bank account: " msgstr "Compte en banque : " @@ -368,22 +368,23 @@ msgstr "Compte en banque : " #: core/templates/core/macros.jinja:115 core/templates/core/page_prop.jinja:14 #: core/templates/core/user_account_detail.jinja:38 #: core/templates/core/user_account_detail.jinja:66 -#: core/templates/core/user_clubs.jinja:32 -#: core/templates/core/user_clubs.jinja:61 -#: core/templates/core/user_detail.jinja:186 -#: core/templates/core/user_edit.jinja:19 -#: core/templates/core/user_preferences.jinja:36 +#: core/templates/core/user_clubs.jinja:34 +#: core/templates/core/user_clubs.jinja:63 +#: core/templates/core/user_edit.jinja:39 +#: core/templates/core/user_edit.jinja:58 +#: core/templates/core/user_edit.jinja:77 +#: core/templates/core/user_preferences.jinja:48 #: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:65 #: election/templates/election/election_detail.jinja:187 #: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134 #: launderette/templates/launderette/launderette_admin.jinja:16 -#: launderette/views.py:227 pedagogy/templates/pedagogy/guide.jinja:67 +#: launderette/views.py:218 pedagogy/templates/pedagogy/guide.jinja:67 #: pedagogy/templates/pedagogy/guide.jinja:90 #: pedagogy/templates/pedagogy/guide.jinja:126 #: pedagogy/templates/pedagogy/uv_detail.jinja:185 -#: sas/templates/sas/album.jinja:27 sas/templates/sas/moderation.jinja:18 -#: sas/templates/sas/picture.jinja:74 sas/templates/sas/picture.jinja:124 +#: sas/templates/sas/album.jinja:37 sas/templates/sas/main.jinja:63 +#: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:64 #: stock/templates/stock/stock_shopping_list.jinja:43 #: stock/templates/stock/stock_shopping_list.jinja:69 #: trombi/templates/trombi/detail.jinja:35 @@ -392,7 +393,7 @@ msgid "Delete" msgstr "Supprimer" #: accounting/templates/accounting/bank_account_details.jinja:18 -#: club/views.py:88 core/views/user.py:197 sas/templates/sas/picture.jinja:86 +#: club/views.py:80 core/views/user.py:196 sas/templates/sas/picture.jinja:79 msgid "Infos" msgstr "Infos" @@ -411,7 +412,7 @@ msgstr "Nouveau compte club" #: accounting/templates/accounting/bank_account_details.jinja:27 #: accounting/templates/accounting/bank_account_list.jinja:22 #: accounting/templates/accounting/club_account_details.jinja:58 -#: accounting/templates/accounting/journal_details.jinja:89 club/views.py:134 +#: accounting/templates/accounting/journal_details.jinja:92 club/views.py:126 #: com/templates/com/news_admin_list.jinja:39 #: com/templates/com/news_admin_list.jinja:68 #: com/templates/com/news_admin_list.jinja:115 @@ -426,7 +427,7 @@ msgstr "Nouveau compte club" #: com/templates/com/weekmail.jinja:61 core/templates/core/file.jinja:38 #: core/templates/core/group_list.jinja:24 core/templates/core/page.jinja:35 #: core/templates/core/poster_list.jinja:40 -#: core/templates/core/user_tools.jinja:43 core/views/user.py:227 +#: core/templates/core/user_tools.jinja:71 core/views/user.py:226 #: counter/templates/counter/cash_summary_list.jinja:53 #: counter/templates/counter/counter_list.jinja:17 #: counter/templates/counter/counter_list.jinja:33 @@ -439,8 +440,7 @@ msgstr "Nouveau compte club" #: pedagogy/templates/pedagogy/guide.jinja:89 #: pedagogy/templates/pedagogy/guide.jinja:125 #: pedagogy/templates/pedagogy/uv_detail.jinja:184 -#: sas/templates/sas/album.jinja:19 sas/templates/sas/picture.jinja:100 -#: trombi/templates/trombi/detail.jinja:9 +#: sas/templates/sas/album.jinja:36 trombi/templates/trombi/detail.jinja:9 #: trombi/templates/trombi/edit_profile.jinja:34 msgid "Edit" msgstr "Éditer" @@ -530,7 +530,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:439 +#: sith/settings.py:444 msgid "Closed" msgstr "Fermé" @@ -575,15 +575,15 @@ msgstr "Voir" #: accounting/templates/accounting/co_list.jinja:4 #: accounting/templates/accounting/journal_details.jinja:19 -#: core/templates/core/user_tools.jinja:63 +#: core/templates/core/user_tools.jinja:103 msgid "Company list" msgstr "Liste des entreprises" -#: accounting/templates/accounting/co_list.jinja:10 +#: accounting/templates/accounting/co_list.jinja:12 msgid "Create new company" msgstr "Nouvelle entreprise" -#: accounting/templates/accounting/co_list.jinja:17 +#: accounting/templates/accounting/co_list.jinja:18 msgid "Companies" msgstr "Entreprises" @@ -629,7 +629,7 @@ msgstr "No" #: counter/templates/counter/last_ops.jinja:20 #: counter/templates/counter/last_ops.jinja:45 #: counter/templates/counter/refilling_list.jinja:16 -#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:375 +#: rootplace/templates/rootplace/logs.jinja:12 sas/views.py:364 #: stock/templates/stock/stock_shopping_list.jinja:25 #: stock/templates/stock/stock_shopping_list.jinja:54 #: trombi/templates/trombi/user_profile.jinja:40 @@ -653,7 +653,7 @@ msgid "Target" msgstr "Cible" #: accounting/templates/accounting/journal_details.jinja:38 -#: core/views/forms.py:98 +#: core/views/forms.py:95 msgid "Code" msgstr "Code" @@ -667,7 +667,7 @@ msgid "Done" msgstr "Effectuées" #: accounting/templates/accounting/journal_details.jinja:41 -#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1084 +#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1078 #: pedagogy/templates/pedagogy/moderation.jinja:13 #: pedagogy/templates/pedagogy/uv_detail.jinja:138 #: trombi/templates/trombi/comment.jinja:4 @@ -701,7 +701,7 @@ msgstr "" "Ouvrez un classeur dans ce compte club, puis sauver " "cette opération à nouveau pour créer l'opération liée." -#: accounting/templates/accounting/journal_details.jinja:93 +#: accounting/templates/accounting/journal_details.jinja:96 msgid "Generate" msgstr "Générer" @@ -784,10 +784,10 @@ msgstr "Opération liée : " #: core/templates/core/file_edit.jinja:8 #: core/templates/core/macros_pages.jinja:25 #: core/templates/core/page_prop.jinja:11 -#: core/templates/core/user_godfathers.jinja:41 -#: core/templates/core/user_preferences.jinja:12 -#: core/templates/core/user_preferences.jinja:19 -#: core/templates/core/user_preferences.jinja:31 +#: core/templates/core/user_godfathers.jinja:62 +#: 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:33 #: subscription/templates/subscription/subscription.jinja:25 @@ -799,7 +799,7 @@ msgstr "Sauver" #: accounting/templates/accounting/refound_account.jinja:4 #: accounting/templates/accounting/refound_account.jinja:9 -#: accounting/views.py:933 +#: accounting/views.py:925 msgid "Refound account" msgstr "Remboursement de compte" @@ -820,189 +820,189 @@ msgstr "Types simplifiés" msgid "New simplified type" msgstr "Nouveau type simplifié" -#: accounting/views.py:247 accounting/views.py:257 accounting/views.py:577 +#: accounting/views.py:239 accounting/views.py:249 accounting/views.py:569 msgid "Journal" msgstr "Classeur" -#: accounting/views.py:267 +#: accounting/views.py:259 msgid "Statement by nature" msgstr "Bilan par nature" -#: accounting/views.py:277 +#: accounting/views.py:269 msgid "Statement by person" msgstr "Bilan par personne" -#: accounting/views.py:287 +#: accounting/views.py:279 msgid "Accounting statement" msgstr "Bilan comptable" -#: accounting/views.py:391 +#: accounting/views.py:383 msgid "Link this operation to the target account" msgstr "Lier cette opération au compte cible" -#: accounting/views.py:421 +#: accounting/views.py:413 msgid "The target must be set." msgstr "La cible doit être indiquée." -#: accounting/views.py:436 +#: accounting/views.py:428 msgid "The amount must be set." msgstr "Le montant doit être indiqué." -#: accounting/views.py:571 accounting/views.py:577 +#: accounting/views.py:563 accounting/views.py:569 msgid "Operation" msgstr "Opération" -#: accounting/views.py:586 +#: accounting/views.py:578 msgid "Financial proof: " msgstr "Justificatif de libellé : " -#: accounting/views.py:589 +#: accounting/views.py:581 #, python-format msgid "Club: %(club_name)s" msgstr "Club : %(club_name)s" -#: accounting/views.py:594 +#: accounting/views.py:586 #, python-format msgid "Label: %(op_label)s" msgstr "Libellé : %(op_label)s" -#: accounting/views.py:597 +#: accounting/views.py:589 #, python-format msgid "Date: %(date)s" msgstr "Date : %(date)s" -#: accounting/views.py:605 +#: accounting/views.py:597 #, python-format msgid "Amount: %(amount).2f €" msgstr "Montant : %(amount).2f €" -#: accounting/views.py:620 +#: accounting/views.py:612 msgid "Debtor" msgstr "Débiteur" -#: accounting/views.py:620 +#: accounting/views.py:612 msgid "Creditor" msgstr "Créditeur" -#: accounting/views.py:625 +#: accounting/views.py:617 msgid "Comment:" msgstr "Commentaire :" -#: accounting/views.py:650 +#: accounting/views.py:642 msgid "Signature:" msgstr "Signature :" -#: accounting/views.py:718 +#: accounting/views.py:710 msgid "General statement" msgstr "Bilan général" -#: accounting/views.py:725 +#: accounting/views.py:717 msgid "No label operations" msgstr "Opérations sans étiquette" -#: accounting/views.py:889 +#: accounting/views.py:881 msgid "Refound this account" msgstr "Rembourser ce compte" -#: club/forms.py:61 club/forms.py:194 +#: club/forms.py:58 club/forms.py:190 msgid "Users to add" msgstr "Utilisateurs à ajouter" -#: club/forms.py:62 club/forms.py:195 core/views/group.py:63 +#: club/forms.py:59 club/forms.py:191 core/views/group.py:52 msgid "Search users to add (one or more)." msgstr "Recherche les utilisateurs à ajouter (un ou plus)." -#: club/forms.py:71 +#: club/forms.py:68 msgid "New Mailing" msgstr "Nouvelle mailing liste" -#: club/forms.py:72 +#: club/forms.py:69 msgid "Subscribe" msgstr "S'abonner" -#: club/forms.py:73 club/forms.py:86 com/templates/com/news_admin_list.jinja:40 +#: club/forms.py:70 club/forms.py:83 com/templates/com/news_admin_list.jinja:40 #: com/templates/com/news_admin_list.jinja:116 #: com/templates/com/news_admin_list.jinja:198 #: com/templates/com/news_admin_list.jinja:274 msgid "Remove" msgstr "Retirer" -#: club/forms.py:76 launderette/views.py:229 +#: club/forms.py:73 launderette/views.py:220 #: pedagogy/templates/pedagogy/moderation.jinja:15 msgid "Action" msgstr "Action" -#: club/forms.py:116 club/tests.py:578 +#: club/forms.py:113 club/tests.py:742 msgid "This field is required" msgstr "Ce champ est obligatoire" -#: club/forms.py:128 club/forms.py:256 club/tests.py:590 +#: club/forms.py:125 club/forms.py:250 club/tests.py:755 msgid "One of the selected users doesn't exist" msgstr "Un des utilisateurs sélectionné n'existe pas" -#: club/forms.py:132 club/tests.py:608 +#: club/forms.py:129 club/tests.py:772 msgid "One of the selected users doesn't have an email address" msgstr "Un des utilisateurs sélectionnés n'a pas d'adresse email" -#: club/forms.py:143 +#: club/forms.py:140 msgid "An action is required" msgstr "Une action est requise" -#: club/forms.py:154 club/tests.py:567 +#: club/forms.py:151 club/tests.py:729 msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:162 counter/forms.py:165 +#: club/forms.py:159 counter/forms.py:165 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/forms.py:166 -#: election/views.py:172 subscription/views.py:49 +#: club/forms.py:160 com/views.py:81 com/views.py:196 counter/forms.py:166 +#: election/views.py:167 subscription/views.py:39 msgid "End date" msgstr "Date de fin" -#: club/forms.py:166 club/templates/club/club_sellings.jinja:21 +#: club/forms.py:163 club/templates/club/club_sellings.jinja:21 #: core/templates/core/user_account_detail.jinja:18 #: core/templates/core/user_account_detail.jinja:51 -#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:158 +#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:149 msgid "Counter" msgstr "Comptoir" -#: club/forms.py:174 counter/views.py:782 +#: club/forms.py:170 counter/views.py:776 msgid "Products" msgstr "Produits" -#: club/forms.py:179 counter/views.py:787 +#: club/forms.py:175 counter/views.py:781 msgid "Archived products" msgstr "Produits archivés" -#: club/forms.py:238 club/templates/club/club_members.jinja:21 -#: club/templates/club/club_members.jinja:46 -#: core/templates/core/user_clubs.jinja:29 +#: club/forms.py:232 club/templates/club/club_members.jinja:22 +#: club/templates/club/club_members.jinja:48 +#: core/templates/core/user_clubs.jinja:31 msgid "Mark as old" msgstr "Marquer comme ancien" -#: club/forms.py:260 +#: club/forms.py:254 msgid "User must be subscriber to take part to a club" msgstr "L'utilisateur doit être cotisant pour faire partie d'un club" -#: club/forms.py:264 core/views/group.py:82 +#: club/forms.py:258 core/views/group.py:71 msgid "You can not add the same user twice" msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur" -#: club/forms.py:285 +#: club/forms.py:279 msgid "You should specify a role" msgstr "Vous devez choisir un rôle" -#: club/forms.py:296 sas/views.py:130 sas/views.py:202 sas/views.py:301 +#: club/forms.py:290 sas/views.py:119 sas/views.py:191 sas/views.py:290 msgid "You do not have the permission to do that" msgstr "Vous n'avez pas la permission de faire cela" -#: club/models.py:53 +#: club/models.py:55 msgid "unix name" msgstr "nom unix" -#: club/models.py:60 +#: club/models.py:62 msgid "" "Enter a valid unix name. This value may contain only letters, numbers ./-/_ " "characters." @@ -1010,94 +1010,94 @@ msgstr "" "Entrez un nom UNIX valide. Cette valeur peut contenir uniquement des " "lettres, des nombres, et les caractères ./-/_" -#: club/models.py:65 +#: club/models.py:67 msgid "A club with that unix name already exists." msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:68 +#: club/models.py:70 msgid "logo" msgstr "logo" -#: club/models.py:70 +#: club/models.py:72 msgid "is active" msgstr "actif" -#: club/models.py:72 +#: club/models.py:74 msgid "short description" msgstr "description courte" -#: club/models.py:74 core/models.py:285 +#: club/models.py:76 core/models.py:363 msgid "address" msgstr "Adresse" -#: club/models.py:94 core/models.py:196 +#: club/models.py:97 core/models.py:274 msgid "home" msgstr "home" -#: club/models.py:118 +#: club/models.py:121 msgid "You can not make loops in clubs" msgstr "Vous ne pouvez pas faire de boucles dans les clubs" -#: club/models.py:132 +#: club/models.py:145 msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:267 counter/models.py:869 counter/models.py:905 -#: eboutic/models.py:63 eboutic/models.py:232 election/models.py:192 -#: launderette/models.py:145 launderette/models.py:213 sas/models.py:244 +#: club/models.py:348 counter/models.py:874 counter/models.py:910 +#: eboutic/models.py:54 eboutic/models.py:223 election/models.py:191 +#: launderette/models.py:141 launderette/models.py:211 sas/models.py:240 #: trombi/models.py:213 msgid "user" msgstr "nom d'utilisateur" -#: club/models.py:284 core/models.py:249 election/models.py:187 -#: election/models.py:223 trombi/models.py:218 +#: club/models.py:365 core/models.py:327 election/models.py:186 +#: election/models.py:222 trombi/models.py:218 msgid "role" msgstr "rôle" -#: club/models.py:289 core/models.py:81 counter/models.py:223 -#: counter/models.py:256 election/models.py:15 election/models.py:120 -#: election/models.py:197 forum/models.py:59 forum/models.py:240 +#: club/models.py:370 core/models.py:85 counter/models.py:213 +#: counter/models.py:248 election/models.py:15 election/models.py:119 +#: election/models.py:196 forum/models.py:59 forum/models.py:243 msgid "description" msgstr "description" -#: club/models.py:299 +#: club/models.py:382 msgid "past member" msgstr "Anciens membres" -#: club/models.py:341 club/models.py:444 +#: club/models.py:434 club/models.py:534 msgid "Email address" msgstr "Adresse email" -#: club/models.py:349 +#: club/models.py:442 msgid "Enter a valid address. Only the root of the address is needed." msgstr "" "Entrez une adresse valide. Seule la racine de l'adresse est nécessaire." -#: club/models.py:353 com/models.py:83 com/models.py:312 core/models.py:863 +#: club/models.py:446 com/models.py:85 com/models.py:322 core/models.py:915 msgid "is moderated" msgstr "est modéré" -#: club/models.py:357 com/models.py:87 com/models.py:316 +#: club/models.py:450 com/models.py:89 com/models.py:326 msgid "moderator" msgstr "modérateur" -#: club/models.py:364 +#: club/models.py:457 msgid "This mailing list already exists." msgstr "Cette liste de diffusion existe déjà." -#: club/models.py:430 club/templates/club/mailing.jinja:23 +#: club/models.py:520 club/templates/club/mailing.jinja:23 msgid "Mailing" msgstr "Liste de diffusion" -#: club/models.py:451 +#: club/models.py:541 msgid "At least user or email is required" msgstr "Au moins un utilisateur ou un email est nécessaire" -#: club/models.py:459 club/tests.py:636 +#: club/models.py:549 club/tests.py:800 msgid "This email is already suscribed in this mailing" msgstr "Cet email est déjà abonné à cette mailing" -#: club/models.py:485 +#: club/models.py:577 msgid "Unregistered user" msgstr "Utilisateur non enregistré" @@ -1110,7 +1110,7 @@ msgid "inactive" msgstr "inactif" #: club/templates/club/club_list.jinja:34 -#: core/templates/core/user_tools.jinja:24 +#: core/templates/core/user_tools.jinja:31 msgid "New club" msgstr "Nouveau club" @@ -1122,37 +1122,37 @@ msgstr "Il n'y a pas de club dans ce site web." msgid "Club members" msgstr "Membres du club" -#: club/templates/club/club_members.jinja:17 +#: club/templates/club/club_members.jinja:18 #: club/templates/club/club_old_members.jinja:9 #: core/templates/core/user_clubs.jinja:16 -#: core/templates/core/user_clubs.jinja:45 +#: core/templates/core/user_clubs.jinja:47 #: trombi/templates/trombi/edit_profile.jinja:23 #: trombi/templates/trombi/export.jinja:56 #: trombi/templates/trombi/user_profile.jinja:39 msgid "Role" msgstr "Rôle" -#: club/templates/club/club_members.jinja:18 +#: club/templates/club/club_members.jinja:19 #: club/templates/club/club_old_members.jinja:10 #: core/templates/core/group_list.jinja:15 #: core/templates/core/user_clubs.jinja:17 -#: core/templates/core/user_clubs.jinja:46 +#: core/templates/core/user_clubs.jinja:48 msgid "Description" msgstr "Description" -#: club/templates/club/club_members.jinja:19 +#: club/templates/club/club_members.jinja:20 #: core/templates/core/user_clubs.jinja:18 #: launderette/templates/launderette/launderette_admin.jinja:45 msgid "Since" msgstr "Depuis" -#: club/templates/club/club_members.jinja:50 +#: club/templates/club/club_members.jinja:52 msgid "There are no members in this club." msgstr "Il n'y a pas de membres dans ce club." -#: club/templates/club/club_members.jinja:78 -#: core/templates/core/file_detail.jinja:19 core/views/forms.py:345 -#: launderette/views.py:227 trombi/templates/trombi/detail.jinja:19 +#: club/templates/club/club_members.jinja:80 +#: core/templates/core/file_detail.jinja:19 core/views/forms.py:343 +#: launderette/views.py:218 trombi/templates/trombi/detail.jinja:19 msgid "Add" msgstr "Ajouter" @@ -1161,17 +1161,17 @@ msgid "Club old members" msgstr "Anciens membres du club" #: club/templates/club/club_old_members.jinja:11 -#: core/templates/core/user_clubs.jinja:47 +#: core/templates/core/user_clubs.jinja:49 msgid "From" msgstr "Du" #: club/templates/club/club_old_members.jinja:12 -#: core/templates/core/user_clubs.jinja:48 +#: core/templates/core/user_clubs.jinja:50 msgid "To" msgstr "Au" -#: club/templates/club/club_sellings.jinja:5 club/views.py:154 -#: club/views.py:483 counter/templates/counter/counter_main.jinja:24 +#: club/templates/club/club_sellings.jinja:5 +#: counter/templates/counter/counter_main.jinja:24 #: counter/templates/counter/last_ops.jinja:41 msgid "Sales" msgstr "Ventes" @@ -1215,7 +1215,7 @@ msgstr "Client" #: club/templates/club/club_sellings.jinja:25 #: core/templates/core/user_account_detail.jinja:21 -#: core/templates/core/user_stats.jinja:28 +#: core/templates/core/user_stats.jinja:44 #: counter/templates/counter/last_ops.jinja:49 msgid "Quantity" msgstr "Quantité" @@ -1225,7 +1225,7 @@ msgstr "Quantité" #: core/templates/core/user_account_detail.jinja:22 #: counter/templates/counter/cash_summary_list.jinja:35 #: counter/templates/counter/last_ops.jinja:50 -#: counter/templates/counter/stats.jinja:21 +#: counter/templates/counter/stats.jinja:23 #: subscription/templates/subscription/stats.jinja:40 #: subscription/templates/subscription/stats.jinja:48 msgid "Total" @@ -1234,7 +1234,7 @@ msgstr "Total" #: club/templates/club/club_sellings.jinja:27 #: core/templates/core/user_account_detail.jinja:23 #: core/templates/core/user_account_detail.jinja:54 -#: core/templates/core/user_detail.jinja:156 +#: core/templates/core/user_detail.jinja:190 #: counter/templates/counter/last_ops.jinja:24 #: counter/templates/counter/last_ops.jinja:51 #: counter/templates/counter/refilling_list.jinja:14 @@ -1242,7 +1242,7 @@ msgid "Payment method" msgstr "Méthode de paiement" #: club/templates/club/club_tools.jinja:4 -#: core/templates/core/user_tools.jinja:101 +#: core/templates/core/user_tools.jinja:157 msgid "Club tools" msgstr "Outils club" @@ -1269,7 +1269,7 @@ msgstr "Nouveau Trombi" #: club/templates/club/club_tools.jinja:14 #: com/templates/com/poster_list.jinja:17 #: core/templates/core/poster_list.jinja:17 -#: core/templates/core/user_tools.jinja:91 +#: core/templates/core/user_tools.jinja:145 msgid "Posters" msgstr "Affiches" @@ -1347,148 +1347,154 @@ msgstr "Aucune page n'existe pour ce club" msgid "Club stats" msgstr "Statistiques du club" -#: club/views.py:98 +#: club/views.py:90 msgid "Members" msgstr "Membres" -#: club/views.py:107 +#: club/views.py:99 msgid "Old members" msgstr "Anciens membres" -#: club/views.py:117 core/templates/core/page.jinja:33 +#: club/views.py:109 core/templates/core/page.jinja:33 msgid "History" msgstr "Historique" -#: club/views.py:125 core/templates/core/base.jinja:129 core/views/user.py:220 -#: sas/templates/sas/picture.jinja:95 trombi/views.py:63 +#: club/views.py:117 core/templates/core/base.jinja:95 core/views/user.py:219 +#: sas/templates/sas/picture.jinja:100 trombi/views.py:62 msgid "Tools" msgstr "Outils" -#: club/views.py:145 +#: club/views.py:137 msgid "Edit club page" msgstr "Éditer la page de club" -#: club/views.py:161 +#: club/views.py:146 club/views.py:472 +#, fuzzy +#| msgid "Selling" +msgid "Sellings" +msgstr "Vente" + +#: club/views.py:153 msgid "Mailing list" msgstr "Listes de diffusion" -#: club/views.py:170 com/views.py:134 +#: club/views.py:162 com/views.py:131 msgid "Posters list" msgstr "Liste d'affiches" -#: club/views.py:180 counter/templates/counter/counter_list.jinja:21 +#: club/views.py:172 counter/templates/counter/counter_list.jinja:21 #: counter/templates/counter/counter_list.jinja:43 #: counter/templates/counter/counter_list.jinja:59 msgid "Props" msgstr "Propriétés" -#: com/models.py:46 +#: com/models.py:45 msgid "alert message" msgstr "message d'alerte" -#: com/models.py:47 +#: com/models.py:46 msgid "info message" msgstr "message d'info" -#: com/models.py:48 +#: com/models.py:47 msgid "weekmail destinations" msgstr "destinataires du weekmail" -#: com/models.py:58 +#: com/models.py:60 msgid "Notice" msgstr "Information" -#: com/models.py:59 +#: com/models.py:61 msgid "Event" msgstr "Événement" -#: com/models.py:60 +#: com/models.py:62 msgid "Weekly" msgstr "Hebdomadaire" -#: com/models.py:61 +#: com/models.py:63 msgid "Call" msgstr "Appel" -#: com/models.py:68 com/models.py:175 com/models.py:255 election/models.py:14 -#: election/models.py:119 election/models.py:159 forum/models.py:251 -#: forum/models.py:309 pedagogy/models.py:101 +#: com/models.py:70 com/models.py:179 com/models.py:261 election/models.py:14 +#: election/models.py:118 election/models.py:158 forum/models.py:254 +#: forum/models.py:312 pedagogy/models.py:100 msgid "title" msgstr "titre" -#: com/models.py:69 +#: com/models.py:71 msgid "summary" msgstr "résumé" -#: com/models.py:70 com/models.py:256 trombi/models.py:197 +#: com/models.py:72 com/models.py:262 trombi/models.py:197 msgid "content" msgstr "contenu" -#: com/models.py:72 core/models.py:1446 launderette/models.py:101 -#: launderette/models.py:139 launderette/models.py:196 stock/models.py:80 -#: stock/models.py:137 +#: com/models.py:74 core/models.py:1523 launderette/models.py:95 +#: launderette/models.py:135 launderette/models.py:194 stock/models.py:79 +#: stock/models.py:136 msgid "type" msgstr "type" -#: com/models.py:80 com/models.py:260 pedagogy/models.py:61 -#: pedagogy/models.py:211 trombi/models.py:187 +#: com/models.py:82 com/models.py:266 pedagogy/models.py:60 +#: pedagogy/models.py:210 trombi/models.py:187 msgid "author" msgstr "auteur" -#: com/models.py:153 +#: com/models.py:157 msgid "news_date" msgstr "date de la nouvelle" -#: com/models.py:156 +#: com/models.py:160 msgid "start_date" msgstr "date de début" -#: com/models.py:157 +#: com/models.py:161 msgid "end_date" msgstr "date de fin" -#: com/models.py:176 +#: com/models.py:180 msgid "intro" msgstr "intro" -#: com/models.py:177 +#: com/models.py:181 msgid "joke" msgstr "blague" -#: com/models.py:178 +#: com/models.py:182 msgid "protip" msgstr "astuce" -#: com/models.py:179 +#: com/models.py:183 msgid "conclusion" msgstr "conclusion" -#: com/models.py:180 +#: com/models.py:184 msgid "sent" msgstr "envoyé" -#: com/models.py:251 +#: com/models.py:257 msgid "weekmail" msgstr "weekmail" -#: com/models.py:269 +#: com/models.py:275 msgid "rank" msgstr "rang" -#: com/models.py:298 core/models.py:828 core/models.py:878 +#: com/models.py:308 core/models.py:880 core/models.py:930 msgid "file" msgstr "fichier" -#: com/models.py:310 +#: com/models.py:320 msgid "display time" msgstr "temps d'affichage" -#: com/models.py:338 +#: com/models.py:348 msgid "Begin date should be before end date" msgstr "La date de début doit être avant celle de fin" -#: com/templates/com/mailing_admin.jinja:4 com/views.py:127 -#: core/templates/core/user_tools.jinja:90 +#: com/templates/com/mailing_admin.jinja:4 com/views.py:124 +#: core/templates/core/user_tools.jinja:144 msgid "Mailing lists administration" msgstr "Administration des mailing listes" @@ -1500,7 +1506,7 @@ msgstr "Administration des mailing listes" #: com/templates/com/news_detail.jinja:39 #: core/templates/core/file_detail.jinja:65 #: core/templates/core/file_moderation.jinja:23 -#: sas/templates/sas/moderation.jinja:17 sas/templates/sas/picture.jinja:122 +#: sas/templates/sas/moderation.jinja:17 sas/templates/sas/picture.jinja:61 msgid "Moderate" msgstr "Modérer" @@ -1536,7 +1542,7 @@ msgstr "Nouvelles" #: com/templates/com/news_admin_list.jinja:11 #: com/templates/com/news_edit.jinja:8 com/templates/com/news_edit.jinja:31 -#: core/templates/core/user_tools.jinja:85 +#: core/templates/core/user_tools.jinja:139 msgid "Create news" msgstr "Créer nouvelle" @@ -1557,7 +1563,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:234 +#: launderette/views.py:225 msgid "Type" msgstr "Type" @@ -1570,8 +1576,8 @@ msgstr "Type" #: com/templates/com/news_admin_list.jinja:249 #: com/templates/com/news_admin_list.jinja:286 #: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48 -#: forum/templates/forum/forum.jinja:24 forum/templates/forum/forum.jinja:43 -#: forum/templates/forum/main.jinja:27 forum/views.py:244 +#: forum/templates/forum/forum.jinja:28 forum/templates/forum/forum.jinja:47 +#: forum/templates/forum/main.jinja:30 forum/views.py:243 #: pedagogy/templates/pedagogy/guide.jinja:60 msgid "Title" msgstr "Titre" @@ -1596,7 +1602,7 @@ msgstr "Résumé" #: com/templates/com/news_admin_list.jinja:252 #: com/templates/com/news_admin_list.jinja:289 #: com/templates/com/weekmail.jinja:17 com/templates/com/weekmail.jinja:46 -#: forum/templates/forum/forum.jinja:47 +#: forum/templates/forum/forum.jinja:51 msgid "Author" msgstr "Auteur" @@ -1641,12 +1647,8 @@ msgstr "Appels affichés" msgid "Calls to moderate" msgstr "Appels à modérer" -#: core/templates/core/base.jinja -msgid "Site version:" -msgstr "Version du site :" - #: com/templates/com/news_admin_list.jinja:242 -#: core/templates/core/base.jinja:183 +#: core/templates/core/base.jinja:210 msgid "Events" msgstr "Événements" @@ -1666,7 +1668,7 @@ msgstr "Retour aux nouvelles" msgid "Author: " msgstr "Auteur : " -#: com/templates/com/news_detail.jinja:37 sas/templates/sas/picture.jinja:90 +#: com/templates/com/news_detail.jinja:37 sas/templates/sas/picture.jinja:92 msgid "Moderator: " msgstr "Modérateur : " @@ -1713,10 +1715,6 @@ msgstr "Administrer les news" msgid "Events today and the next few days" msgstr "Événements aujourd'hui et dans les prochains jours" -#: com/templates/com/news_list.jinja:100 -msgid "All coming events" -msgstr "Tous les événements à venir" - #: com/templates/com/news_list.jinja:82 msgid "Nothing to come..." msgstr "Rien à venir..." @@ -1725,20 +1723,24 @@ msgstr "Rien à venir..." msgid "Coming soon... don't miss!" msgstr "Prochainement... à ne pas rater!" -#: com/templates/com/news_list.jinja:104 +#: com/templates/com/news_list.jinja:101 +msgid "All coming events" +msgstr "Tous les événements à venir" + +#: com/templates/com/news_list.jinja:113 msgid "Agenda" msgstr "Agenda" -#: com/templates/com/news_list.jinja:128 +#: com/templates/com/news_list.jinja:137 msgid "Birthdays" msgstr "Anniversaires" -#: com/templates/com/news_list.jinja:136 +#: com/templates/com/news_list.jinja:145 #, python-format msgid "%(age)s year old" msgstr "%(age)s ans" -#: com/templates/com/news_list.jinja:147 com/tests.py:112 com/tests.py:122 +#: com/templates/com/news_list.jinja:156 com/tests.py:102 com/tests.py:112 msgid "You need an up to date subscription to access this content" msgstr "Votre cotisation doit être à jour pour accéder à cette section" @@ -1761,7 +1763,7 @@ msgstr "Affiche - modifier" #: com/templates/com/poster_list.jinja:20 #: com/templates/com/poster_list.jinja:23 #: com/templates/com/screen_list.jinja:13 -#: core/templates/core/poster_list.jinja:19 sas/templates/sas/main.jinja:53 +#: core/templates/core/poster_list.jinja:19 sas/templates/sas/main.jinja:101 msgid "Create" msgstr "Créer" @@ -1792,7 +1794,7 @@ msgid "Screen - edit" msgstr "Écran - modifier" #: com/templates/com/screen_list.jinja:4 com/templates/com/screen_list.jinja:11 -#: core/templates/core/user_tools.jinja:92 +#: core/templates/core/user_tools.jinja:146 msgid "Screens" msgstr "Écrans" @@ -1807,7 +1809,7 @@ msgid "Slideshow" msgstr "Diaporama" #: com/templates/com/weekmail.jinja:5 com/templates/com/weekmail.jinja:9 -#: com/views.py:104 core/templates/core/user_tools.jinja:83 +#: com/views.py:101 core/templates/core/user_tools.jinja:137 msgid "Weekmail" msgstr "Weekmail" @@ -1850,7 +1852,7 @@ msgstr "Supprimer du Weekmail" #: com/templates/com/weekmail_preview.jinja:9 #: core/templates/core/user_account_detail.jinja:11 -#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:227 +#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:218 #: pedagogy/templates/pedagogy/uv_detail.jinja:12 #: pedagogy/templates/pedagogy/uv_detail.jinja:21 #: stock/templates/stock/shopping_list_items.jinja:9 @@ -1904,90 +1906,90 @@ msgstr "Astuce" msgid "Final word" msgstr "Le mot de la fin" -#: com/views.py:77 +#: com/views.py:74 msgid "Format: 16:9 | Resolution: 1920x1080" msgstr "Format : 16:9 | Résolution : 1920x1080" -#: com/views.py:80 com/views.py:198 election/views.py:171 -#: subscription/views.py:46 +#: com/views.py:77 com/views.py:195 election/views.py:166 +#: subscription/views.py:36 msgid "Start date" msgstr "Date de début" -#: com/views.py:99 +#: com/views.py:96 msgid "Communication administration" msgstr "Administration de la communication" -#: com/views.py:110 core/templates/core/user_tools.jinja:84 +#: com/views.py:107 core/templates/core/user_tools.jinja:138 msgid "Weekmail destinations" msgstr "Destinataires du Weekmail" -#: com/views.py:114 +#: com/views.py:111 msgid "Info message" msgstr "Message d'info" -#: com/views.py:120 +#: com/views.py:117 msgid "Alert message" msgstr "Message d'alerte" -#: com/views.py:141 +#: com/views.py:138 msgid "Screens list" msgstr "Liste d'écrans" -#: com/views.py:200 +#: com/views.py:197 msgid "Until" msgstr "Jusqu'à" -#: com/views.py:202 +#: com/views.py:199 msgid "Automoderation" msgstr "Automodération" -#: com/views.py:209 com/views.py:213 com/views.py:227 +#: com/views.py:206 com/views.py:210 com/views.py:224 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: com/views.py:223 +#: com/views.py:220 msgid "You crazy? You can not finish an event before starting it." msgstr "T'es fou? Un événement ne peut pas finir avant même de commencer." -#: com/views.py:466 +#: com/views.py:459 msgid "Delete and save to regenerate" msgstr "Supprimer et sauver pour régénérer" -#: com/views.py:481 +#: com/views.py:474 msgid "Weekmail of the " msgstr "Weekmail du " -#: com/views.py:591 +#: com/views.py:584 msgid "" "You must be a board member of the selected club to post in the Weekmail." msgstr "" "Vous devez êtres un membre du bureau du club sélectionné pour poster dans le " "Weekmail." -#: core/models.py:76 +#: core/models.py:80 msgid "meta group status" msgstr "status du meta-groupe" -#: core/models.py:78 +#: core/models.py:82 msgid "Whether a group is a meta group or not" msgstr "Si un groupe est un meta-groupe ou pas" -#: core/models.py:131 +#: core/models.py:171 #, python-format msgid "%(value)s is not a valid promo (between 0 and %(end)s)" msgstr "%(value)s n'est pas une promo valide (doit être entre 0 et %(end)s)" -#: core/models.py:149 +#: core/models.py:227 msgid "username" msgstr "nom d'utilisateur" -#: core/models.py:153 +#: core/models.py:231 msgid "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." msgstr "" "Requis. Pas plus de 254 caractères. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:159 +#: core/models.py:237 msgid "" "Enter a valid username. This value may contain only letters, numbers and ./" "+/-/_ characters." @@ -1995,43 +1997,43 @@ msgstr "" "Entrez un nom d'utilisateur correct. Uniquement des lettres, numéros, et ./" "+/-/_" -#: core/models.py:165 +#: core/models.py:243 msgid "A user with that username already exists." msgstr "Un utilisateur de ce nom existe déjà" -#: core/models.py:167 +#: core/models.py:245 msgid "first name" msgstr "Prénom" -#: core/models.py:168 +#: core/models.py:246 msgid "last name" msgstr "Nom" -#: core/models.py:169 +#: core/models.py:247 msgid "email address" msgstr "adresse email" -#: core/models.py:170 +#: core/models.py:248 msgid "date of birth" msgstr "date de naissance" -#: core/models.py:171 +#: core/models.py:249 msgid "nick name" msgstr "surnom" -#: core/models.py:173 +#: core/models.py:251 msgid "staff status" msgstr "status \"staff\"" -#: core/models.py:175 +#: core/models.py:253 msgid "Designates whether the user can log into this admin site." msgstr "Est-ce que l'utilisateur peut se logger à la partie admin du site." -#: core/models.py:178 +#: core/models.py:256 msgid "active" msgstr "actif" -#: core/models.py:181 +#: core/models.py:259 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -2039,173 +2041,183 @@ msgstr "" "Est-ce que l'utilisateur doit être traité comme actif. Désélectionnez au " "lieu de supprimer les comptes." -#: core/models.py:185 +#: core/models.py:263 msgid "date joined" msgstr "date d'inscription" -#: core/models.py:186 +#: core/models.py:264 msgid "last update" msgstr "dernière mise à jour" -#: core/models.py:188 +#: core/models.py:266 msgid "superuser" msgstr "super-utilisateur" -#: core/models.py:190 +#: core/models.py:268 msgid "Designates whether this user is a superuser. " msgstr "Est-ce que l'utilisateur est super-utilisateur." -#: core/models.py:204 +#: core/models.py:282 msgid "profile" msgstr "profil" -#: core/models.py:212 +#: core/models.py:290 msgid "avatar" msgstr "avatar" -#: core/models.py:220 +#: core/models.py:298 msgid "scrub" msgstr "blouse" -#: core/models.py:226 +#: core/models.py:304 msgid "sex" msgstr "Genre" -#: core/models.py:230 +#: core/models.py:308 msgid "Man" msgstr "Homme" -#: core/models.py:230 +#: core/models.py:308 msgid "Woman" msgstr "Femme" -#: core/models.py:232 +#: core/models.py:310 msgid "pronouns" msgstr "pronoms" -#: core/models.py:234 +#: core/models.py:312 msgid "tshirt size" msgstr "taille de t-shirt" -#: core/models.py:237 +#: core/models.py:315 msgid "-" msgstr "-" -#: core/models.py:238 +#: core/models.py:316 msgid "XS" msgstr "XS" -#: core/models.py:239 +#: core/models.py:317 msgid "S" msgstr "S" -#: core/models.py:240 +#: core/models.py:318 msgid "M" msgstr "M" -#: core/models.py:241 +#: core/models.py:319 msgid "L" msgstr "L" -#: core/models.py:242 +#: core/models.py:320 msgid "XL" msgstr "XL" -#: core/models.py:243 +#: core/models.py:321 msgid "XXL" msgstr "XXL" -#: core/models.py:244 +#: core/models.py:322 msgid "XXXL" msgstr "XXXL" -#: core/models.py:252 +#: core/models.py:330 msgid "Student" msgstr "Étudiant" -#: core/models.py:253 +#: core/models.py:331 msgid "Administrative agent" msgstr "Personnel administratif" -#: core/models.py:254 +#: core/models.py:332 msgid "Teacher" msgstr "Enseignant" -#: core/models.py:255 +#: core/models.py:333 msgid "Agent" msgstr "Personnel" -#: core/models.py:256 +#: core/models.py:334 msgid "Doctor" msgstr "Doctorant" -#: core/models.py:257 +#: core/models.py:335 msgid "Former student" msgstr "Ancien étudiant" -#: core/models.py:258 +#: core/models.py:336 msgid "Service" msgstr "Service" -#: core/models.py:264 +#: core/models.py:342 msgid "department" msgstr "département" -#: core/models.py:271 +#: core/models.py:349 msgid "dpt option" msgstr "Filière" -#: core/models.py:273 pedagogy/models.py:74 pedagogy/models.py:303 +#: core/models.py:351 pedagogy/models.py:73 pedagogy/models.py:302 msgid "semester" msgstr "semestre" -#: core/models.py:274 +#: core/models.py:352 msgid "quote" msgstr "citation" -#: core/models.py:275 +#: core/models.py:353 msgid "school" msgstr "école" -#: core/models.py:277 +#: core/models.py:355 msgid "promo" msgstr "promo" -#: core/models.py:280 +#: core/models.py:358 msgid "forum signature" msgstr "signature du forum" -#: core/models.py:282 +#: core/models.py:360 msgid "second email address" msgstr "adresse email secondaire" -#: core/models.py:284 +#: core/models.py:362 msgid "parent phone" msgstr "téléphone des parents" -#: core/models.py:287 +#: core/models.py:365 msgid "parent address" msgstr "adresse des parents" -#: core/models.py:290 +#: core/models.py:368 msgid "is subscriber viewable" msgstr "profil visible par les cotisants" -#: core/models.py:513 +#: core/models.py:569 msgid "A user with that username already exists" msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" -#: core/models.py:651 core/templates/core/macros.jinja:75 +#: core/models.py:709 core/templates/core/macros.jinja:75 #: core/templates/core/macros.jinja:77 core/templates/core/macros.jinja:78 -#: core/templates/core/user_detail.jinja:87 -#: core/templates/core/user_detail.jinja:88 -#: core/templates/core/user_detail.jinja:90 -#: core/templates/core/user_detail.jinja:91 -#: core/templates/core/user_detail.jinja:96 -#: core/templates/core/user_detail.jinja:97 -#: core/templates/core/user_detail.jinja:99 -#: core/templates/core/user_detail.jinja:100 -#: core/templates/core/user_edit.jinja:17 +#: core/templates/core/user_detail.jinja:104 +#: core/templates/core/user_detail.jinja:105 +#: core/templates/core/user_detail.jinja:107 +#: core/templates/core/user_detail.jinja:108 +#: core/templates/core/user_detail.jinja:113 +#: core/templates/core/user_detail.jinja:114 +#: core/templates/core/user_detail.jinja:116 +#: core/templates/core/user_detail.jinja:117 +#: core/templates/core/user_edit.jinja:24 +#: core/templates/core/user_edit.jinja:26 +#: core/templates/core/user_edit.jinja:27 +#: core/templates/core/user_edit.jinja:46 +#: core/templates/core/user_edit.jinja:47 +#: core/templates/core/user_edit.jinja:49 +#: core/templates/core/user_edit.jinja:50 +#: core/templates/core/user_edit.jinja:65 +#: core/templates/core/user_edit.jinja:66 +#: core/templates/core/user_edit.jinja:68 +#: core/templates/core/user_edit.jinja:69 #: election/templates/election/election_detail.jinja:132 #: election/templates/election/election_detail.jinja:134 #: forum/templates/forum/macros.jinja:104 @@ -2214,109 +2226,101 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà" msgid "Profile" msgstr "Profil" -#: core/models.py:779 +#: core/models.py:833 msgid "Visitor" msgstr "Visiteur" -#: core/models.py:787 +#: core/models.py:840 msgid "receive the Weekmail" msgstr "recevoir le Weekmail" -#: core/models.py:789 +#: core/models.py:841 msgid "show your stats to others" msgstr "montrez vos statistiques aux autres" -#: core/models.py:791 +#: core/models.py:843 msgid "get a notification for every click" msgstr "avoir une notification pour chaque click" -#: core/models.py:794 +#: core/models.py:846 msgid "get a notification for every refilling" msgstr "avoir une notification pour chaque rechargement" -#: core/models.py:817 +#: core/models.py:869 msgid "file name" msgstr "nom du fichier" -#: core/models.py:821 core/models.py:1169 +#: core/models.py:873 core/models.py:1245 msgid "parent" msgstr "parent" -#: core/models.py:835 +#: core/models.py:887 msgid "compressed file" msgstr "version allégée" -#: core/models.py:842 +#: core/models.py:894 msgid "thumbnail" msgstr "miniature" -#: core/models.py:850 core/models.py:867 +#: core/models.py:902 core/models.py:919 msgid "owner" msgstr "propriétaire" -#: core/models.py:854 core/models.py:1189 core/views/files.py:193 +#: core/models.py:906 core/models.py:1266 core/views/files.py:224 msgid "edit group" msgstr "groupe d'édition" -#: core/models.py:857 core/models.py:1192 core/views/files.py:196 +#: core/models.py:909 core/models.py:1269 core/views/files.py:227 msgid "view group" msgstr "groupe de vue" -#: core/models.py:859 +#: core/models.py:911 msgid "is folder" msgstr "est un dossier" -#: core/models.py:860 +#: core/models.py:912 msgid "mime type" msgstr "type mime" -#: core/models.py:861 +#: core/models.py:913 msgid "size" msgstr "taille" -#: core/models.py:872 +#: core/models.py:924 msgid "asked for removal" msgstr "retrait demandé" -#: core/models.py:874 +#: core/models.py:926 msgid "is in the SAS" msgstr "est dans le SAS" -#: core/models.py:916 +#: core/models.py:998 msgid "Character '/' not authorized in name" msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier" -#: core/models.py:918 core/models.py:922 +#: core/models.py:1000 core/models.py:1004 msgid "Loop in folder tree" msgstr "Boucle dans l'arborescence des dossiers" -#: core/models.py:925 +#: core/models.py:1007 msgid "You can not make a file be a children of a non folder file" msgstr "" "Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas " "un dossier" -#: core/models.py:936 +#: core/models.py:1018 msgid "Duplicate file" msgstr "Un fichier de ce nom existe déjà" -#: core/models.py:953 +#: core/models.py:1035 msgid "You must provide a file" msgstr "Vous devez fournir un fichier" -#: core/models.py:1093 -msgid "Folder: " -msgstr "Dossier : " - -#: core/models.py:1095 -msgid "File: " -msgstr "Fichier : " - -#: core/models.py:1152 +#: core/models.py:1228 msgid "page unix name" msgstr "nom unix de la page" -#: core/models.py:1158 +#: core/models.py:1234 msgid "" "Enter a valid page name. This value may contain only unaccented letters, " "numbers and ./+/-/_ characters." @@ -2324,55 +2328,55 @@ msgstr "" "Entrez un nom de page correct. Uniquement des lettres non accentuées, " "numéros, et ./+/-/_" -#: core/models.py:1176 +#: core/models.py:1252 msgid "page name" msgstr "nom de la page" -#: core/models.py:1184 +#: core/models.py:1261 msgid "owner group" msgstr "groupe propriétaire" -#: core/models.py:1197 +#: core/models.py:1274 msgid "lock user" msgstr "utilisateur bloquant" -#: core/models.py:1204 +#: core/models.py:1281 msgid "lock_timeout" msgstr "décompte du déblocage" -#: core/models.py:1234 +#: core/models.py:1311 msgid "Duplicate page" msgstr "Une page de ce nom existe déjà" -#: core/models.py:1237 +#: core/models.py:1314 msgid "Loop in page tree" msgstr "Boucle dans l'arborescence des pages" -#: core/models.py:1397 +#: core/models.py:1474 msgid "revision" msgstr "révision" -#: core/models.py:1398 +#: core/models.py:1475 msgid "page title" msgstr "titre de la page" -#: core/models.py:1399 +#: core/models.py:1476 msgid "page content" msgstr "contenu de la page" -#: core/models.py:1443 +#: core/models.py:1520 msgid "url" msgstr "url" -#: core/models.py:1444 +#: core/models.py:1521 msgid "param" msgstr "param" -#: core/models.py:1449 +#: core/models.py:1526 msgid "viewed" msgstr "vue" -#: core/models.py:1507 +#: core/models.py:1586 msgid "operation type" msgstr "type d'opération" @@ -2392,24 +2396,18 @@ msgstr "500, Erreur Serveur" msgid "Welcome!" msgstr "Bienvenue !" -#: core/templates/core/base.jinja:56 -msgid "Username" -msgstr "Nom d'utilisateur" - -#: core/templates/core/base.jinja:58 -msgid "Password" -msgstr "Mot de passe" - -#: core/templates/core/base.jinja:60 core/templates/core/login.jinja:4 +#: core/templates/core/base.jinja:53 core/templates/core/login.jinja:8 +#: core/templates/core/login.jinja:18 core/templates/core/login.jinja:50 #: core/templates/core/password_reset_complete.jinja:5 msgid "Login" msgstr "Connexion" -#: core/templates/core/base.jinja:62 core/templates/core/register.jinja:18 +#: core/templates/core/base.jinja:54 core/templates/core/register.jinja:7 +#: core/templates/core/register.jinja:16 core/templates/core/register.jinja:27 msgid "Register" msgstr "Inscription" -#: core/templates/core/base.jinja:91 core/templates/core/base.jinja:92 +#: core/templates/core/base.jinja:60 core/templates/core/base.jinja:61 #: forum/templates/forum/macros.jinja:171 #: forum/templates/forum/macros.jinja:175 #: matmat/templates/matmat/search_form.jinja:37 @@ -2419,24 +2417,28 @@ msgstr "Inscription" msgid "Search" msgstr "Recherche" -#: core/templates/core/base.jinja:118 +#: core/templates/core/base.jinja:96 +msgid "Logout" +msgstr "Déconnexion" + +#: core/templates/core/base.jinja:144 +msgid "You do not have any unread notification" +msgstr "Vous n'avez aucune notification non lue" + +#: core/templates/core/base.jinja:149 msgid "View more" msgstr "Voir plus" -#: core/templates/core/base.jinja:122 +#: core/templates/core/base.jinja:152 #: forum/templates/forum/last_unread.jinja:17 msgid "Mark all as read" msgstr "Marquer tout comme lu" -#: core/templates/core/base.jinja:132 -msgid "Logout" -msgstr "Déconnexion" - -#: core/templates/core/base.jinja:167 +#: core/templates/core/base.jinja:200 msgid "Main" msgstr "Accueil" -#: core/templates/core/base.jinja:169 +#: core/templates/core/base.jinja:202 msgid "Associations & Clubs" msgstr "Associations & Clubs" @@ -2452,15 +2454,15 @@ msgstr "Les clubs de L'AE" msgid "Others UTBM's Associations" msgstr "Les autres associations de l'UTBM" -#: core/templates/core/base.jinja:187 core/templates/core/user_tools.jinja:118 +#: core/templates/core/base.jinja:212 core/templates/core/user_tools.jinja:180 msgid "Elections" msgstr "Élections" -#: core/templates/core/base.jinja:188 +#: core/templates/core/base.jinja:213 msgid "Big event" msgstr "Grandes Activités" -#: core/templates/core/base.jinja:191 +#: core/templates/core/base.jinja:216 #: forum/templates/forum/favorite_topics.jinja:14 #: forum/templates/forum/last_unread.jinja:14 #: forum/templates/forum/macros.jinja:90 forum/templates/forum/main.jinja:6 @@ -2469,92 +2471,98 @@ msgstr "Grandes Activités" msgid "Forum" msgstr "Forum" -#: core/templates/core/base.jinja:192 +#: core/templates/core/base.jinja:217 msgid "Gallery" msgstr "Photos" -#: core/templates/core/base.jinja:193 counter/models.py:379 +#: core/templates/core/base.jinja:218 counter/models.py:373 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:23 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:17 #: eboutic/templates/eboutic/eboutic_payment_result.jinja:4 -#: sith/settings.py:392 sith/settings.py:400 +#: sith/settings.py:397 sith/settings.py:405 msgid "Eboutic" msgstr "Eboutic" -#: core/templates/core/base.jinja:195 +#: core/templates/core/base.jinja:220 msgid "Services" msgstr "Services" -#: core/templates/core/base.jinja:199 +#: core/templates/core/base.jinja:222 msgid "Matmatronch" msgstr "Matmatronch" -#: core/templates/core/base.jinja:200 launderette/models.py:47 +#: core/templates/core/base.jinja:223 launderette/models.py:39 #: launderette/templates/launderette/launderette_book.jinja:5 #: launderette/templates/launderette/launderette_book_choose.jinja:4 #: launderette/templates/launderette/launderette_main.jinja:4 msgid "Launderette" msgstr "Laverie" -#: core/templates/core/base.jinja:201 core/templates/core/file.jinja:20 -#: core/views/files.py:86 +#: core/templates/core/base.jinja:224 core/templates/core/file.jinja:20 +#: core/views/files.py:110 msgid "Files" msgstr "Fichiers" -#: core/templates/core/base.jinja:202 core/templates/core/user_tools.jinja:109 +#: core/templates/core/base.jinja:225 core/templates/core/user_tools.jinja:171 msgid "Pedagogy" msgstr "Pédagogie" -#: core/templates/core/base.jinja:206 +#: core/templates/core/base.jinja:229 msgid "My Benefits" msgstr "Mes Avantages" -#: core/templates/core/base.jinja:210 +#: core/templates/core/base.jinja:231 msgid "Sponsors" msgstr "Partenaires" -#: core/templates/core/base.jinja:211 +#: core/templates/core/base.jinja:232 msgid "Subscriber benefits" msgstr "Les avantages cotisants" -#: core/templates/core/base.jinja:215 +#: core/templates/core/base.jinja:236 msgid "Help" msgstr "Aide" -#: core/templates/core/base.jinja:219 +#: core/templates/core/base.jinja:238 msgid "FAQ" msgstr "FAQ" -#: core/templates/core/base.jinja:220 core/templates/core/base.jinja:262 +#: core/templates/core/base.jinja:239 core/templates/core/base.jinja:279 msgid "Contacts" msgstr "Contacts" -#: core/templates/core/base.jinja:221 +#: core/templates/core/base.jinja:240 msgid "Wiki" msgstr "Wiki" -#: core/templates/core/base.jinja:263 +#: core/templates/core/base.jinja:280 msgid "Legal notices" msgstr "Mentions légales" -#: core/templates/core/base.jinja:264 +#: core/templates/core/base.jinja:281 msgid "Intellectual property" msgstr "Propriété intellectuelle" -#: core/templates/core/base.jinja:265 +#: core/templates/core/base.jinja:282 msgid "Help & Documentation" msgstr "Aide & Documentation" -#: core/templates/core/base.jinja:266 +#: core/templates/core/base.jinja:283 msgid "R&D" msgstr "R&D" -#: core/templates/core/base.jinja:262 +#: core/templates/core/base.jinja:286 msgid "Site created by the IT Department of the AE" msgstr "Site réalisé par le Pôle Informatique de l'AE" +#: core/templates/core/base.jinja:292 +#, fuzzy +#| msgid "Site version:" +msgid "Sith version:" +msgstr "Version du site :" + #: core/templates/core/create.jinja:4 core/templates/core/create.jinja:8 #, python-format msgid "Create %(name)s" @@ -2613,38 +2621,35 @@ msgstr "Propriétés" #: core/templates/core/file_detail.jinja:13 #: core/templates/core/file_moderation.jinja:20 -#: sas/templates/sas/picture.jinja:88 +#: sas/templates/sas/picture.jinja:86 msgid "Owner: " msgstr "Propriétaire : " -#: core/templates/core/file_detail.jinja:26 sas/templates/sas/album.jinja:28 +#: core/templates/core/file_detail.jinja:26 sas/templates/sas/album.jinja:51 +#: sas/templates/sas/main.jinja:75 msgid "Clear clipboard" msgstr "Vider le presse-papier" -#: core/templates/core/file_detail.jinja:27 sas/templates/sas/album.jinja:29 +#: core/templates/core/file_detail.jinja:27 sas/templates/sas/album.jinja:38 msgid "Cut" msgstr "Couper" -#: core/templates/core/file_detail.jinja:28 sas/templates/sas/album.jinja:30 +#: core/templates/core/file_detail.jinja:28 sas/templates/sas/album.jinja:39 msgid "Paste" msgstr "Coller" -#: core/templates/core/file_detail.jinja:31 sas/templates/sas/album.jinja:33 +#: core/templates/core/file_detail.jinja:31 sas/templates/sas/album.jinja:45 +#: sas/templates/sas/main.jinja:69 msgid "Clipboard: " msgstr "Presse-papier : " -#: sas/templates/sas/album.jinja:69 -#: sas/templates/sas/album.jinja:97 -msgid "To be moderated" -msgstr "A modérer" - #: core/templates/core/file_detail.jinja:53 msgid "Real name: " msgstr "Nom réel : " #: core/templates/core/file_detail.jinja:54 #: core/templates/core/file_moderation.jinja:21 -#: sas/templates/sas/picture.jinja:87 +#: sas/templates/sas/picture.jinja:82 msgid "Date: " msgstr "Date : " @@ -2695,8 +2700,8 @@ msgid "Edit group" msgstr "Éditer le groupe" #: core/templates/core/group_edit.jinja:9 -#: core/templates/core/user_edit.jinja:37 -#: core/templates/core/user_group.jinja:8 +#: core/templates/core/user_edit.jinja:138 +#: core/templates/core/user_group.jinja:13 #: pedagogy/templates/pedagogy/uv_edit.jinja:36 msgid "Update" msgstr "Mettre à jour" @@ -2718,34 +2723,33 @@ msgstr "ID" msgid "Group" msgstr "Groupe" -#: core/templates/core/login.jinja:10 +#: core/templates/core/login.jinja:22 +#, fuzzy +#| msgid "" +#| "Your account doesn't have access to this page. To proceed,\n" +#| " please login with an account that has access." +msgid "" +"Your account doesn't have access to this page. To proceed,\n" +" please login with an account that has access." +msgstr "" +"Votre compte n'a pas accès à cette page. Merci de vous identifier avec un " +"compte qui a accès." + +#: core/templates/core/login.jinja:25 +msgid "Please login or create an account to see this page." +msgstr "Merci de vous identifier ou de créer un compte pour voir cette page." + +#: core/templates/core/login.jinja:31 msgid "Your username and password didn't match. Please try again." msgstr "" "Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Merci de " "réessayer." -#: core/templates/core/login.jinja:15 -msgid "" -"Your account doesn't have access to this page. To proceed,\n" -" please login with an account that has access." -msgstr "" -"Votre compte n'a pas accès à cette page. Merci de vous identifier avec un " -"compte qui a accès." - -#: core/templates/core/login.jinja:18 -msgid "Please login or create an account to see this page." -msgstr "Merci de vous identifier ou de créer un compte pour voir cette page." - -#: core/templates/core/login.jinja:28 -#: counter/templates/counter/counter_main.jinja:56 -msgid "login" -msgstr "login" - -#: core/templates/core/login.jinja:32 +#: core/templates/core/login.jinja:55 msgid "Lost password?" msgstr "Mot de passe perdu ?" -#: core/templates/core/login.jinja:33 +#: core/templates/core/login.jinja:57 msgid "Create account" msgstr "Créer un compte" @@ -2762,11 +2766,11 @@ msgstr "Tweeter" msgid "Subscribed until %(subscription_end)s" msgstr "Cotisant jusqu'au %(subscription_end)s" -#: core/templates/core/macros.jinja:86 core/templates/core/user_edit.jinja:40 +#: core/templates/core/macros.jinja:86 msgid "Account number: " msgstr "Numéro de compte : " -#: core/templates/core/macros.jinja:91 launderette/models.py:217 +#: core/templates/core/macros.jinja:91 launderette/models.py:215 msgid "Slot" msgstr "Créneau" @@ -2977,23 +2981,19 @@ msgstr "Merci d'utiliser notre site !" msgid "The %(site_name)s team" msgstr "L'équipe de %(site_name)s" -#: core/templates/core/register.jinja:3 core/templates/core/register.jinja:6 -msgid "Register a user" -msgstr "Enregistrer un utilisateur" - -#: core/templates/core/register.jinja:9 +#: core/templates/core/register.jinja:19 #, python-format msgid "Welcome %(user_name)s!" msgstr "Bienvenue, %(user_name)s!" -#: core/templates/core/register.jinja:10 +#: core/templates/core/register.jinja:20 msgid "" "You successfully registered and you will soon receive a confirmation mail." msgstr "" "Vous vous êtes correctement enregistré, et vous devriez recevoir rapidement " "un email de confirmation." -#: core/templates/core/register.jinja:12 +#: core/templates/core/register.jinja:21 #, python-format msgid "Your username is %(username)s." msgstr "Votre nom d'utilisateur est %(username)s." @@ -3006,7 +3006,7 @@ msgstr "Résultat de la recherche" msgid "Users" msgstr "Utilisateurs" -#: core/templates/core/search.jinja:18 core/views/user.py:242 +#: core/templates/core/search.jinja:18 core/views/user.py:241 msgid "Clubs" msgstr "Clubs" @@ -3063,7 +3063,7 @@ msgid "Eboutic invoices" msgstr "Facture eboutic" #: core/templates/core/user_account.jinja:57 -#: core/templates/core/user_tools.jinja:37 counter/views.py:807 +#: core/templates/core/user_tools.jinja:58 counter/views.py:801 msgid "Etickets" msgstr "Etickets" @@ -3089,108 +3089,106 @@ msgstr "Clubs" msgid "Current club(s) :" msgstr "Clubs actuels : " -#: core/templates/core/user_clubs.jinja:39 +#: core/templates/core/user_clubs.jinja:41 msgid "Old club(s) :" msgstr "Anciens clubs :" -#: core/templates/core/user_clubs.jinja:69 +#: core/templates/core/user_clubs.jinja:74 msgid "Subscribed mailing lists" msgstr "Mailing listes abonnées" -#: core/templates/core/user_clubs.jinja:71 +#: core/templates/core/user_clubs.jinja:76 msgid "Unsubscribe" msgstr "Se désabonner" -#: core/templates/core/user_detail.jinja:5 +#: core/templates/core/user_detail.jinja:9 #, python-format msgid "%(user_name)s's profile" msgstr "Profil de %(user_name)s" -#: core/templates/core/user_detail.jinja:29 +#: core/templates/core/user_detail.jinja:38 msgid "Pronouns: " msgstr "Pronoms : " -#: core/templates/core/user_detail.jinja:35 +#: core/templates/core/user_detail.jinja:44 msgid "Born: " msgstr "Né le : " -#: core/templates/core/user_detail.jinja:42 +#: core/templates/core/user_detail.jinja:51 msgid "Department: " msgstr "Département : " -#: core/templates/core/user_detail.jinja:49 +#: core/templates/core/user_detail.jinja:59 msgid "Option: " msgstr "Filière : " -#: core/templates/core/user_detail.jinja:56 +#: core/templates/core/user_detail.jinja:66 #: trombi/templates/trombi/export.jinja:20 msgid "Phone: " msgstr "Téléphone : " -#: core/templates/core/user_detail.jinja:63 +#: core/templates/core/user_detail.jinja:73 msgid "Address: " msgstr "Adresse : " -#: core/templates/core/user_detail.jinja:70 +#: core/templates/core/user_detail.jinja:80 msgid "Parents address: " msgstr "Adresse des parents : " -#: core/templates/core/user_detail.jinja:79 +#: core/templates/core/user_detail.jinja:89 msgid "Promo: " msgstr "Promo : " -#: core/templates/core/user_detail.jinja:104 -#: core/templates/core/user_detail.jinja:105 -#: core/templates/core/user_detail.jinja:107 -#: core/templates/core/user_detail.jinja:108 -#: core/templates/core/user_edit.jinja:31 +#: core/templates/core/user_detail.jinja:121 +#: core/templates/core/user_detail.jinja:122 +#: core/templates/core/user_detail.jinja:124 +#: core/templates/core/user_detail.jinja:125 msgid "Avatar" msgstr "Avatar" -#: core/templates/core/user_detail.jinja:112 -#: core/templates/core/user_detail.jinja:113 -#: core/templates/core/user_detail.jinja:115 -#: core/templates/core/user_detail.jinja:116 -#: core/templates/core/user_edit.jinja:34 +#: core/templates/core/user_detail.jinja:129 +#: core/templates/core/user_detail.jinja:130 +#: core/templates/core/user_detail.jinja:132 +#: core/templates/core/user_detail.jinja:133 msgid "Scrub" msgstr "Blouse" -#: core/templates/core/user_detail.jinja:141 +#: core/templates/core/user_detail.jinja:163 msgid "Not subscribed" msgstr "Non cotisant" -#: core/templates/core/user_detail.jinja:143 +#: core/templates/core/user_detail.jinja:166 #: subscription/templates/subscription/subscription.jinja:4 #: subscription/templates/subscription/subscription.jinja:8 msgid "New subscription" msgstr "Nouvelle cotisation" -#: core/templates/core/user_detail.jinja:150 +#: core/templates/core/user_detail.jinja:177 msgid "Subscription history" msgstr "Historique de cotisation" -#: core/templates/core/user_detail.jinja:153 +#: core/templates/core/user_detail.jinja:187 msgid "Subscription start" msgstr "Début de la cotisation" -#: core/templates/core/user_detail.jinja:154 +#: core/templates/core/user_detail.jinja:188 msgid "Subscription end" msgstr "Fin de la cotisation" -#: core/templates/core/user_detail.jinja:155 +#: core/templates/core/user_detail.jinja:189 #: subscription/templates/subscription/stats.jinja:36 msgid "Subscription type" msgstr "Type de cotisation" -#: core/templates/core/user_detail.jinja:177 +#: core/templates/core/user_detail.jinja:213 msgid "Give gift" msgstr "Donner cadeau" -#: core/templates/core/user_detail.jinja:182 +#: core/templates/core/user_detail.jinja:221 msgid "Last given gift :" msgstr "Dernier cadeau donné :" -#: core/templates/core/user_detail.jinja:192 +#: core/templates/core/user_detail.jinja:239 msgid "No gift given yet" msgstr "Aucun cadeau donné pour l'instant" @@ -3198,73 +3196,65 @@ msgstr "Aucun cadeau donné pour l'instant" msgid "Edit user" msgstr "Éditer l'utilisateur" -#: core/templates/core/user_edit.jinja:8 +#: core/templates/core/user_edit.jinja:11 msgid "Edit user profile" msgstr "Éditer le profil de l'utilisateur" -#: core/templates/core/user_edit.jinja:15 -msgid "Current profile: " -msgstr "Profil actuel : " - -#: core/templates/core/user_edit.jinja:25 -msgid "Take picture" -msgstr "Prendre une photo" - -#: core/templates/core/user_edit.jinja:30 -msgid "Current avatar: " -msgstr "Avatar actuel : " - -#: core/templates/core/user_edit.jinja:33 -msgid "Current scrub: " -msgstr "Blouse actuelle : " - -#: core/templates/core/user_edit.jinja:38 -msgid "Username: " -msgstr "Nom d'utilisateur : " - -#: core/templates/core/user_edit.jinja:43 -msgid "Change my password" -msgstr "Changer mon mot de passe" - -#: core/templates/core/user_edit.jinja:45 -msgid "Change user password" -msgstr "Changer le mot de passe" - -#: core/templates/core/user_edit.jinja:50 +#: core/templates/core/user_edit.jinja:35 msgid "To edit your profile picture, ask a member of the AE" msgstr "Pour changer votre photo de profil, demandez à un membre de l'AE" -#: core/templates/core/user_godfathers.jinja:5 +#: core/templates/core/user_edit.jinja:128 +msgid "Change my password" +msgstr "Changer mon mot de passe" + +#: core/templates/core/user_edit.jinja:133 +msgid "Change user password" +msgstr "Changer le mot de passe" + +#: core/templates/core/user_edit.jinja:143 +#, fuzzy +#| msgid "Username: " +msgid "Username:" +msgstr "Nom d'utilisateur : " + +#: core/templates/core/user_edit.jinja:146 +#, fuzzy +#| msgid "Account number: " +msgid "Account number:" +msgstr "Numéro de compte : " + +#: core/templates/core/user_godfathers.jinja:9 #, python-format msgid "%(user_name)s's family" msgstr "Famille de %(user_name)s" -#: core/templates/core/user_godfathers.jinja:10 +#: core/templates/core/user_godfathers.jinja:15 msgid "Show family picture" msgstr "Voir une image de la famille" -#: core/templates/core/user_godfathers.jinja:12 +#: core/templates/core/user_godfathers.jinja:18 msgid "Godfathers / Godmothers" msgstr "Parrains / Marraines" -#: core/templates/core/user_godfathers.jinja:20 +#: core/templates/core/user_godfathers.jinja:32 msgid "Show ancestors tree" msgstr "Voir l'arbre des ancêtres" -#: core/templates/core/user_godfathers.jinja:22 +#: core/templates/core/user_godfathers.jinja:35 #: core/templates/core/user_godfathers_tree.jinja:50 msgid "No godfathers / godmothers" msgstr "Pas de famille" -#: core/templates/core/user_godfathers.jinja:25 core/views/user.py:462 +#: core/templates/core/user_godfathers.jinja:38 core/views/user.py:463 msgid "Godchildren" msgstr "Fillots / Fillotes" -#: core/templates/core/user_godfathers.jinja:33 +#: core/templates/core/user_godfathers.jinja:52 msgid "Show descent tree" msgstr "Voir l'arbre de la descendance" -#: core/templates/core/user_godfathers.jinja:35 +#: core/templates/core/user_godfathers.jinja:55 #: core/templates/core/user_godfathers_tree.jinja:48 msgid "No godchildren" msgstr "Pas de fillots / fillotes" @@ -3302,7 +3292,7 @@ msgstr "Descendants de %(u)s" msgid "Ancestors tree of %(u)s" msgstr "Ancêtres de %(u)s" -#: core/templates/core/user_group.jinja:4 +#: core/templates/core/user_group.jinja:9 #, python-format msgid "Edit user groups for %(user_name)s" msgstr "Éditer les groupes pour %(user_name)s" @@ -3311,240 +3301,257 @@ msgstr "Éditer les groupes pour %(user_name)s" msgid "User list" msgstr "Liste d'utilisateurs" -#: core/templates/core/user_pictures.jinja:4 +#: core/templates/core/user_pictures.jinja:8 #, python-format msgid "%(user_name)s's pictures" msgstr "Photos de %(user_name)s" -#: core/templates/core/user_pictures.jinja:9 +#: core/templates/core/user_pictures.jinja:14 msgid "Download all my pictures" msgstr "Télécharger toutes mes photos" -#: core/templates/core/user_pictures.jinja:83 -msgid "Error downloading your pictures" -msgstr "Erreur de téléchargement de vos photos" +#: core/templates/core/user_pictures.jinja:28 sas/templates/sas/album.jinja:68 +#: sas/templates/sas/album.jinja:96 +msgid "To be moderated" +msgstr "A modérer" -#: core/templates/core/user_picture.jinja: +#: core/templates/core/user_pictures.jinja:37 msgid "Picture Unavailable" msgstr "Photo Indisponible" -#: core/templates/core/user_preferences.jinja:4 -#: core/templates/core/user_preferences.jinja:8 core/views/user.py:234 +#: core/templates/core/user_pictures.jinja:105 +msgid "Error downloading your pictures" +msgstr "Erreur de téléchargement de vos photos" + +#: core/templates/core/user_preferences.jinja:8 +#: core/templates/core/user_preferences.jinja:13 core/views/user.py:233 msgid "Preferences" msgstr "Préférences" -#: core/templates/core/user_preferences.jinja:14 trombi/views.py:58 +#: core/templates/core/user_preferences.jinja:14 +#, fuzzy +#| msgid "Generate" +msgid "General" +msgstr "Générer" + +#: core/templates/core/user_preferences.jinja:21 trombi/views.py:57 msgid "Trombi" msgstr "Trombi" -#: core/templates/core/user_preferences.jinja:22 +#: core/templates/core/user_preferences.jinja:31 #, python-format msgid "You already choose to be in that Trombi: %(trombi)s." msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s." -#: core/templates/core/user_preferences.jinja:23 +#: core/templates/core/user_preferences.jinja:33 msgid "Go to my Trombi tools" msgstr "Allez à mes outils de Trombi" -#: core/templates/core/user_preferences.jinja:26 +#: core/templates/core/user_preferences.jinja:39 msgid "Student cards" msgstr "Cartes étudiante" -#: core/templates/core/user_preferences.jinja:27 +#: 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 +#, fuzzy +#| msgid "" +#| "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." msgid "" "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." +"want to manually\n" +" 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 " "vous même ici. Si vous voulez l'ajouter manuellement par vous même, vous " "aurez besoin d'un lecteur NFC. Nous enregistrons l'UID de la carte qui fait " "14 caractères de long." -#: core/templates/core/user_preferences.jinja:40 -msgid "No student card registered." -msgstr "Aucune carte étudiante enregistrée." - -#: core/templates/core/user_stats.jinja:4 +#: core/templates/core/user_stats.jinja:8 #, python-format msgid "%(user_name)s's stats" msgstr "Stats de %(user_name)s" -#: core/templates/core/user_stats.jinja:9 +#: core/templates/core/user_stats.jinja:16 msgid "Permanencies" msgstr "Permanences" -#: core/templates/core/user_stats.jinja:17 +#: core/templates/core/user_stats.jinja:27 msgid "Buyings" msgstr "Achats" -#: core/templates/core/user_stats.jinja:23 +#: core/templates/core/user_stats.jinja:39 msgid "Product top 10" msgstr "Top 10 produits" -#: core/templates/core/user_stats.jinja:27 counter/forms.py:176 +#: core/templates/core/user_stats.jinja:43 counter/forms.py:176 msgid "Product" msgstr "Produit" -#: core/templates/core/user_tools.jinja:4 +#: core/templates/core/user_tools.jinja:8 #, python-format msgid "%(user_name)s's tools" msgstr "Outils de %(user_name)s" -#: core/templates/core/user_tools.jinja:8 +#: core/templates/core/user_tools.jinja:13 msgid "User Tools" msgstr "Outils utilisateurs" -#: core/templates/core/user_tools.jinja:11 +#: core/templates/core/user_tools.jinja:18 msgid "Sith management" msgstr "Gestion de Sith" -#: core/templates/core/user_tools.jinja:14 core/views/user.py:250 +#: core/templates/core/user_tools.jinja:21 core/views/user.py:249 msgid "Groups" msgstr "Groupes" -#: core/templates/core/user_tools.jinja:15 +#: core/templates/core/user_tools.jinja:22 #: rootplace/templates/rootplace/merge.jinja:4 msgid "Merge users" msgstr "Fusionner deux utilisateurs" -#: core/templates/core/user_tools.jinja:16 +#: core/templates/core/user_tools.jinja:23 #: rootplace/templates/rootplace/logs.jinja:5 msgid "Operation logs" msgstr "Journal d'opérations" -#: core/templates/core/user_tools.jinja:17 +#: core/templates/core/user_tools.jinja:24 #: rootplace/templates/rootplace/delete_user_messages.jinja:4 msgid "Delete user's forum messages" msgstr "Supprimer les messages forum d'un utilisateur" -#: core/templates/core/user_tools.jinja:20 +#: core/templates/core/user_tools.jinja:27 msgid "Subscriptions" msgstr "Cotisations" -#: core/templates/core/user_tools.jinja:23 +#: core/templates/core/user_tools.jinja:30 #: subscription/templates/subscription/stats.jinja:4 msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:29 counter/forms.py:139 -#: counter/views.py:777 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:139 +#: counter/views.py:771 msgid "Counters" msgstr "Comptoirs" -#: core/templates/core/user_tools.jinja:32 +#: core/templates/core/user_tools.jinja:53 msgid "General counters management" msgstr "Gestion générale des comptoirs" -#: core/templates/core/user_tools.jinja:33 +#: core/templates/core/user_tools.jinja:54 msgid "Products management" msgstr "Gestion des produits" -#: core/templates/core/user_tools.jinja:34 +#: core/templates/core/user_tools.jinja:55 msgid "Product types management" msgstr "Gestion des types de produit" -#: core/templates/core/user_tools.jinja:35 -#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:797 +#: core/templates/core/user_tools.jinja:56 +#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:791 msgid "Cash register summaries" msgstr "Relevés de caisse" -#: core/templates/core/user_tools.jinja:36 -#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:802 +#: core/templates/core/user_tools.jinja:57 +#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:796 msgid "Invoices call" msgstr "Appels à facture" -#: core/templates/core/user_tools.jinja:44 core/views/user.py:268 +#: core/templates/core/user_tools.jinja:72 core/views/user.py:268 #: counter/templates/counter/counter_list.jinja:18 #: counter/templates/counter/counter_list.jinja:34 #: counter/templates/counter/counter_list.jinja:56 msgid "Stats" msgstr "Stats" -#: core/templates/core/user_tools.jinja:48 +#: core/templates/core/user_tools.jinja:78 #: counter/templates/counter/counter_list.jinja:38 #: stock/templates/stock/stock_item_list.jinja:11 #: stock/templates/stock/stock_list.jinja:16 msgid "Shopping lists" msgstr "Liste de courses" -#: core/templates/core/user_tools.jinja:50 +#: core/templates/core/user_tools.jinja:80 #: counter/templates/counter/counter_list.jinja:40 msgid "Create new stock" msgstr "Créer nouveau stock" -#: core/templates/core/user_tools.jinja:61 +#: core/templates/core/user_tools.jinja:101 msgid "Refound Account" msgstr "Rembourser un compte" -#: core/templates/core/user_tools.jinja:62 +#: core/templates/core/user_tools.jinja:102 msgid "General accounting" msgstr "Comptabilité générale" -#: core/templates/core/user_tools.jinja:72 +#: core/templates/core/user_tools.jinja:117 msgid "Club account: " msgstr "Compte club : " -#: core/templates/core/user_tools.jinja:79 +#: core/templates/core/user_tools.jinja:133 msgid "Communication" msgstr "Communication" -#: core/templates/core/user_tools.jinja:82 +#: core/templates/core/user_tools.jinja:136 msgid "Create weekmail article" msgstr "Rédiger un nouvel article dans le Weekmail" -#: core/templates/core/user_tools.jinja:86 +#: core/templates/core/user_tools.jinja:140 msgid "Moderate news" msgstr "Modérer les nouvelles" -#: core/templates/core/user_tools.jinja:87 +#: core/templates/core/user_tools.jinja:141 msgid "Edit alert message" msgstr "Éditer le message d'alerte" -#: core/templates/core/user_tools.jinja:88 +#: core/templates/core/user_tools.jinja:142 msgid "Edit information message" msgstr "Éditer le message d'informations" -#: core/templates/core/user_tools.jinja:89 +#: core/templates/core/user_tools.jinja:143 msgid "Moderate files" msgstr "Modérer les fichiers" -#: core/templates/core/user_tools.jinja:95 +#: core/templates/core/user_tools.jinja:149 msgid "Moderate pictures" msgstr "Modérer les photos" -#: core/templates/core/user_tools.jinja:112 +#: core/templates/core/user_tools.jinja:173 #: pedagogy/templates/pedagogy/guide.jinja:20 msgid "Create UV" msgstr "Créer UV" -#: core/templates/core/user_tools.jinja:113 +#: core/templates/core/user_tools.jinja:174 #: pedagogy/templates/pedagogy/guide.jinja:23 #: trombi/templates/trombi/detail.jinja:10 msgid "Moderate comments" msgstr "Modérer les commentaires" -#: core/templates/core/user_tools.jinja:120 +#: core/templates/core/user_tools.jinja:182 msgid "See available elections" msgstr "Voir les élections disponibles" -#: core/templates/core/user_tools.jinja:121 +#: core/templates/core/user_tools.jinja:183 msgid "See archived elections" msgstr "Voir les élections archivées" -#: core/templates/core/user_tools.jinja:123 +#: core/templates/core/user_tools.jinja:185 msgid "Create a new election" msgstr "Créer une nouvelle élection" -#: core/templates/core/user_tools.jinja:128 +#: core/templates/core/user_tools.jinja:191 msgid "Other tools" msgstr "Autres outils" -#: core/templates/core/user_tools.jinja:130 +#: core/templates/core/user_tools.jinja:193 msgid "Convert dokuwiki/BBcode syntax to Markdown" msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown" -#: core/templates/core/user_tools.jinja:131 +#: core/templates/core/user_tools.jinja:194 msgid "Trombi tools" msgstr "Outils Trombi" @@ -3555,110 +3562,110 @@ msgid_plural "%(nb_days)d days, %(remainder)s" msgstr[0] "" msgstr[1] "" -#: core/views/files.py:82 +#: core/views/files.py:107 msgid "Add a new folder" msgstr "Ajouter un nouveau dossier" -#: core/views/files.py:103 +#: core/views/files.py:127 #, python-format msgid "Error creating folder %(folder_name)s: %(msg)s" msgstr "Erreur de création du dossier %(folder_name)s : %(msg)s" -#: core/views/files.py:123 core/views/forms.py:310 core/views/forms.py:317 -#: sas/views.py:94 +#: core/views/files.py:147 core/views/forms.py:308 core/views/forms.py:315 +#: sas/views.py:83 #, python-format msgid "Error uploading file %(file_name)s: %(msg)s" msgstr "Erreur d'envoi du fichier %(file_name)s : %(msg)s" -#: core/views/files.py:198 sas/views.py:378 +#: core/views/files.py:229 sas/views.py:367 msgid "Apply rights recursively" msgstr "Appliquer les droits récursivement" -#: core/views/forms.py:91 +#: core/views/forms.py:88 msgid "Heading" msgstr "Titre" -#: core/views/forms.py:92 +#: core/views/forms.py:89 msgid "Italic" msgstr "Italique" -#: core/views/forms.py:93 +#: core/views/forms.py:90 msgid "Bold" msgstr "Gras" -#: core/views/forms.py:94 +#: core/views/forms.py:91 msgid "Strikethrough" msgstr "Barré" -#: core/views/forms.py:95 +#: core/views/forms.py:92 msgid "Underline" msgstr "Souligné" -#: core/views/forms.py:96 +#: core/views/forms.py:93 msgid "Superscript" msgstr "Exposant" -#: core/views/forms.py:97 +#: core/views/forms.py:94 msgid "Subscript" msgstr "Indice" -#: core/views/forms.py:99 +#: core/views/forms.py:96 msgid "Quote" msgstr "Citation" -#: core/views/forms.py:100 +#: core/views/forms.py:97 msgid "Unordered list" msgstr "Liste non ordonnée" -#: core/views/forms.py:101 +#: core/views/forms.py:98 msgid "Ordered list" msgstr "Liste ordonnée" -#: core/views/forms.py:102 +#: core/views/forms.py:99 msgid "Insert image" msgstr "Insérer image" -#: core/views/forms.py:103 +#: core/views/forms.py:100 msgid "Insert link" msgstr "Insérer lien" -#: core/views/forms.py:104 +#: core/views/forms.py:101 msgid "Insert table" msgstr "Insérer tableau" -#: core/views/forms.py:105 +#: core/views/forms.py:102 msgid "Clean block" msgstr "Nettoyer bloc" -#: core/views/forms.py:106 +#: core/views/forms.py:103 msgid "Toggle preview" msgstr "Activer la prévisualisation" -#: core/views/forms.py:107 +#: core/views/forms.py:104 msgid "Toggle side by side" msgstr "Activer la vue côte à côte" -#: core/views/forms.py:108 +#: core/views/forms.py:105 msgid "Toggle fullscreen" msgstr "Activer le plein écran" -#: core/views/forms.py:109 +#: core/views/forms.py:106 msgid "Markdown guide" msgstr "Guide markdown" -#: core/views/forms.py:125 core/views/forms.py:133 +#: core/views/forms.py:122 core/views/forms.py:130 msgid "Choose file" msgstr "Choisir un fichier" -#: core/views/forms.py:149 core/views/forms.py:157 +#: core/views/forms.py:146 core/views/forms.py:154 msgid "Choose user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:189 +#: core/views/forms.py:186 msgid "Username, email, or account number" msgstr "Nom d'utilisateur, email, ou numéro de compte AE" -#: core/views/forms.py:256 +#: core/views/forms.py:254 msgid "" "Profile: you need to be visible on the picture, in order to be recognized (e." "g. by the barmen)" @@ -3666,67 +3673,68 @@ msgstr "" "Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "(par exemple par les barmen)" -#: core/views/forms.py:258 +#: core/views/forms.py:256 msgid "Avatar: used on the forum" msgstr "Avatar : utilisé sur le forum" -#: core/views/forms.py:259 +#: core/views/forms.py:257 msgid "Scrub: let other know how your scrub looks like!" msgstr "Blouse : montrez aux autres à quoi ressemble votre blouse !" -#: core/views/forms.py:321 +#: core/views/forms.py:319 msgid "Bad image format, only jpeg, png, and gif are accepted" msgstr "Mauvais format d'image, seuls les jpeg, png, et gif sont acceptés" -#: core/views/forms.py:342 +#: core/views/forms.py:340 msgid "Godfather / Godmother" msgstr "Parrain / Marraine" -#: core/views/forms.py:343 +#: core/views/forms.py:341 msgid "Godchild" msgstr "Fillot / Fillote" -#: core/views/forms.py:348 counter/forms.py:55 trombi/views.py:158 +#: core/views/forms.py:346 counter/forms.py:55 trombi/views.py:156 msgid "Select user" msgstr "Choisir un utilisateur" -#: core/views/forms.py:361 core/views/forms.py:379 election/models.py:24 -#: election/views.py:155 +#: core/views/forms.py:359 core/views/forms.py:377 election/models.py:24 +#: election/views.py:150 msgid "edit groups" msgstr "groupe d'édition" -#: core/views/forms.py:364 core/views/forms.py:382 election/models.py:31 -#: election/views.py:158 +#: core/views/forms.py:362 core/views/forms.py:380 election/models.py:31 +#: election/views.py:153 msgid "view groups" msgstr "groupe de vue" -#: core/views/group.py:55 +#: core/views/group.py:44 msgid "Users to remove from group" msgstr "Utilisateurs à retirer du groupe" -#: core/views/group.py:62 +#: core/views/group.py:51 msgid "Users to add to group" msgstr "Utilisateurs à ajouter au groupe" -#: core/views/user.py:202 core/views/user.py:464 core/views/user.py:466 +#: core/views/user.py:201 core/views/user.py:465 core/views/user.py:467 msgid "Family" msgstr "Famille" -#: core/views/user.py:207 trombi/templates/trombi/export.jinja:25 +#: core/views/user.py:206 sas/templates/sas/album.jinja:84 +#: trombi/templates/trombi/export.jinja:25 #: trombi/templates/trombi/user_profile.jinja:11 msgid "Pictures" msgstr "Photos" -#: core/views/user.py:215 +#: core/views/user.py:214 msgid "Galaxy" msgstr "Galaxie" -#: core/views/user.py:608 +#: core/views/user.py:612 msgid "User already has a profile picture" msgstr "L'utilisateur a déjà une photo de profil" -#: counter/app.py:31 counter/models.py:395 counter/models.py:875 -#: counter/models.py:911 launderette/models.py:41 stock/models.py:43 +#: counter/app.py:31 counter/models.py:389 counter/models.py:880 +#: counter/models.py:916 launderette/models.py:33 stock/models.py:42 msgid "counter" msgstr "comptoir" @@ -3750,165 +3758,165 @@ msgstr "Groupes d'achat" msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:63 +#: counter/models.py:53 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:65 +#: counter/models.py:55 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:68 +#: counter/models.py:58 msgid "customer" msgstr "client" -#: counter/models.py:69 +#: counter/models.py:59 msgid "customers" msgstr "clients" -#: counter/models.py:148 counter/views.py:319 +#: counter/models.py:138 counter/views.py:316 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:183 +#: counter/models.py:173 msgid "First name" msgstr "Prénom" -#: counter/models.py:184 +#: counter/models.py:174 msgid "Last name" msgstr "Nom de famille" -#: counter/models.py:185 +#: counter/models.py:175 msgid "Address 1" msgstr "Adresse 1" -#: counter/models.py:186 +#: counter/models.py:176 msgid "Address 2" msgstr "Adresse 2" -#: counter/models.py:187 +#: counter/models.py:177 msgid "Zip code" msgstr "Code postal" -#: counter/models.py:188 +#: counter/models.py:178 msgid "City" msgstr "Ville" -#: counter/models.py:189 +#: counter/models.py:179 msgid "Country" msgstr "Pays" -#: counter/models.py:232 counter/models.py:260 +#: counter/models.py:222 counter/models.py:252 msgid "product type" msgstr "type du produit" -#: counter/models.py:266 +#: counter/models.py:258 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:267 +#: counter/models.py:259 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:268 +#: counter/models.py:260 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:270 +#: counter/models.py:262 msgid "icon" msgstr "icône" -#: counter/models.py:275 +#: counter/models.py:267 msgid "limit age" msgstr "âge limite" -#: counter/models.py:276 +#: counter/models.py:268 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:280 +#: counter/models.py:272 msgid "parent product" msgstr "produit parent" -#: counter/models.py:286 +#: counter/models.py:278 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:288 election/models.py:52 +#: counter/models.py:280 election/models.py:52 msgid "archived" msgstr "archivé" -#: counter/models.py:291 counter/models.py:1006 +#: counter/models.py:283 counter/models.py:1017 msgid "product" msgstr "produit" -#: counter/models.py:374 +#: counter/models.py:368 msgid "products" msgstr "produits" -#: counter/models.py:377 +#: counter/models.py:371 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:379 +#: counter/models.py:373 msgid "Bar" msgstr "Bar" -#: counter/models.py:379 +#: counter/models.py:373 msgid "Office" msgstr "Bureau" -#: counter/models.py:382 +#: counter/models.py:376 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:390 launderette/models.py:207 +#: counter/models.py:384 launderette/models.py:205 msgid "token" msgstr "jeton" -#: counter/models.py:618 +#: counter/models.py:619 msgid "bank" msgstr "banque" -#: counter/models.py:620 counter/models.py:710 +#: counter/models.py:621 counter/models.py:713 msgid "is validated" msgstr "est validé" -#: counter/models.py:623 +#: counter/models.py:624 msgid "refilling" msgstr "rechargement" -#: counter/models.py:687 eboutic/models.py:289 +#: counter/models.py:690 eboutic/models.py:280 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:688 counter/models.py:991 eboutic/models.py:290 +#: counter/models.py:691 counter/models.py:998 eboutic/models.py:281 msgid "quantity" msgstr "quantité" -#: counter/models.py:707 +#: counter/models.py:710 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:707 sith/settings.py:385 sith/settings.py:390 -#: sith/settings.py:410 +#: counter/models.py:710 sith/settings.py:390 sith/settings.py:395 +#: sith/settings.py:415 msgid "Credit card" msgstr "Carte bancaire" -#: counter/models.py:713 +#: counter/models.py:716 msgid "selling" msgstr "vente" -#: counter/models.py:740 +#: counter/models.py:745 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:741 +#: counter/models.py:746 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:743 counter/models.py:766 +#: counter/models.py:748 counter/models.py:771 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3920,69 +3928,73 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:880 +#: counter/models.py:885 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:883 +#: counter/models.py:888 msgid "permanency" msgstr "permanence" -#: counter/models.py:916 +#: counter/models.py:921 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:919 +#: counter/models.py:924 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:987 +#: counter/models.py:994 msgid "cash summary" msgstr "relevé" -#: counter/models.py:990 +#: counter/models.py:997 msgid "value" msgstr "valeur" -#: counter/models.py:992 +#: counter/models.py:1000 msgid "check" msgstr "chèque" -#: counter/models.py:995 +#: counter/models.py:1002 +msgid "True if this is a bank check, else False" +msgstr "Vrai si c'est un chèque, sinon Faux." + +#: counter/models.py:1006 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1010 +#: counter/models.py:1021 msgid "banner" msgstr "bannière" -#: counter/models.py:1012 +#: counter/models.py:1023 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1014 +#: counter/models.py:1025 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1016 +#: counter/models.py:1027 msgid "secret" msgstr "secret" -#: counter/models.py:1072 +#: counter/models.py:1085 msgid "uid" msgstr "uid" -#: counter/models.py:1077 +#: counter/models.py:1090 msgid "student cards" msgstr "cartes étudiante" #: counter/templates/counter/activity.jinja:5 -#: counter/templates/counter/activity.jinja:9 +#: counter/templates/counter/activity.jinja:13 #, python-format msgid "%(counter_name)s activity" msgstr "Activité sur %(counter_name)s" -#: counter/templates/counter/activity.jinja:11 +#: counter/templates/counter/activity.jinja:15 msgid "Barmen list" msgstr "Barmans" @@ -3990,15 +4002,15 @@ msgstr "Barmans" msgid "There is currently no barman connected." msgstr "Il n'y a actuellement aucun barman connecté." -#: counter/templates/counter/activity.jinja:19 +#: counter/templates/counter/activity.jinja:28 msgid "Legend" msgstr "Légende" -#: counter/templates/counter/activity.jinja:20 +#: counter/templates/counter/activity.jinja:32 msgid "counter is open, there's at least one barman connected" msgstr "Le comptoir est ouvert, et il y a au moins un barman connecté" -#: counter/templates/counter/activity.jinja:22 +#: counter/templates/counter/activity.jinja:36 #, python-format msgid "" "counter is open but not active, the last sale was done at least %(minutes)s " @@ -4007,7 +4019,7 @@ msgstr "" "Le comptoir est ouvert, mais inactif. La dernière vente a eu lieu il y a " "%(minutes)s minutes." -#: counter/templates/counter/activity.jinja:24 +#: counter/templates/counter/activity.jinja:40 msgid "counter is not open : no one is connected" msgstr "Le comptoir est fermé" @@ -4028,7 +4040,7 @@ msgstr "Liste des relevés de caisse" msgid "Theoric sums" msgstr "Sommes théoriques" -#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1085 +#: counter/templates/counter/cash_summary_list.jinja:36 counter/views.py:1079 msgid "Emptied" msgstr "Coffre vidé" @@ -4054,7 +4066,7 @@ msgstr "Ce n'est pas un UID de carte étudiante valide" #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 -#: sas/templates/sas/picture.jinja:82 +#: sas/templates/sas/picture.jinja:140 #: subscription/templates/subscription/stats.jinja:19 msgid "Go" msgstr "Valider" @@ -4148,6 +4160,10 @@ msgstr "Merci de vous identifier" msgid "Barman: " msgstr "Barman : " +#: counter/templates/counter/counter_main.jinja:56 +msgid "login" +msgstr "login" + #: counter/templates/counter/eticket_list.jinja:4 #: counter/templates/counter/eticket_list.jinja:10 msgid "Eticket list" @@ -4220,139 +4236,139 @@ msgstr "Vendeur" msgid "%(counter_name)s stats" msgstr "Stats sur %(counter_name)s" -#: counter/templates/counter/stats.jinja:14 +#: counter/templates/counter/stats.jinja:15 #, python-format msgid "Top 100 %(counter_name)s" msgstr "Top 100 %(counter_name)s" -#: counter/templates/counter/stats.jinja:20 -#: counter/templates/counter/stats.jinja:44 -#: counter/templates/counter/stats.jinja:66 +#: counter/templates/counter/stats.jinja:22 +#: counter/templates/counter/stats.jinja:48 +#: counter/templates/counter/stats.jinja:70 msgid "Promo" msgstr "Promo" -#: counter/templates/counter/stats.jinja:22 +#: counter/templates/counter/stats.jinja:24 msgid "Percentage" msgstr "Pourcentage" -#: counter/templates/counter/stats.jinja:38 +#: counter/templates/counter/stats.jinja:41 #, python-format msgid "Top 100 barman %(counter_name)s" msgstr "Top 100 barman %(counter_name)s" -#: counter/templates/counter/stats.jinja:45 -#: counter/templates/counter/stats.jinja:67 +#: counter/templates/counter/stats.jinja:49 +#: counter/templates/counter/stats.jinja:71 msgid "Time" msgstr "Temps" -#: counter/templates/counter/stats.jinja:60 +#: counter/templates/counter/stats.jinja:64 #, python-format msgid "Top 100 barman %(counter_name)s (all semesters)" msgstr "Top 100 barman %(counter_name)s (tous les semestres)" -#: counter/views.py:177 +#: counter/views.py:170 msgid "Cash summary" msgstr "Relevé de caisse" -#: counter/views.py:191 +#: counter/views.py:186 msgid "Last operations" msgstr "Dernières opérations" -#: counter/views.py:206 +#: counter/views.py:203 msgid "Take items from stock" msgstr "Prendre des éléments du stock" -#: counter/views.py:259 +#: counter/views.py:256 msgid "Bad credentials" msgstr "Mauvais identifiants" -#: counter/views.py:261 +#: counter/views.py:258 msgid "User is not barman" msgstr "L'utilisateur n'est pas barman." -#: counter/views.py:266 +#: counter/views.py:263 msgid "Bad location, someone is already logged in somewhere else" msgstr "Mauvais comptoir, quelqu'un est déjà connecté ailleurs" -#: counter/views.py:310 +#: counter/views.py:307 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views.py:313 +#: counter/views.py:310 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views.py:316 +#: counter/views.py:313 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views.py:619 +#: counter/views.py:613 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:771 +#: counter/views.py:765 msgid "Counter administration" msgstr "Administration des comptoirs" -#: counter/views.py:773 +#: counter/views.py:767 msgid "Stocks" msgstr "Stocks" -#: counter/views.py:792 +#: counter/views.py:786 msgid "Product types" msgstr "Types de produit" -#: counter/views.py:1042 +#: counter/views.py:1036 msgid "10 cents" msgstr "10 centimes" -#: counter/views.py:1043 +#: counter/views.py:1037 msgid "20 cents" msgstr "20 centimes" -#: counter/views.py:1044 +#: counter/views.py:1038 msgid "50 cents" msgstr "50 centimes" -#: counter/views.py:1045 +#: counter/views.py:1039 msgid "1 euro" msgstr "1 €" -#: counter/views.py:1046 +#: counter/views.py:1040 msgid "2 euros" msgstr "2 €" -#: counter/views.py:1047 +#: counter/views.py:1041 msgid "5 euros" msgstr "5 €" -#: counter/views.py:1048 +#: counter/views.py:1042 msgid "10 euros" msgstr "10 €" -#: counter/views.py:1049 +#: counter/views.py:1043 msgid "20 euros" msgstr "20 €" -#: counter/views.py:1050 +#: counter/views.py:1044 msgid "50 euros" msgstr "50 €" -#: counter/views.py:1052 +#: counter/views.py:1046 msgid "100 euros" msgstr "100 €" -#: counter/views.py:1055 counter/views.py:1061 counter/views.py:1067 -#: counter/views.py:1073 counter/views.py:1079 +#: counter/views.py:1049 counter/views.py:1055 counter/views.py:1061 +#: counter/views.py:1067 counter/views.py:1073 msgid "Check amount" msgstr "Montant du chèque" -#: counter/views.py:1058 counter/views.py:1064 counter/views.py:1070 -#: counter/views.py:1076 counter/views.py:1082 +#: counter/views.py:1052 counter/views.py:1058 counter/views.py:1064 +#: counter/views.py:1070 counter/views.py:1076 msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views.py:1637 +#: counter/views.py:1632 msgid "people(s)" msgstr "personne(s)" @@ -4387,27 +4403,27 @@ msgstr "%(name)s : ce produit n'existe pas ou n'est peut-être plus disponible." msgid "You cannot buy %(nbr)d %(name)s." msgstr "Vous ne pouvez pas acheter %(nbr)d %(name)s." -#: eboutic/models.py:237 +#: eboutic/models.py:228 msgid "validated" msgstr "validé" -#: eboutic/models.py:247 +#: eboutic/models.py:238 msgid "Invoice already validated" msgstr "Facture déjà validée" -#: eboutic/models.py:286 +#: eboutic/models.py:277 msgid "product id" msgstr "ID du produit" -#: eboutic/models.py:287 +#: eboutic/models.py:278 msgid "product name" msgstr "nom du produit" -#: eboutic/models.py:288 +#: eboutic/models.py:279 msgid "product type id" msgstr "id du type du produit" -#: eboutic/models.py:305 +#: eboutic/models.py:296 msgid "basket" msgstr "panier" @@ -4508,27 +4524,27 @@ msgstr "début des candidatures" msgid "end candidature" msgstr "fin des candidatures" -#: election/models.py:38 election/views.py:161 +#: election/models.py:38 election/views.py:156 msgid "vote groups" msgstr "groupe de vote" -#: election/models.py:45 election/views.py:168 +#: election/models.py:45 election/views.py:163 msgid "candidature groups" msgstr "groupe de candidature" -#: election/models.py:116 election/models.py:163 +#: election/models.py:115 election/models.py:162 msgid "election" msgstr "élection" -#: election/models.py:121 +#: election/models.py:120 msgid "max choice" msgstr "nombre de choix maxi" -#: election/models.py:201 +#: election/models.py:200 msgid "election list" msgstr "liste électorale" -#: election/models.py:226 +#: election/models.py:225 msgid "candidature" msgstr "candidature" @@ -4576,7 +4592,7 @@ msgstr "Vous avez déjà soumis votre vote." msgid "You have voted in this election." msgstr "Vous avez déjà voté pour cette élection." -#: election/templates/election/election_detail.jinja:49 election/views.py:94 +#: election/templates/election/election_detail.jinja:49 election/views.py:89 msgid "Blank vote" msgstr "Vote blanc" @@ -4648,23 +4664,23 @@ msgstr "au" msgid "Polls open from" msgstr "Votes ouverts du" -#: election/views.py:45 +#: election/views.py:40 msgid "You have selected too much candidates." msgstr "Vous avez sélectionné trop de candidats." -#: election/views.py:61 +#: election/views.py:56 msgid "User to candidate" msgstr "Utilisateur se présentant" -#: election/views.py:119 +#: election/views.py:114 msgid "This role already exists for this election" msgstr "Ce rôle existe déjà pour cette élection" -#: election/views.py:174 +#: election/views.py:169 msgid "Start candidature" msgstr "Début des candidatures" -#: election/views.py:176 +#: election/views.py:171 msgid "End candidature" msgstr "Fin des candidatures" @@ -4680,7 +4696,7 @@ msgstr "club propriétaire" msgid "number to choose a specific forum ordering" msgstr "numéro spécifiant l'ordre d'affichage" -#: forum/models.py:93 forum/models.py:247 +#: forum/models.py:93 forum/models.py:250 msgid "the last message" msgstr "le dernier message" @@ -4688,47 +4704,47 @@ msgstr "le dernier message" msgid "number of topics" msgstr "nombre de sujets" -#: forum/models.py:184 +#: forum/models.py:187 msgid "You can not make loops in forums" msgstr "Vous ne pouvez pas faire de boucles dans les forums" -#: forum/models.py:242 +#: forum/models.py:245 msgid "subscribed users" msgstr "utilisateurs abonnés" -#: forum/models.py:252 +#: forum/models.py:255 msgid "number of messages" msgstr "nombre de messages" -#: forum/models.py:310 +#: forum/models.py:313 msgid "message" msgstr "message" -#: forum/models.py:313 +#: forum/models.py:316 msgid "readers" msgstr "lecteurs" -#: forum/models.py:315 +#: forum/models.py:318 msgid "is deleted" msgstr "est supprimé" -#: forum/models.py:395 +#: forum/models.py:401 msgid "Message edited by" msgstr "Message édité par" -#: forum/models.py:396 +#: forum/models.py:402 msgid "Message deleted by" msgstr "Message supprimé par" -#: forum/models.py:397 +#: forum/models.py:403 msgid "Message undeleted by" msgstr "Message restauré par" -#: forum/models.py:409 +#: forum/models.py:415 msgid "action" msgstr "action" -#: forum/models.py:428 +#: forum/models.py:434 msgid "last read date" msgstr "dernière date de lecture" @@ -4739,25 +4755,25 @@ msgstr "dernière date de lecture" msgid "Favorite topics" msgstr "Topics favoris" -#: forum/templates/forum/forum.jinja:14 forum/templates/forum/main.jinja:22 +#: forum/templates/forum/forum.jinja:18 forum/templates/forum/main.jinja:25 msgid "New forum" msgstr "Nouveau forum" -#: forum/templates/forum/forum.jinja:17 forum/templates/forum/reply.jinja:8 +#: forum/templates/forum/forum.jinja:21 forum/templates/forum/reply.jinja:8 #: forum/templates/forum/reply.jinja:28 msgid "New topic" msgstr "Nouveau sujet" -#: forum/templates/forum/forum.jinja:28 forum/templates/forum/main.jinja:31 +#: forum/templates/forum/forum.jinja:32 forum/templates/forum/main.jinja:34 msgid "Topics" msgstr "Sujets" -#: forum/templates/forum/forum.jinja:31 forum/templates/forum/forum.jinja:53 -#: forum/templates/forum/main.jinja:34 +#: forum/templates/forum/forum.jinja:35 forum/templates/forum/forum.jinja:57 +#: forum/templates/forum/main.jinja:37 msgid "Last message" msgstr "Dernier message" -#: forum/templates/forum/forum.jinja:50 +#: forum/templates/forum/forum.jinja:54 msgid "Messages" msgstr "Messages" @@ -4812,51 +4828,59 @@ msgstr "Enlever des favoris" msgid "Mark as favorite" msgstr "Ajouter aux favoris" -#: forum/views.py:190 +#: forum/views.py:189 msgid "Apply rights and club owner recursively" msgstr "Appliquer les droits et le club propriétaire récursivement" -#: forum/views.py:410 +#: forum/views.py:409 #, python-format msgid "%(author)s said" msgstr "Citation de %(author)s" -#: galaxy/models.py:51 +#: galaxy/models.py:57 msgid "star owner" msgstr "propriétaire de l'étoile" -#: galaxy/models.py:56 +#: galaxy/models.py:62 msgid "star mass" msgstr "masse de l'étoile" -#: galaxy/models.py:73 +#: galaxy/models.py:67 +msgid "the galaxy this star belongs to" +msgstr "la galaxie à laquelle cette étoile appartient" + +#: galaxy/models.py:103 msgid "galaxy star 1" msgstr "étoile 1" -#: galaxy/models.py:79 +#: galaxy/models.py:109 msgid "galaxy star 2" msgstr "étoile 2" -#: galaxy/models.py:84 +#: galaxy/models.py:114 msgid "distance" msgstr "distance" -#: galaxy/models.py:86 +#: galaxy/models.py:116 msgid "Distance separating star1 and star2" msgstr "Distance séparant étoile 1 et étoile 2" -#: galaxy/models.py:89 +#: galaxy/models.py:119 msgid "family score" msgstr "score de famille" -#: galaxy/models.py:93 +#: galaxy/models.py:123 msgid "pictures score" msgstr "score de photos" -#: galaxy/models.py:97 +#: galaxy/models.py:127 msgid "clubs score" msgstr "score de club" +#: galaxy/models.py:179 +msgid "The galaxy current state" +msgstr "L'état actuel de la galaxie" + #: galaxy/templates/galaxy/user.jinja:4 #, python-format msgid "%(user_name)s's Galaxy" @@ -4866,31 +4890,31 @@ msgstr "Galaxie de %(user_name)s" msgid "This citizen has not yet joined the galaxy" msgstr "Ce citoyen n'a pas encore rejoint la galaxie" -#: launderette/models.py:97 launderette/models.py:135 +#: launderette/models.py:91 launderette/models.py:131 msgid "launderette" msgstr "laverie" -#: launderette/models.py:103 +#: launderette/models.py:97 msgid "is working" msgstr "fonctionne" -#: launderette/models.py:106 +#: launderette/models.py:100 msgid "Machine" msgstr "Machine" -#: launderette/models.py:141 +#: launderette/models.py:137 msgid "borrow date" msgstr "date d'emprunt" -#: launderette/models.py:152 +#: launderette/models.py:148 msgid "Token" msgstr "Jeton" -#: launderette/models.py:158 +#: launderette/models.py:154 msgid "Token name can not be blank" msgstr "Le nom du jeton ne peut pas être vide" -#: launderette/models.py:201 +#: launderette/models.py:199 msgid "machine" msgstr "machine" @@ -4919,12 +4943,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:622 +#: sith/settings.py:626 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:622 +#: sith/settings.py:626 msgid "Drying" msgstr "Séchage" @@ -4949,25 +4973,25 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:241 +#: launderette/views.py:232 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces" -#: launderette/views.py:261 launderette/views.py:283 +#: launderette/views.py:252 launderette/views.py:274 #, python-format msgid "Token %(token_name)s does not exists" msgstr "Le jeton %(token_name)s n'existe pas" -#: launderette/views.py:272 +#: launderette/views.py:263 #, python-format msgid "Token %(token_name)s already exists" msgstr "Un jeton %(token_name)s existe déjà" -#: launderette/views.py:339 +#: launderette/views.py:330 msgid "User has booked no slot" msgstr "L'utilisateur n'a pas réservé de créneau" -#: launderette/views.py:451 +#: launderette/views.py:442 msgid "Token not found" msgstr "Jeton non trouvé" @@ -4992,27 +5016,27 @@ msgstr "Recherche inversée" msgid "Quick search" msgstr "Recherche rapide" -#: matmat/views.py:72 +#: matmat/views.py:71 msgid "Last/First name or nickname" msgstr "Nom de famille, prénom ou surnom" -#: pedagogy/forms.py:87 +#: pedagogy/forms.py:84 msgid "Do not vote" msgstr "Ne pas voter" -#: pedagogy/forms.py:136 +#: pedagogy/forms.py:133 msgid "This user has already commented on this UV" msgstr "Cet utilisateur a déjà commenté cette UV" -#: pedagogy/forms.py:172 +#: pedagogy/forms.py:169 msgid "Accepted reports" msgstr "Signalements acceptés" -#: pedagogy/forms.py:179 +#: pedagogy/forms.py:176 msgid "Denied reports" msgstr "Signalements refusés" -#: pedagogy/models.py:53 +#: pedagogy/models.py:52 msgid "" "The code of an UV must only contains uppercase characters without accent and " "numbers" @@ -5020,103 +5044,103 @@ msgstr "" "Le code d'une UV doit seulement contenir des caractères majuscule sans " "accents et nombres" -#: pedagogy/models.py:67 +#: pedagogy/models.py:66 msgid "credit type" msgstr "type de crédit" -#: pedagogy/models.py:72 pedagogy/models.py:102 +#: pedagogy/models.py:71 pedagogy/models.py:101 msgid "uv manager" msgstr "gestionnaire d'uv" -#: pedagogy/models.py:80 +#: pedagogy/models.py:79 msgid "language" msgstr "langue" -#: pedagogy/models.py:86 +#: pedagogy/models.py:85 msgid "credits" msgstr "crédits" -#: pedagogy/models.py:94 +#: pedagogy/models.py:93 msgid "departmenmt" msgstr "département" -#: pedagogy/models.py:103 +#: pedagogy/models.py:102 msgid "objectives" msgstr "objectifs" -#: pedagogy/models.py:104 +#: pedagogy/models.py:103 msgid "program" msgstr "programme" -#: pedagogy/models.py:105 +#: pedagogy/models.py:104 msgid "skills" msgstr "compétences" -#: pedagogy/models.py:106 +#: pedagogy/models.py:105 msgid "key concepts" msgstr "concepts clefs" -#: pedagogy/models.py:111 +#: pedagogy/models.py:110 msgid "hours CM" msgstr "heures CM" -#: pedagogy/models.py:118 +#: pedagogy/models.py:117 msgid "hours TD" msgstr "heures TD" -#: pedagogy/models.py:125 +#: pedagogy/models.py:124 msgid "hours TP" msgstr "heures TP" -#: pedagogy/models.py:132 +#: pedagogy/models.py:131 msgid "hours THE" msgstr "heures THE" -#: pedagogy/models.py:139 +#: pedagogy/models.py:138 msgid "hours TE" msgstr "heures TE" -#: pedagogy/models.py:217 pedagogy/models.py:291 +#: pedagogy/models.py:216 pedagogy/models.py:290 msgid "uv" msgstr "UE" -#: pedagogy/models.py:221 +#: pedagogy/models.py:220 msgid "global grade" msgstr "note globale" -#: pedagogy/models.py:228 +#: pedagogy/models.py:227 msgid "utility grade" msgstr "note d'utilité" -#: pedagogy/models.py:235 +#: pedagogy/models.py:234 msgid "interest grade" msgstr "note d'intérêt" -#: pedagogy/models.py:242 +#: pedagogy/models.py:241 msgid "teaching grade" msgstr "note d'enseignement" -#: pedagogy/models.py:249 +#: pedagogy/models.py:248 msgid "work load grade" msgstr "note de charge de travail" -#: pedagogy/models.py:255 +#: pedagogy/models.py:254 msgid "publish date" msgstr "date de publication" -#: pedagogy/models.py:297 +#: pedagogy/models.py:296 msgid "grade" msgstr "note" -#: pedagogy/models.py:317 +#: pedagogy/models.py:316 msgid "report" msgstr "signaler" -#: pedagogy/models.py:323 +#: pedagogy/models.py:322 msgid "reporter" msgstr "signalant" -#: pedagogy/models.py:326 +#: pedagogy/models.py:325 msgid "reason" msgstr "raison" @@ -5240,7 +5264,7 @@ msgstr "Concepts clefs" msgid "UE manager: " msgstr "Gestionnaire d'UE : " -#: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:453 +#: pedagogy/templates/pedagogy/uv_detail.jinja:86 pedagogy/tests.py:405 msgid "" "You already posted a comment on this UV. If you want to comment again, " "please modify or delete your previous comment." @@ -5253,7 +5277,7 @@ msgid "Leave comment" msgstr "Laisser un commentaire" #: pedagogy/templates/pedagogy/uv_detail.jinja:146 -#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:278 +#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:263 #: trombi/templates/trombi/export.jinja:70 msgid "Comments" msgstr "Commentaires" @@ -5328,42 +5352,40 @@ msgstr "Utilisateur qui sera supprimé" msgid "User to be selected" msgstr "Utilisateur à sélectionner" -#: sas/models.py:252 +#: sas/models.py:248 msgid "picture" msgstr "photo" -#: sas/templates/sas/album.jinja:5 sas/templates/sas/main.jinja:4 -#: sas/templates/sas/main.jinja:32 sas/templates/sas/picture.jinja:34 +#: sas/templates/sas/album.jinja:9 sas/templates/sas/main.jinja:8 +#: sas/templates/sas/main.jinja:39 sas/templates/sas/picture.jinja:20 msgid "SAS" msgstr "SAS" -#: sas/templates/sas/album.jinja:102 +#: sas/templates/sas/album.jinja:57 sas/templates/sas/moderation.jinja:10 +msgid "Albums" +msgstr "Albums" + +#: sas/templates/sas/album.jinja:109 msgid "This album does not contain any photos." msgstr "Cet album ne contient aucune photo." -#: sas/templates/sas/album.jinja:53 sas/templates/sas/album.jinja:55 -#: sas/templates/sas/main.jinja:13 sas/templates/sas/main.jinja:15 -#: sas/templates/sas/main.jinja:17 -msgid "preview" -msgstr "miniature" +#: sas/templates/sas/album.jinja:128 +msgid "Upload" +msgstr "Envoyer" + +#: sas/templates/sas/album.jinja:135 +msgid "Template generation time: " +msgstr "Temps de génération du template : " #: sas/templates/sas/main.jinja:42 msgid "You must be logged in to see the SAS." msgstr "Vous devez être connecté pour voir les photos." -#: sas/templates/sas/album.jinja:89 -msgid "Upload" -msgstr "Envoyer" - -#: sas/templates/sas/album.jinja:91 -msgid "Template generation time: " -msgstr "Temps de génération du template : " - -#: sas/templates/sas/main.jinja:34 +#: sas/templates/sas/main.jinja:45 msgid "Latest albums" msgstr "Derniers albums" -#: sas/templates/sas/main.jinja:41 +#: sas/templates/sas/main.jinja:60 sas/templates/sas/main.jinja:79 msgid "All categories" msgstr "Toutes les catégories" @@ -5371,455 +5393,437 @@ msgstr "Toutes les catégories" msgid "SAS moderation" msgstr "Modération du SAS" -#: sas/templates/sas/moderation.jinja:10 -msgid "Albums" -msgstr "Albums" - -#: sas/templates/sas/picture.jinja:68 -msgid "People" -msgstr "Personne(s)" - -#: sas/templates/sas/picture.jinja:97 -msgid "HD version" -msgstr "Version HD" - -#: sas/templates/sas/picture.jinja:101 -msgid "Rotate left" -msgstr "Tourner vers la gauche" - -#: sas/templates/sas/picture.jinja:102 -msgid "Rotate right" -msgstr "Tourner vers la droite" - -#: sas/templates/sas/picture.jinja:103 -msgid "Ask for removal" -msgstr "Demander le retrait" - -#: sas/templates/sas/picture.jinja:119 +#: sas/templates/sas/picture.jinja:54 msgid "Asked for removal" msgstr "Retrait demandé" -#: sas/views.py:49 +#: sas/templates/sas/picture.jinja:103 +msgid "HD version" +msgstr "Version HD" + +#: sas/templates/sas/picture.jinja:105 +msgid "Ask for removal" +msgstr "Demander le retrait" + +#: sas/templates/sas/picture.jinja:136 +msgid "People" +msgstr "Personne(s)" + +#: sas/views.py:39 msgid "Add a new album" msgstr "Ajouter un nouvel album" -#: sas/views.py:53 +#: sas/views.py:42 msgid "Upload images" msgstr "Envoyer les images" -#: sas/views.py:71 +#: sas/views.py:60 #, python-format msgid "Error creating album %(album)s: %(msg)s" msgstr "Erreur de création de l'album %(album)s : %(msg)s" -#: sas/views.py:106 trombi/templates/trombi/detail.jinja:15 +#: sas/views.py:95 trombi/templates/trombi/detail.jinja:15 msgid "Add user" msgstr "Ajouter une personne" -#: sith/settings.py:244 sith/settings.py:447 +#: sith/settings.py:246 sith/settings.py:452 msgid "English" msgstr "Anglais" -#: sith/settings.py:244 sith/settings.py:446 +#: sith/settings.py:246 sith/settings.py:451 msgid "French" msgstr "Français" -#: sith/settings.py:366 +#: sith/settings.py:371 msgid "TC" msgstr "TC" -#: sith/settings.py:367 +#: sith/settings.py:372 msgid "IMSI" msgstr "IMSI" -#: sith/settings.py:368 +#: sith/settings.py:373 msgid "IMAP" msgstr "IMAP" -#: sith/settings.py:369 +#: sith/settings.py:374 msgid "INFO" msgstr "INFO" -#: sith/settings.py:370 +#: sith/settings.py:375 msgid "GI" msgstr "GI" -#: sith/settings.py:371 sith/settings.py:457 +#: sith/settings.py:376 sith/settings.py:462 msgid "E" msgstr "E" -#: sith/settings.py:372 +#: sith/settings.py:377 msgid "EE" msgstr "EE" -#: sith/settings.py:373 +#: sith/settings.py:378 msgid "GESC" msgstr "GESC" -#: sith/settings.py:374 +#: sith/settings.py:379 msgid "GMC" msgstr "GMC" -#: sith/settings.py:375 +#: sith/settings.py:380 msgid "MC" msgstr "MC" -#: sith/settings.py:376 +#: sith/settings.py:381 msgid "EDIM" msgstr "EDIM" -#: sith/settings.py:377 +#: sith/settings.py:382 msgid "Humanities" msgstr "Humanités" -#: sith/settings.py:378 +#: sith/settings.py:383 msgid "N/A" msgstr "N/A" -#: sith/settings.py:382 sith/settings.py:389 sith/settings.py:408 +#: sith/settings.py:387 sith/settings.py:394 sith/settings.py:413 msgid "Check" msgstr "Chèque" -#: sith/settings.py:383 sith/settings.py:391 sith/settings.py:409 +#: sith/settings.py:388 sith/settings.py:396 sith/settings.py:414 msgid "Cash" msgstr "Espèces" -#: sith/settings.py:384 +#: sith/settings.py:389 msgid "Transfert" msgstr "Virement" -#: sith/settings.py:397 +#: sith/settings.py:402 msgid "Belfort" msgstr "Belfort" -#: sith/settings.py:398 +#: sith/settings.py:403 msgid "Sevenans" msgstr "Sevenans" -#: sith/settings.py:399 +#: sith/settings.py:404 msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:427 +#: sith/settings.py:432 msgid "Free" msgstr "Libre" -#: sith/settings.py:428 +#: sith/settings.py:433 msgid "CS" msgstr "CS" -#: sith/settings.py:429 +#: sith/settings.py:434 msgid "TM" msgstr "TM" -#: sith/settings.py:430 +#: sith/settings.py:435 msgid "OM" msgstr "OM" -#: sith/settings.py:431 +#: sith/settings.py:436 msgid "QC" msgstr "QC" -#: sith/settings.py:432 +#: sith/settings.py:437 msgid "EC" msgstr "EC" -#: sith/settings.py:433 +#: sith/settings.py:438 msgid "RN" msgstr "RN" -#: sith/settings.py:434 +#: sith/settings.py:439 msgid "ST" msgstr "ST" -#: sith/settings.py:435 +#: sith/settings.py:440 msgid "EXT" msgstr "EXT" -#: sith/settings.py:440 +#: sith/settings.py:445 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:441 +#: sith/settings.py:446 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:442 +#: sith/settings.py:447 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:448 +#: sith/settings.py:453 msgid "German" msgstr "Allemand" -#: sith/settings.py:449 +#: sith/settings.py:454 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:453 +#: sith/settings.py:458 msgid "A" msgstr "A" -#: sith/settings.py:454 +#: sith/settings.py:459 msgid "B" msgstr "B" -#: sith/settings.py:455 +#: sith/settings.py:460 msgid "C" msgstr "C" -#: sith/settings.py:456 +#: sith/settings.py:461 msgid "D" msgstr "D" -#: sith/settings.py:458 +#: sith/settings.py:463 msgid "FX" msgstr "FX" -#: sith/settings.py:459 +#: sith/settings.py:464 msgid "F" msgstr "F" -#: sith/settings.py:460 +#: sith/settings.py:465 msgid "Abs" msgstr "Abs" -#: sith/settings.py:464 +#: sith/settings.py:469 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:465 +#: sith/settings.py:470 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:502 +#: sith/settings.py:507 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:503 +#: sith/settings.py:508 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:505 +#: sith/settings.py:510 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:509 +#: sith/settings.py:514 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:510 +#: sith/settings.py:515 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:511 +#: sith/settings.py:516 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:512 +#: sith/settings.py:517 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:513 +#: sith/settings.py:518 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:514 +#: sith/settings.py:519 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:515 +#: sith/settings.py:520 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:516 +#: sith/settings.py:521 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:518 +#: sith/settings.py:523 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:522 +#: sith/settings.py:527 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:523 +#: sith/settings.py:528 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:524 +#: sith/settings.py:529 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:526 +#: sith/settings.py:531 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:530 +#: sith/settings.py:535 msgid "One day" msgstr "Un jour" -#: sith/settings.py:531 +#: sith/settings.py:536 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:534 +#: sith/settings.py:539 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:539 +#: sith/settings.py:544 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:544 +#: sith/settings.py:549 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:549 +#: sith/settings.py:554 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:554 +#: sith/settings.py:559 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:560 +#: sith/settings.py:565 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:582 +#: sith/settings.py:585 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:583 +#: sith/settings.py:586 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:584 +#: sith/settings.py:587 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:585 +#: sith/settings.py:588 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:586 +#: sith/settings.py:589 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:587 +#: sith/settings.py:590 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:588 +#: sith/settings.py:591 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:589 +#: sith/settings.py:592 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:590 +#: sith/settings.py:593 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:626 +#: sith/settings.py:630 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:627 +#: sith/settings.py:631 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:630 +#: sith/settings.py:634 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:632 +#: sith/settings.py:636 #, 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:633 +#: sith/settings.py:637 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:634 +#: sith/settings.py:638 #, 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:635 +#: sith/settings.py:639 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:636 +#: sith/settings.py:640 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:637 +#: sith/settings.py:641 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:638 +#: sith/settings.py:642 msgid "You have a notification" msgstr "Vous avez une notification" -#: core/templates/core/base.jinja -msgid "You do not have any unread notification" -msgstr "Vous n'avez aucune notification non lue" - -#: sith/settings.py:624 -#: sith/settings.py:648 -#: sith/settings.py:650 +#: sith/settings.py:654 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:651 +#: sith/settings.py:655 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:652 +#: sith/settings.py:656 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:653 +#: sith/settings.py:657 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:654 +#: sith/settings.py:658 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:662 +#: sith/settings.py:666 msgid "AE tee-shirt" msgstr "Tee-shirt AE" -#: stock/models.py:65 +#: stock/models.py:64 msgid "unit quantity" msgstr "quantité unitaire" -#: stock/models.py:65 +#: stock/models.py:64 msgid "number of element in one box" msgstr "nombre d'éléments par boîte" -#: stock/models.py:68 +#: stock/models.py:67 msgid "effective quantity" msgstr "quantité effective" -#: stock/models.py:68 +#: stock/models.py:67 msgid "number of box" msgstr "nombre de boîtes" -#: stock/models.py:71 +#: stock/models.py:70 msgid "minimal quantity" msgstr "quantité minimale" -#: stock/models.py:74 +#: stock/models.py:73 msgid "" "if the effective quantity is less than the minimal, item is added to the " "shopping list" @@ -5827,27 +5831,27 @@ msgstr "" "si la quantité effective est en dessous du minima, l'item est ajouté àla " "liste de courses" -#: stock/models.py:106 +#: stock/models.py:105 msgid "todo" msgstr "à faire" -#: stock/models.py:127 +#: stock/models.py:126 msgid "shopping lists" msgstr "listes de courses" -#: stock/models.py:143 +#: stock/models.py:142 msgid "quantity to buy" msgstr "quantité à acheter" -#: stock/models.py:145 +#: stock/models.py:144 msgid "quantity to buy during the next shopping session" msgstr "quantité à acheter pendant les prochaines courses" -#: stock/models.py:148 +#: stock/models.py:147 msgid "quantity bought" msgstr "quantité achetée" -#: stock/models.py:150 +#: stock/models.py:149 msgid "quantity bought during the last shopping session" msgstr "quantité achetée pendant les dernières courses" @@ -5968,15 +5972,15 @@ msgstr "Mettre à jour les quantités de %(s)s après les courses" msgid "Update stock quantities" msgstr "Mettre à jour les quantités en stock" -#: stock/views.py:257 +#: stock/views.py:242 msgid "Shopping list name" msgstr "Nom de la liste de courses" -#: stock/views.py:267 +#: stock/views.py:252 msgid " left" msgstr " restant" -#: stock/views.py:273 +#: stock/views.py:258 msgid "" "Add here, items to buy that are not reference as a stock item (example : " "sponge, knife, mugs ...)" @@ -5984,44 +5988,44 @@ msgstr "" "Ajouter ici les éléments non référencé comme élément de stock (example : " "éponge, couteau, mugs ...)" -#: stock/views.py:457 +#: stock/views.py:442 msgid " asked" msgstr " demandé" -#: stock/views.py:549 +#: stock/views.py:534 #, python-format msgid "%(effective_quantity)s left" msgstr "%(effective_quantity)s restant" -#: subscription/models.py:43 +#: subscription/models.py:34 msgid "Bad subscription type" msgstr "Mauvais type de cotisation" -#: subscription/models.py:48 +#: subscription/models.py:39 msgid "Bad payment method" msgstr "Mauvais type de paiement" -#: subscription/models.py:56 +#: subscription/models.py:47 msgid "subscription type" msgstr "type d'inscription" -#: subscription/models.py:62 +#: subscription/models.py:53 msgid "subscription start" msgstr "début de la cotisation" -#: subscription/models.py:63 +#: subscription/models.py:54 msgid "subscription end" msgstr "fin de la cotisation" -#: subscription/models.py:72 +#: subscription/models.py:63 msgid "location" msgstr "lieu" -#: subscription/models.py:92 +#: subscription/models.py:83 msgid "You can not subscribe many time for the same period" msgstr "Vous ne pouvez pas cotiser plusieurs fois pour la même période" -#: subscription/models.py:97 +#: subscription/models.py:88 msgid "Subscription error" msgstr "Erreur de cotisation" @@ -6038,11 +6042,11 @@ msgid "Eboutic is reserved to specific users. In doubt, don't use it." msgstr "" "Eboutic est réservé à des cas particuliers. Dans le doute, ne l'utilisez pas." -#: subscription/views.py:104 +#: subscription/views.py:94 msgid "A user with that email address already exists" msgstr "Un utilisateur avec cette adresse email existe déjà" -#: subscription/views.py:127 +#: subscription/views.py:117 msgid "You must either choose an existing user or create a new one properly" msgstr "" "Vous devez soit choisir un utilisateur existant, soit en créer un proprement" @@ -6262,27 +6266,27 @@ msgstr "" msgid "Edit comment" msgstr "Éditer le commentaire" -#: trombi/views.py:70 +#: trombi/views.py:69 msgid "My profile" msgstr "Mon profil" -#: trombi/views.py:77 +#: trombi/views.py:76 msgid "My pictures" msgstr "Mes photos" -#: trombi/views.py:89 +#: trombi/views.py:88 msgid "Admin tools" msgstr "Admin Trombi" -#: trombi/views.py:222 +#: trombi/views.py:220 msgid "Explain why you rejected the comment" msgstr "Expliquez pourquoi vous refusez le commentaire" -#: trombi/views.py:255 +#: trombi/views.py:253 msgid "Rejected comment" msgstr "Commentaire rejeté" -#: trombi/views.py:257 +#: trombi/views.py:255 #, python-format msgid "" "Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the " @@ -6299,16 +6303,16 @@ msgstr "" "\n" "%(content)s" -#: trombi/views.py:289 +#: trombi/views.py:287 #, python-format msgid "%(name)s (deadline: %(date)s)" msgstr "%(name)s (date limite: %(date)s)" -#: trombi/views.py:299 +#: trombi/views.py:297 msgid "Select trombi" msgstr "Choisir un trombi" -#: trombi/views.py:301 +#: trombi/views.py:299 msgid "" "This allows you to subscribe to a Trombi. Be aware that you can subscribe " "only once, so don't play with that, or you will expose yourself to the " @@ -6318,19 +6322,19 @@ msgstr "" "pouvez vous inscrire qu'à un seul Trombi, donc ne jouez pas avec cet option " "ou vous encourerez la colère des admins!" -#: trombi/views.py:374 +#: trombi/views.py:372 msgid "Personal email (not UTBM)" msgstr "Email personnel (pas UTBM)" -#: trombi/views.py:375 +#: trombi/views.py:373 msgid "Phone" msgstr "Téléphone" -#: trombi/views.py:376 +#: trombi/views.py:374 msgid "Native town" msgstr "Ville d'origine" -#: trombi/views.py:489 +#: trombi/views.py:491 msgid "" "You can not yet write comment, you must wait for the subscription deadline " "to be passed." @@ -6338,11 +6342,47 @@ msgstr "" "Vous ne pouvez pas encore écrire de commentaires, vous devez attendre la fin " "des inscriptions" -#: trombi/views.py:496 +#: trombi/views.py:498 msgid "You can not write comment anymore, the deadline is already passed." msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée." -#: trombi/views.py:509 +#: trombi/views.py:511 #, python-format msgid "Maximum characters: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s" + +#~ msgid "Folder: " +#~ msgstr "Dossier : " + +#~ msgid "File: " +#~ msgstr "Fichier : " + +#~ msgid "Username" +#~ msgstr "Nom d'utilisateur" + +#~ msgid "Password" +#~ msgstr "Mot de passe" + +#~ msgid "Register a user" +#~ msgstr "Enregistrer un utilisateur" + +#~ msgid "Current profile: " +#~ msgstr "Profil actuel : " + +#~ msgid "Take picture" +#~ msgstr "Prendre une photo" + +#~ msgid "Current avatar: " +#~ msgstr "Avatar actuel : " + +#~ msgid "Current scrub: " +#~ msgstr "Blouse actuelle : " + +#~ msgid "preview" +#~ msgstr "miniature" + +#~ msgid "Rotate left" +#~ msgstr "Tourner vers la gauche" + +#~ msgid "Rotate right" +#~ msgstr "Tourner vers la droite" From aa07fa92078169e3405e6279453160d3cd0d2411 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 4 Jul 2024 10:19:24 +0200 Subject: [PATCH 89/95] faster tests --- conftest.py | 2 +- core/management/commands/populate.py | 6 ++- counter/models.py | 7 +-- doc/start/install.rst | 23 +++++++++ eboutic/tests.py | 7 +-- .../commands/generate_galaxy_test_data.py | 49 +++++++------------ galaxy/tests.py | 6 +-- poetry.lock | 10 ++-- pyproject.toml | 1 + rootplace/tests.py | 2 +- sith/settings.py | 11 +++++ 11 files changed, 75 insertions(+), 49 deletions(-) diff --git a/conftest.py b/conftest.py index fb67017d..e209b26d 100644 --- a/conftest.py +++ b/conftest.py @@ -6,7 +6,7 @@ from django.utils.translation import activate @pytest.fixture(scope="session") def django_db_setup(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): - call_command("setup") + call_command("populate") @pytest.fixture(scope="session", autouse=True) diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index a9285653..d42d9987 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -1098,8 +1098,10 @@ Welcome to the wiki page! n = News( title="Repas barman", summary="Enjoy la fin du semestre!", - content="Viens donc t'enjailler avec les autres barmans aux " - "frais du BdF! \o/", + content=( + "Viens donc t'enjailler avec les autres barmans aux " + "frais du BdF! \\o/" + ), type="EVENT", club=bar_club, author=subscriber, diff --git a/counter/models.py b/counter/models.py index 29dfb92d..44818da5 100644 --- a/counter/models.py +++ b/counter/models.py @@ -19,7 +19,8 @@ import base64 import os import random import string -from datetime import date, datetime, timedelta, timezone +from datetime import date, datetime, timedelta +from datetime import timezone as tz from typing import Tuple from dict2xml import dict2xml @@ -549,7 +550,7 @@ class Counter(models.Model): if since is None: since = get_start_of_semester() if isinstance(since, date): - since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc) + since = datetime(since.year, since.month, since.day, tzinfo=tz.utc) return ( self.sellings.filter(date__gte=since) .annotate( @@ -583,7 +584,7 @@ class Counter(models.Model): if since is None: since = get_start_of_semester() if isinstance(since, date): - since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc) + since = datetime(since.year, since.month, since.day, tzinfo=tz.utc) total = self.sellings.filter(date__gte=since).aggregate( total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField()) )["total"] diff --git a/doc/start/install.rst b/doc/start/install.rst index fdb639ce..257fcf46 100644 --- a/doc/start/install.rst +++ b/doc/start/install.rst @@ -207,6 +207,29 @@ Pour lancer les tests il suffit d'utiliser la commande intégrée à django. # Lancer une méthode en particulier de cette même classe pytest core.tests.UserRegistrationTest.test_register_user_form_ok +.. note:: + + Certains tests sont un peu longs à tourner. + Pour ne faire tourner que les tests les plus rapides, + vous pouvez exécutez pytest ainsi : + + .. code-block:: bash + + pytest -m "not slow" + + # vous pouvez toujours faire comme au-dessus + pytest core -m "not slow" + + A l'inverse, vous pouvez ne faire tourner que les tests + lents en remplaçant `-m "not slow"` par `-m slow`. + + De cette manière, votre processus de développement + devrait être un peu plus fluide. + Cependant, n'oubliez pas de bien faire tourner + tous les tests avant de push un commit. + + + Vérifier les dépendances Javascript ----------------------------------- diff --git a/eboutic/tests.py b/eboutic/tests.py index b5e82e8e..b1ea9873 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -220,10 +220,11 @@ class EbouticTest(TestCase): self.client.get(reverse("eboutic:command")) response = self.client.get(et_answer_url) assert response.status_code == 500 - assert ( - "Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'" - in response.content.decode("utf-8"), + msg = ( + "Basket processing failed with error: " + "SuspiciousOperation('Basket total and amount do not match'" ) + assert msg in response.content.decode("utf-8") def test_buy_simple_product_with_credit_card(self): self.client.force_login(self.subscriber) diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py index 191198f7..a1ff05a1 100644 --- a/galaxy/management/commands/generate_galaxy_test_data.py +++ b/galaxy/management/commands/generate_galaxy_test_data.py @@ -147,20 +147,20 @@ class Command(BaseCommand): self.logger.info(f"Creating {u}") self.users.append(u) User.objects.bulk_create(self.users) - self.users = User.objects.filter(username__startswith="galaxy-").all() + self.users = list(User.objects.filter(username__startswith="galaxy-").all()) # now that users are created, create their subscription subs = [] - for i in range(self.NB_USERS): - u = self.users[i] - self.logger.info(f"Registering {u}") + end = Subscription.compute_end(duration=2) + for i, user in enumerate(self.users): + self.logger.info(f"Registering {user}") subs.append( Subscription( - member=u, + member=user, subscription_start=Subscription.compute_start( self.now - timedelta(days=self.NB_USERS - i) ), - subscription_end=Subscription.compute_end(duration=2), + subscription_end=end, ) ) Subscription.objects.bulk_create(subs) @@ -381,28 +381,15 @@ class Command(BaseCommand): u1.godchildren.add(u3) self.logger.info(f"{u1} will be important and close to {u2} and {u3}") pictures_tags = [] - for p in range( # Mix them with other citizen for more chaos - uid - 400, uid - 200 - ): - # users may already be on the pictures - if not self.picts[p].people.filter(user=u1).exists(): - pictures_tags.append( - PeoplePictureRelation(user=u1, picture=self.picts[p]) - ) - if not self.picts[p].people.filter(user=u2).exists(): - pictures_tags.append( - PeoplePictureRelation(user=u2, picture=self.picts[p]) - ) - if not self.picts[p + self.NB_USERS].people.filter(user=u1).exists(): - pictures_tags.append( - PeoplePictureRelation( - user=u1, picture=self.picts[p + self.NB_USERS] - ) - ) - if not self.picts[p + self.NB_USERS].people.filter(user=u2).exists(): - pictures_tags.append( - PeoplePictureRelation( - user=u2, picture=self.picts[p + self.NB_USERS] - ) - ) - PeoplePictureRelation.objects.bulk_create(pictures_tags) + for p in range(uid - 400, uid - 200): + # Mix them with other citizen for more chaos + pictures_tags += [ + PeoplePictureRelation(user=u1, picture=self.picts[p]), + PeoplePictureRelation(user=u2, picture=self.picts[p]), + PeoplePictureRelation(user=u1, picture=self.picts[p + self.NB_USERS]), + PeoplePictureRelation(user=u2, picture=self.picts[p + self.NB_USERS]), + ] + # users may already be on the pictures. + # In this case the conflict will just be ignored + # and nothing will happen for this entry + PeoplePictureRelation.objects.bulk_create(pictures_tags, ignore_conflicts=True) diff --git a/galaxy/tests.py b/galaxy/tests.py index f1a3f092..cfbaf5ca 100644 --- a/galaxy/tests.py +++ b/galaxy/tests.py @@ -25,6 +25,7 @@ import json from pathlib import Path +import pytest from django.core.management import call_command from django.test import TestCase from django.urls import reverse @@ -147,6 +148,7 @@ class GalaxyTestModel(TestCase): galaxy.rule(0) # We want everybody here +@pytest.mark.slow class GalaxyTestView(TestCase): @classmethod def setUpTestData(cls): @@ -196,6 +198,4 @@ class GalaxyTestView(TestCase): # Dump computed state, either for easier debugging, or to copy as new reference if changes are legit (galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state)) - assert ( - state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()), - ) + assert state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()) diff --git a/poetry.lock b/poetry.lock index 06085a2a..318a5623 100644 --- a/poetry.lock +++ b/poetry.lock @@ -573,17 +573,17 @@ test = ["testfixtures"] [[package]] name = "djangorestframework" -version = "3.15.1" +version = "3.15.2" description = "Web APIs for Django, made easy." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"}, - {file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"}, + {file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"}, + {file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"}, ] [package.dependencies] -django = ">=3.0" +django = ">=4.2" [[package]] name = "docutils" diff --git a/pyproject.toml b/pyproject.toml index a2b9fe90..02d916ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,7 @@ select = ["I", "F401"] [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "sith.settings" python_files = ["tests.py", "test_*.py", "*_tests.py"] +markers = ["slow"] [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/rootplace/tests.py b/rootplace/tests.py index 81d0b616..5b9e2af1 100644 --- a/rootplace/tests.py +++ b/rootplace/tests.py @@ -72,7 +72,7 @@ class MergeUserTest(TestCase): assert "B'ian" == self.to_keep.nick_name assert "Jerusalem" == self.to_keep.address assert "Rome" == self.to_keep.parent_address - assert (3, self.to_keep.groups.count()) + assert self.to_keep.groups.count() == 3 groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id) expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id) assert groups == expected diff --git a/sith/settings.py b/sith/settings.py index 0489a8b1..b8d0541d 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -696,6 +696,17 @@ if DEBUG: if TESTING: CAPTCHA_TEST_MODE = True + PASSWORD_HASHERS = [ # not secure, but faster password hasher + "django.contrib.auth.hashers.MD5PasswordHasher", + ] + STORAGES = { # store files in memory rather than using the hard drive + "default": { + "BACKEND": "django.core.files.storage.InMemoryStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, + } if SENTRY_DSN: # Connection to sentry From f1fa8d34bf4bb4e86382b0d009db09a549c60f43 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 4 Jul 2024 14:39:12 +0200 Subject: [PATCH 90/95] fix family relations in generate_galaxy_test_data.py --- .../commands/generate_galaxy_test_data.py | 23 ++++++++++++------- galaxy/ref_galaxy_state.json | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/galaxy/management/commands/generate_galaxy_test_data.py b/galaxy/management/commands/generate_galaxy_test_data.py index a1ff05a1..15cf7f48 100644 --- a/galaxy/management/commands/generate_galaxy_test_data.py +++ b/galaxy/management/commands/generate_galaxy_test_data.py @@ -128,7 +128,7 @@ class Command(BaseCommand): self.clubs.append(Club(unix_name=f"galaxy-club-{i}", name=f"club-{i}")) # We don't need to create corresponding groups here, as the Galaxy doesn't care about them Club.objects.bulk_create(self.clubs) - self.clubs = Club.objects.filter(unix_name__startswith="galaxy-").all() + self.clubs = list(Club.objects.filter(unix_name__startswith="galaxy-").all()) def make_users(self): """ @@ -175,20 +175,22 @@ class Command(BaseCommand): Then it will take 14 other citizen among the previous 200 (godfathers are usually older), and apply another heuristic to determine whether they should have a family link + It will result in approximately 40% of users having at least one godchild + and 70% having at least one godfather. """ if self.users is None: raise RuntimeError( "The `make_users()` method must be called before `make_families()`" ) + godfathers = [] for i in range(200, self.NB_USERS): - godfathers = [] for j in range(i - 200, i, 14): # this will loop 14 times (14² = 196) - if (i / 10) % 10 == (i + j) % 10: + if (i // 10) % 10 == (i + j) % 10: u1 = self.users[i] u2 = self.users[j] + godfathers.append(User.godfathers.through(from_user=u1, to_user=u2)) self.logger.info(f"Making {u2} the godfather of {u1}") - godfathers.append(u2) - u1.godfathers.set(godfathers) + User.godfathers.through.objects.bulk_create(godfathers) def make_club_memberships(self): """ @@ -295,7 +297,7 @@ class Command(BaseCommand): self.picts[i].compressed.name = self.picts[i].name self.picts[i].thumbnail.name = self.picts[i].name Picture.objects.bulk_create(self.picts) - self.picts = Picture.objects.filter(name__startswith="galaxy-").all() + self.picts = list(Picture.objects.filter(name__startswith="galaxy-").all()) def make_pictures_memberships(self): """ @@ -377,8 +379,13 @@ class Command(BaseCommand): u1 = self.users[uid] u2 = self.users[uid - 100] u3 = self.users[uid + 100] - u1.godfathers.add(u2) - u1.godchildren.add(u3) + User.godfathers.through.objects.bulk_create( + [ + User.godfathers.through(from_user=u1, to_user=u2), + User.godfathers.through(from_user=u3, to_user=u2), + ], + ignore_conflicts=True, # in case a relationship has already been created + ) self.logger.info(f"{u1} will be important and close to {u2} and {u3}") pictures_tags = [] for p in range(uid - 400, uid - 200): diff --git a/galaxy/ref_galaxy_state.json b/galaxy/ref_galaxy_state.json index 7c19b796..6f08a6a6 100644 --- a/galaxy/ref_galaxy_state.json +++ b/galaxy/ref_galaxy_state.json @@ -1 +1 @@ -{"nodes": [{"id": 215, "name": "Citizen n\u00b0202", "mass": 5}, {"id": 219, "name": "Citizen n\u00b0206", "mass": 5}, {"id": 221, "name": "Citizen n\u00b0208", "mass": 5}, {"id": 225, "name": "Citizen n\u00b0212", "mass": 5}, {"id": 227, "name": "Citizen n\u00b0214", "mass": 5}, {"id": 228, "name": "Citizen n\u00b0215", "mass": 5}, {"id": 231, "name": "Citizen n\u00b0218", "mass": 5}, {"id": 233, "name": "Citizen n\u00b0220", "mass": 5}, {"id": 243, "name": "Citizen n\u00b0230", "mass": 5}, {"id": 245, "name": "Citizen n\u00b0232", "mass": 5}, {"id": 248, "name": "Citizen n\u00b0235", "mass": 5}, {"id": 249, "name": "Citizen n\u00b0236", "mass": 5}, {"id": 251, "name": "Citizen n\u00b0238", "mass": 5}, {"id": 255, "name": "Citizen n\u00b0242", "mass": 5}, {"id": 257, "name": "Citizen n\u00b0244", "mass": 5}, {"id": 261, "name": "Citizen n\u00b0248", "mass": 5}, {"id": 263, "name": "Citizen n\u00b0250", "mass": 5}, {"id": 273, "name": "Citizen n\u00b0260", "mass": 6}, {"id": 275, "name": "Citizen n\u00b0262", "mass": 5}, {"id": 278, "name": "Citizen n\u00b0265", "mass": 5}, {"id": 279, "name": "Citizen n\u00b0266", "mass": 5}, {"id": 281, "name": "Citizen n\u00b0268", "mass": 5}, {"id": 285, "name": "Citizen n\u00b0272", "mass": 5}, {"id": 287, "name": "Citizen n\u00b0274", "mass": 5}, {"id": 291, "name": "Citizen n\u00b0278", "mass": 5}, {"id": 293, "name": "Citizen n\u00b0280", "mass": 6}, {"id": 303, "name": "Citizen n\u00b0290", "mass": 6}, {"id": 305, "name": "Citizen n\u00b0292", "mass": 5}, {"id": 309, "name": "Citizen n\u00b0296", "mass": 5}, {"id": 311, "name": "Citizen n\u00b0298", "mass": 5}, {"id": 315, "name": "Citizen n\u00b0302", "mass": 5}, {"id": 317, "name": "Citizen n\u00b0304", "mass": 5}, {"id": 321, "name": "Citizen n\u00b0308", "mass": 5}, {"id": 323, "name": "Citizen n\u00b0310", "mass": 6}, {"id": 333, "name": "Citizen n\u00b0320", "mass": 5}, {"id": 335, "name": "Citizen n\u00b0322", "mass": 5}, {"id": 339, "name": "Citizen n\u00b0326", "mass": 5}, {"id": 341, "name": "Citizen n\u00b0328", "mass": 5}, {"id": 345, "name": "Citizen n\u00b0332", "mass": 5}, {"id": 347, "name": "Citizen n\u00b0334", "mass": 5}, {"id": 348, "name": "Citizen n\u00b0335", "mass": 5}, {"id": 351, "name": "Citizen n\u00b0338", "mass": 5}, {"id": 353, "name": "Citizen n\u00b0340", "mass": 6}, {"id": 363, "name": "Citizen n\u00b0350", "mass": 5}, {"id": 365, "name": "Citizen n\u00b0352", "mass": 5}, {"id": 369, "name": "Citizen n\u00b0356", "mass": 5}, {"id": 371, "name": "Citizen n\u00b0358", "mass": 5}, {"id": 375, "name": "Citizen n\u00b0362", "mass": 5}, {"id": 377, "name": "Citizen n\u00b0364", "mass": 5}, {"id": 378, "name": "Citizen n\u00b0365", "mass": 5}, {"id": 381, "name": "Citizen n\u00b0368", "mass": 5}, {"id": 383, "name": "Citizen n\u00b0370", "mass": 5}, {"id": 393, "name": "Citizen n\u00b0380", "mass": 5}, {"id": 395, "name": "Citizen n\u00b0382", "mass": 5}, {"id": 398, "name": "Citizen n\u00b0385", "mass": 5}, {"id": 399, "name": "Citizen n\u00b0386", "mass": 5}, {"id": 401, "name": "Citizen n\u00b0388", "mass": 5}, {"id": 405, "name": "Citizen n\u00b0392", "mass": 5}, {"id": 407, "name": "Citizen n\u00b0394", "mass": 5}, {"id": 411, "name": "Citizen n\u00b0398", "mass": 5}, {"id": 413, "name": "Citizen n\u00b0400", "mass": 10}, {"id": 423, "name": "Citizen n\u00b0410", "mass": 6}, {"id": 425, "name": "Citizen n\u00b0412", "mass": 5}, {"id": 428, "name": "Citizen n\u00b0415", "mass": 5}, {"id": 429, "name": "Citizen n\u00b0416", "mass": 5}, {"id": 431, "name": "Citizen n\u00b0418", "mass": 5}, {"id": 435, "name": "Citizen n\u00b0422", "mass": 5}, {"id": 437, "name": "Citizen n\u00b0424", "mass": 5}, {"id": 441, "name": "Citizen n\u00b0428", "mass": 5}, {"id": 443, "name": "Citizen n\u00b0430", "mass": 5}, {"id": 453, "name": "Citizen n\u00b0440", "mass": 6}, {"id": 455, "name": "Citizen n\u00b0442", "mass": 5}, {"id": 459, "name": "Citizen n\u00b0446", "mass": 5}, {"id": 461, "name": "Citizen n\u00b0448", "mass": 5}, {"id": 465, "name": "Citizen n\u00b0452", "mass": 5}, {"id": 467, "name": "Citizen n\u00b0454", "mass": 5}, {"id": 471, "name": "Citizen n\u00b0458", "mass": 5}, {"id": 473, "name": "Citizen n\u00b0460", "mass": 6}, {"id": 483, "name": "Citizen n\u00b0470", "mass": 5}, {"id": 485, "name": "Citizen n\u00b0472", "mass": 5}, {"id": 489, "name": "Citizen n\u00b0476", "mass": 5}, {"id": 491, "name": "Citizen n\u00b0478", "mass": 5}, {"id": 495, "name": "Citizen n\u00b0482", "mass": 5}, {"id": 497, "name": "Citizen n\u00b0484", "mass": 5}, {"id": 498, "name": "Citizen n\u00b0485", "mass": 5}, {"id": 501, "name": "Citizen n\u00b0488", "mass": 5}, {"id": 503, "name": "Citizen n\u00b0490", "mass": 6}, {"id": 513, "name": "Citizen n\u00b0500", "mass": 10}, {"id": 515, "name": "Citizen n\u00b0502", "mass": 5}, {"id": 519, "name": "Citizen n\u00b0506", "mass": 5}, {"id": 521, "name": "Citizen n\u00b0508", "mass": 5}, {"id": 525, "name": "Citizen n\u00b0512", "mass": 5}, {"id": 527, "name": "Citizen n\u00b0514", "mass": 5}, {"id": 528, "name": "Citizen n\u00b0515", "mass": 5}, {"id": 531, "name": "Citizen n\u00b0518", "mass": 5}, {"id": 533, "name": "Citizen n\u00b0520", "mass": 5}, {"id": 543, "name": "Citizen n\u00b0530", "mass": 5}, {"id": 545, "name": "Citizen n\u00b0532", "mass": 5}, {"id": 548, "name": "Citizen n\u00b0535", "mass": 5}, {"id": 549, "name": "Citizen n\u00b0536", "mass": 5}, {"id": 551, "name": "Citizen n\u00b0538", "mass": 5}, {"id": 555, "name": "Citizen n\u00b0542", "mass": 5}, {"id": 557, "name": "Citizen n\u00b0544", "mass": 5}, {"id": 561, "name": "Citizen n\u00b0548", "mass": 5}, {"id": 563, "name": "Citizen n\u00b0550", "mass": 5}, {"id": 573, "name": "Citizen n\u00b0560", "mass": 6}, {"id": 575, "name": "Citizen n\u00b0562", "mass": 5}, {"id": 578, "name": "Citizen n\u00b0565", "mass": 5}, {"id": 579, "name": "Citizen n\u00b0566", "mass": 5}, {"id": 581, "name": "Citizen n\u00b0568", "mass": 5}, {"id": 585, "name": "Citizen n\u00b0572", "mass": 5}, {"id": 587, "name": "Citizen n\u00b0574", "mass": 5}, {"id": 591, "name": "Citizen n\u00b0578", "mass": 5}, {"id": 593, "name": "Citizen n\u00b0580", "mass": 5}, {"id": 603, "name": "Citizen n\u00b0590", "mass": 6}, {"id": 605, "name": "Citizen n\u00b0592", "mass": 5}, {"id": 609, "name": "Citizen n\u00b0596", "mass": 5}, {"id": 611, "name": "Citizen n\u00b0598", "mass": 5}, {"id": 615, "name": "Citizen n\u00b0602", "mass": 5}, {"id": 617, "name": "Citizen n\u00b0604", "mass": 5}, {"id": 621, "name": "Citizen n\u00b0608", "mass": 5}, {"id": 623, "name": "Citizen n\u00b0610", "mass": 6}, {"id": 633, "name": "Citizen n\u00b0620", "mass": 5}, {"id": 635, "name": "Citizen n\u00b0622", "mass": 5}, {"id": 639, "name": "Citizen n\u00b0626", "mass": 5}, {"id": 641, "name": "Citizen n\u00b0628", "mass": 5}, {"id": 645, "name": "Citizen n\u00b0632", "mass": 5}, {"id": 647, "name": "Citizen n\u00b0634", "mass": 5}, {"id": 648, "name": "Citizen n\u00b0635", "mass": 5}, {"id": 651, "name": "Citizen n\u00b0638", "mass": 5}, {"id": 653, "name": "Citizen n\u00b0640", "mass": 6}, {"id": 663, "name": "Citizen n\u00b0650", "mass": 5}, {"id": 665, "name": "Citizen n\u00b0652", "mass": 5}, {"id": 669, "name": "Citizen n\u00b0656", "mass": 5}, {"id": 671, "name": "Citizen n\u00b0658", "mass": 5}, {"id": 675, "name": "Citizen n\u00b0662", "mass": 5}, {"id": 677, "name": "Citizen n\u00b0664", "mass": 5}, {"id": 678, "name": "Citizen n\u00b0665", "mass": 5}, {"id": 681, "name": "Citizen n\u00b0668", "mass": 5}, {"id": 683, "name": "Citizen n\u00b0670", "mass": 5}, {"id": 693, "name": "Citizen n\u00b0680", "mass": 5}, {"id": 695, "name": "Citizen n\u00b0682", "mass": 5}, {"id": 698, "name": "Citizen n\u00b0685", "mass": 5}, {"id": 699, "name": "Citizen n\u00b0686", "mass": 5}, {"id": 701, "name": "Citizen n\u00b0688", "mass": 5}, {"id": 705, "name": "Citizen n\u00b0692", "mass": 5}, {"id": 707, "name": "Citizen n\u00b0694", "mass": 5}, {"id": 711, "name": "Citizen n\u00b0698", "mass": 5}, {"id": 713, "name": "Citizen n\u00b0700", "mass": 6}, {"id": 723, "name": "Citizen n\u00b0710", "mass": 6}, {"id": 725, "name": "Citizen n\u00b0712", "mass": 5}, {"id": 728, "name": "Citizen n\u00b0715", "mass": 5}, {"id": 729, "name": "Citizen n\u00b0716", "mass": 5}, {"id": 731, "name": "Citizen n\u00b0718", "mass": 5}, {"id": 735, "name": "Citizen n\u00b0722", "mass": 5}, {"id": 737, "name": "Citizen n\u00b0724", "mass": 5}, {"id": 741, "name": "Citizen n\u00b0728", "mass": 5}, {"id": 743, "name": "Citizen n\u00b0730", "mass": 5}, {"id": 753, "name": "Citizen n\u00b0740", "mass": 6}, {"id": 755, "name": "Citizen n\u00b0742", "mass": 5}, {"id": 759, "name": "Citizen n\u00b0746", "mass": 5}, {"id": 761, "name": "Citizen n\u00b0748", "mass": 5}, {"id": 765, "name": "Citizen n\u00b0752", "mass": 5}, {"id": 767, "name": "Citizen n\u00b0754", "mass": 5}, {"id": 771, "name": "Citizen n\u00b0758", "mass": 5}, {"id": 773, "name": "Citizen n\u00b0760", "mass": 6}, {"id": 783, "name": "Citizen n\u00b0770", "mass": 5}, {"id": 785, "name": "Citizen n\u00b0772", "mass": 5}, {"id": 789, "name": "Citizen n\u00b0776", "mass": 5}, {"id": 791, "name": "Citizen n\u00b0778", "mass": 5}, {"id": 795, "name": "Citizen n\u00b0782", "mass": 5}, {"id": 797, "name": "Citizen n\u00b0784", "mass": 5}, {"id": 798, "name": "Citizen n\u00b0785", "mass": 5}, {"id": 801, "name": "Citizen n\u00b0788", "mass": 5}, {"id": 803, "name": "Citizen n\u00b0790", "mass": 5}], "links": [{"source": 228, "target": 225, "value": 8}, {"source": 231, "target": 221, "value": 9}, {"source": 233, "target": 221, "value": 4}, {"source": 245, "target": 233, "value": 4}, {"source": 248, "target": 219, "value": 9}, {"source": 248, "target": 233, "value": 4}, {"source": 248, "target": 245, "value": 8}, {"source": 249, "target": 228, "value": 8}, {"source": 251, "target": 245, "value": 10}, {"source": 251, "target": 249, "value": 9}, {"source": 255, "target": 245, "value": 9}, {"source": 255, "target": 251, "value": 7}, {"source": 257, "target": 219, "value": 8}, {"source": 257, "target": 233, "value": 4}, {"source": 257, "target": 245, "value": 8}, {"source": 257, "target": 248, "value": 7}, {"source": 261, "target": 221, "value": 10}, {"source": 261, "target": 249, "value": 8}, {"source": 273, "target": 219, "value": 10}, {"source": 273, "target": 221, "value": 2}, {"source": 273, "target": 231, "value": 9}, {"source": 273, "target": 248, "value": 9}, {"source": 273, "target": 257, "value": 9}, {"source": 273, "target": 261, "value": 8}, {"source": 275, "target": 225, "value": 3}, {"source": 275, "target": 228, "value": 8}, {"source": 278, "target": 225, "value": 7}, {"source": 278, "target": 228, "value": 3}, {"source": 278, "target": 249, "value": 9}, {"source": 278, "target": 275, "value": 8}, {"source": 279, "target": 221, "value": 4}, {"source": 279, "target": 227, "value": 9}, {"source": 279, "target": 233, "value": 4}, {"source": 281, "target": 221, "value": 10}, {"source": 281, "target": 231, "value": 3}, {"source": 281, "target": 243, "value": 8}, {"source": 281, "target": 273, "value": 10}, {"source": 285, "target": 233, "value": 4}, {"source": 285, "target": 245, "value": 10}, {"source": 285, "target": 248, "value": 10}, {"source": 285, "target": 261, "value": 8}, {"source": 285, "target": 273, "value": 8}, {"source": 287, "target": 225, "value": 8}, {"source": 287, "target": 263, "value": 9}, {"source": 293, "target": 221, "value": 11}, {"source": 293, "target": 243, "value": 1}, {"source": 293, "target": 245, "value": 9}, {"source": 293, "target": 251, "value": 8}, {"source": 293, "target": 255, "value": 8}, {"source": 293, "target": 281, "value": 8}, {"source": 293, "target": 285, "value": 8}, {"source": 293, "target": 291, "value": 8}, {"source": 303, "target": 227, "value": 9}, {"source": 303, "target": 228, "value": 8}, {"source": 303, "target": 249, "value": 10}, {"source": 303, "target": 278, "value": 9}, {"source": 303, "target": 279, "value": 8}, {"source": 303, "target": 293, "value": 9}, {"source": 305, "target": 245, "value": 9}, {"source": 305, "target": 251, "value": 7}, {"source": 305, "target": 255, "value": 2}, {"source": 305, "target": 293, "value": 8}, {"source": 309, "target": 219, "value": 9}, {"source": 309, "target": 221, "value": 8}, {"source": 309, "target": 248, "value": 8}, {"source": 309, "target": 257, "value": 8}, {"source": 309, "target": 261, "value": 9}, {"source": 309, "target": 263, "value": 11}, {"source": 309, "target": 273, "value": 8}, {"source": 311, "target": 249, "value": 8}, {"source": 311, "target": 261, "value": 3}, {"source": 311, "target": 285, "value": 8}, {"source": 315, "target": 215, "value": 4}, {"source": 317, "target": 221, "value": 10}, {"source": 317, "target": 227, "value": 9}, {"source": 317, "target": 233, "value": 10}, {"source": 317, "target": 243, "value": 4}, {"source": 317, "target": 255, "value": 8}, {"source": 317, "target": 279, "value": 8}, {"source": 317, "target": 293, "value": 3}, {"source": 317, "target": 305, "value": 8}, {"source": 321, "target": 219, "value": 10}, {"source": 321, "target": 221, "value": 2}, {"source": 321, "target": 225, "value": 7}, {"source": 321, "target": 228, "value": 8}, {"source": 321, "target": 233, "value": 4}, {"source": 321, "target": 249, "value": 11}, {"source": 321, "target": 251, "value": 8}, {"source": 321, "target": 261, "value": 9}, {"source": 321, "target": 275, "value": 7}, {"source": 321, "target": 278, "value": 7}, {"source": 321, "target": 279, "value": 4}, {"source": 321, "target": 293, "value": 11}, {"source": 321, "target": 309, "value": 8}, {"source": 323, "target": 219, "value": 10}, {"source": 323, "target": 221, "value": 8}, {"source": 323, "target": 248, "value": 9}, {"source": 323, "target": 257, "value": 9}, {"source": 323, "target": 261, "value": 8}, {"source": 323, "target": 273, "value": 1}, {"source": 323, "target": 309, "value": 8}, {"source": 333, "target": 221, "value": 4}, {"source": 333, "target": 233, "value": 0}, {"source": 333, "target": 243, "value": 9}, {"source": 333, "target": 245, "value": 8}, {"source": 333, "target": 248, "value": 8}, {"source": 333, "target": 257, "value": 4}, {"source": 333, "target": 279, "value": 4}, {"source": 333, "target": 281, "value": 8}, {"source": 333, "target": 285, "value": 9}, {"source": 333, "target": 293, "value": 9}, {"source": 333, "target": 309, "value": 8}, {"source": 333, "target": 317, "value": 9}, {"source": 333, "target": 321, "value": 4}, {"source": 335, "target": 233, "value": 10}, {"source": 335, "target": 243, "value": 11}, {"source": 335, "target": 273, "value": 8}, {"source": 335, "target": 285, "value": 3}, {"source": 335, "target": 293, "value": 10}, {"source": 335, "target": 333, "value": 8}, {"source": 339, "target": 243, "value": 11}, {"source": 339, "target": 293, "value": 10}, {"source": 339, "target": 335, "value": 8}, {"source": 341, "target": 251, "value": 9}, {"source": 341, "target": 291, "value": 3}, {"source": 341, "target": 293, "value": 4}, {"source": 341, "target": 303, "value": 8}, {"source": 345, "target": 233, "value": 4}, {"source": 345, "target": 245, "value": 2}, {"source": 345, "target": 248, "value": 8}, {"source": 345, "target": 257, "value": 8}, {"source": 345, "target": 285, "value": 9}, {"source": 345, "target": 333, "value": 8}, {"source": 347, "target": 221, "value": 8}, {"source": 347, "target": 273, "value": 4}, {"source": 347, "target": 285, "value": 8}, {"source": 347, "target": 293, "value": 7}, {"source": 347, "target": 323, "value": 9}, {"source": 348, "target": 219, "value": 8}, {"source": 348, "target": 233, "value": 4}, {"source": 348, "target": 245, "value": 8}, {"source": 348, "target": 248, "value": 4}, {"source": 348, "target": 257, "value": 7}, {"source": 348, "target": 273, "value": 9}, {"source": 348, "target": 285, "value": 10}, {"source": 348, "target": 309, "value": 8}, {"source": 348, "target": 323, "value": 9}, {"source": 348, "target": 333, "value": 8}, {"source": 348, "target": 345, "value": 8}, {"source": 351, "target": 251, "value": 3}, {"source": 351, "target": 263, "value": 8}, {"source": 351, "target": 309, "value": 9}, {"source": 351, "target": 341, "value": 9}, {"source": 353, "target": 215, "value": 8}, {"source": 353, "target": 227, "value": 4}, {"source": 353, "target": 228, "value": 9}, {"source": 353, "target": 249, "value": 10}, {"source": 353, "target": 278, "value": 9}, {"source": 353, "target": 279, "value": 4}, {"source": 353, "target": 293, "value": 9}, {"source": 353, "target": 303, "value": 1}, {"source": 353, "target": 315, "value": 8}, {"source": 353, "target": 341, "value": 8}, {"source": 363, "target": 255, "value": 9}, {"source": 363, "target": 261, "value": 9}, {"source": 363, "target": 263, "value": 2}, {"source": 363, "target": 285, "value": 8}, {"source": 363, "target": 287, "value": 9}, {"source": 363, "target": 305, "value": 8}, {"source": 363, "target": 309, "value": 10}, {"source": 363, "target": 311, "value": 9}, {"source": 363, "target": 317, "value": 7}, {"source": 363, "target": 351, "value": 8}, {"source": 365, "target": 215, "value": 4}, {"source": 365, "target": 225, "value": 9}, {"source": 365, "target": 227, "value": 4}, {"source": 365, "target": 228, "value": 10}, {"source": 365, "target": 275, "value": 9}, {"source": 365, "target": 278, "value": 10}, {"source": 365, "target": 291, "value": 8}, {"source": 365, "target": 293, "value": 9}, {"source": 365, "target": 315, "value": 3}, {"source": 365, "target": 317, "value": 4}, {"source": 365, "target": 321, "value": 9}, {"source": 365, "target": 341, "value": 8}, {"source": 365, "target": 353, "value": 8}, {"source": 369, "target": 219, "value": 4}, {"source": 369, "target": 221, "value": 9}, {"source": 369, "target": 231, "value": 8}, {"source": 369, "target": 248, "value": 8}, {"source": 369, "target": 257, "value": 8}, {"source": 369, "target": 273, "value": 5}, {"source": 369, "target": 281, "value": 8}, {"source": 369, "target": 309, "value": 9}, {"source": 369, "target": 321, "value": 10}, {"source": 369, "target": 323, "value": 10}, {"source": 369, "target": 348, "value": 8}, {"source": 371, "target": 221, "value": 4}, {"source": 371, "target": 261, "value": 10}, {"source": 371, "target": 293, "value": 11}, {"source": 371, "target": 309, "value": 8}, {"source": 371, "target": 321, "value": 3}, {"source": 375, "target": 225, "value": 4}, {"source": 375, "target": 228, "value": 8}, {"source": 375, "target": 275, "value": 4}, {"source": 375, "target": 278, "value": 8}, {"source": 375, "target": 321, "value": 7}, {"source": 375, "target": 365, "value": 9}, {"source": 377, "target": 221, "value": 10}, {"source": 377, "target": 225, "value": 9}, {"source": 377, "target": 227, "value": 4}, {"source": 377, "target": 231, "value": 8}, {"source": 377, "target": 243, "value": 10}, {"source": 377, "target": 255, "value": 10}, {"source": 377, "target": 273, "value": 4}, {"source": 377, "target": 275, "value": 9}, {"source": 377, "target": 279, "value": 10}, {"source": 377, "target": 281, "value": 8}, {"source": 377, "target": 285, "value": 9}, {"source": 377, "target": 293, "value": 10}, {"source": 377, "target": 303, "value": 9}, {"source": 377, "target": 305, "value": 10}, {"source": 377, "target": 317, "value": 9}, {"source": 377, "target": 335, "value": 4}, {"source": 377, "target": 339, "value": 8}, {"source": 377, "target": 353, "value": 4}, {"source": 377, "target": 365, "value": 4}, {"source": 377, "target": 369, "value": 7}, {"source": 377, "target": 375, "value": 8}, {"source": 378, "target": 225, "value": 8}, {"source": 378, "target": 228, "value": 4}, {"source": 378, "target": 249, "value": 8}, {"source": 378, "target": 275, "value": 8}, {"source": 378, "target": 278, "value": 4}, {"source": 378, "target": 303, "value": 9}, {"source": 378, "target": 321, "value": 8}, {"source": 378, "target": 353, "value": 9}, {"source": 378, "target": 365, "value": 10}, {"source": 378, "target": 375, "value": 8}, {"source": 381, "target": 221, "value": 10}, {"source": 381, "target": 231, "value": 4}, {"source": 381, "target": 233, "value": 4}, {"source": 381, "target": 243, "value": 8}, {"source": 381, "target": 257, "value": 8}, {"source": 381, "target": 273, "value": 10}, {"source": 381, "target": 281, "value": 2}, {"source": 381, "target": 291, "value": 9}, {"source": 381, "target": 293, "value": 8}, {"source": 381, "target": 333, "value": 2}, {"source": 381, "target": 341, "value": 9}, {"source": 381, "target": 369, "value": 8}, {"source": 381, "target": 377, "value": 8}, {"source": 383, "target": 221, "value": 4}, {"source": 383, "target": 233, "value": 1}, {"source": 383, "target": 257, "value": 8}, {"source": 383, "target": 279, "value": 4}, {"source": 383, "target": 317, "value": 9}, {"source": 383, "target": 321, "value": 4}, {"source": 383, "target": 333, "value": 1}, {"source": 383, "target": 335, "value": 9}, {"source": 383, "target": 381, "value": 7}, {"source": 393, "target": 243, "value": 1}, {"source": 393, "target": 245, "value": 9}, {"source": 393, "target": 251, "value": 8}, {"source": 393, "target": 255, "value": 8}, {"source": 393, "target": 281, "value": 8}, {"source": 393, "target": 293, "value": 1}, {"source": 393, "target": 305, "value": 8}, {"source": 393, "target": 317, "value": 3}, {"source": 393, "target": 333, "value": 9}, {"source": 393, "target": 335, "value": 10}, {"source": 393, "target": 339, "value": 10}, {"source": 393, "target": 377, "value": 9}, {"source": 393, "target": 381, "value": 8}, {"source": 395, "target": 233, "value": 4}, {"source": 395, "target": 245, "value": 4}, {"source": 395, "target": 248, "value": 8}, {"source": 395, "target": 285, "value": 10}, {"source": 395, "target": 333, "value": 8}, {"source": 395, "target": 345, "value": 3}, {"source": 395, "target": 348, "value": 8}, {"source": 398, "target": 219, "value": 8}, {"source": 398, "target": 233, "value": 4}, {"source": 398, "target": 243, "value": 9}, {"source": 398, "target": 245, "value": 8}, {"source": 398, "target": 248, "value": 4}, {"source": 398, "target": 257, "value": 8}, {"source": 398, "target": 273, "value": 10}, {"source": 398, "target": 281, "value": 8}, {"source": 398, "target": 285, "value": 10}, {"source": 398, "target": 293, "value": 9}, {"source": 398, "target": 309, "value": 9}, {"source": 398, "target": 323, "value": 9}, {"source": 398, "target": 333, "value": 4}, {"source": 398, "target": 345, "value": 7}, {"source": 398, "target": 348, "value": 3}, {"source": 398, "target": 369, "value": 8}, {"source": 398, "target": 381, "value": 8}, {"source": 398, "target": 393, "value": 9}, {"source": 398, "target": 395, "value": 8}, {"source": 399, "target": 228, "value": 9}, {"source": 399, "target": 249, "value": 4}, {"source": 399, "target": 251, "value": 10}, {"source": 399, "target": 278, "value": 8}, {"source": 399, "target": 303, "value": 11}, {"source": 399, "target": 321, "value": 11}, {"source": 399, "target": 353, "value": 10}, {"source": 399, "target": 378, "value": 8}, {"source": 401, "target": 251, "value": 4}, {"source": 401, "target": 263, "value": 8}, {"source": 401, "target": 341, "value": 9}, {"source": 401, "target": 351, "value": 3}, {"source": 401, "target": 363, "value": 8}, {"source": 405, "target": 215, "value": 9}, {"source": 405, "target": 233, "value": 10}, {"source": 405, "target": 245, "value": 10}, {"source": 405, "target": 251, "value": 8}, {"source": 405, "target": 255, "value": 2}, {"source": 405, "target": 263, "value": 11}, {"source": 405, "target": 293, "value": 8}, {"source": 405, "target": 303, "value": 9}, {"source": 405, "target": 305, "value": 2}, {"source": 405, "target": 309, "value": 8}, {"source": 405, "target": 315, "value": 9}, {"source": 405, "target": 317, "value": 8}, {"source": 405, "target": 333, "value": 10}, {"source": 405, "target": 335, "value": 8}, {"source": 405, "target": 351, "value": 9}, {"source": 405, "target": 353, "value": 4}, {"source": 405, "target": 363, "value": 4}, {"source": 405, "target": 365, "value": 9}, {"source": 405, "target": 377, "value": 11}, {"source": 405, "target": 383, "value": 10}, {"source": 405, "target": 393, "value": 8}, {"source": 407, "target": 233, "value": 4}, {"source": 407, "target": 245, "value": 8}, {"source": 407, "target": 257, "value": 4}, {"source": 407, "target": 333, "value": 4}, {"source": 407, "target": 345, "value": 7}, {"source": 407, "target": 381, "value": 8}, {"source": 407, "target": 383, "value": 9}, {"source": 411, "target": 249, "value": 8}, {"source": 411, "target": 261, "value": 4}, {"source": 411, "target": 285, "value": 8}, {"source": 411, "target": 311, "value": 4}, {"source": 411, "target": 363, "value": 9}, {"source": 413, "target": 215, "value": 26}, {"source": 413, "target": 219, "value": 27}, {"source": 413, "target": 221, "value": 26}, {"source": 413, "target": 225, "value": 6}, {"source": 413, "target": 227, "value": 28}, {"source": 413, "target": 228, "value": 6}, {"source": 413, "target": 231, "value": 27}, {"source": 413, "target": 233, "value": 27}, {"source": 413, "target": 243, "value": 29}, {"source": 413, "target": 245, "value": 28}, {"source": 413, "target": 248, "value": 28}, {"source": 413, "target": 249, "value": 29}, {"source": 413, "target": 251, "value": 28}, {"source": 413, "target": 255, "value": 28}, {"source": 413, "target": 257, "value": 29}, {"source": 413, "target": 261, "value": 6}, {"source": 413, "target": 263, "value": 1}, {"source": 413, "target": 273, "value": 7}, {"source": 413, "target": 275, "value": 6}, {"source": 413, "target": 278, "value": 6}, {"source": 413, "target": 279, "value": 29}, {"source": 413, "target": 287, "value": 4}, {"source": 413, "target": 309, "value": 8}, {"source": 413, "target": 321, "value": 7}, {"source": 413, "target": 323, "value": 8}, {"source": 413, "target": 339, "value": 7}, {"source": 413, "target": 351, "value": 7}, {"source": 413, "target": 363, "value": 1}, {"source": 413, "target": 365, "value": 8}, {"source": 413, "target": 375, "value": 8}, {"source": 413, "target": 378, "value": 8}, {"source": 413, "target": 401, "value": 8}, {"source": 413, "target": 405, "value": 9}, {"source": 423, "target": 219, "value": 10}, {"source": 423, "target": 221, "value": 8}, {"source": 423, "target": 248, "value": 8}, {"source": 423, "target": 257, "value": 9}, {"source": 423, "target": 261, "value": 8}, {"source": 423, "target": 273, "value": 1}, {"source": 423, "target": 309, "value": 8}, {"source": 423, "target": 323, "value": 2}, {"source": 423, "target": 347, "value": 8}, {"source": 423, "target": 348, "value": 8}, {"source": 423, "target": 369, "value": 10}, {"source": 423, "target": 398, "value": 9}, {"source": 423, "target": 413, "value": 8}, {"source": 425, "target": 225, "value": 2}, {"source": 425, "target": 228, "value": 8}, {"source": 425, "target": 275, "value": 4}, {"source": 425, "target": 278, "value": 8}, {"source": 425, "target": 287, "value": 8}, {"source": 425, "target": 321, "value": 8}, {"source": 425, "target": 365, "value": 9}, {"source": 425, "target": 375, "value": 3}, {"source": 425, "target": 377, "value": 9}, {"source": 425, "target": 378, "value": 8}, {"source": 425, "target": 413, "value": 8}, {"source": 428, "target": 225, "value": 8}, {"source": 428, "target": 228, "value": 4}, {"source": 428, "target": 249, "value": 8}, {"source": 428, "target": 275, "value": 8}, {"source": 428, "target": 278, "value": 4}, {"source": 428, "target": 303, "value": 9}, {"source": 428, "target": 321, "value": 8}, {"source": 428, "target": 353, "value": 9}, {"source": 428, "target": 365, "value": 9}, {"source": 428, "target": 375, "value": 7}, {"source": 428, "target": 378, "value": 3}, {"source": 428, "target": 399, "value": 8}, {"source": 428, "target": 413, "value": 8}, {"source": 428, "target": 425, "value": 8}, {"source": 429, "target": 221, "value": 11}, {"source": 429, "target": 225, "value": 8}, {"source": 429, "target": 233, "value": 11}, {"source": 429, "target": 243, "value": 9}, {"source": 429, "target": 255, "value": 8}, {"source": 429, "target": 279, "value": 4}, {"source": 429, "target": 287, "value": 9}, {"source": 429, "target": 291, "value": 8}, {"source": 429, "target": 293, "value": 8}, {"source": 429, "target": 305, "value": 8}, {"source": 429, "target": 317, "value": 8}, {"source": 429, "target": 333, "value": 11}, {"source": 429, "target": 335, "value": 9}, {"source": 429, "target": 339, "value": 9}, {"source": 429, "target": 341, "value": 8}, {"source": 429, "target": 377, "value": 4}, {"source": 429, "target": 381, "value": 9}, {"source": 429, "target": 383, "value": 10}, {"source": 429, "target": 393, "value": 8}, {"source": 429, "target": 405, "value": 8}, {"source": 429, "target": 425, "value": 9}, {"source": 431, "target": 221, "value": 10}, {"source": 431, "target": 231, "value": 4}, {"source": 431, "target": 243, "value": 9}, {"source": 431, "target": 273, "value": 10}, {"source": 431, "target": 281, "value": 2}, {"source": 431, "target": 293, "value": 9}, {"source": 431, "target": 333, "value": 8}, {"source": 431, "target": 369, "value": 7}, {"source": 431, "target": 377, "value": 7}, {"source": 431, "target": 381, "value": 2}, {"source": 431, "target": 393, "value": 9}, {"source": 431, "target": 398, "value": 8}, {"source": 435, "target": 273, "value": 8}, {"source": 435, "target": 285, "value": 4}, {"source": 435, "target": 335, "value": 4}, {"source": 435, "target": 377, "value": 9}, {"source": 437, "target": 225, "value": 8}, {"source": 437, "target": 228, "value": 8}, {"source": 437, "target": 249, "value": 8}, {"source": 437, "target": 263, "value": 9}, {"source": 437, "target": 278, "value": 8}, {"source": 437, "target": 285, "value": 8}, {"source": 437, "target": 287, "value": 4}, {"source": 437, "target": 293, "value": 9}, {"source": 437, "target": 303, "value": 10}, {"source": 437, "target": 347, "value": 9}, {"source": 437, "target": 353, "value": 10}, {"source": 437, "target": 363, "value": 9}, {"source": 437, "target": 378, "value": 8}, {"source": 437, "target": 399, "value": 8}, {"source": 437, "target": 413, "value": 4}, {"source": 437, "target": 425, "value": 8}, {"source": 437, "target": 428, "value": 7}, {"source": 437, "target": 429, "value": 9}, {"source": 441, "target": 291, "value": 4}, {"source": 441, "target": 293, "value": 5}, {"source": 441, "target": 303, "value": 8}, {"source": 441, "target": 341, "value": 2}, {"source": 441, "target": 353, "value": 8}, {"source": 441, "target": 365, "value": 8}, {"source": 441, "target": 381, "value": 9}, {"source": 441, "target": 429, "value": 8}, {"source": 443, "target": 243, "value": 2}, {"source": 443, "target": 281, "value": 8}, {"source": 443, "target": 293, "value": 1}, {"source": 443, "target": 317, "value": 4}, {"source": 443, "target": 333, "value": 9}, {"source": 443, "target": 335, "value": 10}, {"source": 443, "target": 339, "value": 10}, {"source": 443, "target": 377, "value": 9}, {"source": 443, "target": 381, "value": 8}, {"source": 443, "target": 393, "value": 1}, {"source": 443, "target": 398, "value": 9}, {"source": 443, "target": 429, "value": 8}, {"source": 443, "target": 431, "value": 9}, {"source": 453, "target": 215, "value": 4}, {"source": 453, "target": 227, "value": 4}, {"source": 453, "target": 228, "value": 8}, {"source": 453, "target": 249, "value": 10}, {"source": 453, "target": 263, "value": 9}, {"source": 453, "target": 278, "value": 8}, {"source": 453, "target": 279, "value": 4}, {"source": 453, "target": 293, "value": 10}, {"source": 453, "target": 303, "value": 1}, {"source": 453, "target": 315, "value": 4}, {"source": 453, "target": 341, "value": 8}, {"source": 453, "target": 353, "value": 0}, {"source": 453, "target": 363, "value": 9}, {"source": 453, "target": 365, "value": 4}, {"source": 453, "target": 377, "value": 4}, {"source": 453, "target": 378, "value": 8}, {"source": 453, "target": 399, "value": 9}, {"source": 453, "target": 401, "value": 8}, {"source": 453, "target": 405, "value": 3}, {"source": 453, "target": 413, "value": 9}, {"source": 453, "target": 428, "value": 8}, {"source": 453, "target": 437, "value": 9}, {"source": 453, "target": 441, "value": 8}, {"source": 455, "target": 245, "value": 10}, {"source": 455, "target": 251, "value": 8}, {"source": 455, "target": 255, "value": 4}, {"source": 455, "target": 263, "value": 9}, {"source": 455, "target": 293, "value": 8}, {"source": 455, "target": 305, "value": 4}, {"source": 455, "target": 363, "value": 9}, {"source": 455, "target": 377, "value": 11}, {"source": 455, "target": 393, "value": 8}, {"source": 455, "target": 401, "value": 8}, {"source": 455, "target": 405, "value": 3}, {"source": 455, "target": 413, "value": 9}, {"source": 455, "target": 429, "value": 8}, {"source": 455, "target": 453, "value": 7}, {"source": 459, "target": 263, "value": 11}, {"source": 459, "target": 309, "value": 4}, {"source": 459, "target": 333, "value": 8}, {"source": 459, "target": 351, "value": 10}, {"source": 459, "target": 363, "value": 10}, {"source": 459, "target": 405, "value": 7}, {"source": 459, "target": 413, "value": 10}, {"source": 461, "target": 215, "value": 8}, {"source": 461, "target": 221, "value": 9}, {"source": 461, "target": 249, "value": 8}, {"source": 461, "target": 261, "value": 2}, {"source": 461, "target": 263, "value": 8}, {"source": 461, "target": 273, "value": 8}, {"source": 461, "target": 285, "value": 8}, {"source": 461, "target": 287, "value": 8}, {"source": 461, "target": 309, "value": 4}, {"source": 461, "target": 311, "value": 4}, {"source": 461, "target": 315, "value": 8}, {"source": 461, "target": 321, "value": 9}, {"source": 461, "target": 323, "value": 8}, {"source": 461, "target": 333, "value": 8}, {"source": 461, "target": 339, "value": 10}, {"source": 461, "target": 353, "value": 8}, {"source": 461, "target": 363, "value": 4}, {"source": 461, "target": 365, "value": 7}, {"source": 461, "target": 371, "value": 9}, {"source": 461, "target": 405, "value": 9}, {"source": 461, "target": 411, "value": 3}, {"source": 461, "target": 413, "value": 2}, {"source": 461, "target": 423, "value": 8}, {"source": 461, "target": 437, "value": 8}, {"source": 461, "target": 453, "value": 4}, {"source": 461, "target": 459, "value": 8}, {"source": 465, "target": 215, "value": 4}, {"source": 465, "target": 227, "value": 8}, {"source": 465, "target": 315, "value": 4}, {"source": 465, "target": 317, "value": 9}, {"source": 465, "target": 353, "value": 8}, {"source": 465, "target": 365, "value": 2}, {"source": 465, "target": 377, "value": 8}, {"source": 465, "target": 405, "value": 9}, {"source": 465, "target": 453, "value": 4}, {"source": 465, "target": 461, "value": 7}, {"source": 467, "target": 243, "value": 9}, {"source": 467, "target": 255, "value": 10}, {"source": 467, "target": 293, "value": 4}, {"source": 467, "target": 305, "value": 8}, {"source": 467, "target": 317, "value": 4}, {"source": 467, "target": 363, "value": 8}, {"source": 467, "target": 393, "value": 4}, {"source": 467, "target": 405, "value": 7}, {"source": 467, "target": 443, "value": 9}, {"source": 471, "target": 221, "value": 4}, {"source": 471, "target": 261, "value": 10}, {"source": 471, "target": 293, "value": 11}, {"source": 471, "target": 309, "value": 8}, {"source": 471, "target": 321, "value": 4}, {"source": 471, "target": 371, "value": 4}, {"source": 471, "target": 461, "value": 8}, {"source": 473, "target": 219, "value": 9}, {"source": 473, "target": 221, "value": 4}, {"source": 473, "target": 248, "value": 8}, {"source": 473, "target": 257, "value": 9}, {"source": 473, "target": 261, "value": 8}, {"source": 473, "target": 273, "value": 1}, {"source": 473, "target": 285, "value": 8}, {"source": 473, "target": 309, "value": 8}, {"source": 473, "target": 323, "value": 1}, {"source": 473, "target": 335, "value": 8}, {"source": 473, "target": 347, "value": 4}, {"source": 473, "target": 348, "value": 8}, {"source": 473, "target": 369, "value": 10}, {"source": 473, "target": 377, "value": 9}, {"source": 473, "target": 398, "value": 8}, {"source": 473, "target": 413, "value": 9}, {"source": 473, "target": 423, "value": 1}, {"source": 473, "target": 435, "value": 8}, {"source": 473, "target": 461, "value": 8}, {"source": 483, "target": 221, "value": 4}, {"source": 483, "target": 233, "value": 1}, {"source": 483, "target": 257, "value": 8}, {"source": 483, "target": 279, "value": 4}, {"source": 483, "target": 317, "value": 9}, {"source": 483, "target": 321, "value": 4}, {"source": 483, "target": 333, "value": 1}, {"source": 483, "target": 335, "value": 10}, {"source": 483, "target": 381, "value": 8}, {"source": 483, "target": 383, "value": 2}, {"source": 483, "target": 405, "value": 10}, {"source": 483, "target": 407, "value": 8}, {"source": 483, "target": 429, "value": 9}, {"source": 485, "target": 233, "value": 4}, {"source": 485, "target": 245, "value": 9}, {"source": 485, "target": 248, "value": 9}, {"source": 485, "target": 273, "value": 8}, {"source": 485, "target": 285, "value": 2}, {"source": 485, "target": 293, "value": 8}, {"source": 485, "target": 333, "value": 8}, {"source": 485, "target": 335, "value": 4}, {"source": 485, "target": 345, "value": 9}, {"source": 485, "target": 347, "value": 8}, {"source": 485, "target": 348, "value": 9}, {"source": 485, "target": 377, "value": 10}, {"source": 485, "target": 395, "value": 9}, {"source": 485, "target": 398, "value": 9}, {"source": 485, "target": 435, "value": 3}, {"source": 485, "target": 437, "value": 9}, {"source": 485, "target": 473, "value": 8}, {"source": 489, "target": 243, "value": 3}, {"source": 489, "target": 251, "value": 8}, {"source": 489, "target": 263, "value": 8}, {"source": 489, "target": 281, "value": 8}, {"source": 489, "target": 287, "value": 9}, {"source": 489, "target": 293, "value": 2}, {"source": 489, "target": 317, "value": 10}, {"source": 489, "target": 333, "value": 10}, {"source": 489, "target": 335, "value": 8}, {"source": 489, "target": 339, "value": 4}, {"source": 489, "target": 341, "value": 9}, {"source": 489, "target": 351, "value": 8}, {"source": 489, "target": 363, "value": 8}, {"source": 489, "target": 377, "value": 8}, {"source": 489, "target": 381, "value": 8}, {"source": 489, "target": 393, "value": 2}, {"source": 489, "target": 398, "value": 9}, {"source": 489, "target": 401, "value": 8}, {"source": 489, "target": 413, "value": 2}, {"source": 489, "target": 429, "value": 9}, {"source": 489, "target": 431, "value": 8}, {"source": 489, "target": 437, "value": 8}, {"source": 489, "target": 443, "value": 3}, {"source": 489, "target": 461, "value": 4}, {"source": 489, "target": 467, "value": 10}, {"source": 491, "target": 291, "value": 4}, {"source": 491, "target": 293, "value": 10}, {"source": 491, "target": 341, "value": 4}, {"source": 491, "target": 365, "value": 8}, {"source": 491, "target": 381, "value": 9}, {"source": 491, "target": 429, "value": 7}, {"source": 491, "target": 441, "value": 3}, {"source": 495, "target": 233, "value": 4}, {"source": 495, "target": 245, "value": 4}, {"source": 495, "target": 248, "value": 8}, {"source": 495, "target": 285, "value": 10}, {"source": 495, "target": 333, "value": 8}, {"source": 495, "target": 345, "value": 4}, {"source": 495, "target": 348, "value": 8}, {"source": 495, "target": 395, "value": 4}, {"source": 495, "target": 398, "value": 8}, {"source": 495, "target": 485, "value": 8}, {"source": 497, "target": 221, "value": 8}, {"source": 497, "target": 263, "value": 5}, {"source": 497, "target": 273, "value": 4}, {"source": 497, "target": 285, "value": 8}, {"source": 497, "target": 293, "value": 8}, {"source": 497, "target": 309, "value": 4}, {"source": 497, "target": 323, "value": 9}, {"source": 497, "target": 347, "value": 4}, {"source": 497, "target": 351, "value": 4}, {"source": 497, "target": 363, "value": 4}, {"source": 497, "target": 405, "value": 4}, {"source": 497, "target": 413, "value": 4}, {"source": 497, "target": 423, "value": 9}, {"source": 497, "target": 437, "value": 9}, {"source": 497, "target": 459, "value": 4}, {"source": 497, "target": 473, "value": 4}, {"source": 497, "target": 485, "value": 8}, {"source": 498, "target": 219, "value": 8}, {"source": 498, "target": 233, "value": 4}, {"source": 498, "target": 245, "value": 8}, {"source": 498, "target": 248, "value": 4}, {"source": 498, "target": 257, "value": 8}, {"source": 498, "target": 273, "value": 10}, {"source": 498, "target": 285, "value": 10}, {"source": 498, "target": 309, "value": 9}, {"source": 498, "target": 323, "value": 10}, {"source": 498, "target": 333, "value": 9}, {"source": 498, "target": 345, "value": 8}, {"source": 498, "target": 348, "value": 4}, {"source": 498, "target": 369, "value": 8}, {"source": 498, "target": 395, "value": 8}, {"source": 498, "target": 398, "value": 4}, {"source": 498, "target": 423, "value": 9}, {"source": 498, "target": 473, "value": 9}, {"source": 498, "target": 485, "value": 9}, {"source": 498, "target": 495, "value": 8}, {"source": 501, "target": 249, "value": 8}, {"source": 501, "target": 251, "value": 4}, {"source": 501, "target": 261, "value": 9}, {"source": 501, "target": 263, "value": 8}, {"source": 501, "target": 311, "value": 9}, {"source": 501, "target": 341, "value": 10}, {"source": 501, "target": 351, "value": 4}, {"source": 501, "target": 363, "value": 8}, {"source": 501, "target": 401, "value": 2}, {"source": 501, "target": 411, "value": 9}, {"source": 501, "target": 413, "value": 8}, {"source": 501, "target": 453, "value": 8}, {"source": 501, "target": 455, "value": 8}, {"source": 501, "target": 461, "value": 9}, {"source": 501, "target": 489, "value": 8}, {"source": 503, "target": 227, "value": 8}, {"source": 503, "target": 228, "value": 8}, {"source": 503, "target": 245, "value": 8}, {"source": 503, "target": 249, "value": 9}, {"source": 503, "target": 251, "value": 9}, {"source": 503, "target": 257, "value": 8}, {"source": 503, "target": 278, "value": 8}, {"source": 503, "target": 279, "value": 9}, {"source": 503, "target": 293, "value": 10}, {"source": 503, "target": 303, "value": 2}, {"source": 503, "target": 341, "value": 8}, {"source": 503, "target": 345, "value": 8}, {"source": 503, "target": 351, "value": 9}, {"source": 503, "target": 353, "value": 1}, {"source": 503, "target": 377, "value": 8}, {"source": 503, "target": 378, "value": 8}, {"source": 503, "target": 399, "value": 9}, {"source": 503, "target": 401, "value": 9}, {"source": 503, "target": 405, "value": 9}, {"source": 503, "target": 407, "value": 7}, {"source": 503, "target": 428, "value": 8}, {"source": 503, "target": 437, "value": 9}, {"source": 503, "target": 441, "value": 8}, {"source": 503, "target": 453, "value": 1}, {"source": 503, "target": 501, "value": 8}, {"source": 513, "target": 215, "value": 26}, {"source": 513, "target": 219, "value": 27}, {"source": 513, "target": 221, "value": 26}, {"source": 513, "target": 225, "value": 6}, {"source": 513, "target": 227, "value": 29}, {"source": 513, "target": 228, "value": 6}, {"source": 513, "target": 231, "value": 28}, {"source": 513, "target": 233, "value": 29}, {"source": 513, "target": 243, "value": 29}, {"source": 513, "target": 245, "value": 28}, {"source": 513, "target": 248, "value": 29}, {"source": 513, "target": 249, "value": 29}, {"source": 513, "target": 251, "value": 28}, {"source": 513, "target": 255, "value": 28}, {"source": 513, "target": 257, "value": 29}, {"source": 513, "target": 261, "value": 29}, {"source": 513, "target": 263, "value": 1}, {"source": 513, "target": 273, "value": 28}, {"source": 513, "target": 275, "value": 6}, {"source": 513, "target": 278, "value": 6}, {"source": 513, "target": 287, "value": 3}, {"source": 513, "target": 309, "value": 8}, {"source": 513, "target": 321, "value": 7}, {"source": 513, "target": 351, "value": 7}, {"source": 513, "target": 363, "value": 1}, {"source": 513, "target": 365, "value": 9}, {"source": 513, "target": 375, "value": 8}, {"source": 513, "target": 378, "value": 8}, {"source": 513, "target": 401, "value": 8}, {"source": 513, "target": 405, "value": 9}, {"source": 513, "target": 413, "value": 0}, {"source": 513, "target": 425, "value": 8}, {"source": 513, "target": 428, "value": 8}, {"source": 513, "target": 437, "value": 4}, {"source": 513, "target": 453, "value": 9}, {"source": 513, "target": 455, "value": 9}, {"source": 513, "target": 459, "value": 9}, {"source": 513, "target": 461, "value": 4}, {"source": 513, "target": 489, "value": 4}, {"source": 513, "target": 497, "value": 4}, {"source": 513, "target": 501, "value": 8}, {"source": 515, "target": 215, "value": 4}, {"source": 515, "target": 315, "value": 4}, {"source": 515, "target": 353, "value": 8}, {"source": 515, "target": 365, "value": 4}, {"source": 515, "target": 405, "value": 9}, {"source": 515, "target": 453, "value": 4}, {"source": 515, "target": 461, "value": 7}, {"source": 515, "target": 465, "value": 3}, {"source": 519, "target": 219, "value": 4}, {"source": 519, "target": 221, "value": 9}, {"source": 519, "target": 231, "value": 8}, {"source": 519, "target": 248, "value": 9}, {"source": 519, "target": 257, "value": 8}, {"source": 519, "target": 273, "value": 5}, {"source": 519, "target": 281, "value": 8}, {"source": 519, "target": 309, "value": 10}, {"source": 519, "target": 321, "value": 10}, {"source": 519, "target": 323, "value": 11}, {"source": 519, "target": 348, "value": 9}, {"source": 519, "target": 369, "value": 2}, {"source": 519, "target": 377, "value": 8}, {"source": 519, "target": 381, "value": 8}, {"source": 519, "target": 398, "value": 8}, {"source": 519, "target": 423, "value": 10}, {"source": 519, "target": 431, "value": 8}, {"source": 519, "target": 473, "value": 10}, {"source": 519, "target": 498, "value": 8}, {"source": 521, "target": 221, "value": 2}, {"source": 521, "target": 233, "value": 8}, {"source": 521, "target": 261, "value": 10}, {"source": 521, "target": 279, "value": 8}, {"source": 521, "target": 293, "value": 11}, {"source": 521, "target": 309, "value": 8}, {"source": 521, "target": 321, "value": 2}, {"source": 521, "target": 333, "value": 8}, {"source": 521, "target": 371, "value": 4}, {"source": 521, "target": 383, "value": 8}, {"source": 521, "target": 461, "value": 9}, {"source": 521, "target": 471, "value": 3}, {"source": 521, "target": 483, "value": 8}, {"source": 525, "target": 225, "value": 2}, {"source": 525, "target": 228, "value": 8}, {"source": 525, "target": 251, "value": 8}, {"source": 525, "target": 273, "value": 8}, {"source": 525, "target": 275, "value": 4}, {"source": 525, "target": 278, "value": 8}, {"source": 525, "target": 285, "value": 9}, {"source": 525, "target": 287, "value": 8}, {"source": 525, "target": 321, "value": 8}, {"source": 525, "target": 335, "value": 9}, {"source": 525, "target": 351, "value": 8}, {"source": 525, "target": 365, "value": 10}, {"source": 525, "target": 375, "value": 4}, {"source": 525, "target": 377, "value": 4}, {"source": 525, "target": 378, "value": 8}, {"source": 525, "target": 401, "value": 8}, {"source": 525, "target": 413, "value": 8}, {"source": 525, "target": 425, "value": 2}, {"source": 525, "target": 428, "value": 8}, {"source": 525, "target": 429, "value": 9}, {"source": 525, "target": 435, "value": 9}, {"source": 525, "target": 437, "value": 8}, {"source": 525, "target": 473, "value": 8}, {"source": 525, "target": 485, "value": 9}, {"source": 525, "target": 501, "value": 8}, {"source": 525, "target": 503, "value": 8}, {"source": 525, "target": 513, "value": 8}, {"source": 527, "target": 227, "value": 4}, {"source": 527, "target": 279, "value": 10}, {"source": 527, "target": 303, "value": 9}, {"source": 527, "target": 317, "value": 10}, {"source": 527, "target": 353, "value": 4}, {"source": 527, "target": 365, "value": 4}, {"source": 527, "target": 377, "value": 4}, {"source": 527, "target": 453, "value": 4}, {"source": 527, "target": 465, "value": 7}, {"source": 527, "target": 503, "value": 8}, {"source": 528, "target": 225, "value": 8}, {"source": 528, "target": 228, "value": 4}, {"source": 528, "target": 249, "value": 8}, {"source": 528, "target": 275, "value": 8}, {"source": 528, "target": 278, "value": 4}, {"source": 528, "target": 303, "value": 10}, {"source": 528, "target": 321, "value": 8}, {"source": 528, "target": 353, "value": 10}, {"source": 528, "target": 365, "value": 10}, {"source": 528, "target": 375, "value": 8}, {"source": 528, "target": 378, "value": 4}, {"source": 528, "target": 399, "value": 8}, {"source": 528, "target": 413, "value": 8}, {"source": 528, "target": 425, "value": 8}, {"source": 528, "target": 428, "value": 4}, {"source": 528, "target": 437, "value": 7}, {"source": 528, "target": 453, "value": 9}, {"source": 528, "target": 503, "value": 8}, {"source": 528, "target": 513, "value": 8}, {"source": 528, "target": 525, "value": 8}, {"source": 531, "target": 221, "value": 10}, {"source": 531, "target": 231, "value": 4}, {"source": 531, "target": 273, "value": 4}, {"source": 531, "target": 279, "value": 9}, {"source": 531, "target": 281, "value": 4}, {"source": 531, "target": 285, "value": 8}, {"source": 531, "target": 335, "value": 8}, {"source": 531, "target": 369, "value": 8}, {"source": 531, "target": 377, "value": 4}, {"source": 531, "target": 381, "value": 4}, {"source": 531, "target": 429, "value": 9}, {"source": 531, "target": 431, "value": 4}, {"source": 531, "target": 435, "value": 7}, {"source": 531, "target": 473, "value": 8}, {"source": 531, "target": 485, "value": 7}, {"source": 531, "target": 519, "value": 8}, {"source": 531, "target": 525, "value": 9}, {"source": 533, "target": 221, "value": 4}, {"source": 533, "target": 233, "value": 0}, {"source": 533, "target": 243, "value": 9}, {"source": 533, "target": 245, "value": 8}, {"source": 533, "target": 248, "value": 8}, {"source": 533, "target": 257, "value": 4}, {"source": 533, "target": 279, "value": 4}, {"source": 533, "target": 281, "value": 8}, {"source": 533, "target": 285, "value": 9}, {"source": 533, "target": 291, "value": 9}, {"source": 533, "target": 293, "value": 9}, {"source": 533, "target": 317, "value": 8}, {"source": 533, "target": 321, "value": 4}, {"source": 533, "target": 333, "value": 0}, {"source": 533, "target": 335, "value": 10}, {"source": 533, "target": 341, "value": 9}, {"source": 533, "target": 345, "value": 8}, {"source": 533, "target": 348, "value": 8}, {"source": 533, "target": 381, "value": 2}, {"source": 533, "target": 383, "value": 1}, {"source": 533, "target": 393, "value": 9}, {"source": 533, "target": 395, "value": 8}, {"source": 533, "target": 398, "value": 4}, {"source": 533, "target": 405, "value": 11}, {"source": 533, "target": 407, "value": 4}, {"source": 533, "target": 429, "value": 4}, {"source": 533, "target": 431, "value": 8}, {"source": 533, "target": 441, "value": 9}, {"source": 533, "target": 443, "value": 9}, {"source": 533, "target": 483, "value": 1}, {"source": 533, "target": 485, "value": 8}, {"source": 533, "target": 489, "value": 8}, {"source": 533, "target": 491, "value": 9}, {"source": 533, "target": 495, "value": 8}, {"source": 533, "target": 498, "value": 8}, {"source": 533, "target": 521, "value": 8}, {"source": 543, "target": 243, "value": 2}, {"source": 543, "target": 281, "value": 8}, {"source": 543, "target": 293, "value": 1}, {"source": 543, "target": 317, "value": 4}, {"source": 543, "target": 333, "value": 10}, {"source": 543, "target": 335, "value": 9}, {"source": 543, "target": 339, "value": 9}, {"source": 543, "target": 377, "value": 8}, {"source": 543, "target": 381, "value": 8}, {"source": 543, "target": 393, "value": 1}, {"source": 543, "target": 398, "value": 9}, {"source": 543, "target": 429, "value": 7}, {"source": 543, "target": 431, "value": 9}, {"source": 543, "target": 443, "value": 2}, {"source": 543, "target": 467, "value": 8}, {"source": 543, "target": 489, "value": 2}, {"source": 543, "target": 533, "value": 8}, {"source": 545, "target": 228, "value": 9}, {"source": 545, "target": 233, "value": 4}, {"source": 545, "target": 243, "value": 9}, {"source": 545, "target": 245, "value": 2}, {"source": 545, "target": 248, "value": 8}, {"source": 545, "target": 249, "value": 8}, {"source": 545, "target": 257, "value": 8}, {"source": 545, "target": 273, "value": 10}, {"source": 545, "target": 278, "value": 9}, {"source": 545, "target": 285, "value": 10}, {"source": 545, "target": 293, "value": 9}, {"source": 545, "target": 303, "value": 11}, {"source": 545, "target": 317, "value": 8}, {"source": 545, "target": 323, "value": 10}, {"source": 545, "target": 333, "value": 8}, {"source": 545, "target": 345, "value": 2}, {"source": 545, "target": 348, "value": 8}, {"source": 545, "target": 353, "value": 11}, {"source": 545, "target": 378, "value": 8}, {"source": 545, "target": 393, "value": 9}, {"source": 545, "target": 395, "value": 4}, {"source": 545, "target": 398, "value": 8}, {"source": 545, "target": 399, "value": 8}, {"source": 545, "target": 407, "value": 8}, {"source": 545, "target": 423, "value": 9}, {"source": 545, "target": 428, "value": 8}, {"source": 545, "target": 437, "value": 8}, {"source": 545, "target": 443, "value": 9}, {"source": 545, "target": 453, "value": 9}, {"source": 545, "target": 473, "value": 9}, {"source": 545, "target": 485, "value": 9}, {"source": 545, "target": 495, "value": 3}, {"source": 545, "target": 498, "value": 8}, {"source": 545, "target": 503, "value": 4}, {"source": 545, "target": 528, "value": 8}, {"source": 545, "target": 533, "value": 8}, {"source": 545, "target": 543, "value": 8}, {"source": 548, "target": 219, "value": 8}, {"source": 548, "target": 233, "value": 4}, {"source": 548, "target": 245, "value": 8}, {"source": 548, "target": 248, "value": 4}, {"source": 548, "target": 257, "value": 8}, {"source": 548, "target": 273, "value": 10}, {"source": 548, "target": 285, "value": 10}, {"source": 548, "target": 309, "value": 9}, {"source": 548, "target": 323, "value": 10}, {"source": 548, "target": 333, "value": 9}, {"source": 548, "target": 345, "value": 8}, {"source": 548, "target": 348, "value": 4}, {"source": 548, "target": 369, "value": 8}, {"source": 548, "target": 395, "value": 8}, {"source": 548, "target": 398, "value": 4}, {"source": 548, "target": 423, "value": 9}, {"source": 548, "target": 473, "value": 9}, {"source": 548, "target": 485, "value": 9}, {"source": 548, "target": 495, "value": 7}, {"source": 548, "target": 498, "value": 3}, {"source": 548, "target": 519, "value": 8}, {"source": 548, "target": 533, "value": 8}, {"source": 548, "target": 545, "value": 8}, {"source": 549, "target": 228, "value": 9}, {"source": 549, "target": 249, "value": 2}, {"source": 549, "target": 251, "value": 10}, {"source": 549, "target": 261, "value": 8}, {"source": 549, "target": 263, "value": 9}, {"source": 549, "target": 278, "value": 9}, {"source": 549, "target": 303, "value": 11}, {"source": 549, "target": 309, "value": 9}, {"source": 549, "target": 311, "value": 8}, {"source": 549, "target": 321, "value": 11}, {"source": 549, "target": 351, "value": 8}, {"source": 549, "target": 353, "value": 10}, {"source": 549, "target": 363, "value": 9}, {"source": 549, "target": 378, "value": 8}, {"source": 549, "target": 399, "value": 4}, {"source": 549, "target": 405, "value": 9}, {"source": 549, "target": 411, "value": 8}, {"source": 549, "target": 413, "value": 8}, {"source": 549, "target": 428, "value": 8}, {"source": 549, "target": 437, "value": 8}, {"source": 549, "target": 453, "value": 10}, {"source": 549, "target": 459, "value": 8}, {"source": 549, "target": 461, "value": 8}, {"source": 549, "target": 497, "value": 4}, {"source": 549, "target": 501, "value": 8}, {"source": 549, "target": 503, "value": 9}, {"source": 549, "target": 513, "value": 8}, {"source": 549, "target": 528, "value": 8}, {"source": 549, "target": 545, "value": 8}, {"source": 551, "target": 251, "value": 4}, {"source": 551, "target": 341, "value": 10}, {"source": 551, "target": 351, "value": 4}, {"source": 551, "target": 401, "value": 4}, {"source": 551, "target": 489, "value": 7}, {"source": 551, "target": 501, "value": 3}, {"source": 551, "target": 503, "value": 9}, {"source": 551, "target": 525, "value": 8}, {"source": 555, "target": 245, "value": 10}, {"source": 555, "target": 251, "value": 8}, {"source": 555, "target": 255, "value": 4}, {"source": 555, "target": 293, "value": 8}, {"source": 555, "target": 305, "value": 4}, {"source": 555, "target": 377, "value": 11}, {"source": 555, "target": 393, "value": 8}, {"source": 555, "target": 405, "value": 4}, {"source": 555, "target": 429, "value": 8}, {"source": 555, "target": 455, "value": 4}, {"source": 557, "target": 219, "value": 8}, {"source": 557, "target": 233, "value": 3}, {"source": 557, "target": 245, "value": 8}, {"source": 557, "target": 248, "value": 8}, {"source": 557, "target": 255, "value": 8}, {"source": 557, "target": 257, "value": 2}, {"source": 557, "target": 273, "value": 11}, {"source": 557, "target": 305, "value": 8}, {"source": 557, "target": 309, "value": 9}, {"source": 557, "target": 317, "value": 9}, {"source": 557, "target": 323, "value": 10}, {"source": 557, "target": 333, "value": 3}, {"source": 557, "target": 335, "value": 9}, {"source": 557, "target": 345, "value": 8}, {"source": 557, "target": 348, "value": 8}, {"source": 557, "target": 363, "value": 9}, {"source": 557, "target": 369, "value": 8}, {"source": 557, "target": 381, "value": 8}, {"source": 557, "target": 383, "value": 4}, {"source": 557, "target": 398, "value": 8}, {"source": 557, "target": 405, "value": 4}, {"source": 557, "target": 407, "value": 4}, {"source": 557, "target": 423, "value": 9}, {"source": 557, "target": 467, "value": 9}, {"source": 557, "target": 473, "value": 9}, {"source": 557, "target": 483, "value": 4}, {"source": 557, "target": 498, "value": 8}, {"source": 557, "target": 503, "value": 7}, {"source": 557, "target": 519, "value": 8}, {"source": 557, "target": 533, "value": 2}, {"source": 557, "target": 545, "value": 8}, {"source": 557, "target": 548, "value": 7}, {"source": 561, "target": 249, "value": 8}, {"source": 561, "target": 261, "value": 2}, {"source": 561, "target": 273, "value": 8}, {"source": 561, "target": 285, "value": 8}, {"source": 561, "target": 311, "value": 4}, {"source": 561, "target": 323, "value": 8}, {"source": 561, "target": 363, "value": 10}, {"source": 561, "target": 411, "value": 4}, {"source": 561, "target": 413, "value": 9}, {"source": 561, "target": 423, "value": 8}, {"source": 561, "target": 461, "value": 2}, {"source": 561, "target": 473, "value": 8}, {"source": 561, "target": 501, "value": 9}, {"source": 561, "target": 549, "value": 8}, {"source": 563, "target": 225, "value": 8}, {"source": 563, "target": 228, "value": 9}, {"source": 563, "target": 263, "value": 2}, {"source": 563, "target": 275, "value": 8}, {"source": 563, "target": 278, "value": 9}, {"source": 563, "target": 287, "value": 8}, {"source": 563, "target": 309, "value": 9}, {"source": 563, "target": 321, "value": 8}, {"source": 563, "target": 351, "value": 8}, {"source": 563, "target": 363, "value": 2}, {"source": 563, "target": 365, "value": 9}, {"source": 563, "target": 375, "value": 8}, {"source": 563, "target": 378, "value": 9}, {"source": 563, "target": 401, "value": 8}, {"source": 563, "target": 405, "value": 9}, {"source": 563, "target": 413, "value": 1}, {"source": 563, "target": 425, "value": 8}, {"source": 563, "target": 428, "value": 9}, {"source": 563, "target": 437, "value": 8}, {"source": 563, "target": 453, "value": 9}, {"source": 563, "target": 455, "value": 10}, {"source": 563, "target": 459, "value": 9}, {"source": 563, "target": 461, "value": 8}, {"source": 563, "target": 489, "value": 8}, {"source": 563, "target": 497, "value": 4}, {"source": 563, "target": 501, "value": 8}, {"source": 563, "target": 513, "value": 1}, {"source": 563, "target": 525, "value": 8}, {"source": 563, "target": 528, "value": 8}, {"source": 563, "target": 549, "value": 8}, {"source": 573, "target": 219, "value": 9}, {"source": 573, "target": 221, "value": 2}, {"source": 573, "target": 227, "value": 8}, {"source": 573, "target": 233, "value": 8}, {"source": 573, "target": 248, "value": 8}, {"source": 573, "target": 249, "value": 8}, {"source": 573, "target": 251, "value": 4}, {"source": 573, "target": 257, "value": 8}, {"source": 573, "target": 261, "value": 8}, {"source": 573, "target": 273, "value": 1}, {"source": 573, "target": 279, "value": 8}, {"source": 573, "target": 285, "value": 8}, {"source": 573, "target": 293, "value": 10}, {"source": 573, "target": 309, "value": 8}, {"source": 573, "target": 317, "value": 10}, {"source": 573, "target": 321, "value": 2}, {"source": 573, "target": 323, "value": 1}, {"source": 573, "target": 333, "value": 9}, {"source": 573, "target": 335, "value": 8}, {"source": 573, "target": 347, "value": 4}, {"source": 573, "target": 348, "value": 8}, {"source": 573, "target": 351, "value": 9}, {"source": 573, "target": 365, "value": 4}, {"source": 573, "target": 369, "value": 9}, {"source": 573, "target": 371, "value": 9}, {"source": 573, "target": 377, "value": 4}, {"source": 573, "target": 383, "value": 9}, {"source": 573, "target": 398, "value": 8}, {"source": 573, "target": 399, "value": 8}, {"source": 573, "target": 401, "value": 9}, {"source": 573, "target": 413, "value": 9}, {"source": 573, "target": 423, "value": 1}, {"source": 573, "target": 435, "value": 8}, {"source": 573, "target": 461, "value": 8}, {"source": 573, "target": 465, "value": 8}, {"source": 573, "target": 471, "value": 9}, {"source": 573, "target": 473, "value": 1}, {"source": 573, "target": 483, "value": 8}, {"source": 573, "target": 485, "value": 8}, {"source": 573, "target": 497, "value": 4}, {"source": 573, "target": 498, "value": 8}, {"source": 573, "target": 501, "value": 9}, {"source": 573, "target": 503, "value": 7}, {"source": 573, "target": 519, "value": 9}, {"source": 573, "target": 521, "value": 4}, {"source": 573, "target": 525, "value": 4}, {"source": 573, "target": 527, "value": 7}, {"source": 573, "target": 531, "value": 7}, {"source": 573, "target": 533, "value": 9}, {"source": 573, "target": 545, "value": 9}, {"source": 573, "target": 548, "value": 8}, {"source": 573, "target": 549, "value": 8}, {"source": 573, "target": 551, "value": 9}, {"source": 573, "target": 557, "value": 8}, {"source": 573, "target": 561, "value": 8}, {"source": 575, "target": 225, "value": 4}, {"source": 575, "target": 228, "value": 8}, {"source": 575, "target": 275, "value": 4}, {"source": 575, "target": 278, "value": 8}, {"source": 575, "target": 321, "value": 8}, {"source": 575, "target": 365, "value": 10}, {"source": 575, "target": 375, "value": 4}, {"source": 575, "target": 377, "value": 10}, {"source": 575, "target": 378, "value": 8}, {"source": 575, "target": 413, "value": 8}, {"source": 575, "target": 425, "value": 4}, {"source": 575, "target": 428, "value": 8}, {"source": 575, "target": 513, "value": 8}, {"source": 575, "target": 525, "value": 3}, {"source": 575, "target": 528, "value": 8}, {"source": 575, "target": 563, "value": 8}, {"source": 578, "target": 225, "value": 8}, {"source": 578, "target": 228, "value": 4}, {"source": 578, "target": 249, "value": 8}, {"source": 578, "target": 275, "value": 8}, {"source": 578, "target": 278, "value": 4}, {"source": 578, "target": 303, "value": 10}, {"source": 578, "target": 321, "value": 8}, {"source": 578, "target": 353, "value": 10}, {"source": 578, "target": 365, "value": 10}, {"source": 578, "target": 375, "value": 8}, {"source": 578, "target": 378, "value": 4}, {"source": 578, "target": 399, "value": 8}, {"source": 578, "target": 413, "value": 8}, {"source": 578, "target": 425, "value": 8}, {"source": 578, "target": 428, "value": 4}, {"source": 578, "target": 437, "value": 8}, {"source": 578, "target": 453, "value": 9}, {"source": 578, "target": 503, "value": 8}, {"source": 578, "target": 513, "value": 8}, {"source": 578, "target": 525, "value": 7}, {"source": 578, "target": 528, "value": 3}, {"source": 578, "target": 545, "value": 8}, {"source": 578, "target": 549, "value": 8}, {"source": 578, "target": 563, "value": 9}, {"source": 578, "target": 575, "value": 8}, {"source": 579, "target": 221, "value": 11}, {"source": 579, "target": 233, "value": 12}, {"source": 579, "target": 279, "value": 4}, {"source": 579, "target": 317, "value": 8}, {"source": 579, "target": 333, "value": 11}, {"source": 579, "target": 383, "value": 10}, {"source": 579, "target": 429, "value": 4}, {"source": 579, "target": 483, "value": 10}, {"source": 579, "target": 531, "value": 9}, {"source": 579, "target": 533, "value": 9}, {"source": 581, "target": 221, "value": 11}, {"source": 581, "target": 231, "value": 4}, {"source": 581, "target": 243, "value": 8}, {"source": 581, "target": 273, "value": 11}, {"source": 581, "target": 281, "value": 2}, {"source": 581, "target": 291, "value": 8}, {"source": 581, "target": 293, "value": 8}, {"source": 581, "target": 333, "value": 9}, {"source": 581, "target": 341, "value": 8}, {"source": 581, "target": 369, "value": 8}, {"source": 581, "target": 377, "value": 8}, {"source": 581, "target": 381, "value": 2}, {"source": 581, "target": 393, "value": 8}, {"source": 581, "target": 398, "value": 9}, {"source": 581, "target": 429, "value": 8}, {"source": 581, "target": 431, "value": 2}, {"source": 581, "target": 441, "value": 9}, {"source": 581, "target": 443, "value": 8}, {"source": 581, "target": 489, "value": 7}, {"source": 581, "target": 491, "value": 8}, {"source": 581, "target": 519, "value": 8}, {"source": 581, "target": 531, "value": 3}, {"source": 581, "target": 533, "value": 4}, {"source": 581, "target": 543, "value": 8}, {"source": 585, "target": 243, "value": 9}, {"source": 585, "target": 273, "value": 8}, {"source": 585, "target": 281, "value": 8}, {"source": 585, "target": 285, "value": 2}, {"source": 585, "target": 293, "value": 2}, {"source": 585, "target": 333, "value": 8}, {"source": 585, "target": 335, "value": 4}, {"source": 585, "target": 347, "value": 4}, {"source": 585, "target": 377, "value": 10}, {"source": 585, "target": 381, "value": 8}, {"source": 585, "target": 393, "value": 9}, {"source": 585, "target": 398, "value": 8}, {"source": 585, "target": 431, "value": 8}, {"source": 585, "target": 435, "value": 4}, {"source": 585, "target": 437, "value": 4}, {"source": 585, "target": 443, "value": 9}, {"source": 585, "target": 473, "value": 8}, {"source": 585, "target": 485, "value": 2}, {"source": 585, "target": 489, "value": 9}, {"source": 585, "target": 497, "value": 4}, {"source": 585, "target": 525, "value": 9}, {"source": 585, "target": 531, "value": 7}, {"source": 585, "target": 533, "value": 7}, {"source": 585, "target": 543, "value": 9}, {"source": 585, "target": 573, "value": 8}, {"source": 585, "target": 581, "value": 8}, {"source": 587, "target": 225, "value": 9}, {"source": 587, "target": 263, "value": 9}, {"source": 587, "target": 285, "value": 9}, {"source": 587, "target": 287, "value": 4}, {"source": 587, "target": 291, "value": 8}, {"source": 587, "target": 335, "value": 9}, {"source": 587, "target": 341, "value": 8}, {"source": 587, "target": 363, "value": 9}, {"source": 587, "target": 381, "value": 10}, {"source": 587, "target": 413, "value": 4}, {"source": 587, "target": 425, "value": 8}, {"source": 587, "target": 429, "value": 4}, {"source": 587, "target": 435, "value": 9}, {"source": 587, "target": 437, "value": 4}, {"source": 587, "target": 441, "value": 8}, {"source": 587, "target": 461, "value": 8}, {"source": 587, "target": 485, "value": 9}, {"source": 587, "target": 489, "value": 9}, {"source": 587, "target": 491, "value": 8}, {"source": 587, "target": 513, "value": 4}, {"source": 587, "target": 525, "value": 7}, {"source": 587, "target": 533, "value": 9}, {"source": 587, "target": 563, "value": 8}, {"source": 587, "target": 581, "value": 8}, {"source": 587, "target": 585, "value": 8}, {"source": 591, "target": 291, "value": 4}, {"source": 591, "target": 293, "value": 10}, {"source": 591, "target": 341, "value": 4}, {"source": 591, "target": 365, "value": 8}, {"source": 591, "target": 381, "value": 10}, {"source": 591, "target": 429, "value": 8}, {"source": 591, "target": 441, "value": 4}, {"source": 591, "target": 491, "value": 4}, {"source": 591, "target": 533, "value": 9}, {"source": 591, "target": 581, "value": 8}, {"source": 591, "target": 587, "value": 8}, {"source": 593, "target": 243, "value": 1}, {"source": 593, "target": 245, "value": 10}, {"source": 593, "target": 251, "value": 8}, {"source": 593, "target": 255, "value": 8}, {"source": 593, "target": 281, "value": 8}, {"source": 593, "target": 293, "value": 1}, {"source": 593, "target": 305, "value": 8}, {"source": 593, "target": 317, "value": 2}, {"source": 593, "target": 333, "value": 10}, {"source": 593, "target": 335, "value": 9}, {"source": 593, "target": 339, "value": 9}, {"source": 593, "target": 377, "value": 8}, {"source": 593, "target": 381, "value": 8}, {"source": 593, "target": 393, "value": 1}, {"source": 593, "target": 398, "value": 9}, {"source": 593, "target": 405, "value": 8}, {"source": 593, "target": 429, "value": 8}, {"source": 593, "target": 431, "value": 9}, {"source": 593, "target": 443, "value": 1}, {"source": 593, "target": 455, "value": 8}, {"source": 593, "target": 467, "value": 4}, {"source": 593, "target": 489, "value": 2}, {"source": 593, "target": 533, "value": 8}, {"source": 593, "target": 543, "value": 1}, {"source": 593, "target": 545, "value": 9}, {"source": 593, "target": 555, "value": 8}, {"source": 593, "target": 581, "value": 8}, {"source": 593, "target": 585, "value": 8}, {"source": 603, "target": 227, "value": 8}, {"source": 603, "target": 228, "value": 8}, {"source": 603, "target": 249, "value": 9}, {"source": 603, "target": 278, "value": 8}, {"source": 603, "target": 279, "value": 9}, {"source": 603, "target": 293, "value": 10}, {"source": 603, "target": 303, "value": 2}, {"source": 603, "target": 341, "value": 8}, {"source": 603, "target": 353, "value": 1}, {"source": 603, "target": 377, "value": 8}, {"source": 603, "target": 378, "value": 8}, {"source": 603, "target": 399, "value": 9}, {"source": 603, "target": 405, "value": 9}, {"source": 603, "target": 428, "value": 8}, {"source": 603, "target": 437, "value": 8}, {"source": 603, "target": 441, "value": 8}, {"source": 603, "target": 453, "value": 1}, {"source": 603, "target": 503, "value": 2}, {"source": 603, "target": 527, "value": 8}, {"source": 603, "target": 528, "value": 8}, {"source": 603, "target": 545, "value": 9}, {"source": 603, "target": 549, "value": 9}, {"source": 603, "target": 578, "value": 8}, {"source": 605, "target": 215, "value": 8}, {"source": 605, "target": 231, "value": 8}, {"source": 605, "target": 245, "value": 10}, {"source": 605, "target": 251, "value": 8}, {"source": 605, "target": 255, "value": 2}, {"source": 605, "target": 281, "value": 8}, {"source": 605, "target": 293, "value": 8}, {"source": 605, "target": 305, "value": 2}, {"source": 605, "target": 315, "value": 8}, {"source": 605, "target": 317, "value": 8}, {"source": 605, "target": 353, "value": 8}, {"source": 605, "target": 363, "value": 8}, {"source": 605, "target": 365, "value": 8}, {"source": 605, "target": 377, "value": 11}, {"source": 605, "target": 381, "value": 8}, {"source": 605, "target": 393, "value": 8}, {"source": 605, "target": 405, "value": 2}, {"source": 605, "target": 429, "value": 8}, {"source": 605, "target": 431, "value": 8}, {"source": 605, "target": 453, "value": 4}, {"source": 605, "target": 455, "value": 4}, {"source": 605, "target": 461, "value": 8}, {"source": 605, "target": 465, "value": 9}, {"source": 605, "target": 467, "value": 8}, {"source": 605, "target": 515, "value": 8}, {"source": 605, "target": 531, "value": 8}, {"source": 605, "target": 555, "value": 3}, {"source": 605, "target": 557, "value": 8}, {"source": 605, "target": 581, "value": 8}, {"source": 605, "target": 593, "value": 8}, {"source": 609, "target": 221, "value": 8}, {"source": 609, "target": 261, "value": 10}, {"source": 609, "target": 263, "value": 12}, {"source": 609, "target": 309, "value": 2}, {"source": 609, "target": 321, "value": 8}, {"source": 609, "target": 333, "value": 8}, {"source": 609, "target": 351, "value": 10}, {"source": 609, "target": 363, "value": 11}, {"source": 609, "target": 371, "value": 8}, {"source": 609, "target": 405, "value": 8}, {"source": 609, "target": 413, "value": 10}, {"source": 609, "target": 459, "value": 4}, {"source": 609, "target": 461, "value": 4}, {"source": 609, "target": 471, "value": 8}, {"source": 609, "target": 497, "value": 4}, {"source": 609, "target": 513, "value": 10}, {"source": 609, "target": 521, "value": 8}, {"source": 609, "target": 549, "value": 8}, {"source": 609, "target": 563, "value": 9}, {"source": 611, "target": 219, "value": 8}, {"source": 611, "target": 248, "value": 8}, {"source": 611, "target": 249, "value": 9}, {"source": 611, "target": 257, "value": 8}, {"source": 611, "target": 261, "value": 4}, {"source": 611, "target": 273, "value": 9}, {"source": 611, "target": 285, "value": 8}, {"source": 611, "target": 309, "value": 8}, {"source": 611, "target": 311, "value": 4}, {"source": 611, "target": 323, "value": 9}, {"source": 611, "target": 348, "value": 8}, {"source": 611, "target": 363, "value": 10}, {"source": 611, "target": 369, "value": 9}, {"source": 611, "target": 398, "value": 8}, {"source": 611, "target": 411, "value": 4}, {"source": 611, "target": 423, "value": 9}, {"source": 611, "target": 461, "value": 4}, {"source": 611, "target": 473, "value": 8}, {"source": 611, "target": 498, "value": 8}, {"source": 611, "target": 501, "value": 9}, {"source": 611, "target": 519, "value": 8}, {"source": 611, "target": 548, "value": 8}, {"source": 611, "target": 549, "value": 7}, {"source": 611, "target": 557, "value": 8}, {"source": 611, "target": 561, "value": 3}, {"source": 611, "target": 573, "value": 8}, {"source": 615, "target": 215, "value": 4}, {"source": 615, "target": 219, "value": 8}, {"source": 615, "target": 248, "value": 9}, {"source": 615, "target": 257, "value": 9}, {"source": 615, "target": 263, "value": 8}, {"source": 615, "target": 273, "value": 11}, {"source": 615, "target": 309, "value": 10}, {"source": 615, "target": 315, "value": 4}, {"source": 615, "target": 323, "value": 11}, {"source": 615, "target": 348, "value": 9}, {"source": 615, "target": 353, "value": 8}, {"source": 615, "target": 363, "value": 9}, {"source": 615, "target": 365, "value": 4}, {"source": 615, "target": 369, "value": 8}, {"source": 615, "target": 398, "value": 9}, {"source": 615, "target": 405, "value": 10}, {"source": 615, "target": 413, "value": 9}, {"source": 615, "target": 423, "value": 10}, {"source": 615, "target": 453, "value": 4}, {"source": 615, "target": 461, "value": 8}, {"source": 615, "target": 465, "value": 4}, {"source": 615, "target": 473, "value": 10}, {"source": 615, "target": 498, "value": 8}, {"source": 615, "target": 513, "value": 9}, {"source": 615, "target": 515, "value": 4}, {"source": 615, "target": 519, "value": 8}, {"source": 615, "target": 548, "value": 8}, {"source": 615, "target": 557, "value": 8}, {"source": 615, "target": 563, "value": 8}, {"source": 615, "target": 573, "value": 9}, {"source": 615, "target": 605, "value": 8}, {"source": 615, "target": 611, "value": 8}, {"source": 617, "target": 221, "value": 10}, {"source": 617, "target": 233, "value": 11}, {"source": 617, "target": 243, "value": 9}, {"source": 617, "target": 255, "value": 10}, {"source": 617, "target": 279, "value": 8}, {"source": 617, "target": 293, "value": 4}, {"source": 617, "target": 305, "value": 8}, {"source": 617, "target": 317, "value": 2}, {"source": 617, "target": 333, "value": 10}, {"source": 617, "target": 363, "value": 8}, {"source": 617, "target": 383, "value": 10}, {"source": 617, "target": 393, "value": 4}, {"source": 617, "target": 405, "value": 8}, {"source": 617, "target": 429, "value": 8}, {"source": 617, "target": 443, "value": 9}, {"source": 617, "target": 467, "value": 4}, {"source": 617, "target": 483, "value": 9}, {"source": 617, "target": 489, "value": 10}, {"source": 617, "target": 533, "value": 9}, {"source": 617, "target": 543, "value": 8}, {"source": 617, "target": 557, "value": 8}, {"source": 617, "target": 579, "value": 8}, {"source": 617, "target": 593, "value": 4}, {"source": 617, "target": 605, "value": 8}, {"source": 621, "target": 221, "value": 1}, {"source": 621, "target": 231, "value": 8}, {"source": 621, "target": 233, "value": 8}, {"source": 621, "target": 261, "value": 10}, {"source": 621, "target": 273, "value": 3}, {"source": 621, "target": 279, "value": 8}, {"source": 621, "target": 281, "value": 8}, {"source": 621, "target": 293, "value": 12}, {"source": 621, "target": 309, "value": 8}, {"source": 621, "target": 321, "value": 2}, {"source": 621, "target": 323, "value": 9}, {"source": 621, "target": 333, "value": 8}, {"source": 621, "target": 347, "value": 8}, {"source": 621, "target": 369, "value": 8}, {"source": 621, "target": 371, "value": 4}, {"source": 621, "target": 377, "value": 8}, {"source": 621, "target": 381, "value": 8}, {"source": 621, "target": 383, "value": 8}, {"source": 621, "target": 423, "value": 8}, {"source": 621, "target": 431, "value": 8}, {"source": 621, "target": 461, "value": 9}, {"source": 621, "target": 471, "value": 4}, {"source": 621, "target": 473, "value": 4}, {"source": 621, "target": 483, "value": 8}, {"source": 621, "target": 497, "value": 8}, {"source": 621, "target": 519, "value": 8}, {"source": 621, "target": 521, "value": 2}, {"source": 621, "target": 531, "value": 8}, {"source": 621, "target": 533, "value": 8}, {"source": 621, "target": 573, "value": 2}, {"source": 621, "target": 581, "value": 8}, {"source": 621, "target": 609, "value": 8}, {"source": 623, "target": 219, "value": 9}, {"source": 623, "target": 221, "value": 8}, {"source": 623, "target": 248, "value": 8}, {"source": 623, "target": 257, "value": 8}, {"source": 623, "target": 261, "value": 9}, {"source": 623, "target": 273, "value": 1}, {"source": 623, "target": 309, "value": 8}, {"source": 623, "target": 323, "value": 2}, {"source": 623, "target": 347, "value": 8}, {"source": 623, "target": 348, "value": 8}, {"source": 623, "target": 369, "value": 9}, {"source": 623, "target": 398, "value": 8}, {"source": 623, "target": 413, "value": 10}, {"source": 623, "target": 423, "value": 2}, {"source": 623, "target": 461, "value": 8}, {"source": 623, "target": 473, "value": 1}, {"source": 623, "target": 497, "value": 8}, {"source": 623, "target": 498, "value": 8}, {"source": 623, "target": 519, "value": 9}, {"source": 623, "target": 545, "value": 10}, {"source": 623, "target": 548, "value": 8}, {"source": 623, "target": 557, "value": 8}, {"source": 623, "target": 561, "value": 7}, {"source": 623, "target": 573, "value": 1}, {"source": 623, "target": 611, "value": 7}, {"source": 623, "target": 615, "value": 8}, {"source": 623, "target": 621, "value": 7}, {"source": 633, "target": 221, "value": 4}, {"source": 633, "target": 233, "value": 0}, {"source": 633, "target": 245, "value": 8}, {"source": 633, "target": 248, "value": 8}, {"source": 633, "target": 257, "value": 4}, {"source": 633, "target": 279, "value": 4}, {"source": 633, "target": 285, "value": 10}, {"source": 633, "target": 317, "value": 8}, {"source": 633, "target": 321, "value": 4}, {"source": 633, "target": 333, "value": 1}, {"source": 633, "target": 335, "value": 10}, {"source": 633, "target": 345, "value": 8}, {"source": 633, "target": 348, "value": 8}, {"source": 633, "target": 381, "value": 4}, {"source": 633, "target": 383, "value": 1}, {"source": 633, "target": 395, "value": 8}, {"source": 633, "target": 398, "value": 8}, {"source": 633, "target": 405, "value": 11}, {"source": 633, "target": 407, "value": 4}, {"source": 633, "target": 429, "value": 9}, {"source": 633, "target": 483, "value": 1}, {"source": 633, "target": 485, "value": 9}, {"source": 633, "target": 495, "value": 8}, {"source": 633, "target": 498, "value": 8}, {"source": 633, "target": 521, "value": 8}, {"source": 633, "target": 533, "value": 1}, {"source": 633, "target": 545, "value": 8}, {"source": 633, "target": 548, "value": 8}, {"source": 633, "target": 557, "value": 2}, {"source": 633, "target": 573, "value": 8}, {"source": 633, "target": 579, "value": 9}, {"source": 633, "target": 617, "value": 8}, {"source": 633, "target": 621, "value": 8}, {"source": 635, "target": 273, "value": 9}, {"source": 635, "target": 285, "value": 4}, {"source": 635, "target": 335, "value": 4}, {"source": 635, "target": 377, "value": 10}, {"source": 635, "target": 435, "value": 4}, {"source": 635, "target": 473, "value": 8}, {"source": 635, "target": 485, "value": 4}, {"source": 635, "target": 525, "value": 9}, {"source": 635, "target": 531, "value": 8}, {"source": 635, "target": 573, "value": 8}, {"source": 635, "target": 585, "value": 3}, {"source": 635, "target": 587, "value": 9}, {"source": 639, "target": 243, "value": 12}, {"source": 639, "target": 293, "value": 11}, {"source": 639, "target": 335, "value": 8}, {"source": 639, "target": 339, "value": 4}, {"source": 639, "target": 377, "value": 8}, {"source": 639, "target": 393, "value": 11}, {"source": 639, "target": 413, "value": 8}, {"source": 639, "target": 429, "value": 10}, {"source": 639, "target": 443, "value": 10}, {"source": 639, "target": 461, "value": 10}, {"source": 639, "target": 489, "value": 4}, {"source": 639, "target": 543, "value": 10}, {"source": 639, "target": 593, "value": 9}, {"source": 641, "target": 291, "value": 4}, {"source": 641, "target": 293, "value": 5}, {"source": 641, "target": 303, "value": 8}, {"source": 641, "target": 341, "value": 2}, {"source": 641, "target": 353, "value": 8}, {"source": 641, "target": 365, "value": 8}, {"source": 641, "target": 381, "value": 10}, {"source": 641, "target": 429, "value": 8}, {"source": 641, "target": 441, "value": 2}, {"source": 641, "target": 453, "value": 8}, {"source": 641, "target": 491, "value": 4}, {"source": 641, "target": 503, "value": 8}, {"source": 641, "target": 533, "value": 9}, {"source": 641, "target": 581, "value": 8}, {"source": 641, "target": 587, "value": 7}, {"source": 641, "target": 591, "value": 3}, {"source": 641, "target": 603, "value": 8}, {"source": 645, "target": 233, "value": 4}, {"source": 645, "target": 245, "value": 2}, {"source": 645, "target": 248, "value": 8}, {"source": 645, "target": 251, "value": 8}, {"source": 645, "target": 255, "value": 8}, {"source": 645, "target": 257, "value": 8}, {"source": 645, "target": 285, "value": 10}, {"source": 645, "target": 293, "value": 8}, {"source": 645, "target": 305, "value": 8}, {"source": 645, "target": 333, "value": 8}, {"source": 645, "target": 345, "value": 2}, {"source": 645, "target": 348, "value": 8}, {"source": 645, "target": 393, "value": 8}, {"source": 645, "target": 395, "value": 4}, {"source": 645, "target": 398, "value": 8}, {"source": 645, "target": 405, "value": 8}, {"source": 645, "target": 407, "value": 8}, {"source": 645, "target": 455, "value": 8}, {"source": 645, "target": 485, "value": 9}, {"source": 645, "target": 495, "value": 4}, {"source": 645, "target": 498, "value": 8}, {"source": 645, "target": 503, "value": 8}, {"source": 645, "target": 533, "value": 8}, {"source": 645, "target": 545, "value": 2}, {"source": 645, "target": 548, "value": 8}, {"source": 645, "target": 555, "value": 8}, {"source": 645, "target": 557, "value": 8}, {"source": 645, "target": 593, "value": 7}, {"source": 645, "target": 605, "value": 8}, {"source": 645, "target": 633, "value": 8}, {"source": 647, "target": 221, "value": 9}, {"source": 647, "target": 273, "value": 4}, {"source": 647, "target": 285, "value": 9}, {"source": 647, "target": 293, "value": 8}, {"source": 647, "target": 323, "value": 9}, {"source": 647, "target": 347, "value": 4}, {"source": 647, "target": 423, "value": 9}, {"source": 647, "target": 437, "value": 9}, {"source": 647, "target": 473, "value": 4}, {"source": 647, "target": 485, "value": 8}, {"source": 647, "target": 497, "value": 4}, {"source": 647, "target": 573, "value": 4}, {"source": 647, "target": 585, "value": 4}, {"source": 647, "target": 621, "value": 8}, {"source": 647, "target": 623, "value": 8}, {"source": 648, "target": 219, "value": 8}, {"source": 648, "target": 233, "value": 4}, {"source": 648, "target": 245, "value": 8}, {"source": 648, "target": 248, "value": 4}, {"source": 648, "target": 257, "value": 8}, {"source": 648, "target": 273, "value": 11}, {"source": 648, "target": 285, "value": 10}, {"source": 648, "target": 309, "value": 9}, {"source": 648, "target": 323, "value": 10}, {"source": 648, "target": 333, "value": 9}, {"source": 648, "target": 345, "value": 8}, {"source": 648, "target": 348, "value": 4}, {"source": 648, "target": 369, "value": 8}, {"source": 648, "target": 395, "value": 8}, {"source": 648, "target": 398, "value": 4}, {"source": 648, "target": 423, "value": 10}, {"source": 648, "target": 473, "value": 9}, {"source": 648, "target": 485, "value": 9}, {"source": 648, "target": 495, "value": 8}, {"source": 648, "target": 498, "value": 4}, {"source": 648, "target": 519, "value": 8}, {"source": 648, "target": 533, "value": 8}, {"source": 648, "target": 545, "value": 8}, {"source": 648, "target": 548, "value": 4}, {"source": 648, "target": 557, "value": 7}, {"source": 648, "target": 573, "value": 8}, {"source": 648, "target": 611, "value": 8}, {"source": 648, "target": 615, "value": 8}, {"source": 648, "target": 623, "value": 8}, {"source": 648, "target": 633, "value": 8}, {"source": 648, "target": 645, "value": 8}, {"source": 651, "target": 251, "value": 4}, {"source": 651, "target": 263, "value": 8}, {"source": 651, "target": 341, "value": 10}, {"source": 651, "target": 351, "value": 4}, {"source": 651, "target": 363, "value": 8}, {"source": 651, "target": 401, "value": 2}, {"source": 651, "target": 413, "value": 8}, {"source": 651, "target": 453, "value": 8}, {"source": 651, "target": 455, "value": 9}, {"source": 651, "target": 489, "value": 8}, {"source": 651, "target": 501, "value": 2}, {"source": 651, "target": 503, "value": 9}, {"source": 651, "target": 513, "value": 8}, {"source": 651, "target": 525, "value": 8}, {"source": 651, "target": 551, "value": 4}, {"source": 651, "target": 563, "value": 8}, {"source": 651, "target": 573, "value": 9}, {"source": 653, "target": 215, "value": 8}, {"source": 653, "target": 227, "value": 4}, {"source": 653, "target": 228, "value": 8}, {"source": 653, "target": 249, "value": 8}, {"source": 653, "target": 263, "value": 8}, {"source": 653, "target": 278, "value": 8}, {"source": 653, "target": 279, "value": 3}, {"source": 653, "target": 293, "value": 10}, {"source": 653, "target": 303, "value": 1}, {"source": 653, "target": 315, "value": 8}, {"source": 653, "target": 341, "value": 8}, {"source": 653, "target": 353, "value": 1}, {"source": 653, "target": 363, "value": 8}, {"source": 653, "target": 365, "value": 8}, {"source": 653, "target": 377, "value": 4}, {"source": 653, "target": 378, "value": 8}, {"source": 653, "target": 399, "value": 9}, {"source": 653, "target": 401, "value": 8}, {"source": 653, "target": 405, "value": 4}, {"source": 653, "target": 413, "value": 8}, {"source": 653, "target": 428, "value": 8}, {"source": 653, "target": 429, "value": 8}, {"source": 653, "target": 437, "value": 8}, {"source": 653, "target": 441, "value": 8}, {"source": 653, "target": 453, "value": 0}, {"source": 653, "target": 455, "value": 8}, {"source": 653, "target": 461, "value": 8}, {"source": 653, "target": 465, "value": 8}, {"source": 653, "target": 501, "value": 8}, {"source": 653, "target": 503, "value": 1}, {"source": 653, "target": 513, "value": 8}, {"source": 653, "target": 515, "value": 8}, {"source": 653, "target": 527, "value": 4}, {"source": 653, "target": 528, "value": 8}, {"source": 653, "target": 531, "value": 8}, {"source": 653, "target": 545, "value": 9}, {"source": 653, "target": 549, "value": 9}, {"source": 653, "target": 563, "value": 8}, {"source": 653, "target": 578, "value": 8}, {"source": 653, "target": 579, "value": 8}, {"source": 653, "target": 603, "value": 1}, {"source": 653, "target": 605, "value": 8}, {"source": 653, "target": 615, "value": 8}, {"source": 653, "target": 641, "value": 8}, {"source": 653, "target": 651, "value": 7}, {"source": 663, "target": 221, "value": 8}, {"source": 663, "target": 261, "value": 9}, {"source": 663, "target": 263, "value": 2}, {"source": 663, "target": 287, "value": 8}, {"source": 663, "target": 309, "value": 4}, {"source": 663, "target": 321, "value": 8}, {"source": 663, "target": 351, "value": 9}, {"source": 663, "target": 363, "value": 2}, {"source": 663, "target": 371, "value": 8}, {"source": 663, "target": 401, "value": 8}, {"source": 663, "target": 405, "value": 8}, {"source": 663, "target": 413, "value": 1}, {"source": 663, "target": 437, "value": 8}, {"source": 663, "target": 453, "value": 9}, {"source": 663, "target": 455, "value": 10}, {"source": 663, "target": 459, "value": 9}, {"source": 663, "target": 461, "value": 4}, {"source": 663, "target": 471, "value": 8}, {"source": 663, "target": 489, "value": 8}, {"source": 663, "target": 497, "value": 4}, {"source": 663, "target": 501, "value": 8}, {"source": 663, "target": 513, "value": 1}, {"source": 663, "target": 521, "value": 9}, {"source": 663, "target": 549, "value": 8}, {"source": 663, "target": 563, "value": 2}, {"source": 663, "target": 587, "value": 8}, {"source": 663, "target": 609, "value": 4}, {"source": 663, "target": 615, "value": 8}, {"source": 663, "target": 621, "value": 8}, {"source": 663, "target": 651, "value": 8}, {"source": 663, "target": 653, "value": 8}, {"source": 665, "target": 215, "value": 4}, {"source": 665, "target": 227, "value": 8}, {"source": 665, "target": 315, "value": 4}, {"source": 665, "target": 317, "value": 10}, {"source": 665, "target": 353, "value": 8}, {"source": 665, "target": 365, "value": 2}, {"source": 665, "target": 377, "value": 8}, {"source": 665, "target": 405, "value": 10}, {"source": 665, "target": 453, "value": 4}, {"source": 665, "target": 461, "value": 8}, {"source": 665, "target": 465, "value": 2}, {"source": 665, "target": 515, "value": 4}, {"source": 665, "target": 527, "value": 8}, {"source": 665, "target": 573, "value": 7}, {"source": 665, "target": 605, "value": 8}, {"source": 665, "target": 615, "value": 3}, {"source": 665, "target": 653, "value": 8}, {"source": 669, "target": 219, "value": 4}, {"source": 669, "target": 221, "value": 5}, {"source": 669, "target": 231, "value": 8}, {"source": 669, "target": 233, "value": 10}, {"source": 669, "target": 245, "value": 8}, {"source": 669, "target": 248, "value": 9}, {"source": 669, "target": 257, "value": 9}, {"source": 669, "target": 273, "value": 5}, {"source": 669, "target": 279, "value": 8}, {"source": 669, "target": 281, "value": 8}, {"source": 669, "target": 309, "value": 10}, {"source": 669, "target": 317, "value": 8}, {"source": 669, "target": 321, "value": 10}, {"source": 669, "target": 323, "value": 11}, {"source": 669, "target": 333, "value": 10}, {"source": 669, "target": 345, "value": 8}, {"source": 669, "target": 348, "value": 9}, {"source": 669, "target": 369, "value": 2}, {"source": 669, "target": 377, "value": 8}, {"source": 669, "target": 381, "value": 8}, {"source": 669, "target": 383, "value": 9}, {"source": 669, "target": 395, "value": 8}, {"source": 669, "target": 398, "value": 9}, {"source": 669, "target": 423, "value": 10}, {"source": 669, "target": 429, "value": 8}, {"source": 669, "target": 431, "value": 8}, {"source": 669, "target": 473, "value": 10}, {"source": 669, "target": 483, "value": 9}, {"source": 669, "target": 495, "value": 8}, {"source": 669, "target": 498, "value": 8}, {"source": 669, "target": 519, "value": 2}, {"source": 669, "target": 531, "value": 8}, {"source": 669, "target": 533, "value": 8}, {"source": 669, "target": 545, "value": 8}, {"source": 669, "target": 548, "value": 8}, {"source": 669, "target": 557, "value": 8}, {"source": 669, "target": 573, "value": 9}, {"source": 669, "target": 579, "value": 8}, {"source": 669, "target": 581, "value": 8}, {"source": 669, "target": 611, "value": 9}, {"source": 669, "target": 615, "value": 7}, {"source": 669, "target": 617, "value": 7}, {"source": 669, "target": 621, "value": 8}, {"source": 669, "target": 623, "value": 9}, {"source": 669, "target": 633, "value": 8}, {"source": 669, "target": 645, "value": 8}, {"source": 669, "target": 648, "value": 7}, {"source": 671, "target": 219, "value": 8}, {"source": 671, "target": 221, "value": 4}, {"source": 671, "target": 225, "value": 8}, {"source": 671, "target": 228, "value": 8}, {"source": 671, "target": 261, "value": 10}, {"source": 671, "target": 275, "value": 8}, {"source": 671, "target": 278, "value": 8}, {"source": 671, "target": 293, "value": 12}, {"source": 671, "target": 309, "value": 8}, {"source": 671, "target": 321, "value": 2}, {"source": 671, "target": 365, "value": 10}, {"source": 671, "target": 369, "value": 8}, {"source": 671, "target": 371, "value": 4}, {"source": 671, "target": 375, "value": 8}, {"source": 671, "target": 378, "value": 8}, {"source": 671, "target": 413, "value": 8}, {"source": 671, "target": 425, "value": 8}, {"source": 671, "target": 428, "value": 8}, {"source": 671, "target": 461, "value": 9}, {"source": 671, "target": 471, "value": 4}, {"source": 671, "target": 513, "value": 8}, {"source": 671, "target": 519, "value": 8}, {"source": 671, "target": 521, "value": 4}, {"source": 671, "target": 525, "value": 8}, {"source": 671, "target": 528, "value": 8}, {"source": 671, "target": 563, "value": 9}, {"source": 671, "target": 573, "value": 9}, {"source": 671, "target": 575, "value": 7}, {"source": 671, "target": 578, "value": 8}, {"source": 671, "target": 609, "value": 7}, {"source": 671, "target": 621, "value": 3}, {"source": 671, "target": 663, "value": 8}, {"source": 671, "target": 669, "value": 7}, {"source": 675, "target": 225, "value": 4}, {"source": 675, "target": 228, "value": 8}, {"source": 675, "target": 275, "value": 4}, {"source": 675, "target": 278, "value": 8}, {"source": 675, "target": 321, "value": 8}, {"source": 675, "target": 365, "value": 10}, {"source": 675, "target": 375, "value": 4}, {"source": 675, "target": 377, "value": 10}, {"source": 675, "target": 378, "value": 8}, {"source": 675, "target": 413, "value": 8}, {"source": 675, "target": 425, "value": 4}, {"source": 675, "target": 428, "value": 8}, {"source": 675, "target": 513, "value": 8}, {"source": 675, "target": 525, "value": 4}, {"source": 675, "target": 528, "value": 8}, {"source": 675, "target": 563, "value": 8}, {"source": 675, "target": 575, "value": 3}, {"source": 675, "target": 578, "value": 8}, {"source": 675, "target": 671, "value": 7}, {"source": 677, "target": 225, "value": 8}, {"source": 677, "target": 227, "value": 4}, {"source": 677, "target": 243, "value": 11}, {"source": 677, "target": 279, "value": 10}, {"source": 677, "target": 287, "value": 8}, {"source": 677, "target": 293, "value": 11}, {"source": 677, "target": 303, "value": 9}, {"source": 677, "target": 317, "value": 10}, {"source": 677, "target": 335, "value": 8}, {"source": 677, "target": 339, "value": 8}, {"source": 677, "target": 353, "value": 4}, {"source": 677, "target": 365, "value": 4}, {"source": 677, "target": 377, "value": 2}, {"source": 677, "target": 393, "value": 10}, {"source": 677, "target": 425, "value": 8}, {"source": 677, "target": 429, "value": 4}, {"source": 677, "target": 437, "value": 8}, {"source": 677, "target": 443, "value": 10}, {"source": 677, "target": 453, "value": 4}, {"source": 677, "target": 465, "value": 8}, {"source": 677, "target": 489, "value": 8}, {"source": 677, "target": 503, "value": 9}, {"source": 677, "target": 525, "value": 8}, {"source": 677, "target": 527, "value": 4}, {"source": 677, "target": 543, "value": 9}, {"source": 677, "target": 573, "value": 8}, {"source": 677, "target": 587, "value": 8}, {"source": 677, "target": 593, "value": 9}, {"source": 677, "target": 603, "value": 8}, {"source": 677, "target": 639, "value": 8}, {"source": 677, "target": 653, "value": 4}, {"source": 677, "target": 665, "value": 8}, {"source": 678, "target": 225, "value": 8}, {"source": 678, "target": 228, "value": 4}, {"source": 678, "target": 249, "value": 8}, {"source": 678, "target": 261, "value": 8}, {"source": 678, "target": 273, "value": 8}, {"source": 678, "target": 275, "value": 8}, {"source": 678, "target": 278, "value": 4}, {"source": 678, "target": 303, "value": 10}, {"source": 678, "target": 321, "value": 8}, {"source": 678, "target": 323, "value": 8}, {"source": 678, "target": 353, "value": 10}, {"source": 678, "target": 365, "value": 10}, {"source": 678, "target": 375, "value": 8}, {"source": 678, "target": 378, "value": 4}, {"source": 678, "target": 399, "value": 8}, {"source": 678, "target": 413, "value": 4}, {"source": 678, "target": 423, "value": 8}, {"source": 678, "target": 425, "value": 8}, {"source": 678, "target": 428, "value": 4}, {"source": 678, "target": 437, "value": 8}, {"source": 678, "target": 453, "value": 9}, {"source": 678, "target": 461, "value": 8}, {"source": 678, "target": 473, "value": 8}, {"source": 678, "target": 503, "value": 9}, {"source": 678, "target": 513, "value": 8}, {"source": 678, "target": 525, "value": 8}, {"source": 678, "target": 528, "value": 4}, {"source": 678, "target": 545, "value": 8}, {"source": 678, "target": 549, "value": 8}, {"source": 678, "target": 561, "value": 8}, {"source": 678, "target": 563, "value": 9}, {"source": 678, "target": 573, "value": 8}, {"source": 678, "target": 575, "value": 8}, {"source": 678, "target": 578, "value": 4}, {"source": 678, "target": 603, "value": 8}, {"source": 678, "target": 623, "value": 8}, {"source": 678, "target": 653, "value": 8}, {"source": 678, "target": 671, "value": 7}, {"source": 678, "target": 675, "value": 8}, {"source": 681, "target": 221, "value": 11}, {"source": 681, "target": 231, "value": 4}, {"source": 681, "target": 243, "value": 8}, {"source": 681, "target": 273, "value": 11}, {"source": 681, "target": 281, "value": 2}, {"source": 681, "target": 293, "value": 8}, {"source": 681, "target": 333, "value": 10}, {"source": 681, "target": 369, "value": 8}, {"source": 681, "target": 377, "value": 8}, {"source": 681, "target": 381, "value": 2}, {"source": 681, "target": 393, "value": 8}, {"source": 681, "target": 398, "value": 9}, {"source": 681, "target": 431, "value": 2}, {"source": 681, "target": 443, "value": 8}, {"source": 681, "target": 489, "value": 8}, {"source": 681, "target": 519, "value": 9}, {"source": 681, "target": 531, "value": 4}, {"source": 681, "target": 533, "value": 9}, {"source": 681, "target": 543, "value": 8}, {"source": 681, "target": 581, "value": 2}, {"source": 681, "target": 585, "value": 9}, {"source": 681, "target": 593, "value": 8}, {"source": 681, "target": 605, "value": 8}, {"source": 681, "target": 621, "value": 8}, {"source": 681, "target": 669, "value": 8}, {"source": 683, "target": 221, "value": 4}, {"source": 683, "target": 233, "value": 1}, {"source": 683, "target": 257, "value": 8}, {"source": 683, "target": 279, "value": 4}, {"source": 683, "target": 317, "value": 8}, {"source": 683, "target": 321, "value": 4}, {"source": 683, "target": 333, "value": 1}, {"source": 683, "target": 335, "value": 10}, {"source": 683, "target": 381, "value": 8}, {"source": 683, "target": 383, "value": 2}, {"source": 683, "target": 405, "value": 11}, {"source": 683, "target": 407, "value": 8}, {"source": 683, "target": 429, "value": 9}, {"source": 683, "target": 483, "value": 2}, {"source": 683, "target": 521, "value": 8}, {"source": 683, "target": 533, "value": 1}, {"source": 683, "target": 557, "value": 4}, {"source": 683, "target": 573, "value": 9}, {"source": 683, "target": 579, "value": 9}, {"source": 683, "target": 617, "value": 8}, {"source": 683, "target": 621, "value": 7}, {"source": 683, "target": 633, "value": 1}, {"source": 683, "target": 669, "value": 8}, {"source": 693, "target": 243, "value": 1}, {"source": 693, "target": 245, "value": 10}, {"source": 693, "target": 251, "value": 8}, {"source": 693, "target": 255, "value": 8}, {"source": 693, "target": 281, "value": 9}, {"source": 693, "target": 293, "value": 0}, {"source": 693, "target": 303, "value": 8}, {"source": 693, "target": 305, "value": 8}, {"source": 693, "target": 317, "value": 2}, {"source": 693, "target": 333, "value": 10}, {"source": 693, "target": 335, "value": 8}, {"source": 693, "target": 339, "value": 8}, {"source": 693, "target": 341, "value": 8}, {"source": 693, "target": 353, "value": 8}, {"source": 693, "target": 377, "value": 8}, {"source": 693, "target": 381, "value": 8}, {"source": 693, "target": 393, "value": 1}, {"source": 693, "target": 398, "value": 9}, {"source": 693, "target": 405, "value": 8}, {"source": 693, "target": 429, "value": 8}, {"source": 693, "target": 431, "value": 9}, {"source": 693, "target": 441, "value": 8}, {"source": 693, "target": 443, "value": 1}, {"source": 693, "target": 453, "value": 8}, {"source": 693, "target": 455, "value": 8}, {"source": 693, "target": 467, "value": 4}, {"source": 693, "target": 489, "value": 2}, {"source": 693, "target": 503, "value": 8}, {"source": 693, "target": 533, "value": 9}, {"source": 693, "target": 543, "value": 1}, {"source": 693, "target": 545, "value": 9}, {"source": 693, "target": 555, "value": 8}, {"source": 693, "target": 581, "value": 8}, {"source": 693, "target": 585, "value": 9}, {"source": 693, "target": 593, "value": 1}, {"source": 693, "target": 603, "value": 8}, {"source": 693, "target": 605, "value": 8}, {"source": 693, "target": 617, "value": 4}, {"source": 693, "target": 639, "value": 8}, {"source": 693, "target": 641, "value": 7}, {"source": 693, "target": 645, "value": 8}, {"source": 693, "target": 653, "value": 8}, {"source": 693, "target": 677, "value": 8}, {"source": 693, "target": 681, "value": 8}, {"source": 695, "target": 233, "value": 4}, {"source": 695, "target": 245, "value": 2}, {"source": 695, "target": 248, "value": 8}, {"source": 695, "target": 257, "value": 8}, {"source": 695, "target": 285, "value": 10}, {"source": 695, "target": 333, "value": 8}, {"source": 695, "target": 345, "value": 2}, {"source": 695, "target": 348, "value": 8}, {"source": 695, "target": 395, "value": 4}, {"source": 695, "target": 398, "value": 8}, {"source": 695, "target": 407, "value": 8}, {"source": 695, "target": 485, "value": 9}, {"source": 695, "target": 495, "value": 4}, {"source": 695, "target": 498, "value": 8}, {"source": 695, "target": 503, "value": 8}, {"source": 695, "target": 533, "value": 8}, {"source": 695, "target": 545, "value": 2}, {"source": 695, "target": 548, "value": 8}, {"source": 695, "target": 557, "value": 8}, {"source": 695, "target": 633, "value": 7}, {"source": 695, "target": 645, "value": 2}, {"source": 695, "target": 648, "value": 8}, {"source": 695, "target": 669, "value": 8}, {"source": 698, "target": 219, "value": 8}, {"source": 698, "target": 233, "value": 4}, {"source": 698, "target": 245, "value": 8}, {"source": 698, "target": 248, "value": 4}, {"source": 698, "target": 257, "value": 8}, {"source": 698, "target": 273, "value": 11}, {"source": 698, "target": 285, "value": 11}, {"source": 698, "target": 309, "value": 10}, {"source": 698, "target": 323, "value": 10}, {"source": 698, "target": 333, "value": 9}, {"source": 698, "target": 345, "value": 8}, {"source": 698, "target": 348, "value": 4}, {"source": 698, "target": 369, "value": 8}, {"source": 698, "target": 395, "value": 8}, {"source": 698, "target": 398, "value": 4}, {"source": 698, "target": 423, "value": 10}, {"source": 698, "target": 473, "value": 9}, {"source": 698, "target": 485, "value": 10}, {"source": 698, "target": 495, "value": 8}, {"source": 698, "target": 498, "value": 4}, {"source": 698, "target": 519, "value": 8}, {"source": 698, "target": 533, "value": 8}, {"source": 698, "target": 545, "value": 8}, {"source": 698, "target": 548, "value": 4}, {"source": 698, "target": 557, "value": 8}, {"source": 698, "target": 573, "value": 9}, {"source": 698, "target": 611, "value": 8}, {"source": 698, "target": 615, "value": 8}, {"source": 698, "target": 623, "value": 8}, {"source": 698, "target": 633, "value": 8}, {"source": 698, "target": 645, "value": 7}, {"source": 698, "target": 648, "value": 3}, {"source": 698, "target": 669, "value": 8}, {"source": 698, "target": 695, "value": 8}, {"source": 699, "target": 221, "value": 8}, {"source": 699, "target": 228, "value": 9}, {"source": 699, "target": 249, "value": 4}, {"source": 699, "target": 251, "value": 10}, {"source": 699, "target": 273, "value": 4}, {"source": 699, "target": 278, "value": 9}, {"source": 699, "target": 293, "value": 10}, {"source": 699, "target": 303, "value": 4}, {"source": 699, "target": 321, "value": 11}, {"source": 699, "target": 323, "value": 8}, {"source": 699, "target": 341, "value": 8}, {"source": 699, "target": 347, "value": 8}, {"source": 699, "target": 353, "value": 4}, {"source": 699, "target": 378, "value": 9}, {"source": 699, "target": 399, "value": 4}, {"source": 699, "target": 423, "value": 8}, {"source": 699, "target": 428, "value": 9}, {"source": 699, "target": 437, "value": 8}, {"source": 699, "target": 441, "value": 8}, {"source": 699, "target": 453, "value": 4}, {"source": 699, "target": 473, "value": 4}, {"source": 699, "target": 497, "value": 8}, {"source": 699, "target": 503, "value": 4}, {"source": 699, "target": 528, "value": 8}, {"source": 699, "target": 545, "value": 8}, {"source": 699, "target": 549, "value": 4}, {"source": 699, "target": 573, "value": 2}, {"source": 699, "target": 578, "value": 8}, {"source": 699, "target": 603, "value": 4}, {"source": 699, "target": 621, "value": 8}, {"source": 699, "target": 623, "value": 8}, {"source": 699, "target": 641, "value": 8}, {"source": 699, "target": 647, "value": 8}, {"source": 699, "target": 653, "value": 4}, {"source": 699, "target": 678, "value": 7}, {"source": 699, "target": 693, "value": 8}, {"source": 701, "target": 227, "value": 8}, {"source": 701, "target": 249, "value": 8}, {"source": 701, "target": 251, "value": 4}, {"source": 701, "target": 261, "value": 8}, {"source": 701, "target": 263, "value": 8}, {"source": 701, "target": 279, "value": 10}, {"source": 701, "target": 303, "value": 9}, {"source": 701, "target": 311, "value": 8}, {"source": 701, "target": 341, "value": 10}, {"source": 701, "target": 351, "value": 4}, {"source": 701, "target": 353, "value": 4}, {"source": 701, "target": 363, "value": 8}, {"source": 701, "target": 377, "value": 8}, {"source": 701, "target": 401, "value": 2}, {"source": 701, "target": 411, "value": 8}, {"source": 701, "target": 413, "value": 8}, {"source": 701, "target": 453, "value": 3}, {"source": 701, "target": 455, "value": 9}, {"source": 701, "target": 461, "value": 8}, {"source": 701, "target": 489, "value": 8}, {"source": 701, "target": 501, "value": 2}, {"source": 701, "target": 503, "value": 4}, {"source": 701, "target": 513, "value": 8}, {"source": 701, "target": 525, "value": 8}, {"source": 701, "target": 527, "value": 8}, {"source": 701, "target": 549, "value": 8}, {"source": 701, "target": 551, "value": 4}, {"source": 701, "target": 561, "value": 8}, {"source": 701, "target": 563, "value": 8}, {"source": 701, "target": 573, "value": 10}, {"source": 701, "target": 603, "value": 8}, {"source": 701, "target": 611, "value": 8}, {"source": 701, "target": 651, "value": 2}, {"source": 701, "target": 653, "value": 2}, {"source": 701, "target": 663, "value": 8}, {"source": 701, "target": 677, "value": 8}, {"source": 705, "target": 245, "value": 11}, {"source": 705, "target": 251, "value": 8}, {"source": 705, "target": 255, "value": 2}, {"source": 705, "target": 293, "value": 9}, {"source": 705, "target": 305, "value": 2}, {"source": 705, "target": 317, "value": 8}, {"source": 705, "target": 363, "value": 8}, {"source": 705, "target": 377, "value": 11}, {"source": 705, "target": 393, "value": 8}, {"source": 705, "target": 405, "value": 2}, {"source": 705, "target": 429, "value": 8}, {"source": 705, "target": 455, "value": 4}, {"source": 705, "target": 467, "value": 8}, {"source": 705, "target": 555, "value": 4}, {"source": 705, "target": 557, "value": 9}, {"source": 705, "target": 593, "value": 8}, {"source": 705, "target": 605, "value": 2}, {"source": 705, "target": 617, "value": 8}, {"source": 705, "target": 645, "value": 8}, {"source": 705, "target": 693, "value": 8}, {"source": 707, "target": 233, "value": 4}, {"source": 707, "target": 245, "value": 9}, {"source": 707, "target": 257, "value": 4}, {"source": 707, "target": 333, "value": 4}, {"source": 707, "target": 345, "value": 8}, {"source": 707, "target": 381, "value": 8}, {"source": 707, "target": 383, "value": 9}, {"source": 707, "target": 407, "value": 4}, {"source": 707, "target": 483, "value": 9}, {"source": 707, "target": 503, "value": 8}, {"source": 707, "target": 533, "value": 4}, {"source": 707, "target": 545, "value": 8}, {"source": 707, "target": 557, "value": 4}, {"source": 707, "target": 633, "value": 4}, {"source": 707, "target": 645, "value": 7}, {"source": 707, "target": 683, "value": 8}, {"source": 707, "target": 695, "value": 8}, {"source": 711, "target": 249, "value": 9}, {"source": 711, "target": 261, "value": 4}, {"source": 711, "target": 285, "value": 8}, {"source": 711, "target": 311, "value": 4}, {"source": 711, "target": 363, "value": 10}, {"source": 711, "target": 411, "value": 4}, {"source": 711, "target": 461, "value": 4}, {"source": 711, "target": 501, "value": 9}, {"source": 711, "target": 549, "value": 8}, {"source": 711, "target": 561, "value": 4}, {"source": 711, "target": 611, "value": 4}, {"source": 711, "target": 701, "value": 8}, {"source": 713, "target": 225, "value": 8}, {"source": 713, "target": 228, "value": 8}, {"source": 713, "target": 255, "value": 10}, {"source": 713, "target": 261, "value": 8}, {"source": 713, "target": 263, "value": 1}, {"source": 713, "target": 275, "value": 8}, {"source": 713, "target": 278, "value": 8}, {"source": 713, "target": 285, "value": 8}, {"source": 713, "target": 287, "value": 4}, {"source": 713, "target": 291, "value": 9}, {"source": 713, "target": 293, "value": 8}, {"source": 713, "target": 305, "value": 8}, {"source": 713, "target": 309, "value": 8}, {"source": 713, "target": 311, "value": 8}, {"source": 713, "target": 317, "value": 8}, {"source": 713, "target": 321, "value": 8}, {"source": 713, "target": 341, "value": 9}, {"source": 713, "target": 351, "value": 9}, {"source": 713, "target": 363, "value": 1}, {"source": 713, "target": 365, "value": 4}, {"source": 713, "target": 375, "value": 8}, {"source": 713, "target": 378, "value": 8}, {"source": 713, "target": 401, "value": 8}, {"source": 713, "target": 405, "value": 4}, {"source": 713, "target": 411, "value": 8}, {"source": 713, "target": 413, "value": 1}, {"source": 713, "target": 425, "value": 8}, {"source": 713, "target": 428, "value": 8}, {"source": 713, "target": 437, "value": 4}, {"source": 713, "target": 441, "value": 9}, {"source": 713, "target": 453, "value": 9}, {"source": 713, "target": 455, "value": 10}, {"source": 713, "target": 459, "value": 8}, {"source": 713, "target": 461, "value": 2}, {"source": 713, "target": 467, "value": 8}, {"source": 713, "target": 489, "value": 4}, {"source": 713, "target": 491, "value": 9}, {"source": 713, "target": 497, "value": 4}, {"source": 713, "target": 501, "value": 8}, {"source": 713, "target": 513, "value": 1}, {"source": 713, "target": 525, "value": 8}, {"source": 713, "target": 528, "value": 8}, {"source": 713, "target": 549, "value": 8}, {"source": 713, "target": 557, "value": 9}, {"source": 713, "target": 561, "value": 8}, {"source": 713, "target": 563, "value": 1}, {"source": 713, "target": 575, "value": 8}, {"source": 713, "target": 578, "value": 8}, {"source": 713, "target": 587, "value": 4}, {"source": 713, "target": 591, "value": 9}, {"source": 713, "target": 605, "value": 8}, {"source": 713, "target": 609, "value": 9}, {"source": 713, "target": 611, "value": 8}, {"source": 713, "target": 615, "value": 9}, {"source": 713, "target": 617, "value": 8}, {"source": 713, "target": 641, "value": 9}, {"source": 713, "target": 651, "value": 8}, {"source": 713, "target": 653, "value": 8}, {"source": 713, "target": 663, "value": 1}, {"source": 713, "target": 671, "value": 7}, {"source": 713, "target": 675, "value": 8}, {"source": 713, "target": 678, "value": 8}, {"source": 713, "target": 701, "value": 8}, {"source": 713, "target": 705, "value": 7}, {"source": 713, "target": 711, "value": 7}, {"source": 723, "target": 219, "value": 8}, {"source": 723, "target": 221, "value": 8}, {"source": 723, "target": 248, "value": 8}, {"source": 723, "target": 257, "value": 8}, {"source": 723, "target": 261, "value": 9}, {"source": 723, "target": 273, "value": 1}, {"source": 723, "target": 309, "value": 9}, {"source": 723, "target": 323, "value": 2}, {"source": 723, "target": 347, "value": 8}, {"source": 723, "target": 348, "value": 8}, {"source": 723, "target": 369, "value": 8}, {"source": 723, "target": 398, "value": 8}, {"source": 723, "target": 413, "value": 10}, {"source": 723, "target": 423, "value": 2}, {"source": 723, "target": 461, "value": 8}, {"source": 723, "target": 473, "value": 1}, {"source": 723, "target": 497, "value": 8}, {"source": 723, "target": 498, "value": 8}, {"source": 723, "target": 519, "value": 8}, {"source": 723, "target": 545, "value": 10}, {"source": 723, "target": 548, "value": 8}, {"source": 723, "target": 557, "value": 8}, {"source": 723, "target": 561, "value": 8}, {"source": 723, "target": 573, "value": 1}, {"source": 723, "target": 611, "value": 8}, {"source": 723, "target": 615, "value": 8}, {"source": 723, "target": 621, "value": 8}, {"source": 723, "target": 623, "value": 2}, {"source": 723, "target": 647, "value": 8}, {"source": 723, "target": 648, "value": 8}, {"source": 723, "target": 669, "value": 8}, {"source": 723, "target": 678, "value": 8}, {"source": 723, "target": 698, "value": 8}, {"source": 723, "target": 699, "value": 8}, {"source": 725, "target": 225, "value": 2}, {"source": 725, "target": 228, "value": 8}, {"source": 725, "target": 273, "value": 8}, {"source": 725, "target": 275, "value": 4}, {"source": 725, "target": 278, "value": 8}, {"source": 725, "target": 285, "value": 8}, {"source": 725, "target": 287, "value": 8}, {"source": 725, "target": 321, "value": 8}, {"source": 725, "target": 335, "value": 8}, {"source": 725, "target": 365, "value": 10}, {"source": 725, "target": 375, "value": 4}, {"source": 725, "target": 377, "value": 4}, {"source": 725, "target": 378, "value": 8}, {"source": 725, "target": 413, "value": 8}, {"source": 725, "target": 425, "value": 2}, {"source": 725, "target": 428, "value": 8}, {"source": 725, "target": 429, "value": 10}, {"source": 725, "target": 435, "value": 8}, {"source": 725, "target": 437, "value": 8}, {"source": 725, "target": 473, "value": 8}, {"source": 725, "target": 485, "value": 8}, {"source": 725, "target": 513, "value": 8}, {"source": 725, "target": 525, "value": 2}, {"source": 725, "target": 528, "value": 8}, {"source": 725, "target": 531, "value": 8}, {"source": 725, "target": 563, "value": 9}, {"source": 725, "target": 573, "value": 8}, {"source": 725, "target": 575, "value": 4}, {"source": 725, "target": 578, "value": 8}, {"source": 725, "target": 585, "value": 8}, {"source": 725, "target": 587, "value": 8}, {"source": 725, "target": 635, "value": 8}, {"source": 725, "target": 671, "value": 7}, {"source": 725, "target": 675, "value": 3}, {"source": 725, "target": 677, "value": 8}, {"source": 725, "target": 678, "value": 7}, {"source": 725, "target": 713, "value": 8}, {"source": 728, "target": 225, "value": 8}, {"source": 728, "target": 228, "value": 4}, {"source": 728, "target": 249, "value": 8}, {"source": 728, "target": 275, "value": 8}, {"source": 728, "target": 278, "value": 4}, {"source": 728, "target": 303, "value": 11}, {"source": 728, "target": 321, "value": 8}, {"source": 728, "target": 353, "value": 10}, {"source": 728, "target": 365, "value": 10}, {"source": 728, "target": 375, "value": 8}, {"source": 728, "target": 378, "value": 4}, {"source": 728, "target": 399, "value": 8}, {"source": 728, "target": 413, "value": 8}, {"source": 728, "target": 425, "value": 8}, {"source": 728, "target": 428, "value": 4}, {"source": 728, "target": 437, "value": 8}, {"source": 728, "target": 453, "value": 10}, {"source": 728, "target": 503, "value": 9}, {"source": 728, "target": 513, "value": 8}, {"source": 728, "target": 525, "value": 8}, {"source": 728, "target": 528, "value": 4}, {"source": 728, "target": 545, "value": 8}, {"source": 728, "target": 549, "value": 8}, {"source": 728, "target": 563, "value": 9}, {"source": 728, "target": 575, "value": 8}, {"source": 728, "target": 578, "value": 4}, {"source": 728, "target": 603, "value": 9}, {"source": 728, "target": 653, "value": 8}, {"source": 728, "target": 671, "value": 8}, {"source": 728, "target": 675, "value": 7}, {"source": 728, "target": 678, "value": 3}, {"source": 728, "target": 699, "value": 8}, {"source": 728, "target": 713, "value": 8}, {"source": 728, "target": 725, "value": 8}, {"source": 729, "target": 221, "value": 11}, {"source": 729, "target": 233, "value": 12}, {"source": 729, "target": 279, "value": 4}, {"source": 729, "target": 291, "value": 8}, {"source": 729, "target": 317, "value": 9}, {"source": 729, "target": 333, "value": 11}, {"source": 729, "target": 341, "value": 8}, {"source": 729, "target": 381, "value": 9}, {"source": 729, "target": 383, "value": 11}, {"source": 729, "target": 429, "value": 2}, {"source": 729, "target": 441, "value": 8}, {"source": 729, "target": 483, "value": 10}, {"source": 729, "target": 491, "value": 8}, {"source": 729, "target": 531, "value": 9}, {"source": 729, "target": 533, "value": 4}, {"source": 729, "target": 579, "value": 4}, {"source": 729, "target": 581, "value": 9}, {"source": 729, "target": 587, "value": 8}, {"source": 729, "target": 591, "value": 8}, {"source": 729, "target": 617, "value": 8}, {"source": 729, "target": 633, "value": 9}, {"source": 729, "target": 641, "value": 8}, {"source": 729, "target": 653, "value": 8}, {"source": 729, "target": 669, "value": 8}, {"source": 729, "target": 683, "value": 9}, {"source": 731, "target": 221, "value": 11}, {"source": 731, "target": 231, "value": 4}, {"source": 731, "target": 273, "value": 11}, {"source": 731, "target": 281, "value": 4}, {"source": 731, "target": 369, "value": 8}, {"source": 731, "target": 377, "value": 8}, {"source": 731, "target": 381, "value": 4}, {"source": 731, "target": 431, "value": 4}, {"source": 731, "target": 519, "value": 9}, {"source": 731, "target": 531, "value": 4}, {"source": 731, "target": 581, "value": 4}, {"source": 731, "target": 605, "value": 8}, {"source": 731, "target": 621, "value": 8}, {"source": 731, "target": 669, "value": 7}, {"source": 731, "target": 681, "value": 3}, {"source": 735, "target": 273, "value": 9}, {"source": 735, "target": 285, "value": 4}, {"source": 735, "target": 335, "value": 4}, {"source": 735, "target": 377, "value": 10}, {"source": 735, "target": 435, "value": 4}, {"source": 735, "target": 473, "value": 8}, {"source": 735, "target": 485, "value": 4}, {"source": 735, "target": 525, "value": 9}, {"source": 735, "target": 531, "value": 8}, {"source": 735, "target": 573, "value": 8}, {"source": 735, "target": 585, "value": 4}, {"source": 735, "target": 587, "value": 9}, {"source": 735, "target": 635, "value": 4}, {"source": 735, "target": 725, "value": 8}, {"source": 737, "target": 225, "value": 9}, {"source": 737, "target": 228, "value": 9}, {"source": 737, "target": 249, "value": 8}, {"source": 737, "target": 263, "value": 9}, {"source": 737, "target": 278, "value": 9}, {"source": 737, "target": 287, "value": 4}, {"source": 737, "target": 303, "value": 11}, {"source": 737, "target": 353, "value": 11}, {"source": 737, "target": 363, "value": 9}, {"source": 737, "target": 378, "value": 8}, {"source": 737, "target": 399, "value": 8}, {"source": 737, "target": 413, "value": 4}, {"source": 737, "target": 425, "value": 8}, {"source": 737, "target": 428, "value": 8}, {"source": 737, "target": 429, "value": 10}, {"source": 737, "target": 437, "value": 2}, {"source": 737, "target": 453, "value": 10}, {"source": 737, "target": 461, "value": 8}, {"source": 737, "target": 489, "value": 9}, {"source": 737, "target": 503, "value": 10}, {"source": 737, "target": 513, "value": 4}, {"source": 737, "target": 525, "value": 8}, {"source": 737, "target": 528, "value": 8}, {"source": 737, "target": 545, "value": 8}, {"source": 737, "target": 549, "value": 8}, {"source": 737, "target": 563, "value": 8}, {"source": 737, "target": 578, "value": 8}, {"source": 737, "target": 587, "value": 4}, {"source": 737, "target": 603, "value": 9}, {"source": 737, "target": 653, "value": 8}, {"source": 737, "target": 663, "value": 8}, {"source": 737, "target": 677, "value": 8}, {"source": 737, "target": 678, "value": 7}, {"source": 737, "target": 699, "value": 8}, {"source": 737, "target": 713, "value": 4}, {"source": 737, "target": 725, "value": 7}, {"source": 737, "target": 728, "value": 7}, {"source": 741, "target": 219, "value": 8}, {"source": 741, "target": 228, "value": 8}, {"source": 741, "target": 233, "value": 4}, {"source": 741, "target": 245, "value": 8}, {"source": 741, "target": 248, "value": 8}, {"source": 741, "target": 249, "value": 8}, {"source": 741, "target": 251, "value": 8}, {"source": 741, "target": 278, "value": 8}, {"source": 741, "target": 285, "value": 10}, {"source": 741, "target": 291, "value": 4}, {"source": 741, "target": 293, "value": 5}, {"source": 741, "target": 303, "value": 4}, {"source": 741, "target": 321, "value": 9}, {"source": 741, "target": 333, "value": 8}, {"source": 741, "target": 339, "value": 8}, {"source": 741, "target": 341, "value": 2}, {"source": 741, "target": 345, "value": 8}, {"source": 741, "target": 348, "value": 8}, {"source": 741, "target": 351, "value": 8}, {"source": 741, "target": 353, "value": 4}, {"source": 741, "target": 365, "value": 8}, {"source": 741, "target": 369, "value": 8}, {"source": 741, "target": 378, "value": 8}, {"source": 741, "target": 381, "value": 10}, {"source": 741, "target": 395, "value": 8}, {"source": 741, "target": 398, "value": 8}, {"source": 741, "target": 399, "value": 8}, {"source": 741, "target": 401, "value": 8}, {"source": 741, "target": 413, "value": 8}, {"source": 741, "target": 428, "value": 8}, {"source": 741, "target": 429, "value": 8}, {"source": 741, "target": 437, "value": 8}, {"source": 741, "target": 441, "value": 2}, {"source": 741, "target": 453, "value": 4}, {"source": 741, "target": 461, "value": 9}, {"source": 741, "target": 485, "value": 9}, {"source": 741, "target": 489, "value": 4}, {"source": 741, "target": 491, "value": 4}, {"source": 741, "target": 495, "value": 8}, {"source": 741, "target": 498, "value": 8}, {"source": 741, "target": 501, "value": 8}, {"source": 741, "target": 503, "value": 4}, {"source": 741, "target": 519, "value": 9}, {"source": 741, "target": 528, "value": 8}, {"source": 741, "target": 533, "value": 4}, {"source": 741, "target": 545, "value": 4}, {"source": 741, "target": 548, "value": 8}, {"source": 741, "target": 549, "value": 8}, {"source": 741, "target": 551, "value": 8}, {"source": 741, "target": 578, "value": 8}, {"source": 741, "target": 581, "value": 9}, {"source": 741, "target": 587, "value": 8}, {"source": 741, "target": 591, "value": 4}, {"source": 741, "target": 603, "value": 4}, {"source": 741, "target": 633, "value": 8}, {"source": 741, "target": 639, "value": 8}, {"source": 741, "target": 641, "value": 2}, {"source": 741, "target": 645, "value": 7}, {"source": 741, "target": 648, "value": 8}, {"source": 741, "target": 651, "value": 8}, {"source": 741, "target": 653, "value": 4}, {"source": 741, "target": 669, "value": 8}, {"source": 741, "target": 671, "value": 7}, {"source": 741, "target": 678, "value": 7}, {"source": 741, "target": 693, "value": 8}, {"source": 741, "target": 695, "value": 7}, {"source": 741, "target": 698, "value": 7}, {"source": 741, "target": 699, "value": 4}, {"source": 741, "target": 701, "value": 8}, {"source": 741, "target": 713, "value": 8}, {"source": 741, "target": 728, "value": 8}, {"source": 741, "target": 729, "value": 8}, {"source": 741, "target": 737, "value": 8}, {"source": 743, "target": 243, "value": 2}, {"source": 743, "target": 281, "value": 9}, {"source": 743, "target": 293, "value": 1}, {"source": 743, "target": 317, "value": 4}, {"source": 743, "target": 333, "value": 10}, {"source": 743, "target": 335, "value": 8}, {"source": 743, "target": 339, "value": 8}, {"source": 743, "target": 377, "value": 8}, {"source": 743, "target": 381, "value": 8}, {"source": 743, "target": 393, "value": 1}, {"source": 743, "target": 398, "value": 9}, {"source": 743, "target": 429, "value": 8}, {"source": 743, "target": 431, "value": 9}, {"source": 743, "target": 443, "value": 2}, {"source": 743, "target": 467, "value": 8}, {"source": 743, "target": 489, "value": 2}, {"source": 743, "target": 533, "value": 9}, {"source": 743, "target": 543, "value": 2}, {"source": 743, "target": 545, "value": 9}, {"source": 743, "target": 581, "value": 8}, {"source": 743, "target": 585, "value": 9}, {"source": 743, "target": 593, "value": 1}, {"source": 743, "target": 617, "value": 8}, {"source": 743, "target": 639, "value": 8}, {"source": 743, "target": 677, "value": 8}, {"source": 743, "target": 681, "value": 7}, {"source": 743, "target": 693, "value": 1}, {"source": 753, "target": 215, "value": 8}, {"source": 753, "target": 227, "value": 4}, {"source": 753, "target": 228, "value": 8}, {"source": 753, "target": 249, "value": 8}, {"source": 753, "target": 278, "value": 8}, {"source": 753, "target": 279, "value": 4}, {"source": 753, "target": 293, "value": 10}, {"source": 753, "target": 303, "value": 1}, {"source": 753, "target": 315, "value": 8}, {"source": 753, "target": 341, "value": 9}, {"source": 753, "target": 353, "value": 1}, {"source": 753, "target": 365, "value": 8}, {"source": 753, "target": 377, "value": 4}, {"source": 753, "target": 378, "value": 8}, {"source": 753, "target": 399, "value": 8}, {"source": 753, "target": 405, "value": 4}, {"source": 753, "target": 428, "value": 8}, {"source": 753, "target": 437, "value": 8}, {"source": 753, "target": 441, "value": 8}, {"source": 753, "target": 453, "value": 0}, {"source": 753, "target": 461, "value": 8}, {"source": 753, "target": 465, "value": 8}, {"source": 753, "target": 503, "value": 1}, {"source": 753, "target": 515, "value": 8}, {"source": 753, "target": 527, "value": 4}, {"source": 753, "target": 528, "value": 8}, {"source": 753, "target": 545, "value": 8}, {"source": 753, "target": 549, "value": 8}, {"source": 753, "target": 578, "value": 8}, {"source": 753, "target": 603, "value": 1}, {"source": 753, "target": 605, "value": 9}, {"source": 753, "target": 615, "value": 8}, {"source": 753, "target": 641, "value": 8}, {"source": 753, "target": 653, "value": 1}, {"source": 753, "target": 665, "value": 7}, {"source": 753, "target": 677, "value": 4}, {"source": 753, "target": 678, "value": 8}, {"source": 753, "target": 693, "value": 8}, {"source": 753, "target": 699, "value": 4}, {"source": 753, "target": 701, "value": 3}, {"source": 753, "target": 728, "value": 8}, {"source": 753, "target": 737, "value": 8}, {"source": 753, "target": 741, "value": 4}, {"source": 755, "target": 245, "value": 11}, {"source": 755, "target": 251, "value": 8}, {"source": 755, "target": 255, "value": 4}, {"source": 755, "target": 263, "value": 12}, {"source": 755, "target": 293, "value": 9}, {"source": 755, "target": 303, "value": 8}, {"source": 755, "target": 305, "value": 4}, {"source": 755, "target": 309, "value": 8}, {"source": 755, "target": 351, "value": 10}, {"source": 755, "target": 353, "value": 8}, {"source": 755, "target": 363, "value": 11}, {"source": 755, "target": 377, "value": 11}, {"source": 755, "target": 393, "value": 8}, {"source": 755, "target": 405, "value": 2}, {"source": 755, "target": 413, "value": 10}, {"source": 755, "target": 429, "value": 8}, {"source": 755, "target": 453, "value": 8}, {"source": 755, "target": 455, "value": 4}, {"source": 755, "target": 459, "value": 8}, {"source": 755, "target": 497, "value": 4}, {"source": 755, "target": 503, "value": 8}, {"source": 755, "target": 513, "value": 10}, {"source": 755, "target": 549, "value": 9}, {"source": 755, "target": 555, "value": 4}, {"source": 755, "target": 563, "value": 9}, {"source": 755, "target": 593, "value": 8}, {"source": 755, "target": 603, "value": 8}, {"source": 755, "target": 605, "value": 4}, {"source": 755, "target": 609, "value": 8}, {"source": 755, "target": 645, "value": 8}, {"source": 755, "target": 653, "value": 8}, {"source": 755, "target": 663, "value": 9}, {"source": 755, "target": 693, "value": 7}, {"source": 755, "target": 705, "value": 3}, {"source": 755, "target": 713, "value": 8}, {"source": 755, "target": 753, "value": 7}, {"source": 759, "target": 263, "value": 12}, {"source": 759, "target": 309, "value": 4}, {"source": 759, "target": 333, "value": 8}, {"source": 759, "target": 351, "value": 10}, {"source": 759, "target": 363, "value": 11}, {"source": 759, "target": 405, "value": 8}, {"source": 759, "target": 413, "value": 11}, {"source": 759, "target": 459, "value": 4}, {"source": 759, "target": 461, "value": 9}, {"source": 759, "target": 497, "value": 4}, {"source": 759, "target": 513, "value": 10}, {"source": 759, "target": 549, "value": 9}, {"source": 759, "target": 563, "value": 9}, {"source": 759, "target": 609, "value": 4}, {"source": 759, "target": 663, "value": 9}, {"source": 759, "target": 713, "value": 8}, {"source": 759, "target": 755, "value": 8}, {"source": 761, "target": 249, "value": 9}, {"source": 761, "target": 261, "value": 2}, {"source": 761, "target": 273, "value": 4}, {"source": 761, "target": 285, "value": 8}, {"source": 761, "target": 311, "value": 4}, {"source": 761, "target": 323, "value": 4}, {"source": 761, "target": 363, "value": 10}, {"source": 761, "target": 411, "value": 4}, {"source": 761, "target": 413, "value": 4}, {"source": 761, "target": 423, "value": 4}, {"source": 761, "target": 461, "value": 2}, {"source": 761, "target": 473, "value": 4}, {"source": 761, "target": 501, "value": 9}, {"source": 761, "target": 549, "value": 8}, {"source": 761, "target": 561, "value": 2}, {"source": 761, "target": 573, "value": 4}, {"source": 761, "target": 611, "value": 4}, {"source": 761, "target": 623, "value": 4}, {"source": 761, "target": 678, "value": 4}, {"source": 761, "target": 701, "value": 8}, {"source": 761, "target": 711, "value": 3}, {"source": 761, "target": 713, "value": 8}, {"source": 761, "target": 723, "value": 4}, {"source": 765, "target": 215, "value": 4}, {"source": 765, "target": 225, "value": 8}, {"source": 765, "target": 227, "value": 8}, {"source": 765, "target": 228, "value": 8}, {"source": 765, "target": 275, "value": 8}, {"source": 765, "target": 278, "value": 8}, {"source": 765, "target": 291, "value": 8}, {"source": 765, "target": 293, "value": 10}, {"source": 765, "target": 315, "value": 4}, {"source": 765, "target": 317, "value": 10}, {"source": 765, "target": 321, "value": 8}, {"source": 765, "target": 341, "value": 8}, {"source": 765, "target": 353, "value": 9}, {"source": 765, "target": 365, "value": 1}, {"source": 765, "target": 375, "value": 8}, {"source": 765, "target": 377, "value": 8}, {"source": 765, "target": 378, "value": 8}, {"source": 765, "target": 405, "value": 10}, {"source": 765, "target": 413, "value": 8}, {"source": 765, "target": 425, "value": 8}, {"source": 765, "target": 428, "value": 8}, {"source": 765, "target": 441, "value": 8}, {"source": 765, "target": 453, "value": 4}, {"source": 765, "target": 461, "value": 8}, {"source": 765, "target": 465, "value": 2}, {"source": 765, "target": 491, "value": 8}, {"source": 765, "target": 513, "value": 8}, {"source": 765, "target": 515, "value": 4}, {"source": 765, "target": 525, "value": 8}, {"source": 765, "target": 527, "value": 8}, {"source": 765, "target": 528, "value": 8}, {"source": 765, "target": 563, "value": 8}, {"source": 765, "target": 573, "value": 8}, {"source": 765, "target": 575, "value": 8}, {"source": 765, "target": 578, "value": 8}, {"source": 765, "target": 591, "value": 8}, {"source": 765, "target": 605, "value": 9}, {"source": 765, "target": 615, "value": 4}, {"source": 765, "target": 641, "value": 8}, {"source": 765, "target": 653, "value": 8}, {"source": 765, "target": 665, "value": 2}, {"source": 765, "target": 671, "value": 8}, {"source": 765, "target": 675, "value": 8}, {"source": 765, "target": 677, "value": 7}, {"source": 765, "target": 678, "value": 8}, {"source": 765, "target": 713, "value": 4}, {"source": 765, "target": 725, "value": 8}, {"source": 765, "target": 728, "value": 8}, {"source": 765, "target": 741, "value": 8}, {"source": 765, "target": 753, "value": 7}, {"source": 767, "target": 225, "value": 8}, {"source": 767, "target": 228, "value": 8}, {"source": 767, "target": 243, "value": 9}, {"source": 767, "target": 255, "value": 10}, {"source": 767, "target": 275, "value": 8}, {"source": 767, "target": 278, "value": 8}, {"source": 767, "target": 293, "value": 4}, {"source": 767, "target": 305, "value": 9}, {"source": 767, "target": 317, "value": 4}, {"source": 767, "target": 321, "value": 8}, {"source": 767, "target": 363, "value": 8}, {"source": 767, "target": 365, "value": 9}, {"source": 767, "target": 375, "value": 8}, {"source": 767, "target": 378, "value": 8}, {"source": 767, "target": 393, "value": 4}, {"source": 767, "target": 405, "value": 8}, {"source": 767, "target": 413, "value": 8}, {"source": 767, "target": 425, "value": 8}, {"source": 767, "target": 428, "value": 8}, {"source": 767, "target": 443, "value": 9}, {"source": 767, "target": 467, "value": 4}, {"source": 767, "target": 489, "value": 10}, {"source": 767, "target": 513, "value": 8}, {"source": 767, "target": 525, "value": 8}, {"source": 767, "target": 528, "value": 8}, {"source": 767, "target": 543, "value": 8}, {"source": 767, "target": 557, "value": 9}, {"source": 767, "target": 563, "value": 8}, {"source": 767, "target": 575, "value": 8}, {"source": 767, "target": 578, "value": 8}, {"source": 767, "target": 593, "value": 4}, {"source": 767, "target": 605, "value": 8}, {"source": 767, "target": 617, "value": 4}, {"source": 767, "target": 671, "value": 8}, {"source": 767, "target": 675, "value": 8}, {"source": 767, "target": 678, "value": 8}, {"source": 767, "target": 693, "value": 4}, {"source": 767, "target": 705, "value": 7}, {"source": 767, "target": 713, "value": 4}, {"source": 767, "target": 725, "value": 8}, {"source": 767, "target": 728, "value": 8}, {"source": 767, "target": 743, "value": 8}, {"source": 767, "target": 765, "value": 7}, {"source": 771, "target": 221, "value": 4}, {"source": 771, "target": 261, "value": 11}, {"source": 771, "target": 293, "value": 12}, {"source": 771, "target": 309, "value": 9}, {"source": 771, "target": 321, "value": 4}, {"source": 771, "target": 371, "value": 4}, {"source": 771, "target": 461, "value": 10}, {"source": 771, "target": 471, "value": 4}, {"source": 771, "target": 521, "value": 4}, {"source": 771, "target": 573, "value": 9}, {"source": 771, "target": 609, "value": 8}, {"source": 771, "target": 621, "value": 4}, {"source": 771, "target": 663, "value": 9}, {"source": 771, "target": 671, "value": 4}, {"source": 773, "target": 219, "value": 8}, {"source": 773, "target": 221, "value": 2}, {"source": 773, "target": 233, "value": 8}, {"source": 773, "target": 248, "value": 8}, {"source": 773, "target": 257, "value": 8}, {"source": 773, "target": 261, "value": 9}, {"source": 773, "target": 273, "value": 1}, {"source": 773, "target": 279, "value": 8}, {"source": 773, "target": 285, "value": 8}, {"source": 773, "target": 309, "value": 9}, {"source": 773, "target": 321, "value": 4}, {"source": 773, "target": 323, "value": 1}, {"source": 773, "target": 333, "value": 8}, {"source": 773, "target": 335, "value": 8}, {"source": 773, "target": 347, "value": 4}, {"source": 773, "target": 348, "value": 8}, {"source": 773, "target": 369, "value": 8}, {"source": 773, "target": 377, "value": 10}, {"source": 773, "target": 383, "value": 8}, {"source": 773, "target": 398, "value": 8}, {"source": 773, "target": 413, "value": 10}, {"source": 773, "target": 423, "value": 1}, {"source": 773, "target": 435, "value": 8}, {"source": 773, "target": 461, "value": 8}, {"source": 773, "target": 473, "value": 1}, {"source": 773, "target": 483, "value": 8}, {"source": 773, "target": 485, "value": 8}, {"source": 773, "target": 497, "value": 4}, {"source": 773, "target": 498, "value": 8}, {"source": 773, "target": 519, "value": 8}, {"source": 773, "target": 521, "value": 8}, {"source": 773, "target": 525, "value": 9}, {"source": 773, "target": 531, "value": 8}, {"source": 773, "target": 533, "value": 8}, {"source": 773, "target": 545, "value": 10}, {"source": 773, "target": 548, "value": 8}, {"source": 773, "target": 557, "value": 8}, {"source": 773, "target": 561, "value": 8}, {"source": 773, "target": 573, "value": 0}, {"source": 773, "target": 585, "value": 8}, {"source": 773, "target": 611, "value": 8}, {"source": 773, "target": 615, "value": 8}, {"source": 773, "target": 621, "value": 2}, {"source": 773, "target": 623, "value": 1}, {"source": 773, "target": 633, "value": 8}, {"source": 773, "target": 635, "value": 8}, {"source": 773, "target": 647, "value": 4}, {"source": 773, "target": 648, "value": 8}, {"source": 773, "target": 669, "value": 8}, {"source": 773, "target": 678, "value": 8}, {"source": 773, "target": 683, "value": 8}, {"source": 773, "target": 698, "value": 7}, {"source": 773, "target": 699, "value": 4}, {"source": 773, "target": 723, "value": 1}, {"source": 773, "target": 725, "value": 8}, {"source": 773, "target": 735, "value": 8}, {"source": 773, "target": 761, "value": 4}, {"source": 783, "target": 221, "value": 4}, {"source": 783, "target": 225, "value": 9}, {"source": 783, "target": 231, "value": 8}, {"source": 783, "target": 233, "value": 1}, {"source": 783, "target": 245, "value": 8}, {"source": 783, "target": 248, "value": 8}, {"source": 783, "target": 257, "value": 8}, {"source": 783, "target": 279, "value": 4}, {"source": 783, "target": 281, "value": 8}, {"source": 783, "target": 285, "value": 10}, {"source": 783, "target": 287, "value": 8}, {"source": 783, "target": 317, "value": 8}, {"source": 783, "target": 321, "value": 4}, {"source": 783, "target": 333, "value": 1}, {"source": 783, "target": 335, "value": 10}, {"source": 783, "target": 345, "value": 8}, {"source": 783, "target": 348, "value": 8}, {"source": 783, "target": 381, "value": 4}, {"source": 783, "target": 383, "value": 2}, {"source": 783, "target": 395, "value": 8}, {"source": 783, "target": 398, "value": 8}, {"source": 783, "target": 405, "value": 11}, {"source": 783, "target": 407, "value": 8}, {"source": 783, "target": 425, "value": 8}, {"source": 783, "target": 429, "value": 4}, {"source": 783, "target": 431, "value": 8}, {"source": 783, "target": 437, "value": 8}, {"source": 783, "target": 483, "value": 2}, {"source": 783, "target": 485, "value": 9}, {"source": 783, "target": 495, "value": 8}, {"source": 783, "target": 498, "value": 8}, {"source": 783, "target": 521, "value": 8}, {"source": 783, "target": 525, "value": 8}, {"source": 783, "target": 531, "value": 8}, {"source": 783, "target": 533, "value": 1}, {"source": 783, "target": 545, "value": 8}, {"source": 783, "target": 548, "value": 8}, {"source": 783, "target": 557, "value": 4}, {"source": 783, "target": 573, "value": 9}, {"source": 783, "target": 579, "value": 8}, {"source": 783, "target": 581, "value": 8}, {"source": 783, "target": 587, "value": 8}, {"source": 783, "target": 605, "value": 8}, {"source": 783, "target": 617, "value": 8}, {"source": 783, "target": 621, "value": 8}, {"source": 783, "target": 633, "value": 1}, {"source": 783, "target": 645, "value": 8}, {"source": 783, "target": 648, "value": 8}, {"source": 783, "target": 669, "value": 8}, {"source": 783, "target": 677, "value": 8}, {"source": 783, "target": 681, "value": 8}, {"source": 783, "target": 683, "value": 2}, {"source": 783, "target": 695, "value": 8}, {"source": 783, "target": 698, "value": 8}, {"source": 783, "target": 707, "value": 8}, {"source": 783, "target": 725, "value": 8}, {"source": 783, "target": 729, "value": 8}, {"source": 783, "target": 731, "value": 7}, {"source": 783, "target": 737, "value": 7}, {"source": 783, "target": 741, "value": 8}, {"source": 783, "target": 773, "value": 7}, {"source": 785, "target": 273, "value": 9}, {"source": 785, "target": 285, "value": 2}, {"source": 785, "target": 293, "value": 8}, {"source": 785, "target": 335, "value": 4}, {"source": 785, "target": 347, "value": 8}, {"source": 785, "target": 377, "value": 10}, {"source": 785, "target": 435, "value": 4}, {"source": 785, "target": 437, "value": 9}, {"source": 785, "target": 473, "value": 8}, {"source": 785, "target": 485, "value": 2}, {"source": 785, "target": 497, "value": 8}, {"source": 785, "target": 525, "value": 9}, {"source": 785, "target": 531, "value": 8}, {"source": 785, "target": 573, "value": 8}, {"source": 785, "target": 585, "value": 2}, {"source": 785, "target": 587, "value": 9}, {"source": 785, "target": 635, "value": 4}, {"source": 785, "target": 647, "value": 8}, {"source": 785, "target": 725, "value": 8}, {"source": 785, "target": 735, "value": 3}, {"source": 785, "target": 773, "value": 7}, {"source": 789, "target": 228, "value": 8}, {"source": 789, "target": 243, "value": 12}, {"source": 789, "target": 249, "value": 8}, {"source": 789, "target": 251, "value": 8}, {"source": 789, "target": 278, "value": 8}, {"source": 789, "target": 293, "value": 12}, {"source": 789, "target": 303, "value": 10}, {"source": 789, "target": 335, "value": 8}, {"source": 789, "target": 339, "value": 4}, {"source": 789, "target": 341, "value": 10}, {"source": 789, "target": 351, "value": 8}, {"source": 789, "target": 353, "value": 10}, {"source": 789, "target": 377, "value": 8}, {"source": 789, "target": 378, "value": 8}, {"source": 789, "target": 393, "value": 11}, {"source": 789, "target": 399, "value": 8}, {"source": 789, "target": 401, "value": 8}, {"source": 789, "target": 413, "value": 8}, {"source": 789, "target": 428, "value": 8}, {"source": 789, "target": 429, "value": 10}, {"source": 789, "target": 437, "value": 8}, {"source": 789, "target": 443, "value": 10}, {"source": 789, "target": 453, "value": 9}, {"source": 789, "target": 461, "value": 10}, {"source": 789, "target": 489, "value": 2}, {"source": 789, "target": 501, "value": 8}, {"source": 789, "target": 503, "value": 9}, {"source": 789, "target": 528, "value": 8}, {"source": 789, "target": 543, "value": 10}, {"source": 789, "target": 545, "value": 8}, {"source": 789, "target": 549, "value": 8}, {"source": 789, "target": 551, "value": 8}, {"source": 789, "target": 578, "value": 8}, {"source": 789, "target": 593, "value": 9}, {"source": 789, "target": 603, "value": 8}, {"source": 789, "target": 639, "value": 4}, {"source": 789, "target": 651, "value": 8}, {"source": 789, "target": 653, "value": 8}, {"source": 789, "target": 677, "value": 8}, {"source": 789, "target": 678, "value": 7}, {"source": 789, "target": 693, "value": 9}, {"source": 789, "target": 699, "value": 8}, {"source": 789, "target": 701, "value": 7}, {"source": 789, "target": 728, "value": 7}, {"source": 789, "target": 737, "value": 7}, {"source": 789, "target": 741, "value": 2}, {"source": 789, "target": 743, "value": 8}, {"source": 789, "target": 753, "value": 8}, {"source": 791, "target": 291, "value": 4}, {"source": 791, "target": 293, "value": 10}, {"source": 791, "target": 341, "value": 4}, {"source": 791, "target": 365, "value": 8}, {"source": 791, "target": 381, "value": 10}, {"source": 791, "target": 429, "value": 8}, {"source": 791, "target": 441, "value": 4}, {"source": 791, "target": 491, "value": 4}, {"source": 791, "target": 533, "value": 9}, {"source": 791, "target": 581, "value": 9}, {"source": 791, "target": 587, "value": 8}, {"source": 791, "target": 591, "value": 4}, {"source": 791, "target": 641, "value": 4}, {"source": 791, "target": 713, "value": 9}, {"source": 791, "target": 729, "value": 7}, {"source": 791, "target": 741, "value": 3}, {"source": 791, "target": 765, "value": 8}, {"source": 795, "target": 233, "value": 4}, {"source": 795, "target": 245, "value": 4}, {"source": 795, "target": 248, "value": 8}, {"source": 795, "target": 285, "value": 10}, {"source": 795, "target": 333, "value": 9}, {"source": 795, "target": 345, "value": 4}, {"source": 795, "target": 348, "value": 8}, {"source": 795, "target": 395, "value": 4}, {"source": 795, "target": 398, "value": 8}, {"source": 795, "target": 485, "value": 9}, {"source": 795, "target": 495, "value": 4}, {"source": 795, "target": 498, "value": 8}, {"source": 795, "target": 533, "value": 8}, {"source": 795, "target": 545, "value": 4}, {"source": 795, "target": 548, "value": 8}, {"source": 795, "target": 633, "value": 8}, {"source": 795, "target": 645, "value": 4}, {"source": 795, "target": 648, "value": 7}, {"source": 795, "target": 669, "value": 8}, {"source": 795, "target": 695, "value": 4}, {"source": 795, "target": 698, "value": 7}, {"source": 795, "target": 741, "value": 7}, {"source": 795, "target": 783, "value": 8}, {"source": 797, "target": 221, "value": 9}, {"source": 797, "target": 225, "value": 8}, {"source": 797, "target": 245, "value": 4}, {"source": 797, "target": 251, "value": 8}, {"source": 797, "target": 257, "value": 8}, {"source": 797, "target": 263, "value": 12}, {"source": 797, "target": 273, "value": 3}, {"source": 797, "target": 275, "value": 8}, {"source": 797, "target": 285, "value": 9}, {"source": 797, "target": 293, "value": 8}, {"source": 797, "target": 309, "value": 8}, {"source": 797, "target": 323, "value": 4}, {"source": 797, "target": 341, "value": 10}, {"source": 797, "target": 345, "value": 4}, {"source": 797, "target": 347, "value": 4}, {"source": 797, "target": 351, "value": 4}, {"source": 797, "target": 363, "value": 11}, {"source": 797, "target": 375, "value": 8}, {"source": 797, "target": 377, "value": 9}, {"source": 797, "target": 395, "value": 8}, {"source": 797, "target": 401, "value": 8}, {"source": 797, "target": 405, "value": 8}, {"source": 797, "target": 407, "value": 8}, {"source": 797, "target": 413, "value": 10}, {"source": 797, "target": 423, "value": 4}, {"source": 797, "target": 425, "value": 8}, {"source": 797, "target": 437, "value": 10}, {"source": 797, "target": 459, "value": 8}, {"source": 797, "target": 473, "value": 2}, {"source": 797, "target": 485, "value": 8}, {"source": 797, "target": 489, "value": 8}, {"source": 797, "target": 495, "value": 8}, {"source": 797, "target": 497, "value": 2}, {"source": 797, "target": 501, "value": 8}, {"source": 797, "target": 503, "value": 8}, {"source": 797, "target": 513, "value": 10}, {"source": 797, "target": 525, "value": 8}, {"source": 797, "target": 545, "value": 2}, {"source": 797, "target": 549, "value": 9}, {"source": 797, "target": 551, "value": 8}, {"source": 797, "target": 557, "value": 8}, {"source": 797, "target": 563, "value": 9}, {"source": 797, "target": 573, "value": 2}, {"source": 797, "target": 575, "value": 8}, {"source": 797, "target": 585, "value": 4}, {"source": 797, "target": 609, "value": 8}, {"source": 797, "target": 621, "value": 8}, {"source": 797, "target": 623, "value": 4}, {"source": 797, "target": 645, "value": 4}, {"source": 797, "target": 647, "value": 4}, {"source": 797, "target": 651, "value": 8}, {"source": 797, "target": 663, "value": 9}, {"source": 797, "target": 669, "value": 7}, {"source": 797, "target": 675, "value": 8}, {"source": 797, "target": 695, "value": 4}, {"source": 797, "target": 699, "value": 8}, {"source": 797, "target": 701, "value": 8}, {"source": 797, "target": 707, "value": 8}, {"source": 797, "target": 713, "value": 8}, {"source": 797, "target": 723, "value": 4}, {"source": 797, "target": 725, "value": 8}, {"source": 797, "target": 741, "value": 8}, {"source": 797, "target": 755, "value": 7}, {"source": 797, "target": 759, "value": 8}, {"source": 797, "target": 773, "value": 2}, {"source": 797, "target": 785, "value": 7}, {"source": 797, "target": 789, "value": 7}, {"source": 797, "target": 795, "value": 7}, {"source": 798, "target": 219, "value": 8}, {"source": 798, "target": 233, "value": 5}, {"source": 798, "target": 245, "value": 8}, {"source": 798, "target": 248, "value": 4}, {"source": 798, "target": 257, "value": 8}, {"source": 798, "target": 273, "value": 11}, {"source": 798, "target": 285, "value": 11}, {"source": 798, "target": 309, "value": 10}, {"source": 798, "target": 323, "value": 11}, {"source": 798, "target": 333, "value": 9}, {"source": 798, "target": 345, "value": 8}, {"source": 798, "target": 348, "value": 4}, {"source": 798, "target": 369, "value": 8}, {"source": 798, "target": 395, "value": 8}, {"source": 798, "target": 398, "value": 4}, {"source": 798, "target": 423, "value": 10}, {"source": 798, "target": 473, "value": 10}, {"source": 798, "target": 485, "value": 10}, {"source": 798, "target": 495, "value": 8}, {"source": 798, "target": 498, "value": 4}, {"source": 798, "target": 519, "value": 8}, {"source": 798, "target": 533, "value": 8}, {"source": 798, "target": 545, "value": 8}, {"source": 798, "target": 548, "value": 4}, {"source": 798, "target": 557, "value": 8}, {"source": 798, "target": 573, "value": 9}, {"source": 798, "target": 611, "value": 9}, {"source": 798, "target": 615, "value": 8}, {"source": 798, "target": 623, "value": 9}, {"source": 798, "target": 633, "value": 8}, {"source": 798, "target": 645, "value": 8}, {"source": 798, "target": 648, "value": 4}, {"source": 798, "target": 669, "value": 8}, {"source": 798, "target": 695, "value": 8}, {"source": 798, "target": 698, "value": 4}, {"source": 798, "target": 723, "value": 8}, {"source": 798, "target": 741, "value": 7}, {"source": 798, "target": 773, "value": 8}, {"source": 798, "target": 783, "value": 8}, {"source": 798, "target": 795, "value": 8}, {"source": 801, "target": 251, "value": 4}, {"source": 801, "target": 263, "value": 8}, {"source": 801, "target": 341, "value": 10}, {"source": 801, "target": 351, "value": 4}, {"source": 801, "target": 363, "value": 8}, {"source": 801, "target": 401, "value": 2}, {"source": 801, "target": 413, "value": 8}, {"source": 801, "target": 453, "value": 9}, {"source": 801, "target": 455, "value": 10}, {"source": 801, "target": 489, "value": 8}, {"source": 801, "target": 501, "value": 2}, {"source": 801, "target": 503, "value": 9}, {"source": 801, "target": 513, "value": 8}, {"source": 801, "target": 525, "value": 8}, {"source": 801, "target": 551, "value": 4}, {"source": 801, "target": 563, "value": 8}, {"source": 801, "target": 573, "value": 10}, {"source": 801, "target": 651, "value": 2}, {"source": 801, "target": 653, "value": 8}, {"source": 801, "target": 663, "value": 8}, {"source": 801, "target": 701, "value": 2}, {"source": 801, "target": 713, "value": 7}, {"source": 801, "target": 741, "value": 8}, {"source": 801, "target": 789, "value": 7}, {"source": 801, "target": 797, "value": 8}, {"source": 803, "target": 227, "value": 8}, {"source": 803, "target": 228, "value": 8}, {"source": 803, "target": 249, "value": 8}, {"source": 803, "target": 278, "value": 8}, {"source": 803, "target": 279, "value": 10}, {"source": 803, "target": 293, "value": 10}, {"source": 803, "target": 303, "value": 2}, {"source": 803, "target": 341, "value": 9}, {"source": 803, "target": 353, "value": 1}, {"source": 803, "target": 377, "value": 8}, {"source": 803, "target": 378, "value": 8}, {"source": 803, "target": 399, "value": 8}, {"source": 803, "target": 405, "value": 10}, {"source": 803, "target": 428, "value": 8}, {"source": 803, "target": 437, "value": 8}, {"source": 803, "target": 441, "value": 8}, {"source": 803, "target": 453, "value": 1}, {"source": 803, "target": 503, "value": 2}, {"source": 803, "target": 527, "value": 8}, {"source": 803, "target": 528, "value": 8}, {"source": 803, "target": 545, "value": 8}, {"source": 803, "target": 549, "value": 8}, {"source": 803, "target": 578, "value": 8}, {"source": 803, "target": 603, "value": 2}, {"source": 803, "target": 641, "value": 8}, {"source": 803, "target": 653, "value": 1}, {"source": 803, "target": 677, "value": 8}, {"source": 803, "target": 678, "value": 8}, {"source": 803, "target": 693, "value": 8}, {"source": 803, "target": 699, "value": 4}, {"source": 803, "target": 701, "value": 8}, {"source": 803, "target": 728, "value": 7}, {"source": 803, "target": 737, "value": 8}, {"source": 803, "target": 741, "value": 3}, {"source": 803, "target": 753, "value": 1}, {"source": 803, "target": 755, "value": 8}, {"source": 803, "target": 789, "value": 7}]} \ No newline at end of file +{"nodes": [{"id": 215, "name": "Citizen n\u00b0202", "mass": 10}, {"id": 219, "name": "Citizen n\u00b0206", "mass": 10}, {"id": 221, "name": "Citizen n\u00b0208", "mass": 11}, {"id": 225, "name": "Citizen n\u00b0212", "mass": 9}, {"id": 227, "name": "Citizen n\u00b0214", "mass": 5}, {"id": 228, "name": "Citizen n\u00b0215", "mass": 8}, {"id": 231, "name": "Citizen n\u00b0218", "mass": 5}, {"id": 233, "name": "Citizen n\u00b0220", "mass": 10}, {"id": 243, "name": "Citizen n\u00b0230", "mass": 8}, {"id": 245, "name": "Citizen n\u00b0232", "mass": 9}, {"id": 248, "name": "Citizen n\u00b0235", "mass": 5}, {"id": 249, "name": "Citizen n\u00b0236", "mass": 8}, {"id": 251, "name": "Citizen n\u00b0238", "mass": 9}, {"id": 255, "name": "Citizen n\u00b0242", "mass": 10}, {"id": 257, "name": "Citizen n\u00b0244", "mass": 10}, {"id": 261, "name": "Citizen n\u00b0248", "mass": 10}, {"id": 263, "name": "Citizen n\u00b0250", "mass": 9}, {"id": 273, "name": "Citizen n\u00b0260", "mass": 10}, {"id": 275, "name": "Citizen n\u00b0262", "mass": 11}, {"id": 278, "name": "Citizen n\u00b0265", "mass": 10}, {"id": 279, "name": "Citizen n\u00b0266", "mass": 10}, {"id": 281, "name": "Citizen n\u00b0268", "mass": 10}, {"id": 285, "name": "Citizen n\u00b0272", "mass": 5}, {"id": 287, "name": "Citizen n\u00b0274", "mass": 9}, {"id": 291, "name": "Citizen n\u00b0278", "mass": 9}, {"id": 293, "name": "Citizen n\u00b0280", "mass": 10}, {"id": 303, "name": "Citizen n\u00b0290", "mass": 8}, {"id": 305, "name": "Citizen n\u00b0292", "mass": 9}, {"id": 309, "name": "Citizen n\u00b0296", "mass": 9}, {"id": 311, "name": "Citizen n\u00b0298", "mass": 8}, {"id": 315, "name": "Citizen n\u00b0302", "mass": 10}, {"id": 317, "name": "Citizen n\u00b0304", "mass": 10}, {"id": 321, "name": "Citizen n\u00b0308", "mass": 11}, {"id": 323, "name": "Citizen n\u00b0310", "mass": 6}, {"id": 333, "name": "Citizen n\u00b0320", "mass": 10}, {"id": 335, "name": "Citizen n\u00b0322", "mass": 10}, {"id": 339, "name": "Citizen n\u00b0326", "mass": 10}, {"id": 341, "name": "Citizen n\u00b0328", "mass": 10}, {"id": 345, "name": "Citizen n\u00b0332", "mass": 9}, {"id": 347, "name": "Citizen n\u00b0334", "mass": 8}, {"id": 348, "name": "Citizen n\u00b0335", "mass": 5}, {"id": 351, "name": "Citizen n\u00b0338", "mass": 9}, {"id": 353, "name": "Citizen n\u00b0340", "mass": 10}, {"id": 363, "name": "Citizen n\u00b0350", "mass": 9}, {"id": 365, "name": "Citizen n\u00b0352", "mass": 5}, {"id": 369, "name": "Citizen n\u00b0356", "mass": 8}, {"id": 371, "name": "Citizen n\u00b0358", "mass": 9}, {"id": 375, "name": "Citizen n\u00b0362", "mass": 11}, {"id": 377, "name": "Citizen n\u00b0364", "mass": 10}, {"id": 378, "name": "Citizen n\u00b0365", "mass": 10}, {"id": 381, "name": "Citizen n\u00b0368", "mass": 10}, {"id": 383, "name": "Citizen n\u00b0370", "mass": 10}, {"id": 393, "name": "Citizen n\u00b0380", "mass": 10}, {"id": 395, "name": "Citizen n\u00b0382", "mass": 10}, {"id": 398, "name": "Citizen n\u00b0385", "mass": 10}, {"id": 399, "name": "Citizen n\u00b0386", "mass": 10}, {"id": 401, "name": "Citizen n\u00b0388", "mass": 10}, {"id": 405, "name": "Citizen n\u00b0392", "mass": 9}, {"id": 407, "name": "Citizen n\u00b0394", "mass": 8}, {"id": 411, "name": "Citizen n\u00b0398", "mass": 8}, {"id": 413, "name": "Citizen n\u00b0400", "mass": 11}, {"id": 423, "name": "Citizen n\u00b0410", "mass": 6}, {"id": 425, "name": "Citizen n\u00b0412", "mass": 9}, {"id": 428, "name": "Citizen n\u00b0415", "mass": 8}, {"id": 429, "name": "Citizen n\u00b0416", "mass": 10}, {"id": 431, "name": "Citizen n\u00b0418", "mass": 5}, {"id": 435, "name": "Citizen n\u00b0422", "mass": 10}, {"id": 437, "name": "Citizen n\u00b0424", "mass": 11}, {"id": 441, "name": "Citizen n\u00b0428", "mass": 10}, {"id": 443, "name": "Citizen n\u00b0430", "mass": 8}, {"id": 453, "name": "Citizen n\u00b0440", "mass": 10}, {"id": 455, "name": "Citizen n\u00b0442", "mass": 10}, {"id": 459, "name": "Citizen n\u00b0446", "mass": 10}, {"id": 461, "name": "Citizen n\u00b0448", "mass": 10}, {"id": 465, "name": "Citizen n\u00b0452", "mass": 5}, {"id": 467, "name": "Citizen n\u00b0454", "mass": 10}, {"id": 471, "name": "Citizen n\u00b0458", "mass": 9}, {"id": 473, "name": "Citizen n\u00b0460", "mass": 10}, {"id": 483, "name": "Citizen n\u00b0470", "mass": 10}, {"id": 485, "name": "Citizen n\u00b0472", "mass": 5}, {"id": 489, "name": "Citizen n\u00b0476", "mass": 8}, {"id": 491, "name": "Citizen n\u00b0478", "mass": 9}, {"id": 495, "name": "Citizen n\u00b0482", "mass": 10}, {"id": 497, "name": "Citizen n\u00b0484", "mass": 10}, {"id": 498, "name": "Citizen n\u00b0485", "mass": 10}, {"id": 501, "name": "Citizen n\u00b0488", "mass": 10}, {"id": 503, "name": "Citizen n\u00b0490", "mass": 8}, {"id": 513, "name": "Citizen n\u00b0500", "mass": 11}, {"id": 515, "name": "Citizen n\u00b0502", "mass": 10}, {"id": 519, "name": "Citizen n\u00b0506", "mass": 10}, {"id": 521, "name": "Citizen n\u00b0508", "mass": 11}, {"id": 525, "name": "Citizen n\u00b0512", "mass": 9}, {"id": 527, "name": "Citizen n\u00b0514", "mass": 5}, {"id": 528, "name": "Citizen n\u00b0515", "mass": 8}, {"id": 531, "name": "Citizen n\u00b0518", "mass": 5}, {"id": 533, "name": "Citizen n\u00b0520", "mass": 10}, {"id": 543, "name": "Citizen n\u00b0530", "mass": 8}, {"id": 545, "name": "Citizen n\u00b0532", "mass": 9}, {"id": 548, "name": "Citizen n\u00b0535", "mass": 5}, {"id": 549, "name": "Citizen n\u00b0536", "mass": 8}, {"id": 551, "name": "Citizen n\u00b0538", "mass": 9}, {"id": 555, "name": "Citizen n\u00b0542", "mass": 10}, {"id": 557, "name": "Citizen n\u00b0544", "mass": 10}, {"id": 561, "name": "Citizen n\u00b0548", "mass": 10}, {"id": 563, "name": "Citizen n\u00b0550", "mass": 9}, {"id": 573, "name": "Citizen n\u00b0560", "mass": 10}, {"id": 575, "name": "Citizen n\u00b0562", "mass": 11}, {"id": 578, "name": "Citizen n\u00b0565", "mass": 10}, {"id": 579, "name": "Citizen n\u00b0566", "mass": 10}, {"id": 581, "name": "Citizen n\u00b0568", "mass": 10}, {"id": 585, "name": "Citizen n\u00b0572", "mass": 5}, {"id": 587, "name": "Citizen n\u00b0574", "mass": 9}, {"id": 591, "name": "Citizen n\u00b0578", "mass": 9}, {"id": 593, "name": "Citizen n\u00b0580", "mass": 10}, {"id": 603, "name": "Citizen n\u00b0590", "mass": 8}, {"id": 605, "name": "Citizen n\u00b0592", "mass": 9}, {"id": 609, "name": "Citizen n\u00b0596", "mass": 9}, {"id": 611, "name": "Citizen n\u00b0598", "mass": 8}, {"id": 615, "name": "Citizen n\u00b0602", "mass": 10}, {"id": 617, "name": "Citizen n\u00b0604", "mass": 10}, {"id": 621, "name": "Citizen n\u00b0608", "mass": 11}, {"id": 623, "name": "Citizen n\u00b0610", "mass": 6}, {"id": 633, "name": "Citizen n\u00b0620", "mass": 10}, {"id": 635, "name": "Citizen n\u00b0622", "mass": 10}, {"id": 639, "name": "Citizen n\u00b0626", "mass": 10}, {"id": 641, "name": "Citizen n\u00b0628", "mass": 10}, {"id": 645, "name": "Citizen n\u00b0632", "mass": 9}, {"id": 647, "name": "Citizen n\u00b0634", "mass": 8}, {"id": 648, "name": "Citizen n\u00b0635", "mass": 5}, {"id": 651, "name": "Citizen n\u00b0638", "mass": 9}, {"id": 653, "name": "Citizen n\u00b0640", "mass": 10}, {"id": 663, "name": "Citizen n\u00b0650", "mass": 9}, {"id": 665, "name": "Citizen n\u00b0652", "mass": 5}, {"id": 669, "name": "Citizen n\u00b0656", "mass": 8}, {"id": 671, "name": "Citizen n\u00b0658", "mass": 9}, {"id": 675, "name": "Citizen n\u00b0662", "mass": 11}, {"id": 677, "name": "Citizen n\u00b0664", "mass": 10}, {"id": 678, "name": "Citizen n\u00b0665", "mass": 10}, {"id": 681, "name": "Citizen n\u00b0668", "mass": 10}, {"id": 683, "name": "Citizen n\u00b0670", "mass": 10}, {"id": 693, "name": "Citizen n\u00b0680", "mass": 10}, {"id": 695, "name": "Citizen n\u00b0682", "mass": 10}, {"id": 698, "name": "Citizen n\u00b0685", "mass": 10}, {"id": 699, "name": "Citizen n\u00b0686", "mass": 10}, {"id": 701, "name": "Citizen n\u00b0688", "mass": 10}, {"id": 705, "name": "Citizen n\u00b0692", "mass": 9}, {"id": 707, "name": "Citizen n\u00b0694", "mass": 8}, {"id": 711, "name": "Citizen n\u00b0698", "mass": 8}, {"id": 713, "name": "Citizen n\u00b0700", "mass": 11}, {"id": 723, "name": "Citizen n\u00b0710", "mass": 6}, {"id": 725, "name": "Citizen n\u00b0712", "mass": 9}, {"id": 728, "name": "Citizen n\u00b0715", "mass": 8}, {"id": 729, "name": "Citizen n\u00b0716", "mass": 10}, {"id": 731, "name": "Citizen n\u00b0718", "mass": 5}, {"id": 735, "name": "Citizen n\u00b0722", "mass": 10}, {"id": 737, "name": "Citizen n\u00b0724", "mass": 11}, {"id": 741, "name": "Citizen n\u00b0728", "mass": 10}, {"id": 743, "name": "Citizen n\u00b0730", "mass": 8}, {"id": 753, "name": "Citizen n\u00b0740", "mass": 10}, {"id": 755, "name": "Citizen n\u00b0742", "mass": 10}, {"id": 759, "name": "Citizen n\u00b0746", "mass": 10}, {"id": 761, "name": "Citizen n\u00b0748", "mass": 10}, {"id": 765, "name": "Citizen n\u00b0752", "mass": 5}, {"id": 767, "name": "Citizen n\u00b0754", "mass": 10}, {"id": 771, "name": "Citizen n\u00b0758", "mass": 9}, {"id": 773, "name": "Citizen n\u00b0760", "mass": 10}, {"id": 783, "name": "Citizen n\u00b0770", "mass": 10}, {"id": 785, "name": "Citizen n\u00b0772", "mass": 5}, {"id": 789, "name": "Citizen n\u00b0776", "mass": 8}, {"id": 791, "name": "Citizen n\u00b0778", "mass": 9}, {"id": 795, "name": "Citizen n\u00b0782", "mass": 10}, {"id": 797, "name": "Citizen n\u00b0784", "mass": 10}, {"id": 798, "name": "Citizen n\u00b0785", "mass": 10}, {"id": 801, "name": "Citizen n\u00b0788", "mass": 10}, {"id": 803, "name": "Citizen n\u00b0790", "mass": 8}], "links": [{"source": 228, "target": 225, "value": 8}, {"source": 231, "target": 221, "value": 9}, {"source": 233, "target": 215, "value": 3}, {"source": 233, "target": 221, "value": 4}, {"source": 245, "target": 233, "value": 4}, {"source": 248, "target": 219, "value": 9}, {"source": 248, "target": 233, "value": 4}, {"source": 248, "target": 245, "value": 8}, {"source": 249, "target": 228, "value": 8}, {"source": 251, "target": 245, "value": 10}, {"source": 251, "target": 249, "value": 9}, {"source": 255, "target": 245, "value": 9}, {"source": 255, "target": 251, "value": 7}, {"source": 257, "target": 219, "value": 8}, {"source": 257, "target": 233, "value": 4}, {"source": 257, "target": 245, "value": 8}, {"source": 257, "target": 248, "value": 7}, {"source": 261, "target": 221, "value": 10}, {"source": 261, "target": 249, "value": 8}, {"source": 273, "target": 219, "value": 10}, {"source": 273, "target": 221, "value": 2}, {"source": 273, "target": 231, "value": 9}, {"source": 273, "target": 248, "value": 9}, {"source": 273, "target": 257, "value": 9}, {"source": 273, "target": 261, "value": 8}, {"source": 275, "target": 225, "value": 3}, {"source": 275, "target": 228, "value": 8}, {"source": 275, "target": 257, "value": 3}, {"source": 278, "target": 225, "value": 7}, {"source": 278, "target": 228, "value": 3}, {"source": 278, "target": 249, "value": 9}, {"source": 278, "target": 275, "value": 8}, {"source": 279, "target": 221, "value": 4}, {"source": 279, "target": 227, "value": 9}, {"source": 279, "target": 233, "value": 2}, {"source": 281, "target": 221, "value": 2}, {"source": 281, "target": 231, "value": 3}, {"source": 281, "target": 243, "value": 8}, {"source": 281, "target": 273, "value": 10}, {"source": 285, "target": 233, "value": 4}, {"source": 285, "target": 245, "value": 10}, {"source": 285, "target": 248, "value": 10}, {"source": 285, "target": 261, "value": 8}, {"source": 285, "target": 273, "value": 8}, {"source": 287, "target": 225, "value": 8}, {"source": 287, "target": 263, "value": 9}, {"source": 293, "target": 221, "value": 11}, {"source": 293, "target": 243, "value": 1}, {"source": 293, "target": 245, "value": 9}, {"source": 293, "target": 251, "value": 8}, {"source": 293, "target": 255, "value": 8}, {"source": 293, "target": 261, "value": 3}, {"source": 293, "target": 281, "value": 8}, {"source": 293, "target": 285, "value": 8}, {"source": 293, "target": 291, "value": 8}, {"source": 303, "target": 227, "value": 9}, {"source": 303, "target": 228, "value": 8}, {"source": 303, "target": 249, "value": 10}, {"source": 303, "target": 278, "value": 9}, {"source": 303, "target": 279, "value": 8}, {"source": 303, "target": 293, "value": 9}, {"source": 305, "target": 245, "value": 9}, {"source": 305, "target": 251, "value": 7}, {"source": 305, "target": 255, "value": 2}, {"source": 305, "target": 293, "value": 8}, {"source": 309, "target": 219, "value": 9}, {"source": 309, "target": 221, "value": 8}, {"source": 309, "target": 248, "value": 8}, {"source": 309, "target": 257, "value": 8}, {"source": 309, "target": 261, "value": 9}, {"source": 309, "target": 263, "value": 11}, {"source": 309, "target": 273, "value": 8}, {"source": 311, "target": 249, "value": 8}, {"source": 311, "target": 261, "value": 3}, {"source": 311, "target": 285, "value": 8}, {"source": 315, "target": 215, "value": 4}, {"source": 315, "target": 311, "value": 3}, {"source": 317, "target": 221, "value": 10}, {"source": 317, "target": 227, "value": 9}, {"source": 317, "target": 233, "value": 10}, {"source": 317, "target": 243, "value": 4}, {"source": 317, "target": 255, "value": 8}, {"source": 317, "target": 279, "value": 8}, {"source": 317, "target": 293, "value": 3}, {"source": 317, "target": 305, "value": 8}, {"source": 321, "target": 219, "value": 10}, {"source": 321, "target": 221, "value": 2}, {"source": 321, "target": 225, "value": 7}, {"source": 321, "target": 228, "value": 8}, {"source": 321, "target": 233, "value": 4}, {"source": 321, "target": 249, "value": 11}, {"source": 321, "target": 251, "value": 8}, {"source": 321, "target": 261, "value": 9}, {"source": 321, "target": 275, "value": 2}, {"source": 321, "target": 278, "value": 7}, {"source": 321, "target": 279, "value": 4}, {"source": 321, "target": 293, "value": 11}, {"source": 321, "target": 309, "value": 8}, {"source": 323, "target": 219, "value": 10}, {"source": 323, "target": 221, "value": 8}, {"source": 323, "target": 248, "value": 9}, {"source": 323, "target": 257, "value": 9}, {"source": 323, "target": 261, "value": 8}, {"source": 323, "target": 273, "value": 1}, {"source": 323, "target": 309, "value": 8}, {"source": 333, "target": 221, "value": 4}, {"source": 333, "target": 233, "value": 0}, {"source": 333, "target": 243, "value": 9}, {"source": 333, "target": 245, "value": 2}, {"source": 333, "target": 248, "value": 8}, {"source": 333, "target": 257, "value": 4}, {"source": 333, "target": 279, "value": 4}, {"source": 333, "target": 281, "value": 8}, {"source": 333, "target": 285, "value": 9}, {"source": 333, "target": 293, "value": 9}, {"source": 333, "target": 309, "value": 8}, {"source": 333, "target": 315, "value": 3}, {"source": 333, "target": 317, "value": 9}, {"source": 333, "target": 321, "value": 4}, {"source": 335, "target": 233, "value": 2}, {"source": 335, "target": 243, "value": 11}, {"source": 335, "target": 273, "value": 8}, {"source": 335, "target": 285, "value": 3}, {"source": 335, "target": 293, "value": 10}, {"source": 335, "target": 303, "value": 3}, {"source": 335, "target": 333, "value": 8}, {"source": 339, "target": 243, "value": 11}, {"source": 339, "target": 279, "value": 3}, {"source": 339, "target": 293, "value": 10}, {"source": 339, "target": 335, "value": 8}, {"source": 341, "target": 251, "value": 9}, {"source": 341, "target": 291, "value": 3}, {"source": 341, "target": 293, "value": 4}, {"source": 341, "target": 303, "value": 8}, {"source": 345, "target": 233, "value": 4}, {"source": 345, "target": 245, "value": 2}, {"source": 345, "target": 248, "value": 8}, {"source": 345, "target": 257, "value": 8}, {"source": 345, "target": 285, "value": 9}, {"source": 345, "target": 333, "value": 8}, {"source": 347, "target": 221, "value": 8}, {"source": 347, "target": 273, "value": 4}, {"source": 347, "target": 285, "value": 8}, {"source": 347, "target": 293, "value": 7}, {"source": 347, "target": 323, "value": 9}, {"source": 348, "target": 219, "value": 8}, {"source": 348, "target": 233, "value": 4}, {"source": 348, "target": 245, "value": 8}, {"source": 348, "target": 248, "value": 4}, {"source": 348, "target": 257, "value": 7}, {"source": 348, "target": 273, "value": 9}, {"source": 348, "target": 285, "value": 10}, {"source": 348, "target": 309, "value": 8}, {"source": 348, "target": 323, "value": 9}, {"source": 348, "target": 333, "value": 8}, {"source": 348, "target": 345, "value": 8}, {"source": 351, "target": 251, "value": 3}, {"source": 351, "target": 263, "value": 8}, {"source": 351, "target": 309, "value": 9}, {"source": 351, "target": 341, "value": 9}, {"source": 353, "target": 215, "value": 8}, {"source": 353, "target": 227, "value": 4}, {"source": 353, "target": 228, "value": 9}, {"source": 353, "target": 249, "value": 10}, {"source": 353, "target": 278, "value": 9}, {"source": 353, "target": 279, "value": 4}, {"source": 353, "target": 293, "value": 9}, {"source": 353, "target": 303, "value": 1}, {"source": 353, "target": 315, "value": 8}, {"source": 353, "target": 341, "value": 8}, {"source": 363, "target": 255, "value": 9}, {"source": 363, "target": 261, "value": 9}, {"source": 363, "target": 263, "value": 2}, {"source": 363, "target": 285, "value": 8}, {"source": 363, "target": 287, "value": 9}, {"source": 363, "target": 305, "value": 8}, {"source": 363, "target": 309, "value": 10}, {"source": 363, "target": 311, "value": 9}, {"source": 363, "target": 317, "value": 7}, {"source": 363, "target": 351, "value": 8}, {"source": 365, "target": 215, "value": 4}, {"source": 365, "target": 225, "value": 9}, {"source": 365, "target": 227, "value": 4}, {"source": 365, "target": 228, "value": 10}, {"source": 365, "target": 275, "value": 9}, {"source": 365, "target": 278, "value": 10}, {"source": 365, "target": 291, "value": 8}, {"source": 365, "target": 293, "value": 9}, {"source": 365, "target": 315, "value": 3}, {"source": 365, "target": 317, "value": 4}, {"source": 365, "target": 321, "value": 9}, {"source": 365, "target": 341, "value": 8}, {"source": 365, "target": 353, "value": 8}, {"source": 369, "target": 219, "value": 4}, {"source": 369, "target": 221, "value": 9}, {"source": 369, "target": 231, "value": 8}, {"source": 369, "target": 248, "value": 8}, {"source": 369, "target": 257, "value": 8}, {"source": 369, "target": 273, "value": 5}, {"source": 369, "target": 281, "value": 8}, {"source": 369, "target": 309, "value": 9}, {"source": 369, "target": 321, "value": 10}, {"source": 369, "target": 323, "value": 10}, {"source": 369, "target": 348, "value": 8}, {"source": 371, "target": 221, "value": 4}, {"source": 371, "target": 261, "value": 10}, {"source": 371, "target": 293, "value": 11}, {"source": 371, "target": 309, "value": 8}, {"source": 371, "target": 321, "value": 3}, {"source": 375, "target": 225, "value": 4}, {"source": 375, "target": 228, "value": 8}, {"source": 375, "target": 275, "value": 4}, {"source": 375, "target": 278, "value": 8}, {"source": 375, "target": 287, "value": 3}, {"source": 375, "target": 321, "value": 7}, {"source": 375, "target": 365, "value": 9}, {"source": 377, "target": 221, "value": 10}, {"source": 377, "target": 225, "value": 9}, {"source": 377, "target": 227, "value": 4}, {"source": 377, "target": 231, "value": 8}, {"source": 377, "target": 243, "value": 10}, {"source": 377, "target": 255, "value": 10}, {"source": 377, "target": 273, "value": 4}, {"source": 377, "target": 275, "value": 2}, {"source": 377, "target": 279, "value": 10}, {"source": 377, "target": 281, "value": 8}, {"source": 377, "target": 285, "value": 9}, {"source": 377, "target": 293, "value": 10}, {"source": 377, "target": 303, "value": 9}, {"source": 377, "target": 305, "value": 10}, {"source": 377, "target": 317, "value": 9}, {"source": 377, "target": 335, "value": 4}, {"source": 377, "target": 339, "value": 8}, {"source": 377, "target": 345, "value": 3}, {"source": 377, "target": 353, "value": 4}, {"source": 377, "target": 365, "value": 4}, {"source": 377, "target": 369, "value": 7}, {"source": 377, "target": 375, "value": 8}, {"source": 378, "target": 225, "value": 8}, {"source": 378, "target": 228, "value": 4}, {"source": 378, "target": 249, "value": 8}, {"source": 378, "target": 275, "value": 8}, {"source": 378, "target": 278, "value": 4}, {"source": 378, "target": 303, "value": 9}, {"source": 378, "target": 321, "value": 8}, {"source": 378, "target": 353, "value": 9}, {"source": 378, "target": 365, "value": 10}, {"source": 378, "target": 375, "value": 8}, {"source": 381, "target": 221, "value": 10}, {"source": 381, "target": 231, "value": 4}, {"source": 381, "target": 233, "value": 4}, {"source": 381, "target": 243, "value": 8}, {"source": 381, "target": 251, "value": 3}, {"source": 381, "target": 257, "value": 8}, {"source": 381, "target": 273, "value": 10}, {"source": 381, "target": 281, "value": 2}, {"source": 381, "target": 291, "value": 9}, {"source": 381, "target": 293, "value": 8}, {"source": 381, "target": 321, "value": 3}, {"source": 381, "target": 333, "value": 2}, {"source": 381, "target": 341, "value": 9}, {"source": 381, "target": 369, "value": 8}, {"source": 381, "target": 377, "value": 8}, {"source": 383, "target": 221, "value": 4}, {"source": 383, "target": 233, "value": 1}, {"source": 383, "target": 257, "value": 8}, {"source": 383, "target": 279, "value": 4}, {"source": 383, "target": 317, "value": 9}, {"source": 383, "target": 321, "value": 4}, {"source": 383, "target": 333, "value": 1}, {"source": 383, "target": 335, "value": 9}, {"source": 383, "target": 381, "value": 7}, {"source": 393, "target": 221, "value": 3}, {"source": 393, "target": 243, "value": 1}, {"source": 393, "target": 245, "value": 9}, {"source": 393, "target": 251, "value": 8}, {"source": 393, "target": 255, "value": 8}, {"source": 393, "target": 281, "value": 8}, {"source": 393, "target": 291, "value": 3}, {"source": 393, "target": 293, "value": 1}, {"source": 393, "target": 305, "value": 8}, {"source": 393, "target": 317, "value": 3}, {"source": 393, "target": 333, "value": 9}, {"source": 393, "target": 335, "value": 10}, {"source": 393, "target": 339, "value": 10}, {"source": 393, "target": 377, "value": 9}, {"source": 393, "target": 381, "value": 8}, {"source": 395, "target": 233, "value": 4}, {"source": 395, "target": 245, "value": 4}, {"source": 395, "target": 248, "value": 8}, {"source": 395, "target": 279, "value": 3}, {"source": 395, "target": 285, "value": 10}, {"source": 395, "target": 333, "value": 8}, {"source": 395, "target": 345, "value": 3}, {"source": 395, "target": 348, "value": 8}, {"source": 398, "target": 219, "value": 8}, {"source": 398, "target": 233, "value": 4}, {"source": 398, "target": 243, "value": 9}, {"source": 398, "target": 245, "value": 8}, {"source": 398, "target": 248, "value": 4}, {"source": 398, "target": 257, "value": 8}, {"source": 398, "target": 273, "value": 10}, {"source": 398, "target": 281, "value": 8}, {"source": 398, "target": 285, "value": 10}, {"source": 398, "target": 293, "value": 9}, {"source": 398, "target": 309, "value": 9}, {"source": 398, "target": 323, "value": 9}, {"source": 398, "target": 333, "value": 4}, {"source": 398, "target": 345, "value": 7}, {"source": 398, "target": 348, "value": 3}, {"source": 398, "target": 369, "value": 8}, {"source": 398, "target": 381, "value": 8}, {"source": 398, "target": 393, "value": 9}, {"source": 398, "target": 395, "value": 8}, {"source": 399, "target": 228, "value": 9}, {"source": 399, "target": 249, "value": 4}, {"source": 399, "target": 251, "value": 10}, {"source": 399, "target": 255, "value": 3}, {"source": 399, "target": 278, "value": 8}, {"source": 399, "target": 303, "value": 11}, {"source": 399, "target": 321, "value": 11}, {"source": 399, "target": 353, "value": 10}, {"source": 399, "target": 378, "value": 8}, {"source": 399, "target": 395, "value": 3}, {"source": 401, "target": 243, "value": 3}, {"source": 401, "target": 251, "value": 4}, {"source": 401, "target": 263, "value": 8}, {"source": 401, "target": 341, "value": 9}, {"source": 401, "target": 351, "value": 3}, {"source": 401, "target": 363, "value": 8}, {"source": 401, "target": 383, "value": 3}, {"source": 405, "target": 215, "value": 9}, {"source": 405, "target": 233, "value": 10}, {"source": 405, "target": 245, "value": 10}, {"source": 405, "target": 251, "value": 8}, {"source": 405, "target": 255, "value": 2}, {"source": 405, "target": 263, "value": 11}, {"source": 405, "target": 293, "value": 8}, {"source": 405, "target": 303, "value": 9}, {"source": 405, "target": 305, "value": 2}, {"source": 405, "target": 309, "value": 8}, {"source": 405, "target": 315, "value": 9}, {"source": 405, "target": 317, "value": 8}, {"source": 405, "target": 333, "value": 10}, {"source": 405, "target": 335, "value": 8}, {"source": 405, "target": 351, "value": 9}, {"source": 405, "target": 353, "value": 4}, {"source": 405, "target": 363, "value": 4}, {"source": 405, "target": 365, "value": 9}, {"source": 405, "target": 377, "value": 11}, {"source": 405, "target": 383, "value": 10}, {"source": 405, "target": 393, "value": 8}, {"source": 407, "target": 233, "value": 4}, {"source": 407, "target": 245, "value": 8}, {"source": 407, "target": 257, "value": 4}, {"source": 407, "target": 333, "value": 4}, {"source": 407, "target": 345, "value": 7}, {"source": 407, "target": 381, "value": 8}, {"source": 407, "target": 383, "value": 9}, {"source": 411, "target": 249, "value": 8}, {"source": 411, "target": 261, "value": 4}, {"source": 411, "target": 285, "value": 8}, {"source": 411, "target": 311, "value": 4}, {"source": 411, "target": 363, "value": 9}, {"source": 413, "target": 215, "value": 26}, {"source": 413, "target": 219, "value": 27}, {"source": 413, "target": 221, "value": 26}, {"source": 413, "target": 225, "value": 6}, {"source": 413, "target": 227, "value": 28}, {"source": 413, "target": 228, "value": 6}, {"source": 413, "target": 231, "value": 27}, {"source": 413, "target": 233, "value": 27}, {"source": 413, "target": 243, "value": 29}, {"source": 413, "target": 245, "value": 28}, {"source": 413, "target": 248, "value": 28}, {"source": 413, "target": 249, "value": 29}, {"source": 413, "target": 251, "value": 28}, {"source": 413, "target": 255, "value": 28}, {"source": 413, "target": 257, "value": 29}, {"source": 413, "target": 261, "value": 6}, {"source": 413, "target": 263, "value": 1}, {"source": 413, "target": 273, "value": 7}, {"source": 413, "target": 275, "value": 6}, {"source": 413, "target": 278, "value": 6}, {"source": 413, "target": 279, "value": 29}, {"source": 413, "target": 287, "value": 4}, {"source": 413, "target": 309, "value": 8}, {"source": 413, "target": 321, "value": 7}, {"source": 413, "target": 323, "value": 8}, {"source": 413, "target": 339, "value": 7}, {"source": 413, "target": 351, "value": 7}, {"source": 413, "target": 353, "value": 3}, {"source": 413, "target": 363, "value": 1}, {"source": 413, "target": 365, "value": 8}, {"source": 413, "target": 375, "value": 8}, {"source": 413, "target": 378, "value": 8}, {"source": 413, "target": 401, "value": 8}, {"source": 413, "target": 405, "value": 9}, {"source": 423, "target": 219, "value": 10}, {"source": 423, "target": 221, "value": 8}, {"source": 423, "target": 248, "value": 8}, {"source": 423, "target": 257, "value": 9}, {"source": 423, "target": 261, "value": 8}, {"source": 423, "target": 273, "value": 1}, {"source": 423, "target": 309, "value": 8}, {"source": 423, "target": 323, "value": 2}, {"source": 423, "target": 347, "value": 8}, {"source": 423, "target": 348, "value": 8}, {"source": 423, "target": 369, "value": 10}, {"source": 423, "target": 398, "value": 9}, {"source": 423, "target": 413, "value": 8}, {"source": 425, "target": 225, "value": 2}, {"source": 425, "target": 228, "value": 8}, {"source": 425, "target": 275, "value": 4}, {"source": 425, "target": 278, "value": 8}, {"source": 425, "target": 287, "value": 8}, {"source": 425, "target": 321, "value": 8}, {"source": 425, "target": 365, "value": 9}, {"source": 425, "target": 375, "value": 3}, {"source": 425, "target": 377, "value": 9}, {"source": 425, "target": 378, "value": 8}, {"source": 425, "target": 413, "value": 8}, {"source": 428, "target": 225, "value": 8}, {"source": 428, "target": 228, "value": 4}, {"source": 428, "target": 249, "value": 8}, {"source": 428, "target": 275, "value": 8}, {"source": 428, "target": 278, "value": 4}, {"source": 428, "target": 303, "value": 9}, {"source": 428, "target": 321, "value": 8}, {"source": 428, "target": 353, "value": 9}, {"source": 428, "target": 365, "value": 9}, {"source": 428, "target": 375, "value": 7}, {"source": 428, "target": 378, "value": 3}, {"source": 428, "target": 399, "value": 8}, {"source": 428, "target": 413, "value": 8}, {"source": 428, "target": 425, "value": 8}, {"source": 429, "target": 221, "value": 11}, {"source": 429, "target": 225, "value": 8}, {"source": 429, "target": 233, "value": 11}, {"source": 429, "target": 243, "value": 9}, {"source": 429, "target": 255, "value": 8}, {"source": 429, "target": 279, "value": 4}, {"source": 429, "target": 287, "value": 9}, {"source": 429, "target": 291, "value": 8}, {"source": 429, "target": 293, "value": 8}, {"source": 429, "target": 305, "value": 8}, {"source": 429, "target": 317, "value": 8}, {"source": 429, "target": 333, "value": 11}, {"source": 429, "target": 335, "value": 9}, {"source": 429, "target": 339, "value": 9}, {"source": 429, "target": 341, "value": 8}, {"source": 429, "target": 377, "value": 4}, {"source": 429, "target": 381, "value": 9}, {"source": 429, "target": 383, "value": 10}, {"source": 429, "target": 393, "value": 8}, {"source": 429, "target": 405, "value": 8}, {"source": 429, "target": 425, "value": 9}, {"source": 431, "target": 221, "value": 10}, {"source": 431, "target": 231, "value": 4}, {"source": 431, "target": 243, "value": 9}, {"source": 431, "target": 273, "value": 10}, {"source": 431, "target": 281, "value": 2}, {"source": 431, "target": 293, "value": 9}, {"source": 431, "target": 333, "value": 8}, {"source": 431, "target": 369, "value": 7}, {"source": 431, "target": 377, "value": 7}, {"source": 431, "target": 381, "value": 2}, {"source": 431, "target": 393, "value": 9}, {"source": 431, "target": 398, "value": 8}, {"source": 435, "target": 263, "value": 3}, {"source": 435, "target": 273, "value": 8}, {"source": 435, "target": 285, "value": 4}, {"source": 435, "target": 333, "value": 3}, {"source": 435, "target": 335, "value": 4}, {"source": 435, "target": 377, "value": 9}, {"source": 437, "target": 225, "value": 8}, {"source": 437, "target": 228, "value": 8}, {"source": 437, "target": 249, "value": 8}, {"source": 437, "target": 251, "value": 3}, {"source": 437, "target": 263, "value": 9}, {"source": 437, "target": 278, "value": 8}, {"source": 437, "target": 285, "value": 8}, {"source": 437, "target": 287, "value": 4}, {"source": 437, "target": 293, "value": 9}, {"source": 437, "target": 303, "value": 10}, {"source": 437, "target": 321, "value": 3}, {"source": 437, "target": 347, "value": 9}, {"source": 437, "target": 353, "value": 10}, {"source": 437, "target": 363, "value": 9}, {"source": 437, "target": 378, "value": 8}, {"source": 437, "target": 399, "value": 8}, {"source": 437, "target": 413, "value": 4}, {"source": 437, "target": 425, "value": 8}, {"source": 437, "target": 428, "value": 7}, {"source": 437, "target": 429, "value": 9}, {"source": 441, "target": 291, "value": 4}, {"source": 441, "target": 293, "value": 5}, {"source": 441, "target": 303, "value": 8}, {"source": 441, "target": 341, "value": 2}, {"source": 441, "target": 353, "value": 8}, {"source": 441, "target": 365, "value": 8}, {"source": 441, "target": 381, "value": 9}, {"source": 441, "target": 429, "value": 8}, {"source": 441, "target": 437, "value": 3}, {"source": 443, "target": 243, "value": 2}, {"source": 443, "target": 281, "value": 8}, {"source": 443, "target": 293, "value": 1}, {"source": 443, "target": 317, "value": 4}, {"source": 443, "target": 333, "value": 9}, {"source": 443, "target": 335, "value": 10}, {"source": 443, "target": 339, "value": 10}, {"source": 443, "target": 377, "value": 9}, {"source": 443, "target": 381, "value": 8}, {"source": 443, "target": 393, "value": 1}, {"source": 443, "target": 398, "value": 9}, {"source": 443, "target": 429, "value": 8}, {"source": 443, "target": 431, "value": 9}, {"source": 453, "target": 215, "value": 4}, {"source": 453, "target": 227, "value": 4}, {"source": 453, "target": 228, "value": 8}, {"source": 453, "target": 249, "value": 10}, {"source": 453, "target": 263, "value": 9}, {"source": 453, "target": 278, "value": 8}, {"source": 453, "target": 279, "value": 4}, {"source": 453, "target": 293, "value": 10}, {"source": 453, "target": 303, "value": 1}, {"source": 453, "target": 315, "value": 4}, {"source": 453, "target": 341, "value": 8}, {"source": 453, "target": 353, "value": 0}, {"source": 453, "target": 363, "value": 9}, {"source": 453, "target": 365, "value": 4}, {"source": 453, "target": 377, "value": 4}, {"source": 453, "target": 378, "value": 8}, {"source": 453, "target": 399, "value": 9}, {"source": 453, "target": 401, "value": 8}, {"source": 453, "target": 405, "value": 3}, {"source": 453, "target": 407, "value": 3}, {"source": 453, "target": 413, "value": 9}, {"source": 453, "target": 428, "value": 8}, {"source": 453, "target": 437, "value": 9}, {"source": 453, "target": 441, "value": 8}, {"source": 455, "target": 245, "value": 10}, {"source": 455, "target": 251, "value": 8}, {"source": 455, "target": 255, "value": 1}, {"source": 455, "target": 263, "value": 9}, {"source": 455, "target": 293, "value": 8}, {"source": 455, "target": 305, "value": 4}, {"source": 455, "target": 363, "value": 9}, {"source": 455, "target": 377, "value": 11}, {"source": 455, "target": 393, "value": 8}, {"source": 455, "target": 395, "value": 3}, {"source": 455, "target": 401, "value": 8}, {"source": 455, "target": 405, "value": 3}, {"source": 455, "target": 413, "value": 9}, {"source": 455, "target": 429, "value": 8}, {"source": 455, "target": 453, "value": 7}, {"source": 459, "target": 263, "value": 11}, {"source": 459, "target": 309, "value": 4}, {"source": 459, "target": 333, "value": 8}, {"source": 459, "target": 351, "value": 10}, {"source": 459, "target": 363, "value": 10}, {"source": 459, "target": 371, "value": 3}, {"source": 459, "target": 405, "value": 7}, {"source": 459, "target": 413, "value": 10}, {"source": 459, "target": 441, "value": 3}, {"source": 461, "target": 215, "value": 8}, {"source": 461, "target": 221, "value": 9}, {"source": 461, "target": 249, "value": 8}, {"source": 461, "target": 261, "value": 2}, {"source": 461, "target": 263, "value": 8}, {"source": 461, "target": 273, "value": 8}, {"source": 461, "target": 285, "value": 8}, {"source": 461, "target": 287, "value": 8}, {"source": 461, "target": 309, "value": 4}, {"source": 461, "target": 311, "value": 4}, {"source": 461, "target": 315, "value": 8}, {"source": 461, "target": 321, "value": 9}, {"source": 461, "target": 323, "value": 8}, {"source": 461, "target": 333, "value": 8}, {"source": 461, "target": 339, "value": 10}, {"source": 461, "target": 353, "value": 8}, {"source": 461, "target": 363, "value": 4}, {"source": 461, "target": 365, "value": 7}, {"source": 461, "target": 371, "value": 9}, {"source": 461, "target": 405, "value": 9}, {"source": 461, "target": 411, "value": 3}, {"source": 461, "target": 413, "value": 2}, {"source": 461, "target": 423, "value": 8}, {"source": 461, "target": 429, "value": 3}, {"source": 461, "target": 437, "value": 8}, {"source": 461, "target": 453, "value": 4}, {"source": 461, "target": 459, "value": 8}, {"source": 465, "target": 215, "value": 4}, {"source": 465, "target": 227, "value": 8}, {"source": 465, "target": 315, "value": 4}, {"source": 465, "target": 317, "value": 9}, {"source": 465, "target": 353, "value": 8}, {"source": 465, "target": 365, "value": 2}, {"source": 465, "target": 377, "value": 8}, {"source": 465, "target": 405, "value": 9}, {"source": 465, "target": 453, "value": 4}, {"source": 465, "target": 461, "value": 7}, {"source": 467, "target": 243, "value": 9}, {"source": 467, "target": 255, "value": 10}, {"source": 467, "target": 293, "value": 4}, {"source": 467, "target": 305, "value": 8}, {"source": 467, "target": 317, "value": 4}, {"source": 467, "target": 363, "value": 8}, {"source": 467, "target": 393, "value": 4}, {"source": 467, "target": 405, "value": 7}, {"source": 467, "target": 443, "value": 9}, {"source": 471, "target": 221, "value": 4}, {"source": 471, "target": 261, "value": 10}, {"source": 471, "target": 293, "value": 11}, {"source": 471, "target": 309, "value": 8}, {"source": 471, "target": 321, "value": 4}, {"source": 471, "target": 371, "value": 4}, {"source": 471, "target": 461, "value": 8}, {"source": 473, "target": 219, "value": 9}, {"source": 473, "target": 221, "value": 4}, {"source": 473, "target": 248, "value": 8}, {"source": 473, "target": 257, "value": 9}, {"source": 473, "target": 261, "value": 8}, {"source": 473, "target": 273, "value": 1}, {"source": 473, "target": 285, "value": 8}, {"source": 473, "target": 309, "value": 8}, {"source": 473, "target": 323, "value": 1}, {"source": 473, "target": 335, "value": 8}, {"source": 473, "target": 347, "value": 4}, {"source": 473, "target": 348, "value": 8}, {"source": 473, "target": 369, "value": 10}, {"source": 473, "target": 377, "value": 9}, {"source": 473, "target": 398, "value": 8}, {"source": 473, "target": 399, "value": 3}, {"source": 473, "target": 413, "value": 9}, {"source": 473, "target": 423, "value": 1}, {"source": 473, "target": 435, "value": 8}, {"source": 473, "target": 461, "value": 8}, {"source": 483, "target": 221, "value": 4}, {"source": 483, "target": 233, "value": 1}, {"source": 483, "target": 257, "value": 8}, {"source": 483, "target": 279, "value": 4}, {"source": 483, "target": 317, "value": 9}, {"source": 483, "target": 321, "value": 4}, {"source": 483, "target": 333, "value": 1}, {"source": 483, "target": 335, "value": 10}, {"source": 483, "target": 381, "value": 8}, {"source": 483, "target": 383, "value": 2}, {"source": 483, "target": 405, "value": 10}, {"source": 483, "target": 407, "value": 8}, {"source": 483, "target": 429, "value": 9}, {"source": 485, "target": 233, "value": 4}, {"source": 485, "target": 245, "value": 9}, {"source": 485, "target": 248, "value": 9}, {"source": 485, "target": 273, "value": 8}, {"source": 485, "target": 285, "value": 2}, {"source": 485, "target": 293, "value": 8}, {"source": 485, "target": 333, "value": 8}, {"source": 485, "target": 335, "value": 4}, {"source": 485, "target": 345, "value": 9}, {"source": 485, "target": 347, "value": 8}, {"source": 485, "target": 348, "value": 9}, {"source": 485, "target": 377, "value": 10}, {"source": 485, "target": 395, "value": 9}, {"source": 485, "target": 398, "value": 9}, {"source": 485, "target": 435, "value": 3}, {"source": 485, "target": 437, "value": 9}, {"source": 485, "target": 473, "value": 8}, {"source": 489, "target": 243, "value": 3}, {"source": 489, "target": 251, "value": 8}, {"source": 489, "target": 263, "value": 8}, {"source": 489, "target": 281, "value": 8}, {"source": 489, "target": 287, "value": 9}, {"source": 489, "target": 293, "value": 2}, {"source": 489, "target": 317, "value": 10}, {"source": 489, "target": 333, "value": 10}, {"source": 489, "target": 335, "value": 8}, {"source": 489, "target": 339, "value": 4}, {"source": 489, "target": 341, "value": 9}, {"source": 489, "target": 351, "value": 8}, {"source": 489, "target": 363, "value": 8}, {"source": 489, "target": 377, "value": 8}, {"source": 489, "target": 381, "value": 8}, {"source": 489, "target": 393, "value": 2}, {"source": 489, "target": 398, "value": 9}, {"source": 489, "target": 401, "value": 8}, {"source": 489, "target": 413, "value": 2}, {"source": 489, "target": 429, "value": 9}, {"source": 489, "target": 431, "value": 8}, {"source": 489, "target": 437, "value": 8}, {"source": 489, "target": 443, "value": 3}, {"source": 489, "target": 461, "value": 4}, {"source": 489, "target": 467, "value": 10}, {"source": 491, "target": 291, "value": 4}, {"source": 491, "target": 293, "value": 10}, {"source": 491, "target": 341, "value": 4}, {"source": 491, "target": 365, "value": 8}, {"source": 491, "target": 381, "value": 9}, {"source": 491, "target": 429, "value": 7}, {"source": 491, "target": 441, "value": 3}, {"source": 495, "target": 233, "value": 4}, {"source": 495, "target": 245, "value": 4}, {"source": 495, "target": 248, "value": 8}, {"source": 495, "target": 285, "value": 10}, {"source": 495, "target": 309, "value": 3}, {"source": 495, "target": 333, "value": 8}, {"source": 495, "target": 345, "value": 4}, {"source": 495, "target": 348, "value": 8}, {"source": 495, "target": 395, "value": 4}, {"source": 495, "target": 398, "value": 8}, {"source": 495, "target": 485, "value": 8}, {"source": 497, "target": 221, "value": 8}, {"source": 497, "target": 263, "value": 5}, {"source": 497, "target": 273, "value": 4}, {"source": 497, "target": 285, "value": 8}, {"source": 497, "target": 293, "value": 8}, {"source": 497, "target": 309, "value": 4}, {"source": 497, "target": 323, "value": 9}, {"source": 497, "target": 347, "value": 4}, {"source": 497, "target": 351, "value": 4}, {"source": 497, "target": 363, "value": 4}, {"source": 497, "target": 405, "value": 4}, {"source": 497, "target": 413, "value": 4}, {"source": 497, "target": 423, "value": 9}, {"source": 497, "target": 437, "value": 2}, {"source": 497, "target": 459, "value": 4}, {"source": 497, "target": 473, "value": 4}, {"source": 497, "target": 485, "value": 8}, {"source": 498, "target": 219, "value": 8}, {"source": 498, "target": 233, "value": 4}, {"source": 498, "target": 245, "value": 8}, {"source": 498, "target": 248, "value": 4}, {"source": 498, "target": 257, "value": 8}, {"source": 498, "target": 273, "value": 10}, {"source": 498, "target": 285, "value": 10}, {"source": 498, "target": 309, "value": 9}, {"source": 498, "target": 323, "value": 10}, {"source": 498, "target": 333, "value": 9}, {"source": 498, "target": 345, "value": 8}, {"source": 498, "target": 348, "value": 4}, {"source": 498, "target": 369, "value": 8}, {"source": 498, "target": 395, "value": 8}, {"source": 498, "target": 398, "value": 4}, {"source": 498, "target": 423, "value": 9}, {"source": 498, "target": 473, "value": 9}, {"source": 498, "target": 485, "value": 9}, {"source": 498, "target": 495, "value": 8}, {"source": 501, "target": 249, "value": 8}, {"source": 501, "target": 251, "value": 4}, {"source": 501, "target": 261, "value": 9}, {"source": 501, "target": 263, "value": 8}, {"source": 501, "target": 311, "value": 9}, {"source": 501, "target": 341, "value": 10}, {"source": 501, "target": 351, "value": 4}, {"source": 501, "target": 363, "value": 8}, {"source": 501, "target": 401, "value": 2}, {"source": 501, "target": 411, "value": 9}, {"source": 501, "target": 413, "value": 2}, {"source": 501, "target": 453, "value": 8}, {"source": 501, "target": 455, "value": 8}, {"source": 501, "target": 461, "value": 9}, {"source": 501, "target": 483, "value": 3}, {"source": 501, "target": 489, "value": 8}, {"source": 503, "target": 227, "value": 8}, {"source": 503, "target": 228, "value": 8}, {"source": 503, "target": 245, "value": 8}, {"source": 503, "target": 249, "value": 9}, {"source": 503, "target": 251, "value": 9}, {"source": 503, "target": 257, "value": 8}, {"source": 503, "target": 278, "value": 8}, {"source": 503, "target": 279, "value": 9}, {"source": 503, "target": 293, "value": 10}, {"source": 503, "target": 303, "value": 2}, {"source": 503, "target": 341, "value": 8}, {"source": 503, "target": 345, "value": 8}, {"source": 503, "target": 351, "value": 9}, {"source": 503, "target": 353, "value": 1}, {"source": 503, "target": 377, "value": 8}, {"source": 503, "target": 378, "value": 8}, {"source": 503, "target": 399, "value": 9}, {"source": 503, "target": 401, "value": 9}, {"source": 503, "target": 405, "value": 9}, {"source": 503, "target": 407, "value": 7}, {"source": 503, "target": 428, "value": 8}, {"source": 503, "target": 437, "value": 9}, {"source": 503, "target": 441, "value": 8}, {"source": 503, "target": 453, "value": 1}, {"source": 503, "target": 501, "value": 8}, {"source": 513, "target": 215, "value": 26}, {"source": 513, "target": 219, "value": 27}, {"source": 513, "target": 221, "value": 26}, {"source": 513, "target": 225, "value": 6}, {"source": 513, "target": 227, "value": 29}, {"source": 513, "target": 228, "value": 6}, {"source": 513, "target": 231, "value": 28}, {"source": 513, "target": 233, "value": 29}, {"source": 513, "target": 243, "value": 29}, {"source": 513, "target": 245, "value": 28}, {"source": 513, "target": 248, "value": 29}, {"source": 513, "target": 249, "value": 29}, {"source": 513, "target": 251, "value": 28}, {"source": 513, "target": 255, "value": 28}, {"source": 513, "target": 257, "value": 29}, {"source": 513, "target": 261, "value": 29}, {"source": 513, "target": 263, "value": 1}, {"source": 513, "target": 273, "value": 28}, {"source": 513, "target": 275, "value": 6}, {"source": 513, "target": 278, "value": 6}, {"source": 513, "target": 287, "value": 3}, {"source": 513, "target": 309, "value": 8}, {"source": 513, "target": 321, "value": 7}, {"source": 513, "target": 351, "value": 7}, {"source": 513, "target": 363, "value": 1}, {"source": 513, "target": 365, "value": 9}, {"source": 513, "target": 375, "value": 8}, {"source": 513, "target": 378, "value": 8}, {"source": 513, "target": 383, "value": 3}, {"source": 513, "target": 401, "value": 8}, {"source": 513, "target": 405, "value": 9}, {"source": 513, "target": 413, "value": 0}, {"source": 513, "target": 425, "value": 8}, {"source": 513, "target": 428, "value": 8}, {"source": 513, "target": 437, "value": 4}, {"source": 513, "target": 453, "value": 2}, {"source": 513, "target": 455, "value": 9}, {"source": 513, "target": 459, "value": 9}, {"source": 513, "target": 461, "value": 4}, {"source": 513, "target": 489, "value": 4}, {"source": 513, "target": 497, "value": 4}, {"source": 513, "target": 501, "value": 8}, {"source": 515, "target": 215, "value": 4}, {"source": 515, "target": 315, "value": 4}, {"source": 515, "target": 353, "value": 8}, {"source": 515, "target": 365, "value": 4}, {"source": 515, "target": 371, "value": 3}, {"source": 515, "target": 405, "value": 9}, {"source": 515, "target": 441, "value": 3}, {"source": 515, "target": 453, "value": 4}, {"source": 515, "target": 461, "value": 7}, {"source": 515, "target": 465, "value": 3}, {"source": 519, "target": 219, "value": 4}, {"source": 519, "target": 221, "value": 9}, {"source": 519, "target": 231, "value": 8}, {"source": 519, "target": 248, "value": 9}, {"source": 519, "target": 257, "value": 8}, {"source": 519, "target": 273, "value": 5}, {"source": 519, "target": 281, "value": 8}, {"source": 519, "target": 309, "value": 10}, {"source": 519, "target": 321, "value": 10}, {"source": 519, "target": 323, "value": 11}, {"source": 519, "target": 347, "value": 3}, {"source": 519, "target": 348, "value": 9}, {"source": 519, "target": 369, "value": 2}, {"source": 519, "target": 377, "value": 8}, {"source": 519, "target": 381, "value": 8}, {"source": 519, "target": 398, "value": 8}, {"source": 519, "target": 423, "value": 10}, {"source": 519, "target": 431, "value": 8}, {"source": 519, "target": 473, "value": 10}, {"source": 519, "target": 498, "value": 8}, {"source": 521, "target": 221, "value": 2}, {"source": 521, "target": 233, "value": 8}, {"source": 521, "target": 261, "value": 10}, {"source": 521, "target": 279, "value": 8}, {"source": 521, "target": 293, "value": 11}, {"source": 521, "target": 309, "value": 8}, {"source": 521, "target": 321, "value": 2}, {"source": 521, "target": 333, "value": 8}, {"source": 521, "target": 335, "value": 3}, {"source": 521, "target": 371, "value": 4}, {"source": 521, "target": 383, "value": 8}, {"source": 521, "target": 405, "value": 3}, {"source": 521, "target": 461, "value": 9}, {"source": 521, "target": 471, "value": 3}, {"source": 521, "target": 483, "value": 8}, {"source": 525, "target": 225, "value": 2}, {"source": 525, "target": 228, "value": 8}, {"source": 525, "target": 251, "value": 8}, {"source": 525, "target": 273, "value": 8}, {"source": 525, "target": 275, "value": 4}, {"source": 525, "target": 278, "value": 8}, {"source": 525, "target": 285, "value": 9}, {"source": 525, "target": 287, "value": 8}, {"source": 525, "target": 321, "value": 8}, {"source": 525, "target": 335, "value": 9}, {"source": 525, "target": 351, "value": 8}, {"source": 525, "target": 365, "value": 10}, {"source": 525, "target": 375, "value": 4}, {"source": 525, "target": 377, "value": 4}, {"source": 525, "target": 378, "value": 8}, {"source": 525, "target": 401, "value": 8}, {"source": 525, "target": 413, "value": 8}, {"source": 525, "target": 425, "value": 2}, {"source": 525, "target": 428, "value": 8}, {"source": 525, "target": 429, "value": 9}, {"source": 525, "target": 435, "value": 9}, {"source": 525, "target": 437, "value": 8}, {"source": 525, "target": 473, "value": 8}, {"source": 525, "target": 485, "value": 9}, {"source": 525, "target": 501, "value": 8}, {"source": 525, "target": 503, "value": 8}, {"source": 525, "target": 513, "value": 8}, {"source": 527, "target": 227, "value": 4}, {"source": 527, "target": 279, "value": 10}, {"source": 527, "target": 303, "value": 9}, {"source": 527, "target": 317, "value": 10}, {"source": 527, "target": 353, "value": 4}, {"source": 527, "target": 365, "value": 4}, {"source": 527, "target": 377, "value": 4}, {"source": 527, "target": 453, "value": 4}, {"source": 527, "target": 465, "value": 7}, {"source": 527, "target": 503, "value": 8}, {"source": 528, "target": 225, "value": 8}, {"source": 528, "target": 228, "value": 4}, {"source": 528, "target": 249, "value": 8}, {"source": 528, "target": 275, "value": 8}, {"source": 528, "target": 278, "value": 4}, {"source": 528, "target": 303, "value": 10}, {"source": 528, "target": 321, "value": 8}, {"source": 528, "target": 353, "value": 10}, {"source": 528, "target": 365, "value": 10}, {"source": 528, "target": 375, "value": 8}, {"source": 528, "target": 378, "value": 4}, {"source": 528, "target": 399, "value": 8}, {"source": 528, "target": 413, "value": 8}, {"source": 528, "target": 425, "value": 8}, {"source": 528, "target": 428, "value": 4}, {"source": 528, "target": 437, "value": 7}, {"source": 528, "target": 453, "value": 9}, {"source": 528, "target": 503, "value": 8}, {"source": 528, "target": 513, "value": 8}, {"source": 528, "target": 525, "value": 8}, {"source": 531, "target": 221, "value": 10}, {"source": 531, "target": 231, "value": 4}, {"source": 531, "target": 273, "value": 4}, {"source": 531, "target": 279, "value": 9}, {"source": 531, "target": 281, "value": 4}, {"source": 531, "target": 285, "value": 8}, {"source": 531, "target": 335, "value": 8}, {"source": 531, "target": 369, "value": 8}, {"source": 531, "target": 377, "value": 4}, {"source": 531, "target": 381, "value": 4}, {"source": 531, "target": 429, "value": 9}, {"source": 531, "target": 431, "value": 4}, {"source": 531, "target": 435, "value": 7}, {"source": 531, "target": 473, "value": 8}, {"source": 531, "target": 485, "value": 7}, {"source": 531, "target": 519, "value": 8}, {"source": 531, "target": 525, "value": 9}, {"source": 533, "target": 221, "value": 4}, {"source": 533, "target": 233, "value": 0}, {"source": 533, "target": 243, "value": 9}, {"source": 533, "target": 245, "value": 8}, {"source": 533, "target": 248, "value": 8}, {"source": 533, "target": 257, "value": 4}, {"source": 533, "target": 279, "value": 4}, {"source": 533, "target": 281, "value": 8}, {"source": 533, "target": 285, "value": 9}, {"source": 533, "target": 291, "value": 9}, {"source": 533, "target": 293, "value": 9}, {"source": 533, "target": 317, "value": 8}, {"source": 533, "target": 321, "value": 4}, {"source": 533, "target": 333, "value": 0}, {"source": 533, "target": 335, "value": 10}, {"source": 533, "target": 341, "value": 9}, {"source": 533, "target": 345, "value": 8}, {"source": 533, "target": 348, "value": 8}, {"source": 533, "target": 375, "value": 3}, {"source": 533, "target": 381, "value": 2}, {"source": 533, "target": 383, "value": 1}, {"source": 533, "target": 393, "value": 9}, {"source": 533, "target": 395, "value": 8}, {"source": 533, "target": 398, "value": 4}, {"source": 533, "target": 405, "value": 11}, {"source": 533, "target": 407, "value": 4}, {"source": 533, "target": 429, "value": 4}, {"source": 533, "target": 431, "value": 8}, {"source": 533, "target": 441, "value": 9}, {"source": 533, "target": 443, "value": 9}, {"source": 533, "target": 483, "value": 1}, {"source": 533, "target": 485, "value": 8}, {"source": 533, "target": 489, "value": 8}, {"source": 533, "target": 491, "value": 9}, {"source": 533, "target": 495, "value": 8}, {"source": 533, "target": 498, "value": 8}, {"source": 533, "target": 515, "value": 3}, {"source": 533, "target": 521, "value": 8}, {"source": 543, "target": 243, "value": 2}, {"source": 543, "target": 281, "value": 8}, {"source": 543, "target": 293, "value": 1}, {"source": 543, "target": 317, "value": 4}, {"source": 543, "target": 333, "value": 10}, {"source": 543, "target": 335, "value": 9}, {"source": 543, "target": 339, "value": 9}, {"source": 543, "target": 377, "value": 8}, {"source": 543, "target": 381, "value": 8}, {"source": 543, "target": 393, "value": 1}, {"source": 543, "target": 398, "value": 9}, {"source": 543, "target": 429, "value": 7}, {"source": 543, "target": 431, "value": 9}, {"source": 543, "target": 443, "value": 2}, {"source": 543, "target": 467, "value": 8}, {"source": 543, "target": 489, "value": 2}, {"source": 543, "target": 533, "value": 8}, {"source": 545, "target": 228, "value": 9}, {"source": 545, "target": 233, "value": 4}, {"source": 545, "target": 243, "value": 9}, {"source": 545, "target": 245, "value": 2}, {"source": 545, "target": 248, "value": 8}, {"source": 545, "target": 249, "value": 8}, {"source": 545, "target": 257, "value": 8}, {"source": 545, "target": 273, "value": 10}, {"source": 545, "target": 278, "value": 9}, {"source": 545, "target": 285, "value": 10}, {"source": 545, "target": 293, "value": 9}, {"source": 545, "target": 303, "value": 11}, {"source": 545, "target": 317, "value": 8}, {"source": 545, "target": 323, "value": 10}, {"source": 545, "target": 333, "value": 8}, {"source": 545, "target": 345, "value": 2}, {"source": 545, "target": 348, "value": 8}, {"source": 545, "target": 353, "value": 11}, {"source": 545, "target": 378, "value": 8}, {"source": 545, "target": 393, "value": 9}, {"source": 545, "target": 395, "value": 4}, {"source": 545, "target": 398, "value": 8}, {"source": 545, "target": 399, "value": 8}, {"source": 545, "target": 407, "value": 8}, {"source": 545, "target": 423, "value": 9}, {"source": 545, "target": 428, "value": 8}, {"source": 545, "target": 437, "value": 8}, {"source": 545, "target": 443, "value": 9}, {"source": 545, "target": 453, "value": 9}, {"source": 545, "target": 473, "value": 9}, {"source": 545, "target": 485, "value": 9}, {"source": 545, "target": 495, "value": 3}, {"source": 545, "target": 498, "value": 8}, {"source": 545, "target": 503, "value": 4}, {"source": 545, "target": 528, "value": 8}, {"source": 545, "target": 533, "value": 8}, {"source": 545, "target": 543, "value": 8}, {"source": 548, "target": 219, "value": 8}, {"source": 548, "target": 233, "value": 4}, {"source": 548, "target": 245, "value": 8}, {"source": 548, "target": 248, "value": 4}, {"source": 548, "target": 257, "value": 8}, {"source": 548, "target": 273, "value": 10}, {"source": 548, "target": 285, "value": 10}, {"source": 548, "target": 309, "value": 9}, {"source": 548, "target": 323, "value": 10}, {"source": 548, "target": 333, "value": 9}, {"source": 548, "target": 345, "value": 8}, {"source": 548, "target": 348, "value": 4}, {"source": 548, "target": 369, "value": 8}, {"source": 548, "target": 395, "value": 8}, {"source": 548, "target": 398, "value": 4}, {"source": 548, "target": 423, "value": 9}, {"source": 548, "target": 473, "value": 9}, {"source": 548, "target": 485, "value": 9}, {"source": 548, "target": 495, "value": 7}, {"source": 548, "target": 498, "value": 3}, {"source": 548, "target": 519, "value": 8}, {"source": 548, "target": 533, "value": 8}, {"source": 548, "target": 545, "value": 8}, {"source": 549, "target": 228, "value": 9}, {"source": 549, "target": 249, "value": 2}, {"source": 549, "target": 251, "value": 10}, {"source": 549, "target": 261, "value": 8}, {"source": 549, "target": 263, "value": 9}, {"source": 549, "target": 278, "value": 9}, {"source": 549, "target": 303, "value": 11}, {"source": 549, "target": 309, "value": 9}, {"source": 549, "target": 311, "value": 8}, {"source": 549, "target": 321, "value": 11}, {"source": 549, "target": 351, "value": 8}, {"source": 549, "target": 353, "value": 10}, {"source": 549, "target": 363, "value": 9}, {"source": 549, "target": 378, "value": 8}, {"source": 549, "target": 399, "value": 4}, {"source": 549, "target": 405, "value": 9}, {"source": 549, "target": 411, "value": 8}, {"source": 549, "target": 413, "value": 8}, {"source": 549, "target": 428, "value": 8}, {"source": 549, "target": 437, "value": 8}, {"source": 549, "target": 453, "value": 10}, {"source": 549, "target": 459, "value": 8}, {"source": 549, "target": 461, "value": 8}, {"source": 549, "target": 497, "value": 4}, {"source": 549, "target": 501, "value": 8}, {"source": 549, "target": 503, "value": 9}, {"source": 549, "target": 513, "value": 8}, {"source": 549, "target": 528, "value": 8}, {"source": 549, "target": 545, "value": 8}, {"source": 551, "target": 251, "value": 4}, {"source": 551, "target": 341, "value": 10}, {"source": 551, "target": 351, "value": 4}, {"source": 551, "target": 401, "value": 4}, {"source": 551, "target": 489, "value": 7}, {"source": 551, "target": 501, "value": 3}, {"source": 551, "target": 503, "value": 9}, {"source": 551, "target": 525, "value": 8}, {"source": 555, "target": 245, "value": 10}, {"source": 555, "target": 251, "value": 8}, {"source": 555, "target": 255, "value": 4}, {"source": 555, "target": 293, "value": 8}, {"source": 555, "target": 305, "value": 4}, {"source": 555, "target": 377, "value": 11}, {"source": 555, "target": 393, "value": 8}, {"source": 555, "target": 405, "value": 4}, {"source": 555, "target": 425, "value": 3}, {"source": 555, "target": 429, "value": 8}, {"source": 555, "target": 455, "value": 4}, {"source": 555, "target": 495, "value": 3}, {"source": 557, "target": 219, "value": 8}, {"source": 557, "target": 233, "value": 3}, {"source": 557, "target": 245, "value": 8}, {"source": 557, "target": 248, "value": 8}, {"source": 557, "target": 255, "value": 8}, {"source": 557, "target": 257, "value": 2}, {"source": 557, "target": 273, "value": 11}, {"source": 557, "target": 305, "value": 8}, {"source": 557, "target": 309, "value": 9}, {"source": 557, "target": 317, "value": 9}, {"source": 557, "target": 323, "value": 10}, {"source": 557, "target": 333, "value": 3}, {"source": 557, "target": 335, "value": 9}, {"source": 557, "target": 345, "value": 8}, {"source": 557, "target": 348, "value": 8}, {"source": 557, "target": 363, "value": 9}, {"source": 557, "target": 369, "value": 8}, {"source": 557, "target": 381, "value": 8}, {"source": 557, "target": 383, "value": 4}, {"source": 557, "target": 398, "value": 8}, {"source": 557, "target": 405, "value": 4}, {"source": 557, "target": 407, "value": 4}, {"source": 557, "target": 413, "value": 3}, {"source": 557, "target": 423, "value": 9}, {"source": 557, "target": 467, "value": 9}, {"source": 557, "target": 473, "value": 9}, {"source": 557, "target": 483, "value": 2}, {"source": 557, "target": 498, "value": 8}, {"source": 557, "target": 503, "value": 7}, {"source": 557, "target": 519, "value": 8}, {"source": 557, "target": 533, "value": 2}, {"source": 557, "target": 545, "value": 8}, {"source": 557, "target": 548, "value": 7}, {"source": 561, "target": 249, "value": 8}, {"source": 561, "target": 261, "value": 2}, {"source": 561, "target": 273, "value": 8}, {"source": 561, "target": 285, "value": 8}, {"source": 561, "target": 311, "value": 4}, {"source": 561, "target": 323, "value": 8}, {"source": 561, "target": 363, "value": 10}, {"source": 561, "target": 411, "value": 4}, {"source": 561, "target": 413, "value": 9}, {"source": 561, "target": 423, "value": 8}, {"source": 561, "target": 459, "value": 3}, {"source": 561, "target": 461, "value": 2}, {"source": 561, "target": 473, "value": 8}, {"source": 561, "target": 501, "value": 9}, {"source": 561, "target": 549, "value": 8}, {"source": 563, "target": 225, "value": 8}, {"source": 563, "target": 228, "value": 9}, {"source": 563, "target": 263, "value": 2}, {"source": 563, "target": 275, "value": 8}, {"source": 563, "target": 278, "value": 9}, {"source": 563, "target": 287, "value": 8}, {"source": 563, "target": 309, "value": 9}, {"source": 563, "target": 321, "value": 8}, {"source": 563, "target": 351, "value": 8}, {"source": 563, "target": 363, "value": 2}, {"source": 563, "target": 365, "value": 9}, {"source": 563, "target": 375, "value": 8}, {"source": 563, "target": 378, "value": 9}, {"source": 563, "target": 401, "value": 8}, {"source": 563, "target": 405, "value": 9}, {"source": 563, "target": 413, "value": 1}, {"source": 563, "target": 425, "value": 8}, {"source": 563, "target": 428, "value": 9}, {"source": 563, "target": 437, "value": 8}, {"source": 563, "target": 453, "value": 9}, {"source": 563, "target": 455, "value": 10}, {"source": 563, "target": 459, "value": 9}, {"source": 563, "target": 461, "value": 8}, {"source": 563, "target": 489, "value": 8}, {"source": 563, "target": 497, "value": 4}, {"source": 563, "target": 501, "value": 8}, {"source": 563, "target": 513, "value": 1}, {"source": 563, "target": 525, "value": 8}, {"source": 563, "target": 528, "value": 8}, {"source": 563, "target": 549, "value": 8}, {"source": 573, "target": 219, "value": 9}, {"source": 573, "target": 221, "value": 2}, {"source": 573, "target": 227, "value": 8}, {"source": 573, "target": 233, "value": 8}, {"source": 573, "target": 248, "value": 8}, {"source": 573, "target": 249, "value": 8}, {"source": 573, "target": 251, "value": 4}, {"source": 573, "target": 257, "value": 8}, {"source": 573, "target": 261, "value": 8}, {"source": 573, "target": 273, "value": 1}, {"source": 573, "target": 279, "value": 8}, {"source": 573, "target": 285, "value": 8}, {"source": 573, "target": 293, "value": 10}, {"source": 573, "target": 309, "value": 8}, {"source": 573, "target": 317, "value": 10}, {"source": 573, "target": 321, "value": 2}, {"source": 573, "target": 323, "value": 1}, {"source": 573, "target": 333, "value": 9}, {"source": 573, "target": 335, "value": 8}, {"source": 573, "target": 347, "value": 4}, {"source": 573, "target": 348, "value": 8}, {"source": 573, "target": 351, "value": 9}, {"source": 573, "target": 365, "value": 4}, {"source": 573, "target": 369, "value": 9}, {"source": 573, "target": 371, "value": 9}, {"source": 573, "target": 377, "value": 4}, {"source": 573, "target": 383, "value": 9}, {"source": 573, "target": 398, "value": 8}, {"source": 573, "target": 399, "value": 8}, {"source": 573, "target": 401, "value": 9}, {"source": 573, "target": 413, "value": 9}, {"source": 573, "target": 423, "value": 1}, {"source": 573, "target": 429, "value": 3}, {"source": 573, "target": 435, "value": 8}, {"source": 573, "target": 461, "value": 8}, {"source": 573, "target": 465, "value": 8}, {"source": 573, "target": 471, "value": 9}, {"source": 573, "target": 473, "value": 1}, {"source": 573, "target": 483, "value": 8}, {"source": 573, "target": 485, "value": 8}, {"source": 573, "target": 497, "value": 4}, {"source": 573, "target": 498, "value": 8}, {"source": 573, "target": 501, "value": 9}, {"source": 573, "target": 503, "value": 7}, {"source": 573, "target": 519, "value": 9}, {"source": 573, "target": 521, "value": 4}, {"source": 573, "target": 525, "value": 4}, {"source": 573, "target": 527, "value": 7}, {"source": 573, "target": 531, "value": 7}, {"source": 573, "target": 533, "value": 9}, {"source": 573, "target": 545, "value": 9}, {"source": 573, "target": 548, "value": 8}, {"source": 573, "target": 549, "value": 8}, {"source": 573, "target": 551, "value": 9}, {"source": 573, "target": 557, "value": 8}, {"source": 573, "target": 561, "value": 8}, {"source": 575, "target": 225, "value": 4}, {"source": 575, "target": 228, "value": 8}, {"source": 575, "target": 275, "value": 4}, {"source": 575, "target": 278, "value": 8}, {"source": 575, "target": 321, "value": 8}, {"source": 575, "target": 365, "value": 10}, {"source": 575, "target": 375, "value": 4}, {"source": 575, "target": 377, "value": 10}, {"source": 575, "target": 378, "value": 8}, {"source": 575, "target": 413, "value": 8}, {"source": 575, "target": 425, "value": 4}, {"source": 575, "target": 428, "value": 8}, {"source": 575, "target": 513, "value": 8}, {"source": 575, "target": 525, "value": 3}, {"source": 575, "target": 528, "value": 8}, {"source": 575, "target": 557, "value": 3}, {"source": 575, "target": 563, "value": 8}, {"source": 578, "target": 225, "value": 8}, {"source": 578, "target": 228, "value": 4}, {"source": 578, "target": 249, "value": 8}, {"source": 578, "target": 275, "value": 8}, {"source": 578, "target": 278, "value": 4}, {"source": 578, "target": 303, "value": 10}, {"source": 578, "target": 321, "value": 8}, {"source": 578, "target": 353, "value": 10}, {"source": 578, "target": 365, "value": 10}, {"source": 578, "target": 375, "value": 8}, {"source": 578, "target": 378, "value": 4}, {"source": 578, "target": 399, "value": 8}, {"source": 578, "target": 413, "value": 8}, {"source": 578, "target": 425, "value": 8}, {"source": 578, "target": 428, "value": 4}, {"source": 578, "target": 437, "value": 8}, {"source": 578, "target": 453, "value": 9}, {"source": 578, "target": 503, "value": 8}, {"source": 578, "target": 513, "value": 8}, {"source": 578, "target": 525, "value": 7}, {"source": 578, "target": 528, "value": 3}, {"source": 578, "target": 545, "value": 8}, {"source": 578, "target": 549, "value": 8}, {"source": 578, "target": 563, "value": 9}, {"source": 578, "target": 575, "value": 8}, {"source": 579, "target": 221, "value": 11}, {"source": 579, "target": 233, "value": 12}, {"source": 579, "target": 279, "value": 4}, {"source": 579, "target": 317, "value": 8}, {"source": 579, "target": 333, "value": 11}, {"source": 579, "target": 383, "value": 10}, {"source": 579, "target": 393, "value": 3}, {"source": 579, "target": 429, "value": 4}, {"source": 579, "target": 483, "value": 10}, {"source": 579, "target": 531, "value": 9}, {"source": 579, "target": 533, "value": 2}, {"source": 581, "target": 221, "value": 11}, {"source": 581, "target": 231, "value": 4}, {"source": 581, "target": 243, "value": 8}, {"source": 581, "target": 273, "value": 11}, {"source": 581, "target": 281, "value": 2}, {"source": 581, "target": 291, "value": 8}, {"source": 581, "target": 293, "value": 8}, {"source": 581, "target": 333, "value": 9}, {"source": 581, "target": 341, "value": 8}, {"source": 581, "target": 369, "value": 8}, {"source": 581, "target": 377, "value": 8}, {"source": 581, "target": 381, "value": 1}, {"source": 581, "target": 393, "value": 8}, {"source": 581, "target": 398, "value": 9}, {"source": 581, "target": 429, "value": 8}, {"source": 581, "target": 431, "value": 2}, {"source": 581, "target": 441, "value": 9}, {"source": 581, "target": 443, "value": 8}, {"source": 581, "target": 489, "value": 7}, {"source": 581, "target": 491, "value": 8}, {"source": 581, "target": 519, "value": 8}, {"source": 581, "target": 521, "value": 3}, {"source": 581, "target": 531, "value": 3}, {"source": 581, "target": 533, "value": 4}, {"source": 581, "target": 543, "value": 8}, {"source": 585, "target": 243, "value": 9}, {"source": 585, "target": 273, "value": 8}, {"source": 585, "target": 281, "value": 8}, {"source": 585, "target": 285, "value": 2}, {"source": 585, "target": 293, "value": 2}, {"source": 585, "target": 333, "value": 8}, {"source": 585, "target": 335, "value": 4}, {"source": 585, "target": 347, "value": 4}, {"source": 585, "target": 377, "value": 10}, {"source": 585, "target": 381, "value": 8}, {"source": 585, "target": 393, "value": 9}, {"source": 585, "target": 398, "value": 8}, {"source": 585, "target": 431, "value": 8}, {"source": 585, "target": 435, "value": 4}, {"source": 585, "target": 437, "value": 4}, {"source": 585, "target": 443, "value": 9}, {"source": 585, "target": 473, "value": 8}, {"source": 585, "target": 485, "value": 2}, {"source": 585, "target": 489, "value": 9}, {"source": 585, "target": 497, "value": 4}, {"source": 585, "target": 525, "value": 9}, {"source": 585, "target": 531, "value": 7}, {"source": 585, "target": 533, "value": 7}, {"source": 585, "target": 543, "value": 9}, {"source": 585, "target": 573, "value": 8}, {"source": 585, "target": 581, "value": 8}, {"source": 587, "target": 225, "value": 9}, {"source": 587, "target": 263, "value": 9}, {"source": 587, "target": 285, "value": 9}, {"source": 587, "target": 287, "value": 4}, {"source": 587, "target": 291, "value": 8}, {"source": 587, "target": 335, "value": 9}, {"source": 587, "target": 341, "value": 8}, {"source": 587, "target": 363, "value": 9}, {"source": 587, "target": 381, "value": 10}, {"source": 587, "target": 413, "value": 4}, {"source": 587, "target": 425, "value": 8}, {"source": 587, "target": 429, "value": 4}, {"source": 587, "target": 435, "value": 9}, {"source": 587, "target": 437, "value": 4}, {"source": 587, "target": 441, "value": 8}, {"source": 587, "target": 461, "value": 8}, {"source": 587, "target": 485, "value": 9}, {"source": 587, "target": 489, "value": 9}, {"source": 587, "target": 491, "value": 8}, {"source": 587, "target": 513, "value": 4}, {"source": 587, "target": 525, "value": 7}, {"source": 587, "target": 533, "value": 9}, {"source": 587, "target": 563, "value": 8}, {"source": 587, "target": 581, "value": 8}, {"source": 587, "target": 585, "value": 8}, {"source": 591, "target": 291, "value": 4}, {"source": 591, "target": 293, "value": 10}, {"source": 591, "target": 341, "value": 4}, {"source": 591, "target": 365, "value": 8}, {"source": 591, "target": 381, "value": 10}, {"source": 591, "target": 429, "value": 8}, {"source": 591, "target": 441, "value": 4}, {"source": 591, "target": 491, "value": 4}, {"source": 591, "target": 533, "value": 9}, {"source": 591, "target": 581, "value": 8}, {"source": 591, "target": 587, "value": 8}, {"source": 593, "target": 243, "value": 1}, {"source": 593, "target": 245, "value": 10}, {"source": 593, "target": 251, "value": 8}, {"source": 593, "target": 255, "value": 8}, {"source": 593, "target": 281, "value": 8}, {"source": 593, "target": 293, "value": 1}, {"source": 593, "target": 305, "value": 8}, {"source": 593, "target": 317, "value": 2}, {"source": 593, "target": 333, "value": 10}, {"source": 593, "target": 335, "value": 9}, {"source": 593, "target": 339, "value": 9}, {"source": 593, "target": 377, "value": 8}, {"source": 593, "target": 381, "value": 8}, {"source": 593, "target": 393, "value": 1}, {"source": 593, "target": 398, "value": 9}, {"source": 593, "target": 405, "value": 8}, {"source": 593, "target": 429, "value": 8}, {"source": 593, "target": 431, "value": 9}, {"source": 593, "target": 443, "value": 1}, {"source": 593, "target": 455, "value": 8}, {"source": 593, "target": 467, "value": 4}, {"source": 593, "target": 489, "value": 2}, {"source": 593, "target": 491, "value": 3}, {"source": 593, "target": 533, "value": 8}, {"source": 593, "target": 543, "value": 1}, {"source": 593, "target": 545, "value": 9}, {"source": 593, "target": 555, "value": 8}, {"source": 593, "target": 561, "value": 3}, {"source": 593, "target": 581, "value": 8}, {"source": 593, "target": 585, "value": 8}, {"source": 603, "target": 227, "value": 8}, {"source": 603, "target": 228, "value": 8}, {"source": 603, "target": 249, "value": 9}, {"source": 603, "target": 278, "value": 8}, {"source": 603, "target": 279, "value": 9}, {"source": 603, "target": 293, "value": 10}, {"source": 603, "target": 303, "value": 2}, {"source": 603, "target": 341, "value": 8}, {"source": 603, "target": 353, "value": 1}, {"source": 603, "target": 377, "value": 8}, {"source": 603, "target": 378, "value": 8}, {"source": 603, "target": 399, "value": 9}, {"source": 603, "target": 405, "value": 9}, {"source": 603, "target": 428, "value": 8}, {"source": 603, "target": 437, "value": 8}, {"source": 603, "target": 441, "value": 8}, {"source": 603, "target": 453, "value": 1}, {"source": 603, "target": 503, "value": 2}, {"source": 603, "target": 527, "value": 8}, {"source": 603, "target": 528, "value": 8}, {"source": 603, "target": 545, "value": 9}, {"source": 603, "target": 549, "value": 9}, {"source": 603, "target": 578, "value": 8}, {"source": 605, "target": 215, "value": 8}, {"source": 605, "target": 231, "value": 8}, {"source": 605, "target": 245, "value": 10}, {"source": 605, "target": 251, "value": 8}, {"source": 605, "target": 255, "value": 2}, {"source": 605, "target": 281, "value": 8}, {"source": 605, "target": 293, "value": 8}, {"source": 605, "target": 305, "value": 2}, {"source": 605, "target": 315, "value": 8}, {"source": 605, "target": 317, "value": 8}, {"source": 605, "target": 353, "value": 8}, {"source": 605, "target": 363, "value": 8}, {"source": 605, "target": 365, "value": 8}, {"source": 605, "target": 377, "value": 11}, {"source": 605, "target": 381, "value": 8}, {"source": 605, "target": 393, "value": 8}, {"source": 605, "target": 405, "value": 2}, {"source": 605, "target": 429, "value": 8}, {"source": 605, "target": 431, "value": 8}, {"source": 605, "target": 453, "value": 4}, {"source": 605, "target": 455, "value": 4}, {"source": 605, "target": 461, "value": 8}, {"source": 605, "target": 465, "value": 9}, {"source": 605, "target": 467, "value": 8}, {"source": 605, "target": 515, "value": 8}, {"source": 605, "target": 531, "value": 8}, {"source": 605, "target": 555, "value": 3}, {"source": 605, "target": 557, "value": 8}, {"source": 605, "target": 581, "value": 8}, {"source": 605, "target": 593, "value": 8}, {"source": 609, "target": 221, "value": 8}, {"source": 609, "target": 261, "value": 10}, {"source": 609, "target": 263, "value": 12}, {"source": 609, "target": 309, "value": 2}, {"source": 609, "target": 321, "value": 8}, {"source": 609, "target": 333, "value": 8}, {"source": 609, "target": 351, "value": 10}, {"source": 609, "target": 363, "value": 11}, {"source": 609, "target": 371, "value": 8}, {"source": 609, "target": 405, "value": 8}, {"source": 609, "target": 413, "value": 10}, {"source": 609, "target": 459, "value": 4}, {"source": 609, "target": 461, "value": 4}, {"source": 609, "target": 471, "value": 8}, {"source": 609, "target": 497, "value": 4}, {"source": 609, "target": 513, "value": 10}, {"source": 609, "target": 521, "value": 8}, {"source": 609, "target": 549, "value": 8}, {"source": 609, "target": 563, "value": 9}, {"source": 611, "target": 219, "value": 8}, {"source": 611, "target": 248, "value": 8}, {"source": 611, "target": 249, "value": 9}, {"source": 611, "target": 257, "value": 8}, {"source": 611, "target": 261, "value": 4}, {"source": 611, "target": 273, "value": 9}, {"source": 611, "target": 285, "value": 8}, {"source": 611, "target": 309, "value": 8}, {"source": 611, "target": 311, "value": 4}, {"source": 611, "target": 323, "value": 9}, {"source": 611, "target": 348, "value": 8}, {"source": 611, "target": 363, "value": 10}, {"source": 611, "target": 369, "value": 9}, {"source": 611, "target": 398, "value": 8}, {"source": 611, "target": 411, "value": 4}, {"source": 611, "target": 423, "value": 9}, {"source": 611, "target": 461, "value": 4}, {"source": 611, "target": 473, "value": 8}, {"source": 611, "target": 498, "value": 8}, {"source": 611, "target": 501, "value": 9}, {"source": 611, "target": 519, "value": 8}, {"source": 611, "target": 548, "value": 8}, {"source": 611, "target": 549, "value": 7}, {"source": 611, "target": 557, "value": 8}, {"source": 611, "target": 561, "value": 3}, {"source": 611, "target": 573, "value": 8}, {"source": 615, "target": 215, "value": 4}, {"source": 615, "target": 219, "value": 8}, {"source": 615, "target": 248, "value": 9}, {"source": 615, "target": 257, "value": 9}, {"source": 615, "target": 263, "value": 8}, {"source": 615, "target": 273, "value": 11}, {"source": 615, "target": 309, "value": 10}, {"source": 615, "target": 315, "value": 4}, {"source": 615, "target": 323, "value": 11}, {"source": 615, "target": 348, "value": 9}, {"source": 615, "target": 353, "value": 8}, {"source": 615, "target": 363, "value": 9}, {"source": 615, "target": 365, "value": 4}, {"source": 615, "target": 369, "value": 8}, {"source": 615, "target": 398, "value": 9}, {"source": 615, "target": 405, "value": 10}, {"source": 615, "target": 413, "value": 9}, {"source": 615, "target": 423, "value": 10}, {"source": 615, "target": 453, "value": 4}, {"source": 615, "target": 461, "value": 8}, {"source": 615, "target": 465, "value": 4}, {"source": 615, "target": 471, "value": 3}, {"source": 615, "target": 473, "value": 10}, {"source": 615, "target": 498, "value": 8}, {"source": 615, "target": 513, "value": 9}, {"source": 615, "target": 515, "value": 4}, {"source": 615, "target": 519, "value": 8}, {"source": 615, "target": 548, "value": 8}, {"source": 615, "target": 557, "value": 8}, {"source": 615, "target": 563, "value": 8}, {"source": 615, "target": 573, "value": 9}, {"source": 615, "target": 605, "value": 8}, {"source": 615, "target": 611, "value": 2}, {"source": 617, "target": 221, "value": 10}, {"source": 617, "target": 233, "value": 11}, {"source": 617, "target": 243, "value": 9}, {"source": 617, "target": 255, "value": 10}, {"source": 617, "target": 279, "value": 8}, {"source": 617, "target": 293, "value": 4}, {"source": 617, "target": 305, "value": 8}, {"source": 617, "target": 317, "value": 2}, {"source": 617, "target": 333, "value": 10}, {"source": 617, "target": 363, "value": 8}, {"source": 617, "target": 383, "value": 10}, {"source": 617, "target": 393, "value": 4}, {"source": 617, "target": 405, "value": 8}, {"source": 617, "target": 429, "value": 8}, {"source": 617, "target": 443, "value": 9}, {"source": 617, "target": 459, "value": 3}, {"source": 617, "target": 467, "value": 4}, {"source": 617, "target": 483, "value": 9}, {"source": 617, "target": 489, "value": 10}, {"source": 617, "target": 533, "value": 9}, {"source": 617, "target": 543, "value": 8}, {"source": 617, "target": 557, "value": 8}, {"source": 617, "target": 579, "value": 8}, {"source": 617, "target": 593, "value": 4}, {"source": 617, "target": 605, "value": 8}, {"source": 621, "target": 221, "value": 1}, {"source": 621, "target": 231, "value": 8}, {"source": 621, "target": 233, "value": 8}, {"source": 621, "target": 261, "value": 10}, {"source": 621, "target": 273, "value": 3}, {"source": 621, "target": 279, "value": 8}, {"source": 621, "target": 281, "value": 8}, {"source": 621, "target": 293, "value": 12}, {"source": 621, "target": 309, "value": 8}, {"source": 621, "target": 321, "value": 2}, {"source": 621, "target": 323, "value": 9}, {"source": 621, "target": 333, "value": 8}, {"source": 621, "target": 347, "value": 8}, {"source": 621, "target": 369, "value": 8}, {"source": 621, "target": 371, "value": 4}, {"source": 621, "target": 377, "value": 8}, {"source": 621, "target": 381, "value": 8}, {"source": 621, "target": 383, "value": 8}, {"source": 621, "target": 423, "value": 8}, {"source": 621, "target": 431, "value": 8}, {"source": 621, "target": 435, "value": 3}, {"source": 621, "target": 461, "value": 9}, {"source": 621, "target": 471, "value": 4}, {"source": 621, "target": 473, "value": 4}, {"source": 621, "target": 483, "value": 8}, {"source": 621, "target": 497, "value": 8}, {"source": 621, "target": 519, "value": 8}, {"source": 621, "target": 521, "value": 2}, {"source": 621, "target": 531, "value": 8}, {"source": 621, "target": 533, "value": 8}, {"source": 621, "target": 573, "value": 2}, {"source": 621, "target": 575, "value": 3}, {"source": 621, "target": 581, "value": 8}, {"source": 621, "target": 609, "value": 8}, {"source": 623, "target": 219, "value": 9}, {"source": 623, "target": 221, "value": 8}, {"source": 623, "target": 248, "value": 8}, {"source": 623, "target": 257, "value": 8}, {"source": 623, "target": 261, "value": 9}, {"source": 623, "target": 273, "value": 1}, {"source": 623, "target": 309, "value": 8}, {"source": 623, "target": 323, "value": 2}, {"source": 623, "target": 347, "value": 8}, {"source": 623, "target": 348, "value": 8}, {"source": 623, "target": 369, "value": 9}, {"source": 623, "target": 398, "value": 8}, {"source": 623, "target": 413, "value": 10}, {"source": 623, "target": 423, "value": 2}, {"source": 623, "target": 461, "value": 8}, {"source": 623, "target": 473, "value": 1}, {"source": 623, "target": 497, "value": 8}, {"source": 623, "target": 498, "value": 8}, {"source": 623, "target": 519, "value": 9}, {"source": 623, "target": 545, "value": 10}, {"source": 623, "target": 548, "value": 8}, {"source": 623, "target": 557, "value": 8}, {"source": 623, "target": 561, "value": 7}, {"source": 623, "target": 573, "value": 1}, {"source": 623, "target": 611, "value": 7}, {"source": 623, "target": 615, "value": 8}, {"source": 623, "target": 621, "value": 7}, {"source": 633, "target": 221, "value": 4}, {"source": 633, "target": 233, "value": 0}, {"source": 633, "target": 245, "value": 8}, {"source": 633, "target": 248, "value": 8}, {"source": 633, "target": 257, "value": 4}, {"source": 633, "target": 279, "value": 4}, {"source": 633, "target": 285, "value": 10}, {"source": 633, "target": 317, "value": 8}, {"source": 633, "target": 321, "value": 4}, {"source": 633, "target": 333, "value": 1}, {"source": 633, "target": 335, "value": 10}, {"source": 633, "target": 345, "value": 8}, {"source": 633, "target": 348, "value": 8}, {"source": 633, "target": 381, "value": 4}, {"source": 633, "target": 383, "value": 1}, {"source": 633, "target": 395, "value": 8}, {"source": 633, "target": 398, "value": 8}, {"source": 633, "target": 405, "value": 11}, {"source": 633, "target": 407, "value": 4}, {"source": 633, "target": 429, "value": 9}, {"source": 633, "target": 483, "value": 1}, {"source": 633, "target": 485, "value": 9}, {"source": 633, "target": 495, "value": 8}, {"source": 633, "target": 498, "value": 8}, {"source": 633, "target": 521, "value": 8}, {"source": 633, "target": 533, "value": 1}, {"source": 633, "target": 545, "value": 2}, {"source": 633, "target": 548, "value": 8}, {"source": 633, "target": 557, "value": 2}, {"source": 633, "target": 573, "value": 8}, {"source": 633, "target": 579, "value": 9}, {"source": 633, "target": 615, "value": 3}, {"source": 633, "target": 617, "value": 8}, {"source": 633, "target": 621, "value": 8}, {"source": 635, "target": 273, "value": 9}, {"source": 635, "target": 285, "value": 4}, {"source": 635, "target": 335, "value": 4}, {"source": 635, "target": 377, "value": 10}, {"source": 635, "target": 435, "value": 4}, {"source": 635, "target": 473, "value": 8}, {"source": 635, "target": 485, "value": 4}, {"source": 635, "target": 525, "value": 9}, {"source": 635, "target": 531, "value": 8}, {"source": 635, "target": 533, "value": 3}, {"source": 635, "target": 573, "value": 8}, {"source": 635, "target": 585, "value": 3}, {"source": 635, "target": 587, "value": 9}, {"source": 635, "target": 603, "value": 3}, {"source": 639, "target": 243, "value": 12}, {"source": 639, "target": 293, "value": 11}, {"source": 639, "target": 335, "value": 8}, {"source": 639, "target": 339, "value": 4}, {"source": 639, "target": 377, "value": 8}, {"source": 639, "target": 393, "value": 11}, {"source": 639, "target": 413, "value": 8}, {"source": 639, "target": 429, "value": 10}, {"source": 639, "target": 443, "value": 10}, {"source": 639, "target": 461, "value": 10}, {"source": 639, "target": 489, "value": 4}, {"source": 639, "target": 543, "value": 10}, {"source": 639, "target": 579, "value": 3}, {"source": 639, "target": 593, "value": 9}, {"source": 641, "target": 291, "value": 4}, {"source": 641, "target": 293, "value": 5}, {"source": 641, "target": 303, "value": 8}, {"source": 641, "target": 341, "value": 2}, {"source": 641, "target": 353, "value": 8}, {"source": 641, "target": 365, "value": 8}, {"source": 641, "target": 381, "value": 10}, {"source": 641, "target": 429, "value": 8}, {"source": 641, "target": 441, "value": 2}, {"source": 641, "target": 453, "value": 8}, {"source": 641, "target": 491, "value": 4}, {"source": 641, "target": 497, "value": 3}, {"source": 641, "target": 503, "value": 8}, {"source": 641, "target": 533, "value": 9}, {"source": 641, "target": 581, "value": 8}, {"source": 641, "target": 587, "value": 7}, {"source": 641, "target": 591, "value": 3}, {"source": 641, "target": 603, "value": 8}, {"source": 645, "target": 233, "value": 4}, {"source": 645, "target": 245, "value": 2}, {"source": 645, "target": 248, "value": 8}, {"source": 645, "target": 251, "value": 8}, {"source": 645, "target": 255, "value": 8}, {"source": 645, "target": 257, "value": 8}, {"source": 645, "target": 285, "value": 10}, {"source": 645, "target": 293, "value": 8}, {"source": 645, "target": 305, "value": 8}, {"source": 645, "target": 333, "value": 8}, {"source": 645, "target": 345, "value": 2}, {"source": 645, "target": 348, "value": 8}, {"source": 645, "target": 393, "value": 8}, {"source": 645, "target": 395, "value": 4}, {"source": 645, "target": 398, "value": 8}, {"source": 645, "target": 405, "value": 8}, {"source": 645, "target": 407, "value": 8}, {"source": 645, "target": 455, "value": 8}, {"source": 645, "target": 485, "value": 9}, {"source": 645, "target": 495, "value": 4}, {"source": 645, "target": 498, "value": 8}, {"source": 645, "target": 503, "value": 8}, {"source": 645, "target": 533, "value": 8}, {"source": 645, "target": 545, "value": 2}, {"source": 645, "target": 548, "value": 8}, {"source": 645, "target": 555, "value": 8}, {"source": 645, "target": 557, "value": 8}, {"source": 645, "target": 593, "value": 7}, {"source": 645, "target": 605, "value": 8}, {"source": 645, "target": 633, "value": 8}, {"source": 647, "target": 221, "value": 9}, {"source": 647, "target": 273, "value": 4}, {"source": 647, "target": 285, "value": 9}, {"source": 647, "target": 293, "value": 8}, {"source": 647, "target": 323, "value": 9}, {"source": 647, "target": 347, "value": 4}, {"source": 647, "target": 423, "value": 9}, {"source": 647, "target": 437, "value": 9}, {"source": 647, "target": 473, "value": 4}, {"source": 647, "target": 485, "value": 8}, {"source": 647, "target": 497, "value": 4}, {"source": 647, "target": 573, "value": 4}, {"source": 647, "target": 585, "value": 4}, {"source": 647, "target": 621, "value": 8}, {"source": 647, "target": 623, "value": 8}, {"source": 648, "target": 219, "value": 8}, {"source": 648, "target": 233, "value": 4}, {"source": 648, "target": 245, "value": 8}, {"source": 648, "target": 248, "value": 4}, {"source": 648, "target": 257, "value": 8}, {"source": 648, "target": 273, "value": 11}, {"source": 648, "target": 285, "value": 10}, {"source": 648, "target": 309, "value": 9}, {"source": 648, "target": 323, "value": 10}, {"source": 648, "target": 333, "value": 9}, {"source": 648, "target": 345, "value": 8}, {"source": 648, "target": 348, "value": 4}, {"source": 648, "target": 369, "value": 8}, {"source": 648, "target": 395, "value": 8}, {"source": 648, "target": 398, "value": 4}, {"source": 648, "target": 423, "value": 10}, {"source": 648, "target": 473, "value": 9}, {"source": 648, "target": 485, "value": 9}, {"source": 648, "target": 495, "value": 8}, {"source": 648, "target": 498, "value": 4}, {"source": 648, "target": 519, "value": 8}, {"source": 648, "target": 533, "value": 8}, {"source": 648, "target": 545, "value": 8}, {"source": 648, "target": 548, "value": 4}, {"source": 648, "target": 557, "value": 7}, {"source": 648, "target": 573, "value": 8}, {"source": 648, "target": 611, "value": 8}, {"source": 648, "target": 615, "value": 8}, {"source": 648, "target": 623, "value": 8}, {"source": 648, "target": 633, "value": 8}, {"source": 648, "target": 645, "value": 8}, {"source": 651, "target": 251, "value": 4}, {"source": 651, "target": 263, "value": 8}, {"source": 651, "target": 341, "value": 10}, {"source": 651, "target": 351, "value": 4}, {"source": 651, "target": 363, "value": 8}, {"source": 651, "target": 401, "value": 2}, {"source": 651, "target": 413, "value": 8}, {"source": 651, "target": 453, "value": 8}, {"source": 651, "target": 455, "value": 9}, {"source": 651, "target": 489, "value": 8}, {"source": 651, "target": 501, "value": 2}, {"source": 651, "target": 503, "value": 9}, {"source": 651, "target": 513, "value": 8}, {"source": 651, "target": 525, "value": 8}, {"source": 651, "target": 551, "value": 4}, {"source": 651, "target": 563, "value": 8}, {"source": 651, "target": 573, "value": 9}, {"source": 653, "target": 215, "value": 8}, {"source": 653, "target": 227, "value": 4}, {"source": 653, "target": 228, "value": 8}, {"source": 653, "target": 249, "value": 8}, {"source": 653, "target": 263, "value": 8}, {"source": 653, "target": 278, "value": 8}, {"source": 653, "target": 279, "value": 3}, {"source": 653, "target": 293, "value": 10}, {"source": 653, "target": 303, "value": 1}, {"source": 653, "target": 315, "value": 8}, {"source": 653, "target": 341, "value": 8}, {"source": 653, "target": 353, "value": 1}, {"source": 653, "target": 363, "value": 8}, {"source": 653, "target": 365, "value": 8}, {"source": 653, "target": 377, "value": 4}, {"source": 653, "target": 378, "value": 8}, {"source": 653, "target": 399, "value": 9}, {"source": 653, "target": 401, "value": 8}, {"source": 653, "target": 405, "value": 4}, {"source": 653, "target": 413, "value": 8}, {"source": 653, "target": 428, "value": 8}, {"source": 653, "target": 429, "value": 8}, {"source": 653, "target": 437, "value": 8}, {"source": 653, "target": 441, "value": 8}, {"source": 653, "target": 453, "value": 0}, {"source": 653, "target": 455, "value": 8}, {"source": 653, "target": 461, "value": 8}, {"source": 653, "target": 465, "value": 8}, {"source": 653, "target": 467, "value": 3}, {"source": 653, "target": 501, "value": 8}, {"source": 653, "target": 503, "value": 1}, {"source": 653, "target": 513, "value": 8}, {"source": 653, "target": 515, "value": 8}, {"source": 653, "target": 527, "value": 4}, {"source": 653, "target": 528, "value": 8}, {"source": 653, "target": 531, "value": 8}, {"source": 653, "target": 545, "value": 9}, {"source": 653, "target": 549, "value": 9}, {"source": 653, "target": 563, "value": 8}, {"source": 653, "target": 578, "value": 8}, {"source": 653, "target": 579, "value": 8}, {"source": 653, "target": 603, "value": 1}, {"source": 653, "target": 605, "value": 8}, {"source": 653, "target": 615, "value": 8}, {"source": 653, "target": 641, "value": 8}, {"source": 653, "target": 651, "value": 7}, {"source": 663, "target": 221, "value": 8}, {"source": 663, "target": 261, "value": 9}, {"source": 663, "target": 263, "value": 2}, {"source": 663, "target": 287, "value": 8}, {"source": 663, "target": 309, "value": 4}, {"source": 663, "target": 321, "value": 8}, {"source": 663, "target": 351, "value": 9}, {"source": 663, "target": 363, "value": 2}, {"source": 663, "target": 371, "value": 8}, {"source": 663, "target": 401, "value": 8}, {"source": 663, "target": 405, "value": 8}, {"source": 663, "target": 413, "value": 1}, {"source": 663, "target": 437, "value": 8}, {"source": 663, "target": 453, "value": 9}, {"source": 663, "target": 455, "value": 10}, {"source": 663, "target": 459, "value": 9}, {"source": 663, "target": 461, "value": 4}, {"source": 663, "target": 471, "value": 8}, {"source": 663, "target": 489, "value": 8}, {"source": 663, "target": 497, "value": 4}, {"source": 663, "target": 501, "value": 8}, {"source": 663, "target": 513, "value": 1}, {"source": 663, "target": 521, "value": 9}, {"source": 663, "target": 549, "value": 8}, {"source": 663, "target": 563, "value": 2}, {"source": 663, "target": 587, "value": 8}, {"source": 663, "target": 609, "value": 4}, {"source": 663, "target": 615, "value": 8}, {"source": 663, "target": 621, "value": 8}, {"source": 663, "target": 651, "value": 8}, {"source": 663, "target": 653, "value": 8}, {"source": 665, "target": 215, "value": 4}, {"source": 665, "target": 227, "value": 8}, {"source": 665, "target": 315, "value": 4}, {"source": 665, "target": 317, "value": 10}, {"source": 665, "target": 353, "value": 8}, {"source": 665, "target": 365, "value": 2}, {"source": 665, "target": 377, "value": 8}, {"source": 665, "target": 405, "value": 10}, {"source": 665, "target": 453, "value": 4}, {"source": 665, "target": 461, "value": 8}, {"source": 665, "target": 465, "value": 2}, {"source": 665, "target": 515, "value": 4}, {"source": 665, "target": 527, "value": 8}, {"source": 665, "target": 573, "value": 7}, {"source": 665, "target": 605, "value": 8}, {"source": 665, "target": 615, "value": 3}, {"source": 665, "target": 653, "value": 8}, {"source": 669, "target": 219, "value": 4}, {"source": 669, "target": 221, "value": 5}, {"source": 669, "target": 231, "value": 8}, {"source": 669, "target": 233, "value": 10}, {"source": 669, "target": 245, "value": 8}, {"source": 669, "target": 248, "value": 9}, {"source": 669, "target": 257, "value": 9}, {"source": 669, "target": 273, "value": 5}, {"source": 669, "target": 279, "value": 8}, {"source": 669, "target": 281, "value": 8}, {"source": 669, "target": 309, "value": 10}, {"source": 669, "target": 317, "value": 8}, {"source": 669, "target": 321, "value": 10}, {"source": 669, "target": 323, "value": 11}, {"source": 669, "target": 333, "value": 10}, {"source": 669, "target": 345, "value": 8}, {"source": 669, "target": 348, "value": 9}, {"source": 669, "target": 369, "value": 2}, {"source": 669, "target": 377, "value": 8}, {"source": 669, "target": 381, "value": 8}, {"source": 669, "target": 383, "value": 9}, {"source": 669, "target": 395, "value": 8}, {"source": 669, "target": 398, "value": 9}, {"source": 669, "target": 423, "value": 10}, {"source": 669, "target": 429, "value": 8}, {"source": 669, "target": 431, "value": 8}, {"source": 669, "target": 473, "value": 10}, {"source": 669, "target": 483, "value": 9}, {"source": 669, "target": 495, "value": 8}, {"source": 669, "target": 498, "value": 8}, {"source": 669, "target": 519, "value": 2}, {"source": 669, "target": 531, "value": 8}, {"source": 669, "target": 533, "value": 8}, {"source": 669, "target": 545, "value": 8}, {"source": 669, "target": 548, "value": 8}, {"source": 669, "target": 557, "value": 8}, {"source": 669, "target": 573, "value": 9}, {"source": 669, "target": 579, "value": 8}, {"source": 669, "target": 581, "value": 8}, {"source": 669, "target": 611, "value": 9}, {"source": 669, "target": 615, "value": 7}, {"source": 669, "target": 617, "value": 7}, {"source": 669, "target": 621, "value": 8}, {"source": 669, "target": 623, "value": 9}, {"source": 669, "target": 633, "value": 8}, {"source": 669, "target": 645, "value": 8}, {"source": 669, "target": 648, "value": 7}, {"source": 671, "target": 219, "value": 8}, {"source": 671, "target": 221, "value": 4}, {"source": 671, "target": 225, "value": 8}, {"source": 671, "target": 228, "value": 8}, {"source": 671, "target": 261, "value": 10}, {"source": 671, "target": 275, "value": 8}, {"source": 671, "target": 278, "value": 8}, {"source": 671, "target": 293, "value": 12}, {"source": 671, "target": 309, "value": 8}, {"source": 671, "target": 321, "value": 2}, {"source": 671, "target": 365, "value": 10}, {"source": 671, "target": 369, "value": 8}, {"source": 671, "target": 371, "value": 4}, {"source": 671, "target": 375, "value": 8}, {"source": 671, "target": 378, "value": 8}, {"source": 671, "target": 413, "value": 8}, {"source": 671, "target": 425, "value": 8}, {"source": 671, "target": 428, "value": 8}, {"source": 671, "target": 461, "value": 9}, {"source": 671, "target": 471, "value": 4}, {"source": 671, "target": 513, "value": 8}, {"source": 671, "target": 519, "value": 8}, {"source": 671, "target": 521, "value": 4}, {"source": 671, "target": 525, "value": 8}, {"source": 671, "target": 528, "value": 8}, {"source": 671, "target": 563, "value": 9}, {"source": 671, "target": 573, "value": 9}, {"source": 671, "target": 575, "value": 7}, {"source": 671, "target": 578, "value": 8}, {"source": 671, "target": 609, "value": 7}, {"source": 671, "target": 621, "value": 3}, {"source": 671, "target": 663, "value": 8}, {"source": 671, "target": 669, "value": 7}, {"source": 675, "target": 225, "value": 4}, {"source": 675, "target": 228, "value": 8}, {"source": 675, "target": 275, "value": 4}, {"source": 675, "target": 278, "value": 8}, {"source": 675, "target": 321, "value": 8}, {"source": 675, "target": 365, "value": 10}, {"source": 675, "target": 375, "value": 4}, {"source": 675, "target": 377, "value": 10}, {"source": 675, "target": 378, "value": 8}, {"source": 675, "target": 413, "value": 8}, {"source": 675, "target": 425, "value": 4}, {"source": 675, "target": 428, "value": 8}, {"source": 675, "target": 513, "value": 8}, {"source": 675, "target": 525, "value": 4}, {"source": 675, "target": 528, "value": 8}, {"source": 675, "target": 563, "value": 8}, {"source": 675, "target": 575, "value": 3}, {"source": 675, "target": 578, "value": 8}, {"source": 675, "target": 587, "value": 3}, {"source": 675, "target": 671, "value": 7}, {"source": 677, "target": 225, "value": 8}, {"source": 677, "target": 227, "value": 4}, {"source": 677, "target": 243, "value": 11}, {"source": 677, "target": 279, "value": 10}, {"source": 677, "target": 287, "value": 8}, {"source": 677, "target": 293, "value": 11}, {"source": 677, "target": 303, "value": 9}, {"source": 677, "target": 317, "value": 10}, {"source": 677, "target": 335, "value": 8}, {"source": 677, "target": 339, "value": 8}, {"source": 677, "target": 353, "value": 4}, {"source": 677, "target": 365, "value": 4}, {"source": 677, "target": 377, "value": 2}, {"source": 677, "target": 393, "value": 10}, {"source": 677, "target": 425, "value": 8}, {"source": 677, "target": 429, "value": 4}, {"source": 677, "target": 437, "value": 8}, {"source": 677, "target": 443, "value": 10}, {"source": 677, "target": 453, "value": 4}, {"source": 677, "target": 465, "value": 8}, {"source": 677, "target": 489, "value": 8}, {"source": 677, "target": 503, "value": 9}, {"source": 677, "target": 525, "value": 8}, {"source": 677, "target": 527, "value": 4}, {"source": 677, "target": 543, "value": 9}, {"source": 677, "target": 573, "value": 8}, {"source": 677, "target": 575, "value": 3}, {"source": 677, "target": 587, "value": 8}, {"source": 677, "target": 593, "value": 9}, {"source": 677, "target": 603, "value": 8}, {"source": 677, "target": 639, "value": 8}, {"source": 677, "target": 645, "value": 3}, {"source": 677, "target": 653, "value": 4}, {"source": 677, "target": 665, "value": 8}, {"source": 678, "target": 225, "value": 8}, {"source": 678, "target": 228, "value": 4}, {"source": 678, "target": 249, "value": 8}, {"source": 678, "target": 261, "value": 8}, {"source": 678, "target": 273, "value": 8}, {"source": 678, "target": 275, "value": 8}, {"source": 678, "target": 278, "value": 4}, {"source": 678, "target": 303, "value": 10}, {"source": 678, "target": 321, "value": 8}, {"source": 678, "target": 323, "value": 8}, {"source": 678, "target": 353, "value": 10}, {"source": 678, "target": 365, "value": 10}, {"source": 678, "target": 375, "value": 8}, {"source": 678, "target": 378, "value": 4}, {"source": 678, "target": 399, "value": 8}, {"source": 678, "target": 413, "value": 4}, {"source": 678, "target": 423, "value": 8}, {"source": 678, "target": 425, "value": 8}, {"source": 678, "target": 428, "value": 4}, {"source": 678, "target": 437, "value": 8}, {"source": 678, "target": 453, "value": 9}, {"source": 678, "target": 461, "value": 8}, {"source": 678, "target": 473, "value": 8}, {"source": 678, "target": 503, "value": 9}, {"source": 678, "target": 513, "value": 8}, {"source": 678, "target": 525, "value": 8}, {"source": 678, "target": 528, "value": 4}, {"source": 678, "target": 545, "value": 8}, {"source": 678, "target": 549, "value": 8}, {"source": 678, "target": 561, "value": 8}, {"source": 678, "target": 563, "value": 9}, {"source": 678, "target": 573, "value": 8}, {"source": 678, "target": 575, "value": 8}, {"source": 678, "target": 578, "value": 4}, {"source": 678, "target": 603, "value": 8}, {"source": 678, "target": 623, "value": 8}, {"source": 678, "target": 653, "value": 8}, {"source": 678, "target": 671, "value": 7}, {"source": 678, "target": 675, "value": 8}, {"source": 681, "target": 221, "value": 11}, {"source": 681, "target": 231, "value": 4}, {"source": 681, "target": 243, "value": 8}, {"source": 681, "target": 273, "value": 11}, {"source": 681, "target": 281, "value": 2}, {"source": 681, "target": 293, "value": 8}, {"source": 681, "target": 333, "value": 10}, {"source": 681, "target": 369, "value": 8}, {"source": 681, "target": 377, "value": 8}, {"source": 681, "target": 381, "value": 2}, {"source": 681, "target": 393, "value": 8}, {"source": 681, "target": 398, "value": 9}, {"source": 681, "target": 431, "value": 2}, {"source": 681, "target": 443, "value": 8}, {"source": 681, "target": 489, "value": 8}, {"source": 681, "target": 519, "value": 9}, {"source": 681, "target": 531, "value": 4}, {"source": 681, "target": 533, "value": 9}, {"source": 681, "target": 543, "value": 8}, {"source": 681, "target": 551, "value": 3}, {"source": 681, "target": 581, "value": 2}, {"source": 681, "target": 585, "value": 9}, {"source": 681, "target": 593, "value": 8}, {"source": 681, "target": 605, "value": 8}, {"source": 681, "target": 621, "value": 2}, {"source": 681, "target": 669, "value": 8}, {"source": 683, "target": 221, "value": 4}, {"source": 683, "target": 233, "value": 1}, {"source": 683, "target": 257, "value": 8}, {"source": 683, "target": 279, "value": 4}, {"source": 683, "target": 317, "value": 8}, {"source": 683, "target": 321, "value": 4}, {"source": 683, "target": 333, "value": 1}, {"source": 683, "target": 335, "value": 10}, {"source": 683, "target": 381, "value": 8}, {"source": 683, "target": 383, "value": 2}, {"source": 683, "target": 405, "value": 11}, {"source": 683, "target": 407, "value": 8}, {"source": 683, "target": 429, "value": 9}, {"source": 683, "target": 483, "value": 2}, {"source": 683, "target": 521, "value": 8}, {"source": 683, "target": 533, "value": 1}, {"source": 683, "target": 557, "value": 4}, {"source": 683, "target": 573, "value": 9}, {"source": 683, "target": 579, "value": 9}, {"source": 683, "target": 617, "value": 8}, {"source": 683, "target": 621, "value": 7}, {"source": 683, "target": 633, "value": 1}, {"source": 683, "target": 669, "value": 8}, {"source": 693, "target": 243, "value": 1}, {"source": 693, "target": 245, "value": 10}, {"source": 693, "target": 251, "value": 8}, {"source": 693, "target": 255, "value": 8}, {"source": 693, "target": 281, "value": 9}, {"source": 693, "target": 293, "value": 0}, {"source": 693, "target": 303, "value": 8}, {"source": 693, "target": 305, "value": 8}, {"source": 693, "target": 317, "value": 2}, {"source": 693, "target": 333, "value": 10}, {"source": 693, "target": 335, "value": 8}, {"source": 693, "target": 339, "value": 8}, {"source": 693, "target": 341, "value": 8}, {"source": 693, "target": 353, "value": 8}, {"source": 693, "target": 377, "value": 8}, {"source": 693, "target": 381, "value": 8}, {"source": 693, "target": 393, "value": 1}, {"source": 693, "target": 398, "value": 9}, {"source": 693, "target": 405, "value": 8}, {"source": 693, "target": 429, "value": 8}, {"source": 693, "target": 431, "value": 9}, {"source": 693, "target": 441, "value": 8}, {"source": 693, "target": 443, "value": 1}, {"source": 693, "target": 453, "value": 8}, {"source": 693, "target": 455, "value": 8}, {"source": 693, "target": 467, "value": 4}, {"source": 693, "target": 489, "value": 2}, {"source": 693, "target": 503, "value": 8}, {"source": 693, "target": 521, "value": 3}, {"source": 693, "target": 533, "value": 9}, {"source": 693, "target": 543, "value": 1}, {"source": 693, "target": 545, "value": 9}, {"source": 693, "target": 555, "value": 8}, {"source": 693, "target": 581, "value": 8}, {"source": 693, "target": 585, "value": 9}, {"source": 693, "target": 591, "value": 3}, {"source": 693, "target": 593, "value": 1}, {"source": 693, "target": 603, "value": 8}, {"source": 693, "target": 605, "value": 8}, {"source": 693, "target": 617, "value": 4}, {"source": 693, "target": 639, "value": 8}, {"source": 693, "target": 641, "value": 7}, {"source": 693, "target": 645, "value": 8}, {"source": 693, "target": 653, "value": 8}, {"source": 693, "target": 677, "value": 8}, {"source": 693, "target": 681, "value": 8}, {"source": 695, "target": 233, "value": 4}, {"source": 695, "target": 245, "value": 2}, {"source": 695, "target": 248, "value": 8}, {"source": 695, "target": 257, "value": 8}, {"source": 695, "target": 285, "value": 10}, {"source": 695, "target": 333, "value": 8}, {"source": 695, "target": 345, "value": 2}, {"source": 695, "target": 348, "value": 8}, {"source": 695, "target": 395, "value": 4}, {"source": 695, "target": 398, "value": 8}, {"source": 695, "target": 407, "value": 8}, {"source": 695, "target": 485, "value": 9}, {"source": 695, "target": 495, "value": 4}, {"source": 695, "target": 498, "value": 8}, {"source": 695, "target": 503, "value": 8}, {"source": 695, "target": 533, "value": 8}, {"source": 695, "target": 545, "value": 2}, {"source": 695, "target": 548, "value": 8}, {"source": 695, "target": 557, "value": 8}, {"source": 695, "target": 579, "value": 3}, {"source": 695, "target": 633, "value": 7}, {"source": 695, "target": 645, "value": 2}, {"source": 695, "target": 648, "value": 8}, {"source": 695, "target": 669, "value": 8}, {"source": 698, "target": 219, "value": 8}, {"source": 698, "target": 233, "value": 4}, {"source": 698, "target": 245, "value": 8}, {"source": 698, "target": 248, "value": 4}, {"source": 698, "target": 257, "value": 8}, {"source": 698, "target": 273, "value": 11}, {"source": 698, "target": 285, "value": 11}, {"source": 698, "target": 309, "value": 10}, {"source": 698, "target": 323, "value": 10}, {"source": 698, "target": 333, "value": 9}, {"source": 698, "target": 345, "value": 8}, {"source": 698, "target": 348, "value": 4}, {"source": 698, "target": 369, "value": 8}, {"source": 698, "target": 395, "value": 8}, {"source": 698, "target": 398, "value": 4}, {"source": 698, "target": 423, "value": 10}, {"source": 698, "target": 473, "value": 9}, {"source": 698, "target": 485, "value": 10}, {"source": 698, "target": 495, "value": 8}, {"source": 698, "target": 498, "value": 4}, {"source": 698, "target": 519, "value": 8}, {"source": 698, "target": 533, "value": 8}, {"source": 698, "target": 545, "value": 8}, {"source": 698, "target": 548, "value": 4}, {"source": 698, "target": 557, "value": 8}, {"source": 698, "target": 573, "value": 9}, {"source": 698, "target": 611, "value": 8}, {"source": 698, "target": 615, "value": 8}, {"source": 698, "target": 623, "value": 8}, {"source": 698, "target": 633, "value": 8}, {"source": 698, "target": 645, "value": 7}, {"source": 698, "target": 648, "value": 3}, {"source": 698, "target": 669, "value": 8}, {"source": 698, "target": 695, "value": 8}, {"source": 699, "target": 221, "value": 8}, {"source": 699, "target": 228, "value": 9}, {"source": 699, "target": 249, "value": 4}, {"source": 699, "target": 251, "value": 10}, {"source": 699, "target": 273, "value": 4}, {"source": 699, "target": 278, "value": 9}, {"source": 699, "target": 293, "value": 10}, {"source": 699, "target": 303, "value": 4}, {"source": 699, "target": 321, "value": 11}, {"source": 699, "target": 323, "value": 8}, {"source": 699, "target": 341, "value": 8}, {"source": 699, "target": 347, "value": 8}, {"source": 699, "target": 353, "value": 4}, {"source": 699, "target": 378, "value": 9}, {"source": 699, "target": 399, "value": 4}, {"source": 699, "target": 423, "value": 8}, {"source": 699, "target": 428, "value": 9}, {"source": 699, "target": 437, "value": 8}, {"source": 699, "target": 441, "value": 8}, {"source": 699, "target": 453, "value": 4}, {"source": 699, "target": 473, "value": 4}, {"source": 699, "target": 497, "value": 8}, {"source": 699, "target": 503, "value": 4}, {"source": 699, "target": 528, "value": 8}, {"source": 699, "target": 545, "value": 8}, {"source": 699, "target": 549, "value": 4}, {"source": 699, "target": 555, "value": 3}, {"source": 699, "target": 573, "value": 2}, {"source": 699, "target": 578, "value": 8}, {"source": 699, "target": 603, "value": 4}, {"source": 699, "target": 621, "value": 8}, {"source": 699, "target": 623, "value": 8}, {"source": 699, "target": 641, "value": 8}, {"source": 699, "target": 647, "value": 8}, {"source": 699, "target": 653, "value": 4}, {"source": 699, "target": 678, "value": 7}, {"source": 699, "target": 693, "value": 8}, {"source": 699, "target": 695, "value": 3}, {"source": 701, "target": 227, "value": 8}, {"source": 701, "target": 249, "value": 8}, {"source": 701, "target": 251, "value": 4}, {"source": 701, "target": 261, "value": 8}, {"source": 701, "target": 263, "value": 8}, {"source": 701, "target": 279, "value": 10}, {"source": 701, "target": 303, "value": 9}, {"source": 701, "target": 311, "value": 8}, {"source": 701, "target": 341, "value": 10}, {"source": 701, "target": 351, "value": 4}, {"source": 701, "target": 353, "value": 4}, {"source": 701, "target": 363, "value": 8}, {"source": 701, "target": 377, "value": 8}, {"source": 701, "target": 401, "value": 2}, {"source": 701, "target": 411, "value": 8}, {"source": 701, "target": 413, "value": 8}, {"source": 701, "target": 453, "value": 3}, {"source": 701, "target": 455, "value": 9}, {"source": 701, "target": 461, "value": 8}, {"source": 701, "target": 489, "value": 8}, {"source": 701, "target": 501, "value": 2}, {"source": 701, "target": 503, "value": 4}, {"source": 701, "target": 513, "value": 8}, {"source": 701, "target": 525, "value": 8}, {"source": 701, "target": 527, "value": 8}, {"source": 701, "target": 543, "value": 3}, {"source": 701, "target": 549, "value": 8}, {"source": 701, "target": 551, "value": 4}, {"source": 701, "target": 561, "value": 8}, {"source": 701, "target": 563, "value": 8}, {"source": 701, "target": 573, "value": 10}, {"source": 701, "target": 603, "value": 8}, {"source": 701, "target": 611, "value": 8}, {"source": 701, "target": 651, "value": 2}, {"source": 701, "target": 653, "value": 2}, {"source": 701, "target": 663, "value": 8}, {"source": 701, "target": 677, "value": 8}, {"source": 701, "target": 683, "value": 3}, {"source": 705, "target": 245, "value": 11}, {"source": 705, "target": 251, "value": 8}, {"source": 705, "target": 255, "value": 2}, {"source": 705, "target": 293, "value": 9}, {"source": 705, "target": 305, "value": 2}, {"source": 705, "target": 317, "value": 8}, {"source": 705, "target": 363, "value": 8}, {"source": 705, "target": 377, "value": 11}, {"source": 705, "target": 393, "value": 8}, {"source": 705, "target": 405, "value": 2}, {"source": 705, "target": 429, "value": 8}, {"source": 705, "target": 455, "value": 4}, {"source": 705, "target": 467, "value": 8}, {"source": 705, "target": 555, "value": 4}, {"source": 705, "target": 557, "value": 9}, {"source": 705, "target": 593, "value": 8}, {"source": 705, "target": 605, "value": 2}, {"source": 705, "target": 617, "value": 8}, {"source": 705, "target": 645, "value": 8}, {"source": 705, "target": 693, "value": 8}, {"source": 707, "target": 233, "value": 4}, {"source": 707, "target": 245, "value": 9}, {"source": 707, "target": 257, "value": 4}, {"source": 707, "target": 333, "value": 4}, {"source": 707, "target": 345, "value": 8}, {"source": 707, "target": 381, "value": 8}, {"source": 707, "target": 383, "value": 9}, {"source": 707, "target": 407, "value": 4}, {"source": 707, "target": 483, "value": 9}, {"source": 707, "target": 503, "value": 8}, {"source": 707, "target": 533, "value": 4}, {"source": 707, "target": 545, "value": 8}, {"source": 707, "target": 557, "value": 4}, {"source": 707, "target": 633, "value": 4}, {"source": 707, "target": 645, "value": 7}, {"source": 707, "target": 683, "value": 8}, {"source": 707, "target": 695, "value": 8}, {"source": 711, "target": 249, "value": 9}, {"source": 711, "target": 261, "value": 4}, {"source": 711, "target": 285, "value": 8}, {"source": 711, "target": 311, "value": 4}, {"source": 711, "target": 363, "value": 10}, {"source": 711, "target": 411, "value": 4}, {"source": 711, "target": 461, "value": 4}, {"source": 711, "target": 501, "value": 9}, {"source": 711, "target": 549, "value": 8}, {"source": 711, "target": 561, "value": 4}, {"source": 711, "target": 611, "value": 4}, {"source": 711, "target": 701, "value": 8}, {"source": 713, "target": 225, "value": 8}, {"source": 713, "target": 228, "value": 8}, {"source": 713, "target": 255, "value": 10}, {"source": 713, "target": 261, "value": 8}, {"source": 713, "target": 263, "value": 1}, {"source": 713, "target": 275, "value": 8}, {"source": 713, "target": 278, "value": 8}, {"source": 713, "target": 285, "value": 8}, {"source": 713, "target": 287, "value": 4}, {"source": 713, "target": 291, "value": 9}, {"source": 713, "target": 293, "value": 8}, {"source": 713, "target": 305, "value": 8}, {"source": 713, "target": 309, "value": 8}, {"source": 713, "target": 311, "value": 8}, {"source": 713, "target": 317, "value": 8}, {"source": 713, "target": 321, "value": 8}, {"source": 713, "target": 341, "value": 9}, {"source": 713, "target": 351, "value": 9}, {"source": 713, "target": 363, "value": 1}, {"source": 713, "target": 365, "value": 4}, {"source": 713, "target": 375, "value": 8}, {"source": 713, "target": 378, "value": 8}, {"source": 713, "target": 401, "value": 8}, {"source": 713, "target": 405, "value": 4}, {"source": 713, "target": 411, "value": 8}, {"source": 713, "target": 413, "value": 1}, {"source": 713, "target": 425, "value": 8}, {"source": 713, "target": 428, "value": 8}, {"source": 713, "target": 437, "value": 4}, {"source": 713, "target": 441, "value": 9}, {"source": 713, "target": 453, "value": 9}, {"source": 713, "target": 455, "value": 10}, {"source": 713, "target": 459, "value": 8}, {"source": 713, "target": 461, "value": 2}, {"source": 713, "target": 467, "value": 8}, {"source": 713, "target": 489, "value": 4}, {"source": 713, "target": 491, "value": 9}, {"source": 713, "target": 497, "value": 4}, {"source": 713, "target": 501, "value": 8}, {"source": 713, "target": 513, "value": 0}, {"source": 713, "target": 525, "value": 8}, {"source": 713, "target": 528, "value": 8}, {"source": 713, "target": 549, "value": 8}, {"source": 713, "target": 557, "value": 9}, {"source": 713, "target": 561, "value": 8}, {"source": 713, "target": 563, "value": 1}, {"source": 713, "target": 575, "value": 8}, {"source": 713, "target": 578, "value": 8}, {"source": 713, "target": 587, "value": 4}, {"source": 713, "target": 591, "value": 9}, {"source": 713, "target": 605, "value": 8}, {"source": 713, "target": 609, "value": 9}, {"source": 713, "target": 611, "value": 8}, {"source": 713, "target": 615, "value": 9}, {"source": 713, "target": 617, "value": 8}, {"source": 713, "target": 641, "value": 9}, {"source": 713, "target": 651, "value": 8}, {"source": 713, "target": 653, "value": 2}, {"source": 713, "target": 663, "value": 1}, {"source": 713, "target": 671, "value": 7}, {"source": 713, "target": 675, "value": 8}, {"source": 713, "target": 678, "value": 8}, {"source": 713, "target": 701, "value": 8}, {"source": 713, "target": 705, "value": 7}, {"source": 713, "target": 711, "value": 7}, {"source": 723, "target": 219, "value": 8}, {"source": 723, "target": 221, "value": 8}, {"source": 723, "target": 248, "value": 8}, {"source": 723, "target": 257, "value": 8}, {"source": 723, "target": 261, "value": 9}, {"source": 723, "target": 273, "value": 1}, {"source": 723, "target": 309, "value": 9}, {"source": 723, "target": 323, "value": 2}, {"source": 723, "target": 347, "value": 8}, {"source": 723, "target": 348, "value": 8}, {"source": 723, "target": 369, "value": 8}, {"source": 723, "target": 398, "value": 8}, {"source": 723, "target": 413, "value": 10}, {"source": 723, "target": 423, "value": 2}, {"source": 723, "target": 461, "value": 8}, {"source": 723, "target": 473, "value": 1}, {"source": 723, "target": 497, "value": 8}, {"source": 723, "target": 498, "value": 8}, {"source": 723, "target": 519, "value": 8}, {"source": 723, "target": 545, "value": 10}, {"source": 723, "target": 548, "value": 8}, {"source": 723, "target": 557, "value": 8}, {"source": 723, "target": 561, "value": 8}, {"source": 723, "target": 573, "value": 1}, {"source": 723, "target": 611, "value": 8}, {"source": 723, "target": 615, "value": 8}, {"source": 723, "target": 621, "value": 8}, {"source": 723, "target": 623, "value": 2}, {"source": 723, "target": 647, "value": 8}, {"source": 723, "target": 648, "value": 8}, {"source": 723, "target": 669, "value": 8}, {"source": 723, "target": 678, "value": 8}, {"source": 723, "target": 698, "value": 8}, {"source": 723, "target": 699, "value": 8}, {"source": 725, "target": 225, "value": 2}, {"source": 725, "target": 228, "value": 8}, {"source": 725, "target": 273, "value": 8}, {"source": 725, "target": 275, "value": 4}, {"source": 725, "target": 278, "value": 8}, {"source": 725, "target": 285, "value": 8}, {"source": 725, "target": 287, "value": 8}, {"source": 725, "target": 321, "value": 8}, {"source": 725, "target": 335, "value": 8}, {"source": 725, "target": 365, "value": 10}, {"source": 725, "target": 375, "value": 4}, {"source": 725, "target": 377, "value": 4}, {"source": 725, "target": 378, "value": 8}, {"source": 725, "target": 413, "value": 8}, {"source": 725, "target": 425, "value": 2}, {"source": 725, "target": 428, "value": 8}, {"source": 725, "target": 429, "value": 10}, {"source": 725, "target": 435, "value": 8}, {"source": 725, "target": 437, "value": 8}, {"source": 725, "target": 473, "value": 8}, {"source": 725, "target": 485, "value": 8}, {"source": 725, "target": 513, "value": 8}, {"source": 725, "target": 525, "value": 2}, {"source": 725, "target": 528, "value": 8}, {"source": 725, "target": 531, "value": 8}, {"source": 725, "target": 563, "value": 9}, {"source": 725, "target": 573, "value": 8}, {"source": 725, "target": 575, "value": 4}, {"source": 725, "target": 578, "value": 8}, {"source": 725, "target": 585, "value": 8}, {"source": 725, "target": 587, "value": 8}, {"source": 725, "target": 635, "value": 8}, {"source": 725, "target": 671, "value": 7}, {"source": 725, "target": 675, "value": 3}, {"source": 725, "target": 677, "value": 8}, {"source": 725, "target": 678, "value": 7}, {"source": 725, "target": 713, "value": 8}, {"source": 728, "target": 225, "value": 8}, {"source": 728, "target": 228, "value": 4}, {"source": 728, "target": 249, "value": 8}, {"source": 728, "target": 275, "value": 8}, {"source": 728, "target": 278, "value": 4}, {"source": 728, "target": 303, "value": 11}, {"source": 728, "target": 321, "value": 8}, {"source": 728, "target": 353, "value": 10}, {"source": 728, "target": 365, "value": 10}, {"source": 728, "target": 375, "value": 8}, {"source": 728, "target": 378, "value": 4}, {"source": 728, "target": 399, "value": 8}, {"source": 728, "target": 413, "value": 8}, {"source": 728, "target": 425, "value": 8}, {"source": 728, "target": 428, "value": 4}, {"source": 728, "target": 437, "value": 8}, {"source": 728, "target": 453, "value": 10}, {"source": 728, "target": 503, "value": 9}, {"source": 728, "target": 513, "value": 8}, {"source": 728, "target": 525, "value": 8}, {"source": 728, "target": 528, "value": 4}, {"source": 728, "target": 545, "value": 8}, {"source": 728, "target": 549, "value": 8}, {"source": 728, "target": 563, "value": 9}, {"source": 728, "target": 575, "value": 8}, {"source": 728, "target": 578, "value": 4}, {"source": 728, "target": 603, "value": 9}, {"source": 728, "target": 653, "value": 8}, {"source": 728, "target": 671, "value": 8}, {"source": 728, "target": 675, "value": 7}, {"source": 728, "target": 678, "value": 3}, {"source": 728, "target": 699, "value": 8}, {"source": 728, "target": 713, "value": 8}, {"source": 728, "target": 725, "value": 8}, {"source": 729, "target": 221, "value": 11}, {"source": 729, "target": 233, "value": 12}, {"source": 729, "target": 279, "value": 4}, {"source": 729, "target": 291, "value": 8}, {"source": 729, "target": 317, "value": 9}, {"source": 729, "target": 333, "value": 11}, {"source": 729, "target": 341, "value": 8}, {"source": 729, "target": 381, "value": 9}, {"source": 729, "target": 383, "value": 11}, {"source": 729, "target": 429, "value": 2}, {"source": 729, "target": 441, "value": 8}, {"source": 729, "target": 483, "value": 10}, {"source": 729, "target": 491, "value": 8}, {"source": 729, "target": 531, "value": 9}, {"source": 729, "target": 533, "value": 4}, {"source": 729, "target": 579, "value": 4}, {"source": 729, "target": 581, "value": 9}, {"source": 729, "target": 587, "value": 8}, {"source": 729, "target": 591, "value": 8}, {"source": 729, "target": 617, "value": 8}, {"source": 729, "target": 633, "value": 9}, {"source": 729, "target": 641, "value": 8}, {"source": 729, "target": 653, "value": 8}, {"source": 729, "target": 669, "value": 8}, {"source": 729, "target": 683, "value": 9}, {"source": 731, "target": 221, "value": 11}, {"source": 731, "target": 231, "value": 4}, {"source": 731, "target": 273, "value": 11}, {"source": 731, "target": 281, "value": 4}, {"source": 731, "target": 369, "value": 8}, {"source": 731, "target": 377, "value": 8}, {"source": 731, "target": 381, "value": 4}, {"source": 731, "target": 431, "value": 4}, {"source": 731, "target": 519, "value": 9}, {"source": 731, "target": 531, "value": 4}, {"source": 731, "target": 581, "value": 4}, {"source": 731, "target": 605, "value": 8}, {"source": 731, "target": 621, "value": 8}, {"source": 731, "target": 669, "value": 7}, {"source": 731, "target": 681, "value": 3}, {"source": 735, "target": 273, "value": 9}, {"source": 735, "target": 285, "value": 4}, {"source": 735, "target": 335, "value": 4}, {"source": 735, "target": 377, "value": 10}, {"source": 735, "target": 435, "value": 4}, {"source": 735, "target": 473, "value": 8}, {"source": 735, "target": 485, "value": 4}, {"source": 735, "target": 525, "value": 9}, {"source": 735, "target": 531, "value": 8}, {"source": 735, "target": 563, "value": 3}, {"source": 735, "target": 573, "value": 8}, {"source": 735, "target": 585, "value": 4}, {"source": 735, "target": 587, "value": 9}, {"source": 735, "target": 633, "value": 3}, {"source": 735, "target": 635, "value": 4}, {"source": 735, "target": 725, "value": 8}, {"source": 737, "target": 225, "value": 9}, {"source": 737, "target": 228, "value": 9}, {"source": 737, "target": 249, "value": 8}, {"source": 737, "target": 263, "value": 9}, {"source": 737, "target": 278, "value": 9}, {"source": 737, "target": 287, "value": 4}, {"source": 737, "target": 303, "value": 11}, {"source": 737, "target": 353, "value": 11}, {"source": 737, "target": 363, "value": 9}, {"source": 737, "target": 378, "value": 8}, {"source": 737, "target": 399, "value": 8}, {"source": 737, "target": 413, "value": 4}, {"source": 737, "target": 425, "value": 8}, {"source": 737, "target": 428, "value": 8}, {"source": 737, "target": 429, "value": 10}, {"source": 737, "target": 437, "value": 2}, {"source": 737, "target": 453, "value": 10}, {"source": 737, "target": 461, "value": 8}, {"source": 737, "target": 489, "value": 9}, {"source": 737, "target": 503, "value": 10}, {"source": 737, "target": 513, "value": 4}, {"source": 737, "target": 525, "value": 8}, {"source": 737, "target": 528, "value": 8}, {"source": 737, "target": 545, "value": 8}, {"source": 737, "target": 549, "value": 8}, {"source": 737, "target": 551, "value": 3}, {"source": 737, "target": 563, "value": 8}, {"source": 737, "target": 578, "value": 8}, {"source": 737, "target": 587, "value": 4}, {"source": 737, "target": 603, "value": 9}, {"source": 737, "target": 621, "value": 3}, {"source": 737, "target": 653, "value": 8}, {"source": 737, "target": 663, "value": 8}, {"source": 737, "target": 677, "value": 8}, {"source": 737, "target": 678, "value": 7}, {"source": 737, "target": 699, "value": 8}, {"source": 737, "target": 713, "value": 4}, {"source": 737, "target": 725, "value": 7}, {"source": 737, "target": 728, "value": 7}, {"source": 741, "target": 219, "value": 8}, {"source": 741, "target": 228, "value": 8}, {"source": 741, "target": 233, "value": 4}, {"source": 741, "target": 245, "value": 8}, {"source": 741, "target": 248, "value": 8}, {"source": 741, "target": 249, "value": 8}, {"source": 741, "target": 251, "value": 8}, {"source": 741, "target": 278, "value": 8}, {"source": 741, "target": 285, "value": 10}, {"source": 741, "target": 291, "value": 4}, {"source": 741, "target": 293, "value": 5}, {"source": 741, "target": 303, "value": 4}, {"source": 741, "target": 321, "value": 9}, {"source": 741, "target": 333, "value": 8}, {"source": 741, "target": 339, "value": 8}, {"source": 741, "target": 341, "value": 2}, {"source": 741, "target": 345, "value": 8}, {"source": 741, "target": 348, "value": 8}, {"source": 741, "target": 351, "value": 8}, {"source": 741, "target": 353, "value": 4}, {"source": 741, "target": 365, "value": 8}, {"source": 741, "target": 369, "value": 8}, {"source": 741, "target": 378, "value": 8}, {"source": 741, "target": 381, "value": 10}, {"source": 741, "target": 395, "value": 8}, {"source": 741, "target": 398, "value": 8}, {"source": 741, "target": 399, "value": 8}, {"source": 741, "target": 401, "value": 8}, {"source": 741, "target": 413, "value": 8}, {"source": 741, "target": 428, "value": 8}, {"source": 741, "target": 429, "value": 8}, {"source": 741, "target": 437, "value": 8}, {"source": 741, "target": 441, "value": 2}, {"source": 741, "target": 453, "value": 4}, {"source": 741, "target": 461, "value": 9}, {"source": 741, "target": 485, "value": 9}, {"source": 741, "target": 489, "value": 4}, {"source": 741, "target": 491, "value": 4}, {"source": 741, "target": 495, "value": 8}, {"source": 741, "target": 498, "value": 8}, {"source": 741, "target": 501, "value": 8}, {"source": 741, "target": 503, "value": 4}, {"source": 741, "target": 519, "value": 9}, {"source": 741, "target": 528, "value": 8}, {"source": 741, "target": 533, "value": 4}, {"source": 741, "target": 545, "value": 4}, {"source": 741, "target": 548, "value": 8}, {"source": 741, "target": 549, "value": 8}, {"source": 741, "target": 551, "value": 8}, {"source": 741, "target": 578, "value": 8}, {"source": 741, "target": 581, "value": 9}, {"source": 741, "target": 587, "value": 8}, {"source": 741, "target": 591, "value": 4}, {"source": 741, "target": 603, "value": 4}, {"source": 741, "target": 633, "value": 8}, {"source": 741, "target": 639, "value": 8}, {"source": 741, "target": 641, "value": 2}, {"source": 741, "target": 645, "value": 7}, {"source": 741, "target": 648, "value": 8}, {"source": 741, "target": 651, "value": 8}, {"source": 741, "target": 653, "value": 4}, {"source": 741, "target": 669, "value": 8}, {"source": 741, "target": 671, "value": 7}, {"source": 741, "target": 678, "value": 7}, {"source": 741, "target": 693, "value": 8}, {"source": 741, "target": 695, "value": 7}, {"source": 741, "target": 698, "value": 7}, {"source": 741, "target": 699, "value": 4}, {"source": 741, "target": 701, "value": 8}, {"source": 741, "target": 713, "value": 8}, {"source": 741, "target": 728, "value": 8}, {"source": 741, "target": 729, "value": 8}, {"source": 741, "target": 737, "value": 2}, {"source": 743, "target": 243, "value": 2}, {"source": 743, "target": 281, "value": 9}, {"source": 743, "target": 293, "value": 1}, {"source": 743, "target": 317, "value": 4}, {"source": 743, "target": 333, "value": 10}, {"source": 743, "target": 335, "value": 8}, {"source": 743, "target": 339, "value": 8}, {"source": 743, "target": 377, "value": 8}, {"source": 743, "target": 381, "value": 8}, {"source": 743, "target": 393, "value": 1}, {"source": 743, "target": 398, "value": 9}, {"source": 743, "target": 429, "value": 8}, {"source": 743, "target": 431, "value": 9}, {"source": 743, "target": 443, "value": 2}, {"source": 743, "target": 467, "value": 8}, {"source": 743, "target": 489, "value": 2}, {"source": 743, "target": 533, "value": 9}, {"source": 743, "target": 543, "value": 2}, {"source": 743, "target": 545, "value": 9}, {"source": 743, "target": 581, "value": 8}, {"source": 743, "target": 585, "value": 9}, {"source": 743, "target": 593, "value": 1}, {"source": 743, "target": 617, "value": 8}, {"source": 743, "target": 639, "value": 8}, {"source": 743, "target": 677, "value": 8}, {"source": 743, "target": 681, "value": 7}, {"source": 743, "target": 693, "value": 1}, {"source": 753, "target": 215, "value": 8}, {"source": 753, "target": 227, "value": 4}, {"source": 753, "target": 228, "value": 8}, {"source": 753, "target": 249, "value": 8}, {"source": 753, "target": 278, "value": 8}, {"source": 753, "target": 279, "value": 4}, {"source": 753, "target": 293, "value": 10}, {"source": 753, "target": 303, "value": 1}, {"source": 753, "target": 315, "value": 8}, {"source": 753, "target": 341, "value": 9}, {"source": 753, "target": 353, "value": 1}, {"source": 753, "target": 365, "value": 8}, {"source": 753, "target": 377, "value": 4}, {"source": 753, "target": 378, "value": 8}, {"source": 753, "target": 399, "value": 8}, {"source": 753, "target": 405, "value": 4}, {"source": 753, "target": 428, "value": 8}, {"source": 753, "target": 437, "value": 8}, {"source": 753, "target": 441, "value": 8}, {"source": 753, "target": 453, "value": 0}, {"source": 753, "target": 461, "value": 8}, {"source": 753, "target": 465, "value": 8}, {"source": 753, "target": 503, "value": 1}, {"source": 753, "target": 515, "value": 8}, {"source": 753, "target": 527, "value": 4}, {"source": 753, "target": 528, "value": 8}, {"source": 753, "target": 545, "value": 8}, {"source": 753, "target": 549, "value": 8}, {"source": 753, "target": 578, "value": 8}, {"source": 753, "target": 603, "value": 1}, {"source": 753, "target": 605, "value": 9}, {"source": 753, "target": 615, "value": 8}, {"source": 753, "target": 641, "value": 8}, {"source": 753, "target": 653, "value": 1}, {"source": 753, "target": 665, "value": 7}, {"source": 753, "target": 677, "value": 4}, {"source": 753, "target": 678, "value": 8}, {"source": 753, "target": 693, "value": 8}, {"source": 753, "target": 699, "value": 4}, {"source": 753, "target": 701, "value": 3}, {"source": 753, "target": 707, "value": 3}, {"source": 753, "target": 728, "value": 8}, {"source": 753, "target": 737, "value": 8}, {"source": 753, "target": 741, "value": 4}, {"source": 755, "target": 245, "value": 11}, {"source": 755, "target": 251, "value": 8}, {"source": 755, "target": 255, "value": 4}, {"source": 755, "target": 263, "value": 12}, {"source": 755, "target": 293, "value": 9}, {"source": 755, "target": 303, "value": 8}, {"source": 755, "target": 305, "value": 4}, {"source": 755, "target": 309, "value": 8}, {"source": 755, "target": 351, "value": 10}, {"source": 755, "target": 353, "value": 8}, {"source": 755, "target": 363, "value": 11}, {"source": 755, "target": 377, "value": 11}, {"source": 755, "target": 393, "value": 8}, {"source": 755, "target": 405, "value": 2}, {"source": 755, "target": 413, "value": 10}, {"source": 755, "target": 429, "value": 8}, {"source": 755, "target": 453, "value": 8}, {"source": 755, "target": 455, "value": 4}, {"source": 755, "target": 459, "value": 8}, {"source": 755, "target": 497, "value": 4}, {"source": 755, "target": 503, "value": 8}, {"source": 755, "target": 513, "value": 10}, {"source": 755, "target": 549, "value": 9}, {"source": 755, "target": 555, "value": 2}, {"source": 755, "target": 563, "value": 9}, {"source": 755, "target": 593, "value": 8}, {"source": 755, "target": 603, "value": 8}, {"source": 755, "target": 605, "value": 4}, {"source": 755, "target": 609, "value": 8}, {"source": 755, "target": 645, "value": 8}, {"source": 755, "target": 653, "value": 8}, {"source": 755, "target": 663, "value": 9}, {"source": 755, "target": 693, "value": 7}, {"source": 755, "target": 695, "value": 3}, {"source": 755, "target": 705, "value": 3}, {"source": 755, "target": 713, "value": 8}, {"source": 755, "target": 753, "value": 7}, {"source": 759, "target": 263, "value": 12}, {"source": 759, "target": 309, "value": 4}, {"source": 759, "target": 333, "value": 8}, {"source": 759, "target": 351, "value": 10}, {"source": 759, "target": 363, "value": 11}, {"source": 759, "target": 405, "value": 8}, {"source": 759, "target": 413, "value": 11}, {"source": 759, "target": 459, "value": 4}, {"source": 759, "target": 461, "value": 9}, {"source": 759, "target": 497, "value": 4}, {"source": 759, "target": 513, "value": 10}, {"source": 759, "target": 549, "value": 9}, {"source": 759, "target": 563, "value": 9}, {"source": 759, "target": 609, "value": 4}, {"source": 759, "target": 663, "value": 9}, {"source": 759, "target": 671, "value": 3}, {"source": 759, "target": 713, "value": 8}, {"source": 759, "target": 741, "value": 3}, {"source": 759, "target": 755, "value": 8}, {"source": 761, "target": 249, "value": 9}, {"source": 761, "target": 261, "value": 2}, {"source": 761, "target": 273, "value": 4}, {"source": 761, "target": 285, "value": 8}, {"source": 761, "target": 311, "value": 4}, {"source": 761, "target": 323, "value": 4}, {"source": 761, "target": 363, "value": 10}, {"source": 761, "target": 411, "value": 4}, {"source": 761, "target": 413, "value": 4}, {"source": 761, "target": 423, "value": 4}, {"source": 761, "target": 461, "value": 2}, {"source": 761, "target": 473, "value": 4}, {"source": 761, "target": 501, "value": 9}, {"source": 761, "target": 549, "value": 8}, {"source": 761, "target": 561, "value": 2}, {"source": 761, "target": 573, "value": 4}, {"source": 761, "target": 611, "value": 4}, {"source": 761, "target": 623, "value": 4}, {"source": 761, "target": 678, "value": 4}, {"source": 761, "target": 701, "value": 8}, {"source": 761, "target": 711, "value": 3}, {"source": 761, "target": 713, "value": 8}, {"source": 761, "target": 723, "value": 4}, {"source": 761, "target": 729, "value": 3}, {"source": 765, "target": 215, "value": 4}, {"source": 765, "target": 225, "value": 8}, {"source": 765, "target": 227, "value": 8}, {"source": 765, "target": 228, "value": 8}, {"source": 765, "target": 275, "value": 8}, {"source": 765, "target": 278, "value": 8}, {"source": 765, "target": 291, "value": 8}, {"source": 765, "target": 293, "value": 10}, {"source": 765, "target": 315, "value": 4}, {"source": 765, "target": 317, "value": 10}, {"source": 765, "target": 321, "value": 8}, {"source": 765, "target": 341, "value": 8}, {"source": 765, "target": 353, "value": 9}, {"source": 765, "target": 365, "value": 1}, {"source": 765, "target": 375, "value": 8}, {"source": 765, "target": 377, "value": 8}, {"source": 765, "target": 378, "value": 8}, {"source": 765, "target": 405, "value": 10}, {"source": 765, "target": 413, "value": 8}, {"source": 765, "target": 425, "value": 8}, {"source": 765, "target": 428, "value": 8}, {"source": 765, "target": 441, "value": 8}, {"source": 765, "target": 453, "value": 4}, {"source": 765, "target": 461, "value": 8}, {"source": 765, "target": 465, "value": 2}, {"source": 765, "target": 491, "value": 8}, {"source": 765, "target": 513, "value": 8}, {"source": 765, "target": 515, "value": 4}, {"source": 765, "target": 525, "value": 8}, {"source": 765, "target": 527, "value": 8}, {"source": 765, "target": 528, "value": 8}, {"source": 765, "target": 563, "value": 8}, {"source": 765, "target": 573, "value": 8}, {"source": 765, "target": 575, "value": 8}, {"source": 765, "target": 578, "value": 8}, {"source": 765, "target": 591, "value": 8}, {"source": 765, "target": 605, "value": 9}, {"source": 765, "target": 615, "value": 4}, {"source": 765, "target": 641, "value": 8}, {"source": 765, "target": 653, "value": 8}, {"source": 765, "target": 665, "value": 2}, {"source": 765, "target": 671, "value": 8}, {"source": 765, "target": 675, "value": 8}, {"source": 765, "target": 677, "value": 7}, {"source": 765, "target": 678, "value": 8}, {"source": 765, "target": 713, "value": 4}, {"source": 765, "target": 725, "value": 8}, {"source": 765, "target": 728, "value": 8}, {"source": 765, "target": 741, "value": 8}, {"source": 765, "target": 753, "value": 7}, {"source": 767, "target": 225, "value": 8}, {"source": 767, "target": 228, "value": 8}, {"source": 767, "target": 243, "value": 9}, {"source": 767, "target": 255, "value": 10}, {"source": 767, "target": 275, "value": 8}, {"source": 767, "target": 278, "value": 8}, {"source": 767, "target": 293, "value": 4}, {"source": 767, "target": 305, "value": 9}, {"source": 767, "target": 317, "value": 4}, {"source": 767, "target": 321, "value": 8}, {"source": 767, "target": 363, "value": 8}, {"source": 767, "target": 365, "value": 9}, {"source": 767, "target": 375, "value": 8}, {"source": 767, "target": 378, "value": 8}, {"source": 767, "target": 393, "value": 4}, {"source": 767, "target": 405, "value": 8}, {"source": 767, "target": 413, "value": 8}, {"source": 767, "target": 425, "value": 8}, {"source": 767, "target": 428, "value": 8}, {"source": 767, "target": 443, "value": 9}, {"source": 767, "target": 467, "value": 4}, {"source": 767, "target": 489, "value": 10}, {"source": 767, "target": 513, "value": 8}, {"source": 767, "target": 525, "value": 8}, {"source": 767, "target": 528, "value": 8}, {"source": 767, "target": 543, "value": 8}, {"source": 767, "target": 557, "value": 9}, {"source": 767, "target": 563, "value": 8}, {"source": 767, "target": 575, "value": 8}, {"source": 767, "target": 578, "value": 8}, {"source": 767, "target": 593, "value": 4}, {"source": 767, "target": 605, "value": 8}, {"source": 767, "target": 617, "value": 4}, {"source": 767, "target": 671, "value": 8}, {"source": 767, "target": 675, "value": 8}, {"source": 767, "target": 678, "value": 8}, {"source": 767, "target": 693, "value": 4}, {"source": 767, "target": 705, "value": 7}, {"source": 767, "target": 713, "value": 4}, {"source": 767, "target": 725, "value": 8}, {"source": 767, "target": 728, "value": 8}, {"source": 767, "target": 743, "value": 8}, {"source": 767, "target": 765, "value": 7}, {"source": 771, "target": 221, "value": 4}, {"source": 771, "target": 261, "value": 11}, {"source": 771, "target": 293, "value": 12}, {"source": 771, "target": 309, "value": 9}, {"source": 771, "target": 321, "value": 4}, {"source": 771, "target": 371, "value": 4}, {"source": 771, "target": 461, "value": 10}, {"source": 771, "target": 471, "value": 4}, {"source": 771, "target": 521, "value": 4}, {"source": 771, "target": 573, "value": 9}, {"source": 771, "target": 609, "value": 8}, {"source": 771, "target": 621, "value": 4}, {"source": 771, "target": 663, "value": 9}, {"source": 771, "target": 671, "value": 4}, {"source": 773, "target": 219, "value": 8}, {"source": 773, "target": 221, "value": 2}, {"source": 773, "target": 233, "value": 8}, {"source": 773, "target": 248, "value": 8}, {"source": 773, "target": 257, "value": 8}, {"source": 773, "target": 261, "value": 9}, {"source": 773, "target": 273, "value": 1}, {"source": 773, "target": 279, "value": 8}, {"source": 773, "target": 285, "value": 8}, {"source": 773, "target": 309, "value": 9}, {"source": 773, "target": 321, "value": 4}, {"source": 773, "target": 323, "value": 1}, {"source": 773, "target": 333, "value": 8}, {"source": 773, "target": 335, "value": 8}, {"source": 773, "target": 347, "value": 4}, {"source": 773, "target": 348, "value": 8}, {"source": 773, "target": 369, "value": 8}, {"source": 773, "target": 377, "value": 10}, {"source": 773, "target": 383, "value": 8}, {"source": 773, "target": 398, "value": 8}, {"source": 773, "target": 413, "value": 10}, {"source": 773, "target": 423, "value": 1}, {"source": 773, "target": 435, "value": 8}, {"source": 773, "target": 461, "value": 8}, {"source": 773, "target": 473, "value": 1}, {"source": 773, "target": 483, "value": 8}, {"source": 773, "target": 485, "value": 8}, {"source": 773, "target": 497, "value": 4}, {"source": 773, "target": 498, "value": 8}, {"source": 773, "target": 519, "value": 8}, {"source": 773, "target": 521, "value": 8}, {"source": 773, "target": 525, "value": 9}, {"source": 773, "target": 531, "value": 8}, {"source": 773, "target": 533, "value": 8}, {"source": 773, "target": 545, "value": 10}, {"source": 773, "target": 548, "value": 8}, {"source": 773, "target": 557, "value": 8}, {"source": 773, "target": 561, "value": 8}, {"source": 773, "target": 573, "value": 0}, {"source": 773, "target": 585, "value": 8}, {"source": 773, "target": 611, "value": 8}, {"source": 773, "target": 615, "value": 8}, {"source": 773, "target": 621, "value": 2}, {"source": 773, "target": 623, "value": 1}, {"source": 773, "target": 633, "value": 8}, {"source": 773, "target": 635, "value": 8}, {"source": 773, "target": 647, "value": 4}, {"source": 773, "target": 648, "value": 8}, {"source": 773, "target": 669, "value": 8}, {"source": 773, "target": 678, "value": 8}, {"source": 773, "target": 683, "value": 8}, {"source": 773, "target": 698, "value": 7}, {"source": 773, "target": 699, "value": 2}, {"source": 773, "target": 723, "value": 1}, {"source": 773, "target": 725, "value": 8}, {"source": 773, "target": 735, "value": 8}, {"source": 773, "target": 761, "value": 4}, {"source": 783, "target": 221, "value": 4}, {"source": 783, "target": 225, "value": 9}, {"source": 783, "target": 231, "value": 8}, {"source": 783, "target": 233, "value": 1}, {"source": 783, "target": 245, "value": 8}, {"source": 783, "target": 248, "value": 8}, {"source": 783, "target": 257, "value": 8}, {"source": 783, "target": 279, "value": 4}, {"source": 783, "target": 281, "value": 8}, {"source": 783, "target": 285, "value": 10}, {"source": 783, "target": 287, "value": 8}, {"source": 783, "target": 317, "value": 8}, {"source": 783, "target": 321, "value": 4}, {"source": 783, "target": 333, "value": 1}, {"source": 783, "target": 335, "value": 10}, {"source": 783, "target": 345, "value": 8}, {"source": 783, "target": 348, "value": 8}, {"source": 783, "target": 381, "value": 4}, {"source": 783, "target": 383, "value": 2}, {"source": 783, "target": 395, "value": 8}, {"source": 783, "target": 398, "value": 8}, {"source": 783, "target": 405, "value": 11}, {"source": 783, "target": 407, "value": 8}, {"source": 783, "target": 425, "value": 8}, {"source": 783, "target": 429, "value": 4}, {"source": 783, "target": 431, "value": 8}, {"source": 783, "target": 437, "value": 8}, {"source": 783, "target": 483, "value": 2}, {"source": 783, "target": 485, "value": 9}, {"source": 783, "target": 495, "value": 8}, {"source": 783, "target": 498, "value": 8}, {"source": 783, "target": 521, "value": 8}, {"source": 783, "target": 525, "value": 8}, {"source": 783, "target": 531, "value": 8}, {"source": 783, "target": 533, "value": 1}, {"source": 783, "target": 545, "value": 8}, {"source": 783, "target": 548, "value": 8}, {"source": 783, "target": 557, "value": 4}, {"source": 783, "target": 573, "value": 9}, {"source": 783, "target": 579, "value": 8}, {"source": 783, "target": 581, "value": 8}, {"source": 783, "target": 587, "value": 8}, {"source": 783, "target": 605, "value": 8}, {"source": 783, "target": 617, "value": 8}, {"source": 783, "target": 621, "value": 8}, {"source": 783, "target": 633, "value": 1}, {"source": 783, "target": 645, "value": 8}, {"source": 783, "target": 648, "value": 8}, {"source": 783, "target": 669, "value": 8}, {"source": 783, "target": 677, "value": 8}, {"source": 783, "target": 681, "value": 8}, {"source": 783, "target": 683, "value": 2}, {"source": 783, "target": 695, "value": 8}, {"source": 783, "target": 698, "value": 8}, {"source": 783, "target": 707, "value": 8}, {"source": 783, "target": 725, "value": 8}, {"source": 783, "target": 729, "value": 8}, {"source": 783, "target": 731, "value": 7}, {"source": 783, "target": 737, "value": 7}, {"source": 783, "target": 741, "value": 8}, {"source": 783, "target": 773, "value": 7}, {"source": 785, "target": 273, "value": 9}, {"source": 785, "target": 285, "value": 2}, {"source": 785, "target": 293, "value": 8}, {"source": 785, "target": 335, "value": 4}, {"source": 785, "target": 347, "value": 8}, {"source": 785, "target": 377, "value": 10}, {"source": 785, "target": 435, "value": 4}, {"source": 785, "target": 437, "value": 9}, {"source": 785, "target": 473, "value": 8}, {"source": 785, "target": 485, "value": 2}, {"source": 785, "target": 497, "value": 8}, {"source": 785, "target": 525, "value": 9}, {"source": 785, "target": 531, "value": 8}, {"source": 785, "target": 573, "value": 8}, {"source": 785, "target": 585, "value": 2}, {"source": 785, "target": 587, "value": 9}, {"source": 785, "target": 635, "value": 4}, {"source": 785, "target": 647, "value": 8}, {"source": 785, "target": 725, "value": 8}, {"source": 785, "target": 735, "value": 3}, {"source": 785, "target": 773, "value": 7}, {"source": 789, "target": 228, "value": 8}, {"source": 789, "target": 243, "value": 12}, {"source": 789, "target": 249, "value": 8}, {"source": 789, "target": 251, "value": 8}, {"source": 789, "target": 278, "value": 8}, {"source": 789, "target": 293, "value": 12}, {"source": 789, "target": 303, "value": 10}, {"source": 789, "target": 335, "value": 8}, {"source": 789, "target": 339, "value": 4}, {"source": 789, "target": 341, "value": 10}, {"source": 789, "target": 351, "value": 8}, {"source": 789, "target": 353, "value": 10}, {"source": 789, "target": 377, "value": 8}, {"source": 789, "target": 378, "value": 8}, {"source": 789, "target": 393, "value": 11}, {"source": 789, "target": 399, "value": 8}, {"source": 789, "target": 401, "value": 8}, {"source": 789, "target": 413, "value": 8}, {"source": 789, "target": 428, "value": 8}, {"source": 789, "target": 429, "value": 10}, {"source": 789, "target": 437, "value": 8}, {"source": 789, "target": 443, "value": 10}, {"source": 789, "target": 453, "value": 9}, {"source": 789, "target": 461, "value": 10}, {"source": 789, "target": 489, "value": 2}, {"source": 789, "target": 501, "value": 8}, {"source": 789, "target": 503, "value": 9}, {"source": 789, "target": 528, "value": 8}, {"source": 789, "target": 543, "value": 10}, {"source": 789, "target": 545, "value": 8}, {"source": 789, "target": 549, "value": 8}, {"source": 789, "target": 551, "value": 8}, {"source": 789, "target": 578, "value": 8}, {"source": 789, "target": 593, "value": 9}, {"source": 789, "target": 603, "value": 8}, {"source": 789, "target": 639, "value": 4}, {"source": 789, "target": 651, "value": 8}, {"source": 789, "target": 653, "value": 8}, {"source": 789, "target": 677, "value": 8}, {"source": 789, "target": 678, "value": 7}, {"source": 789, "target": 693, "value": 9}, {"source": 789, "target": 699, "value": 8}, {"source": 789, "target": 701, "value": 7}, {"source": 789, "target": 728, "value": 7}, {"source": 789, "target": 737, "value": 7}, {"source": 789, "target": 741, "value": 2}, {"source": 789, "target": 743, "value": 8}, {"source": 789, "target": 753, "value": 8}, {"source": 791, "target": 291, "value": 4}, {"source": 791, "target": 293, "value": 10}, {"source": 791, "target": 341, "value": 4}, {"source": 791, "target": 365, "value": 8}, {"source": 791, "target": 381, "value": 10}, {"source": 791, "target": 429, "value": 8}, {"source": 791, "target": 441, "value": 4}, {"source": 791, "target": 491, "value": 4}, {"source": 791, "target": 533, "value": 9}, {"source": 791, "target": 581, "value": 9}, {"source": 791, "target": 587, "value": 8}, {"source": 791, "target": 591, "value": 4}, {"source": 791, "target": 641, "value": 4}, {"source": 791, "target": 713, "value": 9}, {"source": 791, "target": 729, "value": 7}, {"source": 791, "target": 741, "value": 3}, {"source": 791, "target": 765, "value": 8}, {"source": 795, "target": 233, "value": 4}, {"source": 795, "target": 245, "value": 4}, {"source": 795, "target": 248, "value": 8}, {"source": 795, "target": 285, "value": 10}, {"source": 795, "target": 333, "value": 9}, {"source": 795, "target": 345, "value": 4}, {"source": 795, "target": 348, "value": 8}, {"source": 795, "target": 395, "value": 4}, {"source": 795, "target": 398, "value": 8}, {"source": 795, "target": 485, "value": 9}, {"source": 795, "target": 495, "value": 4}, {"source": 795, "target": 498, "value": 8}, {"source": 795, "target": 533, "value": 8}, {"source": 795, "target": 545, "value": 4}, {"source": 795, "target": 548, "value": 8}, {"source": 795, "target": 609, "value": 3}, {"source": 795, "target": 633, "value": 8}, {"source": 795, "target": 645, "value": 4}, {"source": 795, "target": 648, "value": 7}, {"source": 795, "target": 669, "value": 8}, {"source": 795, "target": 695, "value": 4}, {"source": 795, "target": 698, "value": 7}, {"source": 795, "target": 741, "value": 7}, {"source": 795, "target": 783, "value": 8}, {"source": 797, "target": 221, "value": 9}, {"source": 797, "target": 225, "value": 8}, {"source": 797, "target": 245, "value": 4}, {"source": 797, "target": 251, "value": 8}, {"source": 797, "target": 257, "value": 8}, {"source": 797, "target": 263, "value": 12}, {"source": 797, "target": 273, "value": 3}, {"source": 797, "target": 275, "value": 8}, {"source": 797, "target": 285, "value": 9}, {"source": 797, "target": 293, "value": 8}, {"source": 797, "target": 309, "value": 8}, {"source": 797, "target": 323, "value": 4}, {"source": 797, "target": 341, "value": 10}, {"source": 797, "target": 345, "value": 4}, {"source": 797, "target": 347, "value": 4}, {"source": 797, "target": 351, "value": 4}, {"source": 797, "target": 363, "value": 11}, {"source": 797, "target": 375, "value": 8}, {"source": 797, "target": 377, "value": 9}, {"source": 797, "target": 395, "value": 8}, {"source": 797, "target": 401, "value": 8}, {"source": 797, "target": 405, "value": 8}, {"source": 797, "target": 407, "value": 8}, {"source": 797, "target": 413, "value": 10}, {"source": 797, "target": 423, "value": 4}, {"source": 797, "target": 425, "value": 8}, {"source": 797, "target": 437, "value": 10}, {"source": 797, "target": 459, "value": 8}, {"source": 797, "target": 473, "value": 2}, {"source": 797, "target": 485, "value": 8}, {"source": 797, "target": 489, "value": 8}, {"source": 797, "target": 495, "value": 8}, {"source": 797, "target": 497, "value": 2}, {"source": 797, "target": 501, "value": 8}, {"source": 797, "target": 503, "value": 8}, {"source": 797, "target": 513, "value": 10}, {"source": 797, "target": 525, "value": 8}, {"source": 797, "target": 545, "value": 2}, {"source": 797, "target": 549, "value": 9}, {"source": 797, "target": 551, "value": 8}, {"source": 797, "target": 557, "value": 8}, {"source": 797, "target": 563, "value": 9}, {"source": 797, "target": 573, "value": 2}, {"source": 797, "target": 575, "value": 8}, {"source": 797, "target": 585, "value": 4}, {"source": 797, "target": 609, "value": 8}, {"source": 797, "target": 621, "value": 8}, {"source": 797, "target": 623, "value": 4}, {"source": 797, "target": 645, "value": 4}, {"source": 797, "target": 647, "value": 4}, {"source": 797, "target": 651, "value": 8}, {"source": 797, "target": 663, "value": 9}, {"source": 797, "target": 669, "value": 7}, {"source": 797, "target": 675, "value": 8}, {"source": 797, "target": 695, "value": 4}, {"source": 797, "target": 699, "value": 8}, {"source": 797, "target": 701, "value": 8}, {"source": 797, "target": 707, "value": 8}, {"source": 797, "target": 713, "value": 8}, {"source": 797, "target": 723, "value": 4}, {"source": 797, "target": 725, "value": 8}, {"source": 797, "target": 737, "value": 3}, {"source": 797, "target": 741, "value": 8}, {"source": 797, "target": 755, "value": 7}, {"source": 797, "target": 759, "value": 8}, {"source": 797, "target": 773, "value": 2}, {"source": 797, "target": 785, "value": 7}, {"source": 797, "target": 789, "value": 7}, {"source": 797, "target": 795, "value": 7}, {"source": 798, "target": 219, "value": 8}, {"source": 798, "target": 233, "value": 5}, {"source": 798, "target": 245, "value": 8}, {"source": 798, "target": 248, "value": 4}, {"source": 798, "target": 257, "value": 8}, {"source": 798, "target": 273, "value": 11}, {"source": 798, "target": 285, "value": 11}, {"source": 798, "target": 309, "value": 10}, {"source": 798, "target": 323, "value": 11}, {"source": 798, "target": 333, "value": 9}, {"source": 798, "target": 345, "value": 8}, {"source": 798, "target": 348, "value": 4}, {"source": 798, "target": 369, "value": 8}, {"source": 798, "target": 395, "value": 8}, {"source": 798, "target": 398, "value": 4}, {"source": 798, "target": 423, "value": 10}, {"source": 798, "target": 473, "value": 10}, {"source": 798, "target": 485, "value": 10}, {"source": 798, "target": 495, "value": 8}, {"source": 798, "target": 498, "value": 4}, {"source": 798, "target": 519, "value": 8}, {"source": 798, "target": 533, "value": 8}, {"source": 798, "target": 545, "value": 8}, {"source": 798, "target": 548, "value": 4}, {"source": 798, "target": 557, "value": 8}, {"source": 798, "target": 573, "value": 9}, {"source": 798, "target": 611, "value": 9}, {"source": 798, "target": 615, "value": 8}, {"source": 798, "target": 623, "value": 9}, {"source": 798, "target": 633, "value": 8}, {"source": 798, "target": 645, "value": 8}, {"source": 798, "target": 648, "value": 4}, {"source": 798, "target": 669, "value": 8}, {"source": 798, "target": 695, "value": 8}, {"source": 798, "target": 698, "value": 4}, {"source": 798, "target": 723, "value": 8}, {"source": 798, "target": 741, "value": 7}, {"source": 798, "target": 773, "value": 8}, {"source": 798, "target": 783, "value": 8}, {"source": 798, "target": 795, "value": 8}, {"source": 801, "target": 251, "value": 4}, {"source": 801, "target": 263, "value": 8}, {"source": 801, "target": 341, "value": 10}, {"source": 801, "target": 351, "value": 4}, {"source": 801, "target": 363, "value": 8}, {"source": 801, "target": 401, "value": 2}, {"source": 801, "target": 413, "value": 8}, {"source": 801, "target": 453, "value": 9}, {"source": 801, "target": 455, "value": 10}, {"source": 801, "target": 489, "value": 8}, {"source": 801, "target": 501, "value": 2}, {"source": 801, "target": 503, "value": 9}, {"source": 801, "target": 513, "value": 8}, {"source": 801, "target": 525, "value": 8}, {"source": 801, "target": 551, "value": 4}, {"source": 801, "target": 563, "value": 8}, {"source": 801, "target": 573, "value": 10}, {"source": 801, "target": 651, "value": 2}, {"source": 801, "target": 653, "value": 8}, {"source": 801, "target": 663, "value": 8}, {"source": 801, "target": 701, "value": 2}, {"source": 801, "target": 713, "value": 2}, {"source": 801, "target": 741, "value": 8}, {"source": 801, "target": 783, "value": 3}, {"source": 801, "target": 789, "value": 7}, {"source": 801, "target": 797, "value": 8}, {"source": 803, "target": 227, "value": 8}, {"source": 803, "target": 228, "value": 8}, {"source": 803, "target": 249, "value": 8}, {"source": 803, "target": 278, "value": 8}, {"source": 803, "target": 279, "value": 10}, {"source": 803, "target": 293, "value": 10}, {"source": 803, "target": 303, "value": 2}, {"source": 803, "target": 341, "value": 9}, {"source": 803, "target": 353, "value": 1}, {"source": 803, "target": 377, "value": 8}, {"source": 803, "target": 378, "value": 8}, {"source": 803, "target": 399, "value": 8}, {"source": 803, "target": 405, "value": 10}, {"source": 803, "target": 428, "value": 8}, {"source": 803, "target": 437, "value": 8}, {"source": 803, "target": 441, "value": 8}, {"source": 803, "target": 453, "value": 1}, {"source": 803, "target": 503, "value": 2}, {"source": 803, "target": 527, "value": 8}, {"source": 803, "target": 528, "value": 8}, {"source": 803, "target": 545, "value": 8}, {"source": 803, "target": 549, "value": 8}, {"source": 803, "target": 578, "value": 8}, {"source": 803, "target": 603, "value": 2}, {"source": 803, "target": 641, "value": 8}, {"source": 803, "target": 653, "value": 1}, {"source": 803, "target": 677, "value": 8}, {"source": 803, "target": 678, "value": 8}, {"source": 803, "target": 693, "value": 8}, {"source": 803, "target": 699, "value": 4}, {"source": 803, "target": 701, "value": 8}, {"source": 803, "target": 728, "value": 7}, {"source": 803, "target": 737, "value": 8}, {"source": 803, "target": 741, "value": 3}, {"source": 803, "target": 753, "value": 1}, {"source": 803, "target": 755, "value": 8}, {"source": 803, "target": 789, "value": 7}]} \ No newline at end of file From 71fe9559b161807a44633417eb1725bb848b9639 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Thu, 4 Jul 2024 14:39:32 +0200 Subject: [PATCH 91/95] parallelize the CI --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5bbaded..ef484de2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,10 @@ jobs: tests: name: Run tests and generate coverage report runs-on: ubuntu-latest + strategy: + fail-fast: false # don't interrupt the other test processes + matrix: + pytest-mark: [slow, not slow] steps: - name: Check out repository uses: actions/checkout@v4 @@ -28,7 +32,7 @@ jobs: - uses: ./.github/actions/setup_xapian - uses: ./.github/actions/compile_messages - name: Run tests - run: poetry run coverage run -m pytest + run: poetry run coverage run -m pytest -m "${{ matrix.pytest-mark }}" - name: Generate coverage report run: | poetry run coverage report From e1cf1c786d12dbbd09b3ab595c3c36cf116e1aa5 Mon Sep 17 00:00:00 2001 From: Sli Date: Thu, 4 Jul 2024 19:44:22 +0200 Subject: [PATCH 92/95] Fix missing xapian install step in deploy workflows --- .github/workflows/deploy.yml | 1 + .github/workflows/taiste.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2baa9578..d478e690 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -38,6 +38,7 @@ jobs: git pull poetry install + poetry run ./manage.py install_xapian poetry run ./manage.py migrate echo "yes" | poetry run ./manage.py collectstatic poetry run ./manage.py compilestatic diff --git a/.github/workflows/taiste.yml b/.github/workflows/taiste.yml index 83cab1d6..d7e1e9d9 100644 --- a/.github/workflows/taiste.yml +++ b/.github/workflows/taiste.yml @@ -37,6 +37,7 @@ jobs: git pull poetry install + poetry run ./manage.py install_xapian poetry run ./manage.py migrate echo "yes" | poetry run ./manage.py collectstatic poetry run ./manage.py compilestatic From d811896e21680b544b0b903d2dee3b978593c97c Mon Sep 17 00:00:00 2001 From: thomas girod Date: Fri, 5 Jul 2024 13:14:58 +0200 Subject: [PATCH 93/95] update pillow --- poetry.lock | 158 +++++++++++++++++++++++++++---------------------- pyproject.toml | 2 +- 2 files changed, 89 insertions(+), 71 deletions(-) diff --git a/poetry.lock b/poetry.lock index 318a5623..9a4aee70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -948,82 +948,100 @@ files = [ [[package]] name = "pillow" -version = "9.5.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, - {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, - {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, - {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, - {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, - {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, - {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, - {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, - {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, - {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, - {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, - {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, - {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, - {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, - {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, - {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, - {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, - {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, - {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, - {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, - {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, - {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, - {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, - {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, - {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, - {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, - {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, - {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, - {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, - {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, - {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, - {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, - {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, - {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, - {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, - {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, - {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, - {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, + {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"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=7.3)", "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"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" @@ -1853,4 +1871,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "b090426042093af41cfbbced92a1a47bf0834ce88865dc7f51d0b7b04fda99a7" +content-hash = "322ddb65133404d3323c9c6da9b6c693b05c98ec5776bb1e7a84b300706cfa14" diff --git a/pyproject.toml b/pyproject.toml index 02d916ac..ccfb71d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ license = "GPL-3.0-only" [tool.poetry.dependencies] python = "^3.10,<3.12" # Version is held back by mistune Django = "^4.2.13" -Pillow = "^9.2" +Pillow = "^10.4.0" mistune = "^0.8.4" django-jinja = "^2.10" cryptography = "^40.0" From 70fdc2edf2c5eb29f21dcf9a6dd90839b5361c69 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Wed, 26 Jun 2024 15:29:05 +0200 Subject: [PATCH 94/95] update cryptography --- eboutic/tests.py | 11 +++-- eboutic/tests/test.py | 31 +++++++++------ eboutic/views.py | 24 +++++------ poetry.lock | 93 ++++++++++++++++++++----------------------- pyproject.toml | 5 +-- 5 files changed, 83 insertions(+), 81 deletions(-) diff --git a/eboutic/tests.py b/eboutic/tests.py index b1ea9873..d99c8536 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -26,11 +26,14 @@ import base64 import json import urllib +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.hashes import SHA1 +from cryptography.hazmat.primitives.serialization import load_pem_private_key from django.conf import settings from django.db.models import Max from django.test import TestCase from django.urls import reverse -from OpenSSL import crypto from core.models import User from counter.models import Counter, Customer, Product, Selling @@ -67,12 +70,12 @@ class EbouticTest(TestCase): basket_id = basket.id amount = int(basket.get_total() * 100) query = f"Amount={amount}&BasketID={basket_id}&Auto=42&Error=00000" - with open("./eboutic/tests/private_key.pem") as f: + with open("./eboutic/tests/private_key.pem", "br") as f: PRIVKEY = f.read() with open("./eboutic/tests/public_key.pem") as f: settings.SITH_EBOUTIC_PUB_KEY = f.read() - privkey = crypto.load_privatekey(crypto.FILETYPE_PEM, PRIVKEY) - sig = crypto.sign(privkey, query.encode("utf-8"), "sha1") + key: RSAPrivateKey = load_pem_private_key(PRIVKEY, None) + sig = key.sign(query.encode("utf-8"), PKCS1v15(), SHA1()) b64sig = base64.b64encode(sig).decode("ascii") url = reverse("eboutic:etransation_autoanswer") + "?%s&Sig=%s" % ( diff --git a/eboutic/tests/test.py b/eboutic/tests/test.py index 359db7ff..5527f116 100755 --- a/eboutic/tests/test.py +++ b/eboutic/tests/test.py @@ -8,28 +8,33 @@ import base64 -from OpenSSL import crypto +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.hashes import SHA1 +from cryptography.hazmat.primitives.serialization import ( + load_pem_private_key, + load_pem_public_key, +) -with open("./private_key.pem") as f: - PRVKEY = f.read() -with open("./public_key.pem") as f: +with open("./private_key.pem", "br") as f: + PRIVKEY = f.read() +with open("./public_key.pem", "br") as f: PUBKEY = f.read() data = "Amount=400&BasketID=4000&Auto=42&Error=00000\n".encode("utf-8") # Sign -prvkey = crypto.load_privatekey(crypto.FILETYPE_PEM, PRVKEY) -sig = crypto.sign(prvkey, data, "sha1") -b64sig = base64.b64encode(sig) +privkey: RSAPrivateKey = load_pem_private_key(PRIVKEY, None) +signature = privkey.sign(data, PKCS1v15(), SHA1()) +b64sig = base64.b64encode(signature) print(b64sig) # Verify -pubkey = crypto.load_publickey(crypto.FILETYPE_PEM, PUBKEY) -cert = crypto.X509() -cert.set_pubkey(pubkey) -sig = base64.b64decode(b64sig) +pubkey = load_pem_public_key(PUBKEY) +signature = base64.b64decode(b64sig) try: - crypto.verify(cert, sig, data, "sha1") + pubkey.verify(signature, data, PKCS1v15(), SHA1()) print("Verify OK") -except: +except InvalidSignature as e: print("Verify failed") diff --git a/eboutic/views.py b/eboutic/views.py index c240d64d..8c39bf64 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -20,6 +20,11 @@ from datetime import datetime from urllib.parse import unquote import sentry_sdk +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.hashes import SHA1 +from cryptography.hazmat.primitives.serialization import load_pem_public_key from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.exceptions import SuspiciousOperation @@ -29,7 +34,6 @@ from django.shortcuts import redirect, render from django.utils.decorators import method_decorator from django.views.decorators.http import require_GET, require_POST from django.views.generic import TemplateView, View -from OpenSSL import crypto from counter.forms import BillingInfoForm from counter.models import Counter, Customer, Product @@ -180,18 +184,14 @@ class EtransactionAutoAnswer(View): required = {"Amount", "BasketID", "Error", "Sig"} if not required.issubset(set(request.GET.keys())): return HttpResponse("Bad arguments", status=400) - key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) - cert = crypto.X509() - cert.set_pubkey(key) - sig = base64.b64decode(request.GET["Sig"]) + pubkey: RSAPublicKey = load_pem_public_key( + settings.SITH_EBOUTIC_PUB_KEY.encode("utf-8") + ) + signature = base64.b64decode(request.GET["Sig"]) try: - crypto.verify( - cert, - sig, - "&".join(request.META["QUERY_STRING"].split("&")[:-1]).encode("utf-8"), - "sha1", - ) - except: + data = "&".join(request.META["QUERY_STRING"].split("&")[:-1]) + pubkey.verify(signature, data.encode("utf-8"), PKCS1v15(), SHA1()) + except InvalidSignature: return HttpResponse("Bad signature", status=400) # Payment authorized: # * 'Error' is '00000' diff --git a/poetry.lock b/poetry.lock index 9a4aee70..32fcaf72 100644 --- a/poetry.lock +++ b/poetry.lock @@ -62,13 +62,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "certifi" -version = "2024.6.2" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, - {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] @@ -336,44 +336,57 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "40.0.2" +version = "42.0.8" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b"}, - {file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2"}, - {file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b"}, - {file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"}, - {file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c"}, - {file = "cryptography-40.0.2-cp36-abi3-win32.whl", hash = "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9"}, - {file = "cryptography-40.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a"}, - {file = "cryptography-40.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e"}, - {file = "cryptography-40.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404"}, - {file = "cryptography-40.0.2.tar.gz", hash = "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, + {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, + {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, + {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, + {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, + {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, + {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, + {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, + {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, + {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, + {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, + {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, + {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, + {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, ] [package.dependencies] -cffi = ">=1.12" +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)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -pep8test = ["black", "check-manifest", "mypy", "ruff"] -sdist = ["setuptools-rust (>=0.11.4)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] -tox = ["tox"] [[package]] name = "decorator" @@ -1247,24 +1260,6 @@ files = [ {file = "pygraphviz-1.13.tar.gz", hash = "sha256:6ad8aa2f26768830a5a1cfc8a14f022d13df170a8f6fdfd68fd1aa1267000964"}, ] -[[package]] -name = "pyopenssl" -version = "23.2.0" -description = "Python wrapper module around the OpenSSL library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, - {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, -] - -[package.dependencies] -cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" - -[package.extras] -docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] -test = ["flaky", "pretend", "pytest (>=3.0.1)"] - [[package]] name = "pytest" version = "8.2.2" @@ -1871,4 +1866,4 @@ filelock = ">=3.4" [metadata] lock-version = "2.0" python-versions = "^3.10,<3.12" -content-hash = "322ddb65133404d3323c9c6da9b6c693b05c98ec5776bb1e7a84b300706cfa14" +content-hash = "c33378496709848054a8e4ecd1ebf74df12f15a1bb66ab61d2958e6a3c40f812" diff --git a/pyproject.toml b/pyproject.toml index ccfb71d1..2808d848 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,9 +24,8 @@ python = "^3.10,<3.12" # Version is held back by mistune Django = "^4.2.13" Pillow = "^10.4.0" mistune = "^0.8.4" -django-jinja = "^2.10" -cryptography = "^40.0" -pyOpenSSL = "^23.1.1" +django-jinja = "^2.11" +cryptography = "^42.0.8" pytz = "^2021.1" djangorestframework = "^3.13" django-phonenumber-field = "^6.3" From 09e0b31bc9c4c62c32aba18d833ebe33b77bd8b4 Mon Sep 17 00:00:00 2001 From: thomas girod Date: Mon, 8 Jul 2024 10:03:27 +0200 Subject: [PATCH 95/95] =?UTF-8?q?remove=20Eurock=C3=A9ennes=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eboutic/templates/eboutic/eboutic_main.jinja | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 002f0709..9a8470d5 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -90,24 +90,6 @@ {% endif %} -
    -
    -

    Partenariat Eurockéennes 2024

    -
    - {% if user.is_subscribed %} - Billetterie Weezevent - - {% else %} -
    Vous devez être cotisant pour accéder à la billeterie des Eurockéennes
    - {% endif %} -
    -
    - {% for priority_groups in products|groupby('priority')|reverse %} {% for category, items in priority_groups.list|groupby('category') %} {% if items|count > 0 %}
    UtilisateurRôleDescription
    Cotis 2 semestres128.00 €
    Barbar31.70 €