Merge pull request #842 from ae-utbm/3dsv2-again

Add the new 3DSv2 fields
This commit is contained in:
thomas girod 2024-09-28 17:59:37 +02:00 committed by GitHub
commit 8ec3074488
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 351 additions and 146 deletions

View File

@ -306,6 +306,12 @@ a:not(.button) {
align-items: center; align-items: center;
text-align: justify; text-align: justify;
&.alert-yellow {
background-color: rgb(255, 255, 240);
color: rgb(99, 87, 6);
border: rgb(192, 180, 16) 1px solid;
}
&.alert-green { &.alert-green {
background-color: rgb(245, 255, 245); background-color: rgb(245, 255, 245);
color: rgb(3, 84, 63); color: rgb(3, 84, 63);

View File

@ -46,6 +46,7 @@ class CustomerAdmin(SearchModelAdmin):
@admin.register(BillingInfo) @admin.register(BillingInfo)
class BillingInfoAdmin(admin.ModelAdmin): class BillingInfoAdmin(admin.ModelAdmin):
list_display = ("first_name", "last_name", "address_1", "city", "country") list_display = ("first_name", "last_name", "address_1", "city", "country")
autocomplete_fields = ("customer",)
@admin.register(Counter) @admin.register(Counter)

View File

@ -2,6 +2,7 @@ from ajax_select import make_ajax_field
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.widgets import RegionalPhoneNumberWidget
from core.views.forms import NFCTextInput, SelectDate, SelectDateTime from core.views.forms import NFCTextInput, SelectDate, SelectDateTime
from counter.models import ( from counter.models import (
@ -26,7 +27,11 @@ class BillingInfoForm(forms.ModelForm):
"zip_code", "zip_code",
"city", "city",
"country", "country",
"phone_number",
] ]
widgets = {
"phone_number": RegionalPhoneNumberWidget,
}
class StudentCardForm(forms.ModelForm): class StudentCardForm(forms.ModelForm):

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2024-09-26 10:28
import phonenumber_field.modelfields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("counter", "0022_alter_product_icon"),
]
operations = [
migrations.AddField(
model_name="billinginfo",
name="phone_number",
field=phonenumber_field.modelfields.PhoneNumberField(
max_length=128, null=True, region=None, verbose_name="Phone number"
),
),
]

View File

@ -34,6 +34,7 @@ from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_countries.fields import CountryField from django_countries.fields import CountryField
from phonenumber_field.modelfields import PhoneNumberField
from accounting.models import CurrencyField from accounting.models import CurrencyField
from club.models import Club from club.models import Club
@ -176,6 +177,14 @@ class BillingInfo(models.Model):
city = models.CharField(_("City"), max_length=50) city = models.CharField(_("City"), max_length=50)
country = CountryField(blank_label=_("Country")) country = CountryField(blank_label=_("Country"))
# This table was created during the A22 semester.
# However, later on, CA asked for the phone number to be added to the billing info.
# As the table was already created, this new field had to be nullable,
# even tough it is required by the bank and shouldn't be null.
# If one day there is no null phone number remaining,
# please make the field non-nullable.
phone_number = PhoneNumberField(_("Phone number"), null=True, blank=False)
def __str__(self): def __str__(self):
return f"{self.first_name} {self.last_name}" return f"{self.first_name} {self.last_name}"
@ -192,6 +201,8 @@ class BillingInfo(models.Model):
"ZipCode": self.zip_code, "ZipCode": self.zip_code,
"City": self.city, "City": self.city,
"CountryCode": self.country.numeric, # ISO-3166-1 numeric code "CountryCode": self.country.numeric, # ISO-3166-1 numeric code
"MobilePhone": self.phone_number.as_national.replace(" ", ""),
"CountryCodeMobilePhone": f"+{self.phone_number.country_code}",
} }
} }
if self.address_2: if self.address_2:

View File

@ -315,6 +315,7 @@ class TestBillingInfo:
"zip_code": "34301", "zip_code": "34301",
"city": "Sète", "city": "Sète",
"country": "FR", "country": "FR",
"phone_number": "0612345678",
} }
def test_edit_infos(self, client: Client, payload: dict): def test_edit_infos(self, client: Client, payload: dict):
@ -356,7 +357,7 @@ class TestBillingInfo:
for key, val in payload.items(): for key, val in payload.items():
assert getattr(infos, key) == val assert getattr(infos, key) == val
def test_invalid_data(self, client: Client, payload): def test_invalid_data(self, client: Client, payload: dict[str, str]):
user = subscriber_user.make() user = subscriber_user.make()
client.force_login(user) client.force_login(user)
# address_1, zip_code and country are missing # address_1, zip_code and country are missing
@ -391,6 +392,60 @@ class TestBillingInfo:
) )
assert response.status_code == expected_code assert response.status_code == expected_code
@pytest.mark.parametrize(
"phone_number",
["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"],
)
def test_phone_number_format(
self, client: Client, payload: dict, phone_number: str
):
"""Test that various formats of phone numbers are accepted."""
user = subscriber_user.make()
client.force_login(user)
payload["phone_number"] = phone_number
response = client.put(
reverse("api:put_billing_info", args=[user.id]),
json.dumps(payload),
content_type="application/json",
)
assert response.status_code == 200
infos = BillingInfo.objects.get(customer__user=user)
assert infos.phone_number == "0612345678"
assert infos.phone_number.country_code == 33
def test_foreign_phone_number(self, client: Client, payload: dict):
"""Test that a foreign phone number is accepted."""
user = subscriber_user.make()
client.force_login(user)
payload["phone_number"] = "+49612345678"
response = client.put(
reverse("api:put_billing_info", args=[user.id]),
json.dumps(payload),
content_type="application/json",
)
assert response.status_code == 200
infos = BillingInfo.objects.get(customer__user=user)
assert infos.phone_number.as_national == "06123 45678"
assert infos.phone_number.country_code == 49
@pytest.mark.parametrize(
"phone_number", ["061234567a", "06 12 34 56", "061234567879", "azertyuiop"]
)
def test_invalid_phone_number(
self, client: Client, payload: dict, phone_number: str
):
"""Test that invalid phone numbers are rejected."""
user = subscriber_user.make()
client.force_login(user)
payload["phone_number"] = phone_number
response = client.put(
reverse("api:put_billing_info", args=[user.id]),
json.dumps(payload),
content_type="application/json",
)
assert response.status_code == 422
assert not BillingInfo.objects.filter(customer__user=user).exists()
class TestBarmanConnection(TestCase): class TestBarmanConnection(TestCase):
@classmethod @classmethod

View File

@ -1,6 +1,11 @@
from typing import Annotated
from ninja import ModelSchema, Schema from ninja import ModelSchema, Schema
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
# from phonenumber_field.phonenumber import PhoneNumber
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
from counter.models import BillingInfo from counter.models import BillingInfo
@ -31,3 +36,8 @@ class BillingInfoSchema(ModelSchema):
"country", "country",
] ]
fields_optional = ["customer"] fields_optional = ["customer"]
# for reasons described in the model, BillingInfo.phone_number
# in nullable, but null values shouldn't be actually allowed,
# so we force the field to be required
phone_number: Annotated[PhoneNumber, PhoneNumberValidator(default_region="FR")]

View File

@ -42,7 +42,16 @@ document.addEventListener("alpine:init", () => {
this.req_state = res.ok this.req_state = res.ok
? BillingInfoReqState.SUCCESS ? BillingInfoReqState.SUCCESS
: BillingInfoReqState.FAILURE; : BillingInfoReqState.FAILURE;
if (res.ok) { if (res.status === 422) {
const errors = (await res.json())["detail"].map((err) => err["loc"]).flat();
Array.from(form.querySelectorAll("input"))
.filter((elem) => errors.includes(elem.name))
.forEach((elem) => {
elem.setCustomValidity(gettext("Incorrect value"));
elem.reportValidity();
elem.oninput = () => elem.setCustomValidity("");
});
} else if (res.ok) {
Alpine.store("billing_inputs").fill(); Alpine.store("billing_inputs").fill();
} }
}, },

View File

@ -98,13 +98,20 @@
</form> </form>
</div> </div>
<br> <br>
{% if must_fill_billing_infos %} {% if billing_infos_state == BillingInfoState.EMPTY %}
<p> <div class="alert alert-yellow">
<i>
{% trans %}You must fill your billing infos if you want to pay with your credit {% trans %}You must fill your billing infos if you want to pay with your credit
card{% endtrans %} card{% endtrans %}
</i> </div>
</p> {% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %}
<div class="alert alert-yellow">
{% trans %}
The Crédit Agricole changed its policy related to the billing
information that must be provided in order to pay with a credit card.
If you want to pay with your credit card, you must add a phone number
to the data you already provided.
{% endtrans %}
</div>
{% endif %} {% endif %}
<form method="post" action="{{ settings.SITH_EBOUTIC_ET_URL }}" name="bank-pay-form"> <form method="post" action="{{ settings.SITH_EBOUTIC_ET_URL }}" name="bank-pay-form">
<template x-data x-for="[key, value] in Object.entries($store.billing_inputs.data)"> <template x-data x-for="[key, value] in Object.entries($store.billing_inputs.data)">
@ -113,7 +120,7 @@
<input <input
type="submit" type="submit"
id="bank-submit-button" id="bank-submit-button"
{% if must_fill_billing_infos %}disabled="disabled"{% endif %} {% if billing_infos_state != BillingInfoState.VALID %}disabled="disabled"{% endif %}
value="{% trans %}Pay with credit card{% endtrans %}" value="{% trans %}Pay with credit card{% endtrans %}"
/> />
</form> </form>

View File

@ -16,6 +16,7 @@
import base64 import base64
import json import json
from datetime import datetime from datetime import datetime
from enum import Enum
import sentry_sdk import sentry_sdk
from cryptography.exceptions import InvalidSignature from cryptography.exceptions import InvalidSignature
@ -82,6 +83,12 @@ def payment_result(request, result: str) -> HttpResponse:
return render(request, "eboutic/eboutic_payment_result.jinja", context) return render(request, "eboutic/eboutic_payment_result.jinja", context)
class BillingInfoState(Enum):
VALID = 1
EMPTY = 2
MISSING_PHONE_NUMBER = 3
class EbouticCommand(LoginRequiredMixin, TemplateView): class EbouticCommand(LoginRequiredMixin, TemplateView):
template_name = "eboutic/eboutic_makecommand.jinja" template_name = "eboutic/eboutic_makecommand.jinja"
basket: Basket basket: Basket
@ -130,9 +137,16 @@ class EbouticCommand(LoginRequiredMixin, TemplateView):
default_billing_info = customer.billing_infos default_billing_info = customer.billing_infos
else: else:
kwargs["customer_amount"] = None kwargs["customer_amount"] = None
kwargs["must_fill_billing_infos"] = default_billing_info is None # make the enum available in the template
if not kwargs["must_fill_billing_infos"]: kwargs["BillingInfoState"] = BillingInfoState
# the user has already filled its billing_infos, thus we can if default_billing_info is None:
kwargs["billing_infos_state"] = BillingInfoState.EMPTY
elif default_billing_info.phone_number is None:
kwargs["billing_infos_state"] = BillingInfoState.MISSING_PHONE_NUMBER
else:
kwargs["billing_infos_state"] = BillingInfoState.VALID
if kwargs["billing_infos_state"] == BillingInfoState.VALID:
# the user has already filled all of its billing_infos, thus we can
# get it without expecting an error # get it without expecting an error
kwargs["billing_infos"] = dict(self.basket.get_e_transaction_data()) kwargs["billing_infos"] = dict(self.basket.get_e_transaction_data())
kwargs["basket"] = self.basket kwargs["basket"] = self.basket

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-17 12:21+0200\n" "POT-Creation-Date: 2024-09-26 17:51+0200\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia <skia@libskia.so>\n" "Last-Translator: Skia <skia@libskia.so>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -18,8 +18,8 @@ msgstr ""
#: accounting/models.py:62 accounting/models.py:103 accounting/models.py:136 #: accounting/models.py:62 accounting/models.py:103 accounting/models.py:136
#: accounting/models.py:203 club/models.py:54 com/models.py:274 #: accounting/models.py:203 club/models.py:54 com/models.py:274
#: com/models.py:293 counter/models.py:209 counter/models.py:242 #: com/models.py:293 counter/models.py:213 counter/models.py:246
#: counter/models.py:377 forum/models.py:59 launderette/models.py:29 #: counter/models.py:381 forum/models.py:59 launderette/models.py:29
#: launderette/models.py:84 launderette/models.py:122 stock/models.py:36 #: launderette/models.py:84 launderette/models.py:122 stock/models.py:36
#: stock/models.py:57 stock/models.py:97 stock/models.py:125 #: stock/models.py:57 stock/models.py:97 stock/models.py:125
msgid "name" msgid "name"
@ -66,8 +66,8 @@ msgid "account number"
msgstr "numéro de compte" msgstr "numéro de compte"
#: accounting/models.py:109 accounting/models.py:140 club/models.py:344 #: accounting/models.py:109 accounting/models.py:140 club/models.py:344
#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:265 #: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:269
#: counter/models.py:379 trombi/models.py:210 #: counter/models.py:383 trombi/models.py:210
msgid "club" msgid "club"
msgstr "club" msgstr "club"
@ -88,12 +88,12 @@ msgstr "Compte club"
msgid "%(club_account)s on %(bank_account)s" msgid "%(club_account)s on %(bank_account)s"
msgstr "%(club_account)s sur %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s"
#: accounting/models.py:201 club/models.py:350 counter/models.py:860 #: accounting/models.py:201 club/models.py:350 counter/models.py:864
#: election/models.py:16 launderette/models.py:179 #: election/models.py:16 launderette/models.py:179
msgid "start date" msgid "start date"
msgstr "date de début" msgstr "date de début"
#: accounting/models.py:202 club/models.py:351 counter/models.py:861 #: accounting/models.py:202 club/models.py:351 counter/models.py:865
#: election/models.py:17 #: election/models.py:17
msgid "end date" msgid "end date"
msgstr "date de fin" msgstr "date de fin"
@ -106,8 +106,8 @@ msgstr "est fermé"
msgid "club account" msgid "club account"
msgstr "compte club" msgstr "compte club"
#: accounting/models.py:212 accounting/models.py:272 counter/models.py:56 #: accounting/models.py:212 accounting/models.py:272 counter/models.py:57
#: counter/models.py:583 #: counter/models.py:587
msgid "amount" msgid "amount"
msgstr "montant" msgstr "montant"
@ -127,20 +127,20 @@ msgstr "numéro"
msgid "journal" msgid "journal"
msgstr "classeur" msgstr "classeur"
#: accounting/models.py:273 core/models.py:940 core/models.py:1442 #: accounting/models.py:273 core/models.py:940 core/models.py:1460
#: core/models.py:1487 core/models.py:1516 core/models.py:1540 #: core/models.py:1505 core/models.py:1534 core/models.py:1558
#: counter/models.py:593 counter/models.py:686 counter/models.py:896 #: counter/models.py:597 counter/models.py:690 counter/models.py:900
#: eboutic/models.py:57 eboutic/models.py:173 forum/models.py:311 #: eboutic/models.py:57 eboutic/models.py:173 forum/models.py:311
#: forum/models.py:412 stock/models.py:96 #: forum/models.py:412 stock/models.py:96
msgid "date" msgid "date"
msgstr "date" msgstr "date"
#: accounting/models.py:274 counter/models.py:211 counter/models.py:897 #: accounting/models.py:274 counter/models.py:215 counter/models.py:901
#: pedagogy/models.py:207 stock/models.py:99 #: pedagogy/models.py:207 stock/models.py:99
msgid "comment" msgid "comment"
msgstr "commentaire" msgstr "commentaire"
#: accounting/models.py:276 counter/models.py:595 counter/models.py:688 #: accounting/models.py:276 counter/models.py:599 counter/models.py:692
#: subscription/models.py:56 #: subscription/models.py:56
msgid "payment method" msgid "payment method"
msgstr "méthode de paiement" msgstr "méthode de paiement"
@ -166,8 +166,8 @@ msgid "accounting type"
msgstr "type comptable" msgstr "type comptable"
#: accounting/models.py:311 accounting/models.py:450 accounting/models.py:483 #: accounting/models.py:311 accounting/models.py:450 accounting/models.py:483
#: accounting/models.py:515 core/models.py:1515 core/models.py:1541 #: accounting/models.py:515 core/models.py:1533 core/models.py:1559
#: counter/models.py:652 #: counter/models.py:656
msgid "label" msgid "label"
msgstr "étiquette" msgstr "étiquette"
@ -266,7 +266,7 @@ msgstr ""
"Vous devez fournir soit un type comptable simplifié ou un type comptable " "Vous devez fournir soit un type comptable simplifié ou un type comptable "
"standard" "standard"
#: accounting/models.py:442 counter/models.py:252 pedagogy/models.py:41 #: accounting/models.py:442 counter/models.py:256 pedagogy/models.py:41
msgid "code" msgid "code"
msgstr "code" msgstr "code"
@ -370,7 +370,7 @@ msgstr "Compte en banque : "
#: core/templates/core/user_account_detail.jinja:66 #: core/templates/core/user_account_detail.jinja:66
#: core/templates/core/user_clubs.jinja:34 #: core/templates/core/user_clubs.jinja:34
#: core/templates/core/user_clubs.jinja:63 #: core/templates/core/user_clubs.jinja:63
#: core/templates/core/user_edit.jinja:57 #: core/templates/core/user_edit.jinja:62
#: core/templates/core/user_preferences.jinja:48 #: core/templates/core/user_preferences.jinja:48
#: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:35
#: counter/templates/counter/last_ops.jinja:65 #: counter/templates/counter/last_ops.jinja:65
@ -968,11 +968,11 @@ msgstr "Une action est requise"
msgid "You must specify at least an user or an email address" 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" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email"
#: club/forms.py:153 counter/forms.py:186 #: club/forms.py:153 counter/forms.py:187
msgid "Begin date" msgid "Begin date"
msgstr "Date de début" msgstr "Date de début"
#: club/forms.py:156 com/views.py:82 com/views.py:201 counter/forms.py:189 #: club/forms.py:156 com/views.py:82 com/views.py:201 counter/forms.py:190
#: election/views.py:167 subscription/views.py:38 #: election/views.py:167 subscription/views.py:38
msgid "End date" msgid "End date"
msgstr "Date de fin" msgstr "Date de fin"
@ -1058,7 +1058,7 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs"
msgid "A club with that unix_name already exists" msgid "A club with that unix_name already exists"
msgstr "Un club avec ce nom UNIX existe déjà." msgstr "Un club avec ce nom UNIX existe déjà."
#: club/models.py:336 counter/models.py:851 counter/models.py:887 #: club/models.py:336 counter/models.py:855 counter/models.py:891
#: eboutic/models.py:53 eboutic/models.py:169 election/models.py:183 #: eboutic/models.py:53 eboutic/models.py:169 election/models.py:183
#: launderette/models.py:136 launderette/models.py:198 sas/models.py:270 #: launderette/models.py:136 launderette/models.py:198 sas/models.py:270
#: trombi/models.py:206 #: trombi/models.py:206
@ -1070,8 +1070,8 @@ msgstr "nom d'utilisateur"
msgid "role" msgid "role"
msgstr "rôle" msgstr "rôle"
#: club/models.py:358 core/models.py:89 counter/models.py:210 #: club/models.py:358 core/models.py:89 counter/models.py:214
#: counter/models.py:243 election/models.py:13 election/models.py:115 #: counter/models.py:247 election/models.py:13 election/models.py:115
#: election/models.py:188 forum/models.py:60 forum/models.py:244 #: election/models.py:188 forum/models.py:60 forum/models.py:244
msgid "description" msgid "description"
msgstr "description" msgstr "description"
@ -1440,7 +1440,7 @@ msgstr "résumé"
msgid "content" msgid "content"
msgstr "contenu" msgstr "contenu"
#: com/models.py:71 core/models.py:1485 launderette/models.py:92 #: com/models.py:71 core/models.py:1503 launderette/models.py:92
#: launderette/models.py:130 launderette/models.py:181 stock/models.py:74 #: launderette/models.py:130 launderette/models.py:181 stock/models.py:74
#: stock/models.py:129 #: stock/models.py:129
msgid "type" msgid "type"
@ -2217,7 +2217,7 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
#: core/templates/core/user_detail.jinja:110 #: core/templates/core/user_detail.jinja:110
#: core/templates/core/user_detail.jinja:112 #: core/templates/core/user_detail.jinja:112
#: core/templates/core/user_detail.jinja:113 #: core/templates/core/user_detail.jinja:113
#: core/templates/core/user_edit.jinja:16 #: core/templates/core/user_edit.jinja:21
#: election/templates/election/election_detail.jinja:132 #: election/templates/election/election_detail.jinja:132
#: election/templates/election/election_detail.jinja:134 #: election/templates/election/election_detail.jinja:134
#: forum/templates/forum/macros.jinja:104 #: forum/templates/forum/macros.jinja:104
@ -2250,7 +2250,7 @@ msgstr "avoir une notification pour chaque rechargement"
msgid "file name" msgid "file name"
msgstr "nom du fichier" msgstr "nom du fichier"
#: core/models.py:899 core/models.py:1234 #: core/models.py:899 core/models.py:1252
msgid "parent" msgid "parent"
msgstr "parent" msgstr "parent"
@ -2266,11 +2266,11 @@ msgstr "miniature"
msgid "owner" msgid "owner"
msgstr "propriétaire" msgstr "propriétaire"
#: core/models.py:932 core/models.py:1251 core/views/files.py:223 #: core/models.py:932 core/models.py:1269 core/views/files.py:223
msgid "edit group" msgid "edit group"
msgstr "groupe d'édition" msgstr "groupe d'édition"
#: core/models.py:935 core/models.py:1254 core/views/files.py:226 #: core/models.py:935 core/models.py:1272 core/views/files.py:226
msgid "view group" msgid "view group"
msgstr "groupe de vue" msgstr "groupe de vue"
@ -2316,11 +2316,11 @@ msgstr "Un fichier de ce nom existe déjà"
msgid "You must provide a file" msgid "You must provide a file"
msgstr "Vous devez fournir un fichier" msgstr "Vous devez fournir un fichier"
#: core/models.py:1217 #: core/models.py:1235
msgid "page unix name" msgid "page unix name"
msgstr "nom unix de la page" msgstr "nom unix de la page"
#: core/models.py:1223 #: core/models.py:1241
msgid "" msgid ""
"Enter a valid page name. This value may contain only unaccented letters, " "Enter a valid page name. This value may contain only unaccented letters, "
"numbers and ./+/-/_ characters." "numbers and ./+/-/_ characters."
@ -2328,55 +2328,55 @@ msgstr ""
"Entrez un nom de page correct. Uniquement des lettres non accentuées, " "Entrez un nom de page correct. Uniquement des lettres non accentuées, "
"numéros, et ./+/-/_" "numéros, et ./+/-/_"
#: core/models.py:1241 #: core/models.py:1259
msgid "page name" msgid "page name"
msgstr "nom de la page" msgstr "nom de la page"
#: core/models.py:1246 #: core/models.py:1264
msgid "owner group" msgid "owner group"
msgstr "groupe propriétaire" msgstr "groupe propriétaire"
#: core/models.py:1259 #: core/models.py:1277
msgid "lock user" msgid "lock user"
msgstr "utilisateur bloquant" msgstr "utilisateur bloquant"
#: core/models.py:1266 #: core/models.py:1284
msgid "lock_timeout" msgid "lock_timeout"
msgstr "décompte du déblocage" msgstr "décompte du déblocage"
#: core/models.py:1316 #: core/models.py:1334
msgid "Duplicate page" msgid "Duplicate page"
msgstr "Une page de ce nom existe déjà" msgstr "Une page de ce nom existe déjà"
#: core/models.py:1319 #: core/models.py:1337
msgid "Loop in page tree" msgid "Loop in page tree"
msgstr "Boucle dans l'arborescence des pages" msgstr "Boucle dans l'arborescence des pages"
#: core/models.py:1439 #: core/models.py:1457
msgid "revision" msgid "revision"
msgstr "révision" msgstr "révision"
#: core/models.py:1440 #: core/models.py:1458
msgid "page title" msgid "page title"
msgstr "titre de la page" msgstr "titre de la page"
#: core/models.py:1441 #: core/models.py:1459
msgid "page content" msgid "page content"
msgstr "contenu de la page" msgstr "contenu de la page"
#: core/models.py:1482 #: core/models.py:1500
msgid "url" msgid "url"
msgstr "url" msgstr "url"
#: core/models.py:1483 #: core/models.py:1501
msgid "param" msgid "param"
msgstr "param" msgstr "param"
#: core/models.py:1488 #: core/models.py:1506
msgid "viewed" msgid "viewed"
msgstr "vue" msgstr "vue"
#: core/models.py:1546 #: core/models.py:1564
msgid "operation type" msgid "operation type"
msgstr "type d'opération" msgstr "type d'opération"
@ -2474,7 +2474,7 @@ msgstr "Forum"
msgid "Gallery" msgid "Gallery"
msgstr "Photos" msgstr "Photos"
#: core/templates/core/base.jinja:225 counter/models.py:387 #: core/templates/core/base.jinja:225 counter/models.py:391
#: counter/templates/counter/counter_list.jinja:11 #: counter/templates/counter/counter_list.jinja:11
#: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:4
#: eboutic/templates/eboutic/eboutic_main.jinja:22 #: eboutic/templates/eboutic/eboutic_main.jinja:22
@ -2693,7 +2693,7 @@ msgid "Edit group"
msgstr "Éditer le groupe" msgstr "Éditer le groupe"
#: core/templates/core/group_edit.jinja:9 #: core/templates/core/group_edit.jinja:9
#: core/templates/core/user_edit.jinja:268 #: core/templates/core/user_edit.jinja:170
#: core/templates/core/user_group.jinja:13 #: core/templates/core/user_group.jinja:13
#: pedagogy/templates/pedagogy/uv_edit.jinja:36 #: pedagogy/templates/pedagogy/uv_edit.jinja:36
msgid "Update" msgid "Update"
@ -3184,39 +3184,35 @@ msgstr "Aucun cadeau donné pour l'instant"
msgid "Edit user" msgid "Edit user"
msgstr "Éditer l'utilisateur" msgstr "Éditer l'utilisateur"
#: core/templates/core/user_edit.jinja:36 #: core/templates/core/user_edit.jinja:41
msgid "Enable camera" msgid "Enable camera"
msgstr "Activer la caméra" msgstr "Activer la caméra"
#: core/templates/core/user_edit.jinja:44 #: core/templates/core/user_edit.jinja:49
msgid "Take a picture" msgid "Take a picture"
msgstr "Prendre une photo" msgstr "Prendre une photo"
#: core/templates/core/user_edit.jinja:64 #: core/templates/core/user_edit.jinja:69
msgid "To edit your profile picture, ask a member of the AE" 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" msgstr "Pour changer votre photo de profil, demandez à un membre de l'AE"
#: core/templates/core/user_edit.jinja:173 #: core/templates/core/user_edit.jinja:98
msgid "captured"
msgstr "capturé"
#: core/templates/core/user_edit.jinja:196
msgid "Edit user profile" msgid "Edit user profile"
msgstr "Éditer le profil de l'utilisateur" msgstr "Éditer le profil de l'utilisateur"
#: core/templates/core/user_edit.jinja:258 #: core/templates/core/user_edit.jinja:160
msgid "Change my password" msgid "Change my password"
msgstr "Changer mon mot de passe" msgstr "Changer mon mot de passe"
#: core/templates/core/user_edit.jinja:263 #: core/templates/core/user_edit.jinja:165
msgid "Change user password" msgid "Change user password"
msgstr "Changer le mot de passe" msgstr "Changer le mot de passe"
#: core/templates/core/user_edit.jinja:273 #: core/templates/core/user_edit.jinja:175
msgid "Username:" msgid "Username:"
msgstr "Nom d'utilisateur : " msgstr "Nom d'utilisateur : "
#: core/templates/core/user_edit.jinja:276 #: core/templates/core/user_edit.jinja:178
msgid "Account number:" msgid "Account number:"
msgstr "Numéro de compte : " msgstr "Numéro de compte : "
@ -3350,7 +3346,7 @@ msgstr "Achats"
msgid "Product top 10" msgid "Product top 10"
msgstr "Top 10 produits" msgstr "Top 10 produits"
#: core/templates/core/user_stats.jinja:43 counter/forms.py:200 #: core/templates/core/user_stats.jinja:43 counter/forms.py:201
msgid "Product" msgid "Product"
msgstr "Produit" msgstr "Produit"
@ -3395,7 +3391,7 @@ msgstr "Cotisations"
msgid "Subscription stats" msgid "Subscription stats"
msgstr "Statistiques de cotisation" msgstr "Statistiques de cotisation"
#: core/templates/core/user_tools.jinja:48 counter/forms.py:159 #: core/templates/core/user_tools.jinja:48 counter/forms.py:160
#: counter/views.py:728 #: counter/views.py:728
msgid "Counters" msgid "Counters"
msgstr "Comptoirs" msgstr "Comptoirs"
@ -3654,7 +3650,7 @@ msgstr "Parrain / Marraine"
msgid "Godchild" msgid "Godchild"
msgstr "Fillot / Fillote" msgstr "Fillot / Fillote"
#: core/views/forms.py:338 counter/forms.py:67 trombi/views.py:149 #: core/views/forms.py:338 counter/forms.py:68 trombi/views.py:149
msgid "Select user" msgid "Select user"
msgstr "Choisir un utilisateur" msgstr "Choisir un utilisateur"
@ -3713,24 +3709,24 @@ msgstr "Photos"
msgid "Galaxy" msgid "Galaxy"
msgstr "Galaxie" msgstr "Galaxie"
#: counter/apps.py:30 counter/models.py:403 counter/models.py:857 #: counter/apps.py:30 counter/models.py:407 counter/models.py:861
#: counter/models.py:893 launderette/models.py:32 stock/models.py:39 #: counter/models.py:897 launderette/models.py:32 stock/models.py:39
msgid "counter" msgid "counter"
msgstr "comptoir" msgstr "comptoir"
#: counter/forms.py:48 #: counter/forms.py:49
msgid "This UID is invalid" msgid "This UID is invalid"
msgstr "Cet UID est invalide" msgstr "Cet UID est invalide"
#: counter/forms.py:89 #: counter/forms.py:90
msgid "User not found" msgid "User not found"
msgstr "Utilisateur non trouvé" msgstr "Utilisateur non trouvé"
#: counter/forms.py:145 #: counter/forms.py:146
msgid "Parent product" msgid "Parent product"
msgstr "Produit parent" msgstr "Produit parent"
#: counter/forms.py:151 #: counter/forms.py:152
msgid "Buying groups" msgid "Buying groups"
msgstr "Groupes d'achat" msgstr "Groupes d'achat"
@ -3738,165 +3734,169 @@ msgstr "Groupes d'achat"
msgid "Ecocup regularization" msgid "Ecocup regularization"
msgstr "Régularization des ecocups" msgstr "Régularization des ecocups"
#: counter/models.py:55 #: counter/models.py:56
msgid "account id" msgid "account id"
msgstr "numéro de compte" msgstr "numéro de compte"
#: counter/models.py:57 #: counter/models.py:58
msgid "recorded product" msgid "recorded product"
msgstr "produits consignés" msgstr "produits consignés"
#: counter/models.py:60 #: counter/models.py:61
msgid "customer" msgid "customer"
msgstr "client" msgstr "client"
#: counter/models.py:61 #: counter/models.py:62
msgid "customers" msgid "customers"
msgstr "clients" msgstr "clients"
#: counter/models.py:73 counter/views.py:309 #: counter/models.py:74 counter/views.py:309
msgid "Not enough money" msgid "Not enough money"
msgstr "Solde insuffisant" msgstr "Solde insuffisant"
#: counter/models.py:171 #: counter/models.py:172
msgid "First name" msgid "First name"
msgstr "Prénom" msgstr "Prénom"
#: counter/models.py:172 #: counter/models.py:173
msgid "Last name" msgid "Last name"
msgstr "Nom de famille" msgstr "Nom de famille"
#: counter/models.py:173 #: counter/models.py:174
msgid "Address 1" msgid "Address 1"
msgstr "Adresse 1" msgstr "Adresse 1"
#: counter/models.py:174 #: counter/models.py:175
msgid "Address 2" msgid "Address 2"
msgstr "Adresse 2" msgstr "Adresse 2"
#: counter/models.py:175 #: counter/models.py:176
msgid "Zip code" msgid "Zip code"
msgstr "Code postal" msgstr "Code postal"
#: counter/models.py:176 #: counter/models.py:177
msgid "City" msgid "City"
msgstr "Ville" msgstr "Ville"
#: counter/models.py:177 #: counter/models.py:178
msgid "Country" msgid "Country"
msgstr "Pays" msgstr "Pays"
#: counter/models.py:221 counter/models.py:247 #: counter/models.py:179
msgid "Phone number"
msgstr "Numéro de téléphone"
#: counter/models.py:225 counter/models.py:251
msgid "product type" msgid "product type"
msgstr "type du produit" msgstr "type du produit"
#: counter/models.py:253 #: counter/models.py:257
msgid "purchase price" msgid "purchase price"
msgstr "prix d'achat" msgstr "prix d'achat"
#: counter/models.py:254 #: counter/models.py:258
msgid "selling price" msgid "selling price"
msgstr "prix de vente" msgstr "prix de vente"
#: counter/models.py:255 #: counter/models.py:259
msgid "special selling price" msgid "special selling price"
msgstr "prix de vente spécial" msgstr "prix de vente spécial"
#: counter/models.py:262 #: counter/models.py:266
msgid "icon" msgid "icon"
msgstr "icône" msgstr "icône"
#: counter/models.py:267 #: counter/models.py:271
msgid "limit age" msgid "limit age"
msgstr "âge limite" msgstr "âge limite"
#: counter/models.py:268 #: counter/models.py:272
msgid "tray price" msgid "tray price"
msgstr "prix plateau" msgstr "prix plateau"
#: counter/models.py:272 #: counter/models.py:276
msgid "parent product" msgid "parent product"
msgstr "produit parent" msgstr "produit parent"
#: counter/models.py:278 #: counter/models.py:282
msgid "buying groups" msgid "buying groups"
msgstr "groupe d'achat" msgstr "groupe d'achat"
#: counter/models.py:280 election/models.py:50 #: counter/models.py:284 election/models.py:50
msgid "archived" msgid "archived"
msgstr "archivé" msgstr "archivé"
#: counter/models.py:283 counter/models.py:993 #: counter/models.py:287 counter/models.py:997
msgid "product" msgid "product"
msgstr "produit" msgstr "produit"
#: counter/models.py:382 #: counter/models.py:386
msgid "products" msgid "products"
msgstr "produits" msgstr "produits"
#: counter/models.py:385 #: counter/models.py:389
msgid "counter type" msgid "counter type"
msgstr "type de comptoir" msgstr "type de comptoir"
#: counter/models.py:387 #: counter/models.py:391
msgid "Bar" msgid "Bar"
msgstr "Bar" msgstr "Bar"
#: counter/models.py:387 #: counter/models.py:391
msgid "Office" msgid "Office"
msgstr "Bureau" msgstr "Bureau"
#: counter/models.py:390 #: counter/models.py:394
msgid "sellers" msgid "sellers"
msgstr "vendeurs" msgstr "vendeurs"
#: counter/models.py:398 launderette/models.py:192 #: counter/models.py:402 launderette/models.py:192
msgid "token" msgid "token"
msgstr "jeton" msgstr "jeton"
#: counter/models.py:601 #: counter/models.py:605
msgid "bank" msgid "bank"
msgstr "banque" msgstr "banque"
#: counter/models.py:603 counter/models.py:693 #: counter/models.py:607 counter/models.py:697
msgid "is validated" msgid "is validated"
msgstr "est validé" msgstr "est validé"
#: counter/models.py:606 #: counter/models.py:610
msgid "refilling" msgid "refilling"
msgstr "rechargement" msgstr "rechargement"
#: counter/models.py:670 eboutic/models.py:227 #: counter/models.py:674 eboutic/models.py:227
msgid "unit price" msgid "unit price"
msgstr "prix unitaire" msgstr "prix unitaire"
#: counter/models.py:671 counter/models.py:973 eboutic/models.py:228 #: counter/models.py:675 counter/models.py:977 eboutic/models.py:228
msgid "quantity" msgid "quantity"
msgstr "quantité" msgstr "quantité"
#: counter/models.py:690 #: counter/models.py:694
msgid "Sith account" msgid "Sith account"
msgstr "Compte utilisateur" msgstr "Compte utilisateur"
#: counter/models.py:690 sith/settings.py:405 sith/settings.py:410 #: counter/models.py:694 sith/settings.py:405 sith/settings.py:410
#: sith/settings.py:430 #: sith/settings.py:430
msgid "Credit card" msgid "Credit card"
msgstr "Carte bancaire" msgstr "Carte bancaire"
#: counter/models.py:696 #: counter/models.py:700
msgid "selling" msgid "selling"
msgstr "vente" msgstr "vente"
#: counter/models.py:800 #: counter/models.py:804
msgid "Unknown event" msgid "Unknown event"
msgstr "Événement inconnu" msgstr "Événement inconnu"
#: counter/models.py:801 #: counter/models.py:805
#, python-format #, python-format
msgid "Eticket bought for the event %(event)s" msgid "Eticket bought for the event %(event)s"
msgstr "Eticket acheté pour l'événement %(event)s" msgstr "Eticket acheté pour l'événement %(event)s"
#: counter/models.py:803 counter/models.py:826 #: counter/models.py:807 counter/models.py:830
#, python-format #, python-format
msgid "" msgid ""
"You bought an eticket for the event %(event)s.\n" "You bought an eticket for the event %(event)s.\n"
@ -3908,63 +3908,63 @@ msgstr ""
"Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte "
"%(url)s." "%(url)s."
#: counter/models.py:862 #: counter/models.py:866
msgid "last activity date" msgid "last activity date"
msgstr "dernière activité" msgstr "dernière activité"
#: counter/models.py:865 #: counter/models.py:869
msgid "permanency" msgid "permanency"
msgstr "permanence" msgstr "permanence"
#: counter/models.py:898 #: counter/models.py:902
msgid "emptied" msgid "emptied"
msgstr "coffre vidée" msgstr "coffre vidée"
#: counter/models.py:901 #: counter/models.py:905
msgid "cash register summary" msgid "cash register summary"
msgstr "relevé de caisse" msgstr "relevé de caisse"
#: counter/models.py:969 #: counter/models.py:973
msgid "cash summary" msgid "cash summary"
msgstr "relevé" msgstr "relevé"
#: counter/models.py:972 #: counter/models.py:976
msgid "value" msgid "value"
msgstr "valeur" msgstr "valeur"
#: counter/models.py:975 #: counter/models.py:979
msgid "check" msgid "check"
msgstr "chèque" msgstr "chèque"
#: counter/models.py:977 #: counter/models.py:981
msgid "True if this is a bank check, else False" msgid "True if this is a bank check, else False"
msgstr "Vrai si c'est un chèque, sinon Faux." msgstr "Vrai si c'est un chèque, sinon Faux."
#: counter/models.py:981 #: counter/models.py:985
msgid "cash register summary item" msgid "cash register summary item"
msgstr "élément de relevé de caisse" msgstr "élément de relevé de caisse"
#: counter/models.py:997 #: counter/models.py:1001
msgid "banner" msgid "banner"
msgstr "bannière" msgstr "bannière"
#: counter/models.py:999 #: counter/models.py:1003
msgid "event date" msgid "event date"
msgstr "date de l'événement" msgstr "date de l'événement"
#: counter/models.py:1001 #: counter/models.py:1005
msgid "event title" msgid "event title"
msgstr "titre de l'événement" msgstr "titre de l'événement"
#: counter/models.py:1003 #: counter/models.py:1007
msgid "secret" msgid "secret"
msgstr "secret" msgstr "secret"
#: counter/models.py:1042 #: counter/models.py:1046
msgid "uid" msgid "uid"
msgstr "uid" msgstr "uid"
#: counter/models.py:1047 #: counter/models.py:1051
msgid "student cards" msgid "student cards"
msgstr "cartes étudiante" msgstr "cartes étudiante"
@ -4439,7 +4439,7 @@ msgstr "Solde restant : "
msgid "Billing information" msgid "Billing information"
msgstr "Informations de facturation" msgstr "Informations de facturation"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:104 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:103
msgid "" msgid ""
"You must fill your billing infos if you want to pay with your credit\n" "You must fill your billing infos if you want to pay with your credit\n"
" card" " card"
@ -4447,32 +4447,50 @@ msgstr ""
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer " "Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
"par carte bancaire" "par carte bancaire"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:117 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:108
msgid ""
"\n"
" The Crédit Agricole changed its policy related to the "
"billing\n"
" information that must be provided in order to pay with a "
"credit card.\n"
" If you want to pay with your credit card, you must add a "
"phone number\n"
" to the data you already provided.\n"
" "
msgstr ""
"\n"
"Le Crédit Agricole a changé sa politique relative aux informations à "
"fournir pour effectuer un paiement par carte bancaire. De ce fait, si vous "
"souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux "
"données que vous aviez déjà fourni."
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:124
msgid "Pay with credit card" msgid "Pay with credit card"
msgstr "Payer avec une carte bancaire" msgstr "Payer avec une carte bancaire"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:122 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:129
msgid "" msgid ""
"AE account payment disabled because your basket contains refilling items." "AE account payment disabled because your basket contains refilling items."
msgstr "" msgstr ""
"Paiement par compte AE désactivé parce que votre panier contient des bons de " "Paiement par compte AE désactivé parce que votre panier contient des bons de "
"rechargement." "rechargement."
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:124 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:131
msgid "" msgid ""
"AE account payment disabled because you do not have enough money remaining." "AE account payment disabled because you do not have enough money remaining."
msgstr "" msgstr ""
"Paiement par compte AE désactivé parce que votre solde est insuffisant." "Paiement par compte AE désactivé parce que votre solde est insuffisant."
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:129 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:136
msgid "Pay with Sith account" msgid "Pay with Sith account"
msgstr "Payer avec un compte AE" msgstr "Payer avec un compte AE"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:140 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:147
msgid "Billing info registration success" msgid "Billing info registration success"
msgstr "Informations de facturation enregistrées" msgstr "Informations de facturation enregistrées"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:141 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:148
msgid "Billing info registration failure" msgid "Billing info registration failure"
msgstr "Echec de l'enregistrement des informations de facturation." msgstr "Echec de l'enregistrement des informations de facturation."
@ -6325,3 +6343,6 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
#, python-format #, python-format
msgid "Maximum characters: %(max_length)s" msgid "Maximum characters: %(max_length)s"
msgstr "Nombre de caractères max: %(max_length)s" msgstr "Nombre de caractères max: %(max_length)s"
#~ msgid "captured"
#~ msgstr "capturé"

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-03 15:22+0200\n" "POT-Creation-Date: 2024-09-27 22:32+0200\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n" "PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n" "Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -16,9 +16,24 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: core/static/user/js/family_graph.js:230
#: core/static/user/js/family_graph.js:233
msgid "family_tree.%(extension)s" msgid "family_tree.%(extension)s"
msgstr "arbre_genealogique.%(extension)s" msgstr "arbre_genealogique.%(extension)s"
#: sas/static/sas/js/picture.js:52
#: core/static/user/js/user_edit.js:93
#, javascript-format
msgid "captured.%s"
msgstr "capture.%s"
#: eboutic/static/eboutic/js/makecommand.js:50
msgid "Incorrect value"
msgstr "Valeur incorrecte"
#: sas/static/sas/js/viewer.js:196
msgid "Couldn't moderate picture"
msgstr "Echec de la suppression de la photo"
#: sas/static/sas/js/viewer.js:209
msgid "Couldn't delete picture" msgid "Couldn't delete picture"
msgstr "Echec de la suppression de la photo" msgstr "Echec de la suppression de la photo"

29
poetry.lock generated
View File

@ -1813,6 +1813,33 @@ files = [
[package.dependencies] [package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-extra-types"
version = "2.9.0"
description = "Extra Pydantic types."
optional = false
python-versions = ">=3.8"
files = []
develop = false
[package.dependencies]
pydantic = ">=2.5.2"
typing-extensions = "*"
[package.extras]
all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<3)", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"]
pendulum = ["pendulum (>=3.0.0,<4.0.0)"]
phonenumbers = ["phonenumbers (>=8,<9)"]
pycountry = ["pycountry (>=23)"]
python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<3)"]
semver = ["semver (>=3.0.2)"]
[package.source]
type = "git"
url = "https://github.com/pydantic/pydantic-extra-types.git"
reference = "HEAD"
resolved_reference = "58db4b096d7c90566d3d48d51b4665c01a591df6"
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.18.0" version = "2.18.0"
@ -2626,4 +2653,4 @@ filelock = ">=3.4"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "b6202203d272cecdb607ea8ebc1ba12dd8369e4f387f65692c4a9681915e6f48" content-hash = "c9c49497cc576b24c96ea914b74ef5c3a0c2981c488a599752f05aabb575f8d8"

View File

@ -45,6 +45,10 @@ dict2xml = "^1.7.3"
Sphinx = "^5" # Needed for building xapian Sphinx = "^5" # Needed for building xapian
tomli = "^2.0.1" tomli = "^2.0.1"
django-honeypot = "^1.2.1" django-honeypot = "^1.2.1"
# When I introduced pydantic-extra-types, I needed *right now*
# the PhoneNumberValidator class which was on the master branch but not released yet.
# Once it's released, switch this to a regular version.
pydantic-extra-types = { git = "https://github.com/pydantic/pydantic-extra-types.git", rev = "58db4b0" }
[tool.poetry.group.prod.dependencies] [tool.poetry.group.prod.dependencies]
# deps used in prod, but unnecessary for development # deps used in prod, but unnecessary for development