1 Commits

12 changed files with 186 additions and 319 deletions

View File

@@ -6,7 +6,7 @@ from ninja_extra.pagination import PageNumberPaginationExtra
from ninja_extra.schemas import PaginatedResponseSchema from ninja_extra.schemas import PaginatedResponseSchema
from api.auth import ApiKeyAuth from api.auth import ApiKeyAuth
from api.permissions import CanView, HasPerm from api.permissions import CanAccessLookup, CanView, HasPerm
from club.models import Club, Membership from club.models import Club, Membership
from club.schemas import ( from club.schemas import (
ClubSchema, ClubSchema,
@@ -22,11 +22,13 @@ class ClubController(ControllerBase):
@route.get( @route.get(
"/search", "/search",
response=PaginatedResponseSchema[SimpleClubSchema], response=PaginatedResponseSchema[SimpleClubSchema],
auth=[ApiKeyAuth(), SessionAuth()],
permissions=[CanAccessLookup],
url_name="search_club", url_name="search_club",
) )
@paginate(PageNumberPaginationExtra, page_size=50) @paginate(PageNumberPaginationExtra, page_size=50)
def search_club(self, filters: Query[ClubSearchFilterSchema]): def search_club(self, filters: Query[ClubSearchFilterSchema]):
return filters.filter(Club.objects.order_by("name")).values() return filters.filter(Club.objects.all())
@route.get( @route.get(
"/{int:club_id}", "/{int:club_id}",

View File

@@ -315,22 +315,3 @@ class JoinClubForm(ClubMemberForm):
_("You are already a member of this club"), code="invalid" _("You are already a member of this club"), code="invalid"
) )
return super().clean() return super().clean()
class ClubSearchForm(forms.ModelForm):
class Meta:
model = Club
fields = ["name"]
widgets = {"name": forms.SearchInput(attrs={"autocomplete": "off"})}
club_status = forms.NullBooleanField(
label=_("Club status"),
widget=forms.RadioSelect(
choices=[(True, _("Active")), (False, _("Inactive")), ("", _("All clubs"))],
),
initial=True,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["name"].required = False

View File

@@ -30,7 +30,7 @@ class ClubProfileSchema(ModelSchema):
class Meta: class Meta:
model = Club model = Club
fields = ["id", "name", "logo", "is_active", "short_description"] fields = ["id", "name", "logo"]
url: str url: str

View File

@@ -1,47 +0,0 @@
#club-list {
display: flex;
flex-direction: column;
gap: 2em;
padding: 2em;
.card {
display: block;
background-color: unset;
.club-image {
float: left;
margin-right: 2rem;
margin-bottom: .5rem;
width: 150px;
height: 150px;
border-radius: 10%;
background-color: rgba(173, 173, 173, 0.2);
@media screen and (max-width: 500px) {
width: 100px;
height: 100px;
}
}
i.club-image {
display: flex;
flex-direction: column;
justify-content: center;
color: black;
}
.content {
display: block;
text-align: justify;
h4 {
margin-top: 0;
margin-right: .5rem;
}
p {
font-size: 100%;
}
}
}
}

View File

@@ -1,75 +1,52 @@
{% if is_fragment %} {% extends "core/base.jinja" %}
{% extends "core/base_fragment.jinja" %}
{# Don't display tabs and errors #} {% block title -%}
{% block tabs %} {% trans %}Club list{% endtrans %}
{% endblock %} {%- endblock %}
{% block errors %}
{% endblock %}
{% else %}
{% extends "core/base.jinja" %}
{% block additional_css %}
<link rel="stylesheet" href="{{ static("club/list.scss") }}">
{% endblock %}
{% block description -%}
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
{%- endblock %}
{% block title -%}
{% trans %}Club list{% endtrans %}
{%- endblock %}
{% endif %}
{% from "core/macros.jinja" import paginate_htmx %} {% block description -%}
{% trans %}The list of all clubs existing at UTBM.{% endtrans %}
{%- endblock %}
{% macro display_club(club) -%}
{% if club.is_active or user.is_root %}
<li><a href="{{ url('club:club_view', club_id=club.id) }}">{{ club.name }}</a>
{% if not club.is_active %}
({% trans %}inactive{% endtrans %})
{% endif %}
{% if club.president %} - <a href="{{ url('core:user_profile', user_id=club.president.user.id) }}">{{ club.president.user }}</a>{% endif %}
{% if club.short_description %}<p>{{ club.short_description|markdown }}</p>{% endif %}
{% endif %}
{%- if club.children.all()|length != 0 %}
<ul>
{%- for c in club.children.order_by('name').prefetch_related("children") %}
{{ display_club(c) }}
{%- endfor %}
</ul>
{%- endif -%}
</li>
{%- endmacro %}
{% block content %} {% block content %}
<main> {% if user.is_root %}
<h3>{% trans %}Filters{% endtrans %}</h3> <p><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></p>
<form {% endif %}
id="club-list-filters" {% if club_list %}
hx-get="{{ url("club:club_list") }}"
hx-target="#content"
hx-swap="outerHtml"
>
<div class="row gap-4x">
{{ form }}
</div>
<button type="submit" class="btn btn-blue margin-bottom">
<i class="fa fa-magnifying-glass"></i>{% trans %}Search{% endtrans %}
</button>
</form>
<h3>{% trans %}Club list{% endtrans %}</h3> <h3>{% trans %}Club list{% endtrans %}</h3>
{% if user.has_perm("club.add_club") %} <ul>
<br> {%- for club in club_list %}
<a href="{{ url('club:club_new') }}" class="btn btn-blue"> {{ display_club(club) }}
<i class="fa fa-plus"></i> {% trans %}New club{% endtrans %} {%- endfor %}
</a> </ul>
{% endif %} {% else %}
<section class="aria-busy-grow" id="club-list"> {% trans %}There is no club in this website.{% endtrans %}
{% for club in object_list %} {% endif %}
<div class="card">
{% set club_url = club.get_absolute_url() %}
<a href="{{ club_url }}">
{% if club.logo %}
<img class="club-image" src="{{ club.logo.url }}" alt="logo {{ club.name }}">
{% else %}
<i class="fa-regular fa-image fa-4x club-image"></i>
{% endif %}
</a>
<div class="content">
<a href="{{ club_url }}">
<h4>
{{ club.name }} {% if not club.is_active %}({% trans %}inactive{% endtrans %}){% endif %}
</h4>
</a>
{{ club.short_description|markdown }}
</div>
</div>
{% endfor %}
</section>
{% if is_paginated %}
{{ paginate_htmx(request, page_obj, paginator) }}
{% endif %}
</main>
{% endblock %} {% endblock %}

View File

@@ -1,15 +1,12 @@
from datetime import timedelta from datetime import timedelta
import pytest import pytest
from django.test import Client
from django.urls import reverse
from django.utils.timezone import localdate from django.utils.timezone import localdate
from model_bakery import baker from model_bakery import baker
from model_bakery.recipe import Recipe from model_bakery.recipe import Recipe
from club.models import Club, Membership from club.models import Club, Membership
from core.baker_recipes import subscriber_user from core.baker_recipes import subscriber_user
from core.models import User
@pytest.mark.django_db @pytest.mark.django_db
@@ -28,14 +25,3 @@ def test_club_queryset_having_board_member():
club_ids = Club.objects.having_board_member(user).values_list("id", flat=True) club_ids = Club.objects.having_board_member(user).values_list("id", flat=True)
assert set(club_ids) == {clubs[1].id, clubs[2].id} assert set(club_ids) == {clubs[1].id, clubs[2].id}
@pytest.mark.parametrize("nb_additional_clubs", [10, 30])
@pytest.mark.parametrize("is_fragment", [True, False])
@pytest.mark.django_db
def test_club_list(client: Client, nb_additional_clubs: int, is_fragment):
client.force_login(baker.make(User))
baker.make(Club, _quantity=nb_additional_clubs)
headers = {"HX-Request": True} if is_fragment else {}
res = client.get(reverse("club:club_list"), headers=headers)
assert res.status_code == 200

View File

@@ -44,19 +44,13 @@ from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, View from django.views.generic import DetailView, ListView, View
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import ( from django.views.generic.edit import CreateView, DeleteView, UpdateView
CreateView,
DeleteView,
FormMixin,
UpdateView,
)
from club.forms import ( from club.forms import (
ClubAddMemberForm, ClubAddMemberForm,
ClubAdminEditForm, ClubAdminEditForm,
ClubEditForm, ClubEditForm,
ClubOldMemberForm, ClubOldMemberForm,
ClubSearchForm,
JoinClubForm, JoinClubForm,
MailingForm, MailingForm,
SellingsForm, SellingsForm,
@@ -72,12 +66,7 @@ from com.views import (
from core.auth.mixins import CanEditMixin, PermissionOrClubBoardRequiredMixin from core.auth.mixins import CanEditMixin, PermissionOrClubBoardRequiredMixin
from core.models import Page, PageRev from core.models import Page, PageRev
from core.views import BasePageEditView, DetailFormView, UseFragmentsMixin from core.views import BasePageEditView, DetailFormView, UseFragmentsMixin
from core.views.mixins import ( from core.views.mixins import FragmentMixin, FragmentRenderer, TabedViewMixin
AllowFragment,
FragmentMixin,
FragmentRenderer,
TabedViewMixin,
)
from counter.models import Selling from counter.models import Selling
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -191,41 +180,15 @@ class ClubTabsMixin(TabedViewMixin):
return tab_list return tab_list
class ClubListView(AllowFragment, FormMixin, ListView): class ClubListView(ListView):
"""List the clubs of the AE, with a form to perform basic search. """List the Clubs."""
Notes:
This view is fully public, because we want to advertise as much as possible
the cultural life of the AE.
In accordance with that matter, searching and listing the clubs is done
entirely server-side (no AlpineJS involved) ;
this is done this way in order to be sure the page is the most accessible
and SEO-friendly possible, even if it makes the UX slightly less smooth.
"""
model = Club
template_name = "club/club_list.jinja" template_name = "club/club_list.jinja"
form_class = ClubSearchForm queryset = (
queryset = Club.objects.order_by("name") Club.objects.filter(parent=None).order_by("name").prefetch_related("children")
paginate_by = 1 )
context_object_name = "club_list"
def get_form_kwargs(self):
res = super().get_form_kwargs()
if self.request.GET:
res |= {"data": self.request.GET, "initial": self.request.GET}
return res
def get_queryset(self):
form: ClubSearchForm = self.get_form()
qs = self.queryset
if not form.is_bound:
return qs.filter(is_active=True)
if not form.is_valid():
return qs.none()
if name := form.cleaned_data.get("name"):
qs = qs.filter(name__icontains=name)
if (is_active := form.cleaned_data.get("club_status")) is not None:
qs = qs.filter(is_active=is_active)
return qs
class ClubView(ClubTabsMixin, DetailView): class ClubView(ClubTabsMixin, DetailView):

View File

@@ -39,12 +39,16 @@ class Command(BaseCommand):
return None return None
return xapian.version_string() return xapian.version_string()
def _desired_version(self) -> str: def _desired_version(self) -> tuple[str, str, str]:
with open( with open(
Path(__file__).parent.parent.parent.parent / "pyproject.toml", "rb" Path(__file__).parent.parent.parent.parent / "pyproject.toml", "rb"
) as f: ) as f:
pyproject = tomli.load(f) pyproject = tomli.load(f)
return pyproject["tool"]["xapian"]["version"] return (
pyproject["tool"]["xapian"]["version"],
pyproject["tool"]["xapian"]["core-sha256"],
pyproject["tool"]["xapian"]["bindings-sha256"],
)
def handle(self, *args, force: bool, **options): def handle(self, *args, force: bool, **options):
if not os.environ.get("VIRTUAL_ENV", None): if not os.environ.get("VIRTUAL_ENV", None):
@@ -53,7 +57,7 @@ class Command(BaseCommand):
) )
return return
desired = self._desired_version() desired, core_checksum, bindings_checksum = self._desired_version()
if desired == self._current_version(): if desired == self._current_version():
if not force: if not force:
self.stdout.write( self.stdout.write(
@@ -65,7 +69,12 @@ class Command(BaseCommand):
f"Installing xapian version {desired} at {os.environ['VIRTUAL_ENV']}" f"Installing xapian version {desired} at {os.environ['VIRTUAL_ENV']}"
) )
subprocess.run( subprocess.run(
[str(Path(__file__).parent / "install_xapian.sh"), desired], [
str(Path(__file__).parent / "install_xapian.sh"),
desired,
core_checksum,
bindings_checksum,
],
env=dict(os.environ), env=dict(os.environ),
check=True, check=True,
) )

View File

@@ -1,7 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Originates from https://gist.github.com/jorgecarleitao/ab6246c86c936b9c55fd # Originates from https://gist.github.com/jorgecarleitao/ab6246c86c936b9c55fd
# first argument of the script is Xapian version (e.g. 1.2.19) # first argument of the script is Xapian version (e.g. 1.2.19)
# second argument of the script is core sha256
# second argument of the script is binding sha256
VERSION="$1" VERSION="$1"
CORE_SHA256="$2"
BINDINGS_SHA256="$3"
# Cleanup env vars for auto discovery mechanism # Cleanup env vars for auto discovery mechanism
unset CPATH unset CPATH
@@ -21,9 +25,15 @@ BINDINGS=xapian-bindings-$VERSION
# download # download
echo "Downloading source..." echo "Downloading source..."
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz" curl -O "https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz" || exit 1
echo "${CORE_SHA256} ${CORE}.tar.xz" | sha256sum -c - || exit 1
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz" curl -O "https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz"
echo "${BINDINGS_SHA256} ${BINDINGS}.tar.xz" | sha256sum -c - || exit 1
# extract # extract
echo "Extracting source..." echo "Extracting source..."
tar xf "${CORE}.tar.xz" tar xf "${CORE}.tar.xz"

View File

@@ -5,8 +5,9 @@
<details name="navbar" class="menu"> <details name="navbar" class="menu">
<summary class="head">{% trans %}Associations & Clubs{% endtrans %}</summary> <summary class="head">{% trans %}Associations & Clubs{% endtrans %}</summary>
<ul class="content"> <ul class="content">
<li><a href="{{ url("core:page", page_name="ae") }}">{% trans %}AE{% endtrans %}</a></li> <li><a href="{{ url('core:page', page_name='ae') }}">{% trans %}AE{% endtrans %}</a></li>
<li><a href="{{ url("club:club_list") }}">{% trans %}AE's clubs{% endtrans %}</a></li> <li><a href="{{ url('core:page', page_name='clubs') }}">{% trans %}AE's clubs{% endtrans %}</a></li>
<li><a href="{{ url('core:page', page_name='utbm-associations') }}">{% trans %}Others UTBM's Associations{% endtrans %}</a></li>
</ul> </ul>
</details> </details>
<details name="navbar" class="menu"> <details name="navbar" class="menu">

View File

@@ -310,36 +310,16 @@ msgid "The list of all clubs existing at UTBM."
msgstr "La liste de tous les clubs existants à l'UTBM" msgstr "La liste de tous les clubs existants à l'UTBM"
#: club/templates/club/club_list.jinja #: club/templates/club/club_list.jinja
msgid "Filters" msgid "inactive"
msgstr "Filtres" msgstr "inactif"
#: club/templates/club/club_list.jinja
msgid "Name"
msgstr "Nom"
#: club/templates/club/club_list.jinja
msgid "Club state"
msgstr "Etat du club"
#: club/templates/club/club_list.jinja
msgid "Active"
msgstr "Actif"
#: club/templates/club/club_list.jinja
msgid "Inactive"
msgstr "Inactif"
#: club/templates/club/club_list.jinja
msgid "All clubs"
msgstr "Tous les clubs"
#: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja #: club/templates/club/club_list.jinja core/templates/core/user_tools.jinja
msgid "New club" msgid "New club"
msgstr "Nouveau club" msgstr "Nouveau club"
#: club/templates/club/club_list.jinja #: club/templates/club/club_list.jinja
msgid "inactive" msgid "There is no club in this website."
msgstr "inactif" msgstr "Il n'y a pas de club dans ce site web."
#: club/templates/club/club_members.jinja #: club/templates/club/club_members.jinja
msgid "Club members" msgid "Club members"
@@ -1901,6 +1881,10 @@ msgstr "L'AE"
msgid "AE's clubs" msgid "AE's clubs"
msgstr "Les clubs de L'AE" msgstr "Les clubs de L'AE"
#: core/templates/core/base/navbar.jinja
msgid "Others UTBM's Associations"
msgstr "Les autres associations de l'UTBM"
#: core/templates/core/base/navbar.jinja #: core/templates/core/base/navbar.jinja
msgid "Big event" msgid "Big event"
msgstr "Grandes Activités" msgstr "Grandes Activités"

View File

@@ -4,54 +4,54 @@ version = "3"
description = "Le web Sith de l'AE" description = "Le web Sith de l'AE"
readme = "README.md" readme = "README.md"
authors = [ authors = [
{ name = "Skia", email = "skia@hya.sk" }, { name = "Skia", email = "skia@hya.sk" },
{ name = "klmp200", email = "antoine@bartuccio.fr" }, { name = "klmp200", email = "antoine@bartuccio.fr" },
{ name = "Krophil", email = "pierre.brunet@krophil.fr" }, { name = "Krophil", email = "pierre.brunet@krophil.fr" },
{ name = "Maréchal", email = "thgirod@hotmail.com" }, { name = "Maréchal", email = "thgirod@hotmail.com" },
{ name = "Och", email = "francescowitz68@gmail.com" }, { name = "Och", email = "francescowitz68@gmail.com" },
{ name = "tleb", email = "tleb@openmailbox.org" }, { name = "tleb", email = "tleb@openmailbox.org" },
{ name = "Soldat", email = "ryan-68@live.fr" }, { name = "Soldat", email = "ryan-68@live.fr" },
{ name = "Nabos", email = "gnikwo@hotmail.com" }, { name = "Nabos", email = "gnikwo@hotmail.com" },
{ name = "Terre", email = "jbaptiste.lenglet+git@gmail.com" }, { name = "Terre", email = "jbaptiste.lenglet+git@gmail.com" },
{ name = "Lo-J", email = "renaudg779@gmail.com" }, { name = "Lo-J", email = "renaudg779@gmail.com" },
{ name = "Vial", email = "robin.trioux@utbm.fr" }, { name = "Vial", email = "robin.trioux@utbm.fr" },
] ]
license = { text = "GPL-3.0-only" } license = { text = "GPL-3.0-only" }
requires-python = "<4.0,>=3.12" requires-python = "<4.0,>=3.12"
dependencies = [ dependencies = [
"django>=5.2.12,<6.0.0", "django>=5.2.12,<6.0.0",
"django-ninja>=1.5.3,<6.0.0", "django-ninja>=1.5.3,<6.0.0",
"django-ninja-extra>=0.31.0", "django-ninja-extra>=0.31.0",
"Pillow>=12.1.1,<13.0.0", "Pillow>=12.1.1,<13.0.0",
"mistune>=3.2.0,<4.0.0", "mistune>=3.2.0,<4.0.0",
"django-jinja<3.0.0,>=2.11.0", "django-jinja<3.0.0,>=2.11.0",
"cryptography>=46.0.5,<47.0.0", "cryptography>=46.0.5,<47.0.0",
"django-phonenumber-field>=8.4.0,<9.0.0", "django-phonenumber-field>=8.4.0,<9.0.0",
"phonenumbers>=9.0.25,<10.0.0", "phonenumbers>=9.0.25,<10.0.0",
"reportlab>=4.4.10,<5.0.0", "reportlab>=4.4.10,<5.0.0",
"django-haystack<4.0.0,>=3.3.0", "django-haystack<4.0.0,>=3.3.0",
"xapian-haystack<4.0.0,>=3.1.0", "xapian-haystack<4.0.0,>=3.1.0",
"libsass<1.0.0,>=0.23.0", "libsass<1.0.0,>=0.23.0",
"django-ordered-model<4.0.0,>=3.7.4", "django-ordered-model<4.0.0,>=3.7.4",
"django-simple-captcha<1.0.0,>=0.6.3", "django-simple-captcha<1.0.0,>=0.6.3",
"python-dateutil<3.0.0.0,>=2.9.0.post0", "python-dateutil<3.0.0.0,>=2.9.0.post0",
"sentry-sdk>=2.54.0,<3.0.0", "sentry-sdk>=2.54.0,<3.0.0",
"jinja2<4.0.0,>=3.1.6", "jinja2<4.0.0,>=3.1.6",
"django-countries>=8.2.0,<9.0.0", "django-countries>=8.2.0,<9.0.0",
"dict2xml>=1.7.8,<2.0.0", "dict2xml>=1.7.8,<2.0.0",
"Sphinx<6,>=5", "Sphinx<6,>=5",
"tomli>=2.4.0,<3.0.0", "tomli>=2.4.0,<3.0.0",
"django-honeypot>=1.3.0,<2", "django-honeypot>=1.3.0,<2",
"pydantic-extra-types>=2.11.0,<3.0.0", "pydantic-extra-types>=2.11.0,<3.0.0",
"ical>=11.1.0,<12", "ical>=11.1.0,<12",
"redis[hiredis]>=5.3.0,<8.0.0", "redis[hiredis]>=5.3.0,<8.0.0",
"environs[django]>=14.5.0,<15.0.0", "environs[django]>=14.5.0,<15.0.0",
"requests>=2.32.5,<3.0.0", "requests>=2.32.5,<3.0.0",
"honcho>=2.0.0", "honcho>=2.0.0",
"psutil>=7.2.2,<8.0.0", "psutil>=7.2.2,<8.0.0",
"celery[redis]>=5.6.2,<7", "celery[redis]>=5.6.2,<7",
"django-celery-results>=2.5.1", "django-celery-results>=2.5.1",
"django-celery-beat>=2.9.0", "django-celery-beat>=2.9.0",
] ]
[project.urls] [project.urls]
@@ -59,73 +59,74 @@ homepage = "https://ae.utbm.fr/"
documentation = "https://sith-ae.readthedocs.io/" documentation = "https://sith-ae.readthedocs.io/"
[dependency-groups] [dependency-groups]
prod = [ prod = ["psycopg[c]>=3.3.3,<4.0.0"]
"psycopg[c]>=3.3.3,<4.0.0",
]
dev = [ dev = [
"django-debug-toolbar>=6.2.0,<7", "django-debug-toolbar>=6.2.0,<7",
"ipython>=9.11.0,<10.0.0", "ipython>=9.11.0,<10.0.0",
"pre-commit>=4.5.1,<5.0.0", "pre-commit>=4.5.1,<5.0.0",
"ruff>=0.15.5,<1.0.0", "ruff>=0.15.5,<1.0.0",
"djhtml>=3.0.10,<4.0.0", "djhtml>=3.0.10,<4.0.0",
"faker>=40.8.0,<41.0.0", "faker>=40.8.0,<41.0.0",
"rjsmin>=1.2.5,<2.0.0", "rjsmin>=1.2.5,<2.0.0",
] ]
tests = [ tests = [
"freezegun>=1.5.5,<2.0.0", "freezegun>=1.5.5,<2.0.0",
"pytest>=9.0.2,<10.0.0", "pytest>=9.0.2,<10.0.0",
"pytest-cov>=7.0.0,<8.0.0", "pytest-cov>=7.0.0,<8.0.0",
"pytest-django<5.0.0,>=4.12.0", "pytest-django<5.0.0,>=4.12.0",
"model-bakery<2.0.0,>=1.23.3", "model-bakery<2.0.0,>=1.23.3",
"beautifulsoup4>=4.14.3,<5", "beautifulsoup4>=4.14.3,<5",
"lxml>=6.0.2,<7", "lxml>=6.0.2,<7",
] ]
docs = [ docs = [
"mkdocs<2.0.0,>=1.6.1", "mkdocs<2.0.0,>=1.6.1",
"mkdocs-material>=9.7.5,<10.0.0", "mkdocs-material>=9.7.5,<10.0.0",
"mkdocstrings>=1.0.3,<2.0.0", "mkdocstrings>=1.0.3,<2.0.0",
"mkdocstrings-python>=2.0.3,<3.0.0", "mkdocstrings-python>=2.0.3,<3.0.0",
"mkdocs-include-markdown-plugin>=7.2.1,<8.0.0", "mkdocs-include-markdown-plugin>=7.2.1,<8.0.0",
] ]
[tool.uv] [tool.uv]
default-groups = ["dev", "tests", "docs"] default-groups = ["dev", "tests", "docs"]
[tool.xapian] [tool.xapian]
version = "1.4.29" version = "1.4.31"
core-sha256 = "fecf609ea2efdc8a64be369715aac733336a11f7480a6545244964ae6bc80811"
bindings-sha256 = "a38cc7ba4188cc0bd27dc7369f03906772047087a1c54f1b93355d5e9103c304"
[tool.ruff] [tool.ruff]
output-format = "concise" # makes ruff error logs easier to read output-format = "concise" # makes ruff error logs easier to read
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = [
"A", # shadowing of Python builtins "A", # shadowing of Python builtins
"B", "B",
"C4", # use comprehensions when possible "C4", # use comprehensions when possible
"DJ", # django-specific rules, "DJ", # django-specific rules,
"E", # pycodestyle (https://docs.astral.sh/ruff/rules/#pycodestyle-e-w) "E", # pycodestyle (https://docs.astral.sh/ruff/rules/#pycodestyle-e-w)
"ERA", # commented code "ERA", # commented code
"F", # pyflakes (https://docs.astral.sh/ruff/rules/#pyflakes-f) "F", # pyflakes (https://docs.astral.sh/ruff/rules/#pyflakes-f)
"FBT", # boolean trap "FBT", # boolean trap
"FLY", # f-string instead of str.join "FLY", # f-string instead of str.join
"FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb "FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb
"I", # isort "I", # isort
"INT", # gettext "INT", # gettext
"PERF", # performance "PERF", # performance
"PLW", # pylint warnings (https://docs.astral.sh/ruff/rules/#pylint-pl) "PLW", # pylint warnings (https://docs.astral.sh/ruff/rules/#pylint-pl)
"RUF", # Ruff specific rules "RUF", # Ruff specific rules
"SIM", # simplify (https://docs.astral.sh/ruff/rules/#flake8-simplify-sim) "SIM", # simplify (https://docs.astral.sh/ruff/rules/#flake8-simplify-sim)
"T100", # breakpoint() "T100", # breakpoint()
"T2", # print statements "T2", # print statements
"TCH", # type-checking block "TCH", # type-checking block
"UP008", # Use super() instead of super(__class__, self) "UP008", # Use super() instead of super(__class__, self)
"UP009", # utf-8 encoding declaration is unnecessary "UP009", # utf-8 encoding declaration is unnecessary
] ]
ignore = [ ignore = [
"DJ001", # null=True in CharField/TextField. this one would require a migration "DJ001", # null=True in CharField/TextField. this one would require a migration
"E501", # line too long. The rule is too harsh, and the formatter deals with it in most cases "E501", # line too long. The rule is too harsh, and the formatter deals with it in most cases
"RUF012" # mutable class attributes. This rule doesn't integrate well with django "RUF012", # mutable class attributes. This rule doesn't integrate well with django
] ]
[tool.ruff.lint.pydocstyle] [tool.ruff.lint.pydocstyle]
@@ -141,4 +142,4 @@ sith = "sith.pytest"
[tool.pytest.ini_options] [tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "sith.settings" DJANGO_SETTINGS_MODULE = "sith.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"] python_files = ["tests.py", "test_*.py", "*_tests.py"]
markers = ["slow"] markers = ["slow"]