2 Commits

Author SHA1 Message Date
imperosol
6709befb1f fix timezone issues 2025-11-23 01:30:44 +01:00
imperosol
ddfb88ca2a remove settings.FORM_RENDERER 2025-11-23 01:22:15 +01:00
13 changed files with 503 additions and 582 deletions

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import json import json
import math import math
import uuid import uuid
from datetime import date from datetime import date, datetime, timezone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django import forms from django import forms
@@ -489,13 +489,14 @@ class InvoiceCallForm(forms.Form):
def __init__(self, *args, month: date, **kwargs): def __init__(self, *args, month: date, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.month = month self.month = month
month_start = datetime(month.year, month.month, month.day, tzinfo=timezone.utc)
self.clubs = list( self.clubs = list(
Club.objects.filter( Club.objects.filter(
Exists( Exists(
Selling.objects.filter( Selling.objects.filter(
club=OuterRef("pk"), club=OuterRef("pk"),
date__gte=month, date__gte=month_start,
date__lte=month + relativedelta(months=1), date__lte=month_start + relativedelta(months=1),
) )
) )
).annotate( ).annotate(

View File

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

View File

@@ -6,7 +6,7 @@ from django.contrib.auth.models import Permission
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import Client from django.test import Client
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import localdate from django.utils.timezone import now
from model_bakery import baker from model_bakery import baker
from pytest_django.asserts import assertRedirects from pytest_django.asserts import assertRedirects
@@ -57,7 +57,7 @@ def test_invoice_call_view(client: Client, query: dict | None):
@pytest.mark.django_db @pytest.mark.django_db
def test_invoice_call_form(): def test_invoice_call_form():
Selling.objects.all().delete() Selling.objects.all().delete()
month = localdate() - relativedelta(months=1) month = now() - relativedelta(months=1)
clubs = baker.make(Club, _quantity=2) clubs = baker.make(Club, _quantity=2)
recipe = sale_recipe.extend(date=month, customer=baker.make(Customer, amount=10000)) recipe = sale_recipe.extend(date=month, customer=baker.make(Customer, amount=10000))
recipe.make(club=clubs[0], quantity=2, unit_price=200) recipe.make(club=clubs[0], quantity=2, unit_price=200)

View File

@@ -12,7 +12,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE" # OR WITHIN THE LOCAL FILE "LICENSE"
# #
# #
from datetime import timedelta from datetime import datetime, timedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
@@ -23,6 +23,7 @@ from django.forms.models import modelform_factory
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import get_current_timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import DetailView, ListView, TemplateView from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
@@ -285,7 +286,13 @@ class CounterStatView(PermissionRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add stats to the context.""" """Add stats to the context."""
counter: Counter = self.object counter: Counter = self.object
semester_start = get_start_of_semester() start_date = get_start_of_semester()
semester_start = datetime(
start_date.year,
start_date.month,
start_date.day,
tzinfo=get_current_timezone(),
)
office_hours = counter.get_top_barmen() office_hours = counter.get_top_barmen()
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs.update( kwargs.update(

View File

@@ -12,7 +12,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE" # OR WITHIN THE LOCAL FILE "LICENSE"
# #
# #
from datetime import datetime from datetime import datetime, timezone
from urllib.parse import urlencode from urllib.parse import urlencode
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
@@ -63,7 +63,8 @@ class InvoiceCallView(
"""Add sums to the context.""" """Add sums to the context."""
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC")
start_date = self.get_month() month = self.get_month()
start_date = datetime(month.year, month.month, month.day, tzinfo=timezone.utc)
end_date = start_date + relativedelta(months=1) end_date = start_date + relativedelta(months=1)
kwargs["sum_cb"] = Refilling.objects.filter( kwargs["sum_cb"] = Refilling.objects.filter(

View File

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

View File

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

View File

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

View File

@@ -216,7 +216,7 @@ TEMPLATES = [
}, },
}, },
] ]
FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
HAYSTACK_CONNECTIONS = { HAYSTACK_CONNECTIONS = {
"default": { "default": {

959
uv.lock generated

File diff suppressed because it is too large Load Diff