1 Commits

Author SHA1 Message Date
imperosol
acdb9660f6 deps: bump django-ninja to 1.5.0 2025-11-23 00:48:32 +01:00
27 changed files with 695 additions and 652 deletions

View File

@@ -1,18 +1,16 @@
from typing import Annotated
from annotated_types import MinLen
from django.db.models import Q
from ninja import Field, FilterSchema, ModelSchema
from ninja import FilterLookup, FilterSchema, ModelSchema
from club.models import Club, Membership
from core.schemas import SimpleUserSchema
from core.schemas import NonEmptyStr, SimpleUserSchema
class ClubSearchFilterSchema(FilterSchema):
search: Annotated[str, MinLen(1)] | None = Field(None, q="name__icontains")
search: Annotated[NonEmptyStr | None, FilterLookup("name__icontains")] = None
is_active: bool | None = None
parent_id: int | None = None
parent_name: str | None = Field(None, q="parent__name__icontains")
exclude_ids: set[int] | None = None
def filter_exclude_ids(self, value: set[int] | None):

View File

@@ -1,9 +1,9 @@
from datetime import datetime
from typing import Annotated
from ninja import FilterSchema, ModelSchema
from ninja import FilterLookup, FilterSchema, ModelSchema
from ninja_extra import service_resolver
from ninja_extra.context import RouteContext
from pydantic import Field
from club.schemas import ClubProfileSchema
from com.models import News, NewsDate
@@ -11,12 +11,12 @@ from core.markdown import markdown
class NewsDateFilterSchema(FilterSchema):
before: datetime | None = Field(None, q="end_date__lt")
after: datetime | None = Field(None, q="start_date__gt")
club_id: int | None = Field(None, q="news__club_id")
before: Annotated[datetime | None, FilterLookup("end_date__lt")] = None
after: Annotated[datetime | None, FilterLookup("start_date__gt")] = None
club_id: Annotated[int | None, FilterLookup("news__club_id")] = None
news_id: int | None = None
is_published: bool | None = Field(None, q="news__is_published")
title: str | None = Field(None, q="news__title__icontains")
is_published: Annotated[bool | None, FilterLookup("news__is_published")] = None
title: Annotated[str | None, FilterLookup("news__title__icontains")] = None
class NewsSchema(ModelSchema):

View File

@@ -350,6 +350,7 @@ class Command(BaseCommand):
date=make_aware(
self.faker.date_time_between(customer.since, localdate())
),
is_validated=True,
)
)
sales.extend(this_customer_sales)

View File

@@ -15,6 +15,8 @@ from pydantic_core.core_schema import ValidationInfo
from core.models import Group, QuickUploadImage, SithFile, User
from core.utils import is_image
NonEmptyStr = Annotated[str, MinLen(1)]
class UploadedImage(UploadedFile):
@classmethod

View File

@@ -187,7 +187,11 @@ class TestFilterInactive(TestCase):
time_inactive = time_active - timedelta(days=3)
counter, seller = baker.make(Counter), baker.make(User)
sale_recipe = Recipe(
Selling, counter=counter, club=counter.club, seller=seller, unit_price=0
Selling,
counter=counter,
club=counter.club,
seller=seller,
is_validated=True,
)
cls.users = [

View File

@@ -24,6 +24,12 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
PAYMENT_METHOD = [
("CHECK", _("Check")),
("CASH", _("Cash")),
("CARD", _("Credit card")),
]
class CounterConfig(AppConfig):
name = "counter"

View File

@@ -136,10 +136,7 @@ class GetUserForm(forms.Form):
class RefillForm(forms.ModelForm):
allowed_refilling_methods = [
Refilling.PaymentMethod.CASH,
Refilling.PaymentMethod.CARD,
]
allowed_refilling_methods = ["CASH", "CARD"]
error_css_class = "error"
required_css_class = "required"
@@ -149,7 +146,7 @@ class RefillForm(forms.ModelForm):
class Meta:
model = Refilling
fields = ["amount", "payment_method"]
fields = ["amount", "payment_method", "bank"]
widgets = {"payment_method": forms.RadioSelect}
def __init__(self, *args, **kwargs):
@@ -163,6 +160,9 @@ class RefillForm(forms.ModelForm):
if self.fields["payment_method"].initial not in self.allowed_refilling_methods:
self.fields["payment_method"].initial = self.allowed_refilling_methods[0]
if "CHECK" not in self.allowed_refilling_methods:
del self.fields["bank"]
class CounterEditForm(forms.ModelForm):
class Meta:

View File

@@ -119,6 +119,7 @@ class Command(BaseCommand):
quantity=1,
unit_price=account.amount,
date=now(),
is_validated=True,
)
for account in accounts
]

View File

@@ -1,84 +0,0 @@
# Generated by Django 5.2.8 on 2025-11-19 17:59
from django.db import migrations, models
from django.db.migrations.state import StateApps
from django.db.models import Case, When
def migrate_selling_payment_method(apps: StateApps, schema_editor):
# 0 <=> SITH_ACCOUNT is the default value, so no need to migrate it
Selling = apps.get_model("counter", "Selling")
Selling.objects.filter(payment_method_str="CARD").update(payment_method=1)
def migrate_selling_payment_method_reverse(apps: StateApps, schema_editor):
Selling = apps.get_model("counter", "Selling")
Selling.objects.filter(payment_method=1).update(payment_method_str="CARD")
def migrate_refilling_payment_method(apps: StateApps, schema_editor):
Refilling = apps.get_model("counter", "Refilling")
Refilling.objects.update(
payment_method=Case(
When(payment_method_str="CARD", then=0),
When(payment_method_str="CASH", then=1),
When(payment_method_str="CHECK", then=2),
)
)
def migrate_refilling_payment_method_reverse(apps: StateApps, schema_editor):
Refilling = apps.get_model("counter", "Refilling")
Refilling.objects.update(
payment_method_str=Case(
When(payment_method=0, then="CARD"),
When(payment_method=1, then="CASH"),
When(payment_method=2, then="CHECK"),
)
)
class Migration(migrations.Migration):
dependencies = [("counter", "0034_alter_selling_date_selling_date_month_idx")]
operations = [
migrations.RemoveField(model_name="selling", name="is_validated"),
migrations.RemoveField(model_name="refilling", name="is_validated"),
migrations.RemoveField(model_name="refilling", name="bank"),
migrations.RenameField(
model_name="selling",
old_name="payment_method",
new_name="payment_method_str",
),
migrations.AddField(
model_name="selling",
name="payment_method",
field=models.PositiveSmallIntegerField(
choices=[(0, "Sith account"), (1, "Credit card")],
default=0,
verbose_name="payment method",
),
),
migrations.RunPython(
migrate_selling_payment_method, migrate_selling_payment_method_reverse
),
migrations.RemoveField(model_name="selling", name="payment_method_str"),
migrations.RenameField(
model_name="refilling",
old_name="payment_method",
new_name="payment_method_str",
),
migrations.AddField(
model_name="refilling",
name="payment_method",
field=models.PositiveSmallIntegerField(
choices=[(0, "Credit card"), (1, "Cash"), (2, "Check")],
default=0,
verbose_name="payment method",
),
),
migrations.RunPython(
migrate_refilling_payment_method, migrate_refilling_payment_method_reverse
),
migrations.RemoveField(model_name="refilling", name="payment_method_str"),
]

View File

@@ -44,6 +44,7 @@ from club.models import Club
from core.fields import ResizedImageField
from core.models import Group, Notification, User
from core.utils import get_start_of_semester
from counter.apps import PAYMENT_METHOD
from counter.fields import CurrencyField
from subscription.models import Subscription
@@ -79,8 +80,7 @@ class CustomerQuerySet(models.QuerySet):
)
money_out = Subquery(
Selling.objects.filter(
customer=OuterRef("pk"),
payment_method=Selling.PaymentMethod.SITH_ACCOUNT,
customer=OuterRef("pk"), payment_method="SITH_ACCOUNT"
)
.values("customer_id")
.annotate(res=Sum(F("unit_price") * F("quantity"), default=0))
@@ -731,11 +731,6 @@ class RefillingQuerySet(models.QuerySet):
class Refilling(models.Model):
"""Handle the refilling."""
class PaymentMethod(models.IntegerChoices):
CARD = 0, _("Credit card")
CASH = 1, _("Cash")
CHECK = 2, _("Check")
counter = models.ForeignKey(
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
)
@@ -750,9 +745,16 @@ class Refilling(models.Model):
Customer, related_name="refillings", blank=False, on_delete=models.CASCADE
)
date = models.DateTimeField(_("date"))
payment_method = models.PositiveSmallIntegerField(
_("payment method"), choices=PaymentMethod, default=PaymentMethod.CARD
payment_method = models.CharField(
_("payment method"),
max_length=255,
choices=PAYMENT_METHOD,
default="CARD",
)
bank = models.CharField(
_("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER"
)
is_validated = models.BooleanField(_("is validated"), default=False)
objects = RefillingQuerySet.as_manager()
@@ -769,9 +771,10 @@ class Refilling(models.Model):
if not self.date:
self.date = timezone.now()
self.full_clean()
if self._state.adding:
if not self.is_validated:
self.customer.amount += self.amount
self.customer.save()
self.is_validated = True
if self.customer.user.preferences.notify_on_refill:
Notification(
user=self.customer.user,
@@ -811,10 +814,6 @@ class SellingQuerySet(models.QuerySet):
class Selling(models.Model):
"""Handle the sellings."""
class PaymentMethod(models.IntegerChoices):
SITH_ACCOUNT = 0, _("Sith account")
CARD = 1, _("Credit card")
# We make sure that sellings have a way begger label than any product name is allowed to
label = models.CharField(_("label"), max_length=128)
product = models.ForeignKey(
@@ -851,9 +850,13 @@ class Selling(models.Model):
on_delete=models.SET_NULL,
)
date = models.DateTimeField(_("date"), db_index=True)
payment_method = models.PositiveSmallIntegerField(
_("payment method"), choices=PaymentMethod, default=PaymentMethod.SITH_ACCOUNT
payment_method = models.CharField(
_("payment method"),
max_length=255,
choices=[("SITH_ACCOUNT", _("Sith account")), ("CARD", _("Credit card"))],
default="SITH_ACCOUNT",
)
is_validated = models.BooleanField(_("is validated"), default=False)
objects = SellingQuerySet.as_manager()
@@ -872,12 +875,10 @@ class Selling(models.Model):
if not self.date:
self.date = timezone.now()
self.full_clean()
if (
self._state.adding
and self.payment_method == self.PaymentMethod.SITH_ACCOUNT
):
if not self.is_validated:
self.customer.amount -= self.quantity * self.unit_price
self.customer.save(allow_negative=allow_negative)
self.is_validated = True
user = self.customer.user
if user.was_subscribed:
if (
@@ -947,9 +948,7 @@ class Selling(models.Model):
def is_owned_by(self, user: User) -> bool:
if user.is_anonymous:
return False
return self.payment_method != self.PaymentMethod.CARD and user.is_owner(
self.counter
)
return self.payment_method != "CARD" and user.is_owner(self.counter)
def can_be_viewed_by(self, user: User) -> bool:
if (
@@ -959,7 +958,7 @@ class Selling(models.Model):
return user == self.customer.user
def delete(self, *args, **kwargs):
if self.payment_method == Selling.PaymentMethod.SITH_ACCOUNT:
if self.payment_method == "SITH_ACCOUNT":
self.customer.amount += self.quantity * self.unit_price
self.customer.save()
super().delete(*args, **kwargs)

View File

@@ -1,13 +1,12 @@
from datetime import datetime
from typing import Annotated, Self
from annotated_types import MinLen
from django.urls import reverse
from ninja import Field, FilterSchema, ModelSchema, Schema
from ninja import FilterLookup, FilterSchema, ModelSchema, Schema
from pydantic import model_validator
from club.schemas import SimpleClubSchema
from core.schemas import GroupSchema, SimpleUserSchema
from core.schemas import GroupSchema, NonEmptyStr, SimpleUserSchema
from counter.models import Counter, Product, ProductType
@@ -21,7 +20,7 @@ class CounterSchema(ModelSchema):
class CounterFilterSchema(FilterSchema):
search: Annotated[str, MinLen(1)] = Field(None, q="name__icontains")
search: Annotated[NonEmptyStr | None, FilterLookup("name__icontains")] = None
class SimplifiedCounterSchema(ModelSchema):
@@ -93,18 +92,18 @@ class ProductSchema(ModelSchema):
class ProductFilterSchema(FilterSchema):
search: Annotated[str, MinLen(1)] | None = Field(
None, q=["name__icontains", "code__icontains"]
)
is_archived: bool | None = Field(None, q="archived")
buying_groups: set[int] | None = Field(None, q="buying_groups__in")
product_type: set[int] | None = Field(None, q="product_type__in")
club: set[int] | None = Field(None, q="club__in")
counter: set[int] | None = Field(None, q="counters__in")
search: Annotated[
NonEmptyStr | None, FilterLookup(["name__icontains", "code__icontains"])
] = None
is_archived: Annotated[bool | None, FilterLookup("archived")] = None
buying_groups: Annotated[set[int] | None, FilterLookup("buying_groups__in")] = None
product_type: Annotated[set[int] | None, FilterLookup("product_type__in")] = None
club: Annotated[set[int] | None, FilterLookup("club__in")] = None
counter: Annotated[set[int] | None, FilterLookup("counters__in")] = None
class SaleFilterSchema(FilterSchema):
before: datetime | None = Field(None, q="date__lt")
after: datetime | None = Field(None, q="date__gt")
counters: set[int] | None = Field(None, q="counter__in")
products: set[int] | None = Field(None, q="product__in")
before: Annotated[datetime | None, FilterLookup("date__lt")] = None
after: Annotated[datetime | None, FilterLookup("date__gt")] = None
counters: Annotated[set[int] | None, FilterLookup("counter__in")] = None
products: Annotated[set[int] | None, FilterLookup("product__in")] = None

View File

@@ -116,6 +116,7 @@ class TestAccountDumpCommand(TestAccountDump):
operation: Selling = customer.buyings.order_by("date").last()
assert operation.unit_price == initial_amount
assert operation.counter_id == settings.SITH_COUNTER_ACCOUNT_DUMP_ID
assert operation.is_validated is True
dump = customer.dumps.last()
assert dump.dump_operation == operation

View File

@@ -53,7 +53,7 @@ def set_age(user: User, age: int):
def force_refill_user(user: User, amount: Decimal | int):
baker.make(Refilling, amount=amount, customer=user.customer)
baker.make(Refilling, amount=amount, customer=user.customer, is_validated=False)
class TestFullClickBase(TestCase):
@@ -115,10 +115,18 @@ class TestRefilling(TestFullClickBase):
) -> HttpResponse:
used_client = client if client is not None else self.client
return used_client.post(
reverse("counter:refilling_create", kwargs={"customer_id": user.pk}),
{"amount": str(amount), "payment_method": Refilling.PaymentMethod.CASH},
reverse(
"counter:refilling_create",
kwargs={"customer_id": user.pk},
),
{
"amount": str(amount),
"payment_method": "CASH",
"bank": "OTHER",
},
HTTP_REFERER=reverse(
"counter:click", kwargs={"counter_id": counter.id, "user_id": user.pk}
"counter:click",
kwargs={"counter_id": counter.id, "user_id": user.pk},
),
)
@@ -141,7 +149,11 @@ class TestRefilling(TestFullClickBase):
"counter:refilling_create",
kwargs={"customer_id": self.customer.pk},
),
{"amount": "10", "payment_method": "CASH"},
{
"amount": "10",
"payment_method": "CASH",
"bank": "OTHER",
},
)
self.client.force_login(self.club_admin)

View File

@@ -298,6 +298,7 @@ def test_update_balance():
_quantity=len(customers),
unit_price=10,
quantity=1,
payment_method="SITH_ACCOUNT",
_save_related=True,
),
*sale_recipe.prepare(
@@ -305,12 +306,14 @@ def test_update_balance():
_quantity=3,
unit_price=5,
quantity=2,
payment_method="SITH_ACCOUNT",
_save_related=True,
),
sale_recipe.prepare(
customer=customers[4],
quantity=1,
unit_price=50,
payment_method="SITH_ACCOUNT",
_save_related=True,
),
*sale_recipe.prepare(
@@ -321,7 +324,7 @@ def test_update_balance():
_quantity=len(customers),
unit_price=50,
quantity=1,
payment_method=Selling.PaymentMethod.CARD,
payment_method="CARD",
_save_related=True,
),
]

View File

@@ -67,13 +67,15 @@ class InvoiceCallView(
end_date = start_date + relativedelta(months=1)
kwargs["sum_cb"] = Refilling.objects.filter(
payment_method=Refilling.PaymentMethod.CARD,
payment_method="CARD",
is_validated=True,
date__gte=start_date,
date__lte=end_date,
).aggregate(res=Sum("amount", default=0))["res"]
kwargs["sum_cb"] += (
Selling.objects.filter(
payment_method=Selling.PaymentMethod.CARD,
payment_method="CARD",
is_validated=True,
date__gte=start_date,
date__lte=end_date,
)

View File

@@ -110,9 +110,7 @@ class Basket(models.Model):
)["total"]
)
def generate_sales(
self, counter, seller: User, payment_method: Selling.PaymentMethod
):
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.
@@ -253,7 +251,8 @@ class Invoice(models.Model):
customer=customer,
operator=self.user,
amount=i.product_unit_price * i.quantity,
payment_method=Refilling.PaymentMethod.CARD,
payment_method="CARD",
bank="OTHER",
date=self.date,
)
new.save()
@@ -268,7 +267,8 @@ class Invoice(models.Model):
customer=customer,
unit_price=i.product_unit_price,
quantity=i.quantity,
payment_method=Selling.PaymentMethod.CARD,
payment_method="CARD",
is_validated=True,
date=self.date,
)
new.save()

View File

@@ -108,22 +108,12 @@ def test_eboutic_basket_expiry(
client.force_login(customer.user)
if sellings:
for date in sellings:
sale_recipe.make(
customer=customer,
counter=eboutic,
date=iter(sellings),
_quantity=len(sellings),
_bulk_create=True,
)
if refillings:
refill_recipe.make(
customer=customer,
counter=eboutic,
date=iter(refillings),
_quantity=len(refillings),
_bulk_create=True,
customer=customer, counter=eboutic, date=date, is_validated=True
)
for date in refillings:
refill_recipe.make(customer=customer, counter=eboutic, date=date)
assert (
f'x-data="basket({int(expected.timestamp() * 1000) if expected else "null"})"'

View File

@@ -114,13 +114,13 @@ class TestPaymentSith(TestPaymentBase):
"quantity"
)
assert len(sellings) == 2
assert sellings[0].payment_method == Selling.PaymentMethod.SITH_ACCOUNT
assert sellings[0].payment_method == "SITH_ACCOUNT"
assert sellings[0].quantity == 1
assert sellings[0].unit_price == self.snack.selling_price
assert sellings[0].counter.type == "EBOUTIC"
assert sellings[0].product == self.snack
assert sellings[1].payment_method == Selling.PaymentMethod.SITH_ACCOUNT
assert sellings[1].payment_method == "SITH_ACCOUNT"
assert sellings[1].quantity == 2
assert sellings[1].unit_price == self.beer.selling_price
assert sellings[1].counter.type == "EBOUTIC"
@@ -198,13 +198,13 @@ class TestPaymentCard(TestPaymentBase):
"quantity"
)
assert len(sellings) == 2
assert sellings[0].payment_method == Selling.PaymentMethod.CARD
assert sellings[0].payment_method == "CARD"
assert sellings[0].quantity == 1
assert sellings[0].unit_price == self.snack.selling_price
assert sellings[0].counter.type == "EBOUTIC"
assert sellings[0].product == self.snack
assert sellings[1].payment_method == Selling.PaymentMethod.CARD
assert sellings[1].payment_method == "CARD"
assert sellings[1].quantity == 2
assert sellings[1].unit_price == self.beer.selling_price
assert sellings[1].counter.type == "EBOUTIC"

View File

@@ -275,9 +275,7 @@ class EbouticPayWithSith(CanViewMixin, SingleObjectMixin, View):
return redirect("eboutic:payment_result", "failure")
eboutic = get_eboutic()
sales = basket.generate_sales(
eboutic, basket.user, Selling.PaymentMethod.SITH_ACCOUNT
)
sales = basket.generate_sales(eboutic, basket.user, "SITH_ACCOUNT")
try:
with transaction.atomic():
# Selling.save has some important business logic in it.

View File

@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-19 21:00+0100\n"
"POT-Creation-Date: 2025-11-12 21:44+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -2928,6 +2928,18 @@ msgstr "Photos"
msgid "Account"
msgstr "Compte"
#: counter/apps.py sith/settings.py
msgid "Check"
msgstr "Chèque"
#: counter/apps.py sith/settings.py
msgid "Cash"
msgstr "Espèces"
#: counter/apps.py counter/models.py sith/settings.py
msgid "Credit card"
msgstr "Carte bancaire"
#: counter/apps.py counter/models.py
msgid "counter"
msgstr "comptoir"
@@ -3140,29 +3152,21 @@ msgstr "vendeurs"
msgid "token"
msgstr "jeton"
#: counter/models.py sith/settings.py
msgid "Credit card"
msgstr "Carte bancaire"
#: counter/models.py sith/settings.py
msgid "Cash"
msgstr "Espèces"
#: counter/models.py sith/settings.py
msgid "Check"
msgstr "Chèque"
#: counter/models.py subscription/models.py
msgid "payment method"
msgstr "méthode de paiement"
#: counter/models.py
msgid "refilling"
msgstr "rechargement"
msgid "bank"
msgstr "banque"
#: counter/models.py
msgid "Sith account"
msgstr "Compte utilisateur"
msgid "is validated"
msgstr "est validé"
#: counter/models.py
msgid "refilling"
msgstr "rechargement"
#: counter/models.py eboutic/models.py
msgid "unit price"
@@ -3172,6 +3176,10 @@ msgstr "prix unitaire"
msgid "quantity"
msgstr "quantité"
#: counter/models.py
msgid "Sith account"
msgstr "Compte utilisateur"
#: counter/models.py
msgid "selling"
msgstr "vente"
@@ -3324,10 +3332,6 @@ msgid ""
"%(value)s” value has the correct format (YYYY-MM) but it is an invalid date."
msgstr "La valeur « %(value)s » a le bon format, mais est une date invalide."
#: counter/models.py
msgid "is validated"
msgstr "est validé"
#: counter/models.py
msgid "invoice date"
msgstr "date de la facture"

View File

@@ -1,9 +1,9 @@
from typing import Literal
from typing import Annotated, Literal
from django.db.models import Q
from django.utils import html
from haystack.query import SearchQuerySet
from ninja import FilterSchema, ModelSchema, Schema
from ninja import FilterLookup, FilterSchema, ModelSchema, Schema
from pydantic import AliasPath, ConfigDict, Field, TypeAdapter
from pydantic.alias_generators import to_camel
@@ -114,13 +114,14 @@ class UvSchema(ModelSchema):
class UvFilterSchema(FilterSchema):
search: str | None = Field(None, q="code__icontains")
search: Annotated[str | None, FilterLookup("code__icontains")] = None
semester: set[Literal["AUTUMN", "SPRING"]] | None = None
credit_type: set[Literal["CS", "TM", "EC", "OM", "QC"]] | None = Field(
None, q="credit_type__in"
)
credit_type: Annotated[
set[Literal["CS", "TM", "EC", "OM", "QC"]] | None,
FilterLookup("credit_type__in"),
] = None
language: str = "FR"
department: set[str] | None = Field(None, q="department__in")
department: Annotated[set[str] | None, FilterLookup("department__in")] = None
def filter_search(self, value: str | None) -> Q:
"""Special filter for the search text.

View File

@@ -20,8 +20,8 @@ license = { text = "GPL-3.0-only" }
requires-python = "<4.0,>=3.12"
dependencies = [
"django>=5.2.8,<6.0.0",
"django-ninja>=1.4.5,<2.0.0",
"django-ninja-extra>=0.30.2,<1.0.0",
"django-ninja>=1.5.0,<6.0.0",
"django-ninja-extra>=0.30.6",
"Pillow>=12.0.0,<13.0.0",
"mistune>=3.1.4,<4.0.0",
"django-jinja<3.0.0,>=2.11.0",

View File

@@ -114,6 +114,7 @@ class TestMergeUser(TestCase):
seller=self.root,
unit_price=2,
quantity=2,
payment_method="SITH_ACCOUNT",
).save()
Selling(
label="barbar",
@@ -124,6 +125,7 @@ class TestMergeUser(TestCase):
seller=self.root,
unit_price=2,
quantity=4,
payment_method="SITH_ACCOUNT",
).save()
today = localtime(now()).date()
# both subscriptions began last month and shall end in 5 months
@@ -195,6 +197,7 @@ class TestMergeUser(TestCase):
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)
@@ -222,6 +225,7 @@ class TestMergeUser(TestCase):
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)

View File

@@ -2,20 +2,19 @@ from datetime import datetime
from pathlib import Path
from typing import Annotated
from annotated_types import MinLen
from django.urls import reverse
from ninja import FilterSchema, ModelSchema, Schema
from ninja import FilterLookup, FilterSchema, ModelSchema, Schema
from pydantic import Field, NonNegativeInt
from core.schemas import SimpleUserSchema, UserProfileSchema
from core.schemas import NonEmptyStr, SimpleUserSchema, UserProfileSchema
from sas.models import Album, Picture, PictureModerationRequest
class AlbumFilterSchema(FilterSchema):
search: Annotated[str, MinLen(1)] | None = Field(None, q="name__icontains")
before_date: datetime | None = Field(None, q="event_date__lte")
after_date: datetime | None = Field(None, q="event_date__gte")
parent_id: int | None = Field(None, q="parent_id")
search: Annotated[NonEmptyStr | None, FilterLookup("name__icontains")] = None
before_date: Annotated[datetime | None, FilterLookup("event_date__lte")] = None
after_date: Annotated[datetime | None, FilterLookup("event_date__gte")] = None
parent_id: Annotated[int | None, FilterLookup("parent_id")] = None
class SimpleAlbumSchema(ModelSchema):
@@ -60,10 +59,12 @@ class AlbumAutocompleteSchema(ModelSchema):
class PictureFilterSchema(FilterSchema):
before_date: datetime | None = Field(None, q="date__lte")
after_date: datetime | None = Field(None, q="date__gte")
users_identified: set[int] | None = Field(None, q="people__user_id__in")
album_id: int | None = Field(None, q="parent_id")
before_date: Annotated[datetime | None, FilterLookup("date__lte")] = None
after_date: Annotated[datetime | None, FilterLookup("date__gte")] = None
users_identified: Annotated[
set[int] | None, FilterLookup("people__user_id__in")
] = None
album_id: Annotated[int | None, FilterLookup("parent_id")] = None
class PictureSchema(ModelSchema):

View File

@@ -440,6 +440,19 @@ SITH_SUBSCRIPTION_LOCATIONS = [
SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")]
SITH_COUNTER_BANK = [
("OTHER", "Autre"),
("SOCIETE-GENERALE", "Société générale"),
("BANQUE-POPULAIRE", "Banque populaire"),
("BNP", "BNP"),
("CAISSE-EPARGNE", "Caisse d'épargne"),
("CIC", "CIC"),
("CREDIT-AGRICOLE", "Crédit Agricole"),
("CREDIT-MUTUEL", "Credit Mutuel"),
("CREDIT-LYONNAIS", "Credit Lyonnais"),
("LA-POSTE", "La Poste"),
]
SITH_PEDAGOGY_UV_TYPE = [
("FREE", _("Free")),
("CS", _("CS")),

View File

@@ -24,6 +24,7 @@ from django.views.generic import CreateView, DetailView, TemplateView
from django.views.generic.edit import FormView
from core.views.group import PermissionGroupsUpdateView
from counter.apps import PAYMENT_METHOD
from subscription.forms import (
SelectionDateForm,
SubscriptionExistingUserForm,
@@ -128,6 +129,6 @@ class SubscriptionsStatsView(FormView):
subscription_end__gte=self.end_date, subscription_start__lte=self.start_date
)
kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS
kwargs["payment_types"] = settings.SITH_SUBSCRIPTION_PAYMENT_METHOD
kwargs["payment_types"] = PAYMENT_METHOD
kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS
return kwargs

955
uv.lock generated

File diff suppressed because it is too large Load Diff