mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-01 03:48:04 +00:00
Merge pull request #898 from ae-utbm/taiste
Complete webpack migration, introduction of tom select, better SAS moderation workflow, more ruff and bugfixes
This commit is contained in:
commit
e6f25fb707
@ -15,7 +15,16 @@
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from accounting.models import *
|
from accounting.models import (
|
||||||
|
AccountingType,
|
||||||
|
BankAccount,
|
||||||
|
ClubAccount,
|
||||||
|
Company,
|
||||||
|
GeneralJournal,
|
||||||
|
Label,
|
||||||
|
Operation,
|
||||||
|
SimplifiedAccountingType,
|
||||||
|
)
|
||||||
|
|
||||||
admin.site.register(BankAccount)
|
admin.site.register(BankAccount)
|
||||||
admin.site.register(ClubAccount)
|
admin.site.register(ClubAccount)
|
||||||
|
@ -82,9 +82,7 @@ class Company(models.Model):
|
|||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
@ -127,9 +125,7 @@ class BankAccount(models.Model):
|
|||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
m = self.club.get_membership_for(user)
|
m = self.club.get_membership_for(user)
|
||||||
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ClubAccount(models.Model):
|
class ClubAccount(models.Model):
|
||||||
@ -161,29 +157,20 @@ class ClubAccount(models.Model):
|
|||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
m = self.club.get_membership_for(user)
|
m = self.club.get_membership_for(user)
|
||||||
if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
"""Check if that object can be viewed by the given user."""
|
"""Check if that object can be viewed by the given user."""
|
||||||
m = self.club.get_membership_for(user)
|
m = self.club.get_membership_for(user)
|
||||||
if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has_open_journal(self):
|
def has_open_journal(self):
|
||||||
for j in self.journals.all():
|
return self.journals.filter(closed=False).exists()
|
||||||
if not j.closed:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_open_journal(self):
|
def get_open_journal(self):
|
||||||
return self.journals.filter(closed=False).first()
|
return self.journals.filter(closed=False).first()
|
||||||
@ -228,17 +215,13 @@ class GeneralJournal(models.Model):
|
|||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.club_account.can_be_edited_by(user):
|
return self.club_account.can_be_edited_by(user)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
||||||
return True
|
return True
|
||||||
if self.club_account.can_be_edited_by(user):
|
return self.club_account.can_be_edited_by(user)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
return self.club_account.can_be_viewed_by(user)
|
return self.club_account.can_be_viewed_by(user)
|
||||||
@ -416,9 +399,7 @@ class Operation(models.Model):
|
|||||||
if self.journal.closed:
|
if self.journal.closed:
|
||||||
return False
|
return False
|
||||||
m = self.journal.club_account.club.get_membership_for(user)
|
m = self.journal.club_account.club.get_membership_for(user)
|
||||||
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
@ -427,9 +408,7 @@ class Operation(models.Model):
|
|||||||
if self.journal.closed:
|
if self.journal.closed:
|
||||||
return False
|
return False
|
||||||
m = self.journal.club_account.club.get_membership_for(user)
|
m = self.journal.club_account.club.get_membership_for(user)
|
||||||
if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
|
return m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class AccountingType(models.Model):
|
class AccountingType(models.Model):
|
||||||
@ -472,9 +451,7 @@ class AccountingType(models.Model):
|
|||||||
"""Check if that object can be edited by the given user."""
|
"""Check if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class SimplifiedAccountingType(models.Model):
|
class SimplifiedAccountingType(models.Model):
|
||||||
|
@ -102,7 +102,7 @@ class TestOperation(TestCase):
|
|||||||
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
||||||
)
|
)
|
||||||
at.save()
|
at.save()
|
||||||
l = Label.objects.create(club_account=self.journal.club_account, name="bob")
|
label = Label.objects.create(club_account=self.journal.club_account, name="bob")
|
||||||
self.client.force_login(User.objects.get(username="comptable"))
|
self.client.force_login(User.objects.get(username="comptable"))
|
||||||
self.op1 = Operation(
|
self.op1 = Operation(
|
||||||
journal=self.journal,
|
journal=self.journal,
|
||||||
@ -111,7 +111,7 @@ class TestOperation(TestCase):
|
|||||||
remark="Test bilan",
|
remark="Test bilan",
|
||||||
mode="CASH",
|
mode="CASH",
|
||||||
done=True,
|
done=True,
|
||||||
label=l,
|
label=label,
|
||||||
accounting_type=at,
|
accounting_type=at,
|
||||||
target_type="USER",
|
target_type="USER",
|
||||||
target_id=self.skia.id,
|
target_id=self.skia.id,
|
||||||
@ -124,7 +124,7 @@ class TestOperation(TestCase):
|
|||||||
remark="Test bilan",
|
remark="Test bilan",
|
||||||
mode="CASH",
|
mode="CASH",
|
||||||
done=True,
|
done=True,
|
||||||
label=l,
|
label=label,
|
||||||
accounting_type=at,
|
accounting_type=at,
|
||||||
target_type="USER",
|
target_type="USER",
|
||||||
target_id=self.skia.id,
|
target_id=self.skia.id,
|
||||||
|
@ -15,7 +15,41 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from accounting.views import *
|
from accounting.views import (
|
||||||
|
AccountingTypeCreateView,
|
||||||
|
AccountingTypeEditView,
|
||||||
|
AccountingTypeListView,
|
||||||
|
BankAccountCreateView,
|
||||||
|
BankAccountDeleteView,
|
||||||
|
BankAccountDetailView,
|
||||||
|
BankAccountEditView,
|
||||||
|
BankAccountListView,
|
||||||
|
ClubAccountCreateView,
|
||||||
|
ClubAccountDeleteView,
|
||||||
|
ClubAccountDetailView,
|
||||||
|
ClubAccountEditView,
|
||||||
|
CompanyCreateView,
|
||||||
|
CompanyEditView,
|
||||||
|
CompanyListView,
|
||||||
|
JournalAccountingStatementView,
|
||||||
|
JournalCreateView,
|
||||||
|
JournalDeleteView,
|
||||||
|
JournalDetailView,
|
||||||
|
JournalEditView,
|
||||||
|
JournalNatureStatementView,
|
||||||
|
JournalPersonStatementView,
|
||||||
|
LabelCreateView,
|
||||||
|
LabelDeleteView,
|
||||||
|
LabelEditView,
|
||||||
|
LabelListView,
|
||||||
|
OperationCreateView,
|
||||||
|
OperationEditView,
|
||||||
|
OperationPDFView,
|
||||||
|
RefoundAccountView,
|
||||||
|
SimplifiedAccountingTypeCreateView,
|
||||||
|
SimplifiedAccountingTypeEditView,
|
||||||
|
SimplifiedAccountingTypeListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Accounting types
|
# Accounting types
|
||||||
|
@ -182,7 +182,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "parent" in self.request.GET.keys():
|
if "parent" in self.request.GET:
|
||||||
obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
ret["bank_account"] = obj.id
|
ret["bank_account"] = obj.id
|
||||||
@ -264,7 +264,7 @@ class JournalCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "parent" in self.request.GET.keys():
|
if "parent" in self.request.GET:
|
||||||
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
ret["club_account"] = obj.id
|
ret["club_account"] = obj.id
|
||||||
@ -362,7 +362,7 @@ class OperationForm(forms.ModelForm):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.cleaned_data = super().clean()
|
self.cleaned_data = super().clean()
|
||||||
if "target_type" in self.cleaned_data.keys():
|
if "target_type" in self.cleaned_data:
|
||||||
if (
|
if (
|
||||||
self.cleaned_data.get("user") is None
|
self.cleaned_data.get("user") is None
|
||||||
and self.cleaned_data.get("club") is None
|
and self.cleaned_data.get("club") is None
|
||||||
@ -633,19 +633,17 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
|
|||||||
ret = collections.OrderedDict()
|
ret = collections.OrderedDict()
|
||||||
statement = collections.OrderedDict()
|
statement = collections.OrderedDict()
|
||||||
total_sum = 0
|
total_sum = 0
|
||||||
for sat in [None] + list(
|
for sat in [
|
||||||
SimplifiedAccountingType.objects.order_by("label").all()
|
None,
|
||||||
):
|
*list(SimplifiedAccountingType.objects.order_by("label")),
|
||||||
|
]:
|
||||||
amount = queryset.filter(
|
amount = queryset.filter(
|
||||||
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
|
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
|
||||||
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
|
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
|
||||||
if sat:
|
label = sat.label if sat is not None else ""
|
||||||
sat = sat.label
|
|
||||||
else:
|
|
||||||
sat = ""
|
|
||||||
if amount:
|
if amount:
|
||||||
total_sum += amount
|
total_sum += amount
|
||||||
statement[sat] = amount
|
statement[label] = amount
|
||||||
ret[movement_type] = statement
|
ret[movement_type] = statement
|
||||||
ret[movement_type + "_sum"] = total_sum
|
ret[movement_type + "_sum"] = total_sum
|
||||||
return ret
|
return ret
|
||||||
@ -668,15 +666,12 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
|
|||||||
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
|
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
|
||||||
)
|
)
|
||||||
statement[_("No label operations")] = no_label_statement
|
statement[_("No label operations")] = no_label_statement
|
||||||
for l in labels:
|
for label in labels:
|
||||||
l_stmt = collections.OrderedDict()
|
l_stmt = collections.OrderedDict()
|
||||||
l_stmt.update(
|
journals = self.object.operations.filter(label=label).all()
|
||||||
self.statement(self.object.operations.filter(label=l).all(), "CREDIT")
|
l_stmt.update(self.statement(journals, "CREDIT"))
|
||||||
)
|
l_stmt.update(self.statement(journals, "DEBIT"))
|
||||||
l_stmt.update(
|
statement[label] = l_stmt
|
||||||
self.statement(self.object.operations.filter(label=l).all(), "DEBIT")
|
|
||||||
)
|
|
||||||
statement[l] = l_stmt
|
|
||||||
return statement
|
return statement
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -798,7 +793,7 @@ class LabelCreateView(
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "parent" in self.request.GET.keys():
|
if "parent" in self.request.GET:
|
||||||
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first()
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
ret["club_account"] = obj.id
|
ret["club_account"] = obj.id
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false,
|
"ignoreUnknown": false,
|
||||||
"ignore": ["core/static/vendored", "*.min.*", "staticfiles/generated"]
|
"ignore": ["*.min.*", "staticfiles/generated"]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
@ -111,8 +111,8 @@ class MailingForm(forms.Form):
|
|||||||
"""Convert given users into real users and check their validity."""
|
"""Convert given users into real users and check their validity."""
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
users = []
|
users = []
|
||||||
for user in cleaned_data["subscription_users"]:
|
for user_id in cleaned_data["subscription_users"]:
|
||||||
user = User.objects.filter(id=user).first()
|
user = User.objects.filter(id=user_id).first()
|
||||||
if not user:
|
if not user:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("One of the selected users doesn't exist"), code="invalid"
|
_("One of the selected users doesn't exist"), code="invalid"
|
||||||
@ -128,7 +128,7 @@ class MailingForm(forms.Form):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
if not "action" in cleaned_data:
|
if "action" not in cleaned_data:
|
||||||
# If there is no action provided, we can stop here
|
# If there is no action provided, we can stop here
|
||||||
raise forms.ValidationError(_("An action is required"), code="invalid")
|
raise forms.ValidationError(_("An action is required"), code="invalid")
|
||||||
|
|
||||||
|
@ -389,9 +389,7 @@ class Membership(models.Model):
|
|||||||
if user.is_root or user.is_board_member:
|
if user.is_root or user.is_board_member:
|
||||||
return True
|
return True
|
||||||
membership = self.club.get_membership_for(user)
|
membership = self.club.get_membership_for(user)
|
||||||
if membership is not None and membership.role >= self.role:
|
return membership is not None and membership.role >= self.role
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
|
51
club/urls.py
51
club/urls.py
@ -24,7 +24,32 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from club.views import *
|
from club.views import (
|
||||||
|
ClubCreateView,
|
||||||
|
ClubEditPropView,
|
||||||
|
ClubEditView,
|
||||||
|
ClubListView,
|
||||||
|
ClubMailingView,
|
||||||
|
ClubMembersView,
|
||||||
|
ClubOldMembersView,
|
||||||
|
ClubPageEditView,
|
||||||
|
ClubPageHistView,
|
||||||
|
ClubRevView,
|
||||||
|
ClubSellingCSVView,
|
||||||
|
ClubSellingView,
|
||||||
|
ClubStatView,
|
||||||
|
ClubToolsView,
|
||||||
|
ClubView,
|
||||||
|
MailingAutoGenerationView,
|
||||||
|
MailingDeleteView,
|
||||||
|
MailingSubscriptionDeleteView,
|
||||||
|
MembershipDeleteView,
|
||||||
|
MembershipSetOldView,
|
||||||
|
PosterCreateView,
|
||||||
|
PosterDeleteView,
|
||||||
|
PosterEditView,
|
||||||
|
PosterListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", ClubListView.as_view(), name="club_list"),
|
path("", ClubListView.as_view(), name="club_list"),
|
||||||
@ -32,32 +57,20 @@ urlpatterns = [
|
|||||||
path("stats/", ClubStatView.as_view(), name="club_stats"),
|
path("stats/", ClubStatView.as_view(), name="club_stats"),
|
||||||
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
|
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/rev/<int:rev_id>/",
|
"<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev"
|
||||||
ClubRevView.as_view(),
|
|
||||||
name="club_view_rev",
|
|
||||||
),
|
),
|
||||||
path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"),
|
path("<int:club_id>/hist/", ClubPageHistView.as_view(), name="club_hist"),
|
||||||
path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"),
|
path("<int:club_id>/edit/", ClubEditView.as_view(), name="club_edit"),
|
||||||
path(
|
path("<int:club_id>/edit/page/", ClubPageEditView.as_view(), name="club_edit_page"),
|
||||||
"<int:club_id>/edit/page/",
|
|
||||||
ClubPageEditView.as_view(),
|
|
||||||
name="club_edit_page",
|
|
||||||
),
|
|
||||||
path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"),
|
path("<int:club_id>/members/", ClubMembersView.as_view(), name="club_members"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/elderlies/",
|
"<int:club_id>/elderlies/",
|
||||||
ClubOldMembersView.as_view(),
|
ClubOldMembersView.as_view(),
|
||||||
name="club_old_members",
|
name="club_old_members",
|
||||||
),
|
),
|
||||||
|
path("<int:club_id>/sellings/", ClubSellingView.as_view(), name="club_sellings"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/sellings/",
|
"<int:club_id>/sellings/csv/", ClubSellingCSVView.as_view(), name="sellings_csv"
|
||||||
ClubSellingView.as_view(),
|
|
||||||
name="club_sellings",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"<int:club_id>/sellings/csv/",
|
|
||||||
ClubSellingCSVView.as_view(),
|
|
||||||
name="sellings_csv",
|
|
||||||
),
|
),
|
||||||
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
|
path("<int:club_id>/prop/", ClubEditPropView.as_view(), name="club_prop"),
|
||||||
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
|
path("<int:club_id>/tools/", ClubToolsView.as_view(), name="tools"),
|
||||||
@ -89,9 +102,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
|
path("<int:club_id>/poster/", PosterListView.as_view(), name="poster_list"),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/poster/create/",
|
"<int:club_id>/poster/create/", PosterCreateView.as_view(), name="poster_create"
|
||||||
PosterCreateView.as_view(),
|
|
||||||
name="poster_create",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<int:club_id>/poster/<int:poster_id>/edit/",
|
"<int:club_id>/poster/<int:poster_id>/edit/",
|
||||||
|
@ -397,7 +397,8 @@ class ClubSellingCSVView(ClubSellingView):
|
|||||||
row.append(selling.customer.user.get_display_name())
|
row.append(selling.customer.user.get_display_name())
|
||||||
else:
|
else:
|
||||||
row.append("")
|
row.append("")
|
||||||
row = row + [
|
row = [
|
||||||
|
*row,
|
||||||
selling.label,
|
selling.label,
|
||||||
selling.quantity,
|
selling.quantity,
|
||||||
selling.quantity * selling.unit_price,
|
selling.quantity * selling.unit_price,
|
||||||
@ -408,7 +409,7 @@ class ClubSellingCSVView(ClubSellingView):
|
|||||||
row.append(selling.product.purchase_price)
|
row.append(selling.product.purchase_price)
|
||||||
row.append(selling.product.selling_price - selling.product.purchase_price)
|
row.append(selling.product.selling_price - selling.product.purchase_price)
|
||||||
else:
|
else:
|
||||||
row = row + ["", "", ""]
|
row = [*row, "", "", ""]
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
@ -622,9 +623,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
|||||||
def remove_subscription(self, cleaned_data):
|
def remove_subscription(self, cleaned_data):
|
||||||
"""Remove specified users from a mailing list."""
|
"""Remove specified users from a mailing list."""
|
||||||
fields = [
|
fields = [
|
||||||
cleaned_data[key]
|
val for key, val in cleaned_data.items() if key.startswith("removal_")
|
||||||
for key in cleaned_data.keys()
|
|
||||||
if key.startswith("removal_")
|
|
||||||
]
|
]
|
||||||
for field in fields:
|
for field in fields:
|
||||||
for sub in field:
|
for sub in field:
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
from com.models import *
|
from com.models import News, Poster, Screen, Sith, Weekmail
|
||||||
|
|
||||||
|
|
||||||
@admin.register(News)
|
@admin.register(News)
|
||||||
|
53
com/urls.py
53
com/urls.py
@ -16,7 +16,36 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from club.views import MailingDeleteView
|
from club.views import MailingDeleteView
|
||||||
from com.views import *
|
from com.views import (
|
||||||
|
AlertMsgEditView,
|
||||||
|
InfoMsgEditView,
|
||||||
|
MailingListAdminView,
|
||||||
|
MailingModerateView,
|
||||||
|
NewsAdminListView,
|
||||||
|
NewsCreateView,
|
||||||
|
NewsDeleteView,
|
||||||
|
NewsDetailView,
|
||||||
|
NewsEditView,
|
||||||
|
NewsListView,
|
||||||
|
NewsModerateView,
|
||||||
|
PosterCreateView,
|
||||||
|
PosterDeleteView,
|
||||||
|
PosterEditView,
|
||||||
|
PosterListView,
|
||||||
|
PosterModerateListView,
|
||||||
|
PosterModerateView,
|
||||||
|
ScreenCreateView,
|
||||||
|
ScreenDeleteView,
|
||||||
|
ScreenEditView,
|
||||||
|
ScreenListView,
|
||||||
|
ScreenSlideshowView,
|
||||||
|
WeekmailArticleCreateView,
|
||||||
|
WeekmailArticleDeleteView,
|
||||||
|
WeekmailArticleEditView,
|
||||||
|
WeekmailDestinationEditView,
|
||||||
|
WeekmailEditView,
|
||||||
|
WeekmailPreviewView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
|
path("sith/edit/alert/", AlertMsgEditView.as_view(), name="alert_edit"),
|
||||||
@ -46,15 +75,9 @@ urlpatterns = [
|
|||||||
path("news/", NewsListView.as_view(), name="news_list"),
|
path("news/", NewsListView.as_view(), name="news_list"),
|
||||||
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
|
path("news/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
|
||||||
path("news/create/", NewsCreateView.as_view(), name="news_new"),
|
path("news/create/", NewsCreateView.as_view(), name="news_new"),
|
||||||
|
path("news/<int:news_id>/delete/", NewsDeleteView.as_view(), name="news_delete"),
|
||||||
path(
|
path(
|
||||||
"news/<int:news_id>/delete/",
|
"news/<int:news_id>/moderate/", NewsModerateView.as_view(), name="news_moderate"
|
||||||
NewsDeleteView.as_view(),
|
|
||||||
name="news_delete",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"news/<int:news_id>/moderate/",
|
|
||||||
NewsModerateView.as_view(),
|
|
||||||
name="news_moderate",
|
|
||||||
),
|
),
|
||||||
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
|
path("news/<int:news_id>/edit/", NewsEditView.as_view(), name="news_edit"),
|
||||||
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
|
path("news/<int:news_id>/", NewsDetailView.as_view(), name="news_detail"),
|
||||||
@ -71,11 +94,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("poster/", PosterListView.as_view(), name="poster_list"),
|
path("poster/", PosterListView.as_view(), name="poster_list"),
|
||||||
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
|
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
|
||||||
path(
|
path("poster/<int:poster_id>/edit/", PosterEditView.as_view(), name="poster_edit"),
|
||||||
"poster/<int:poster_id>/edit/",
|
|
||||||
PosterEditView.as_view(),
|
|
||||||
name="poster_edit",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"poster/<int:poster_id>/delete/",
|
"poster/<int:poster_id>/delete/",
|
||||||
PosterDeleteView.as_view(),
|
PosterDeleteView.as_view(),
|
||||||
@ -98,11 +117,7 @@ urlpatterns = [
|
|||||||
ScreenSlideshowView.as_view(),
|
ScreenSlideshowView.as_view(),
|
||||||
name="screen_slideshow",
|
name="screen_slideshow",
|
||||||
),
|
),
|
||||||
path(
|
path("screen/<int:screen_id>/edit/", ScreenEditView.as_view(), name="screen_edit"),
|
||||||
"screen/<int:screen_id>/edit/",
|
|
||||||
ScreenEditView.as_view(),
|
|
||||||
name="screen_edit",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"screen/<int:screen_id>/delete/",
|
"screen/<int:screen_id>/delete/",
|
||||||
ScreenDeleteView.as_view(),
|
ScreenDeleteView.as_view(),
|
||||||
|
47
com/views.py
47
com/views.py
@ -86,12 +86,11 @@ class PosterForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop("user", None)
|
self.user = kwargs.pop("user", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.user:
|
if self.user and not self.user.is_com_admin:
|
||||||
if not self.user.is_com_admin:
|
self.fields["club"].queryset = Club.objects.filter(
|
||||||
self.fields["club"].queryset = Club.objects.filter(
|
id__in=self.user.clubs_with_rights
|
||||||
id__in=self.user.clubs_with_rights
|
)
|
||||||
)
|
self.fields.pop("display_time")
|
||||||
self.fields.pop("display_time")
|
|
||||||
|
|
||||||
|
|
||||||
class ComTabsMixin(TabedViewMixin):
|
class ComTabsMixin(TabedViewMixin):
|
||||||
@ -312,7 +311,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid() and "preview" not in request.POST.keys():
|
if form.is_valid() and "preview" not in request.POST:
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
self.object = form.instance
|
self.object = form.instance
|
||||||
@ -354,13 +353,13 @@ class NewsModerateView(CanEditMixin, SingleObjectMixin):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "remove" in request.GET.keys():
|
if "remove" in request.GET:
|
||||||
self.object.is_moderated = False
|
self.object.is_moderated = False
|
||||||
else:
|
else:
|
||||||
self.object.is_moderated = True
|
self.object.is_moderated = True
|
||||||
self.object.moderator = request.user
|
self.object.moderator = request.user
|
||||||
self.object.save()
|
self.object.save()
|
||||||
if "next" in self.request.GET.keys():
|
if "next" in self.request.GET:
|
||||||
return redirect(self.request.GET["next"])
|
return redirect(self.request.GET["next"])
|
||||||
return redirect("com:news_admin_list")
|
return redirect("com:news_admin_list")
|
||||||
|
|
||||||
@ -424,7 +423,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai
|
|||||||
try:
|
try:
|
||||||
self.object.send() # This should fail
|
self.object.send() # This should fail
|
||||||
except SMTPRecipientsRefused as e:
|
except SMTPRecipientsRefused as e:
|
||||||
users = User.objects.filter(email__in=e.recipients.keys())
|
users = User.objects.filter(email__in=e.recipients)
|
||||||
for u in users:
|
for u in users:
|
||||||
u.preferences.receive_weekmail = False
|
u.preferences.receive_weekmail = False
|
||||||
u.preferences.save()
|
u.preferences.save()
|
||||||
@ -471,7 +470,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "up_article" in request.GET.keys():
|
if "up_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
|
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
|
||||||
)
|
)
|
||||||
@ -483,7 +482,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
art.save()
|
art.save()
|
||||||
prev_art.save()
|
prev_art.save()
|
||||||
self.quick_notif_list += ["qn_success"]
|
self.quick_notif_list += ["qn_success"]
|
||||||
if "down_article" in request.GET.keys():
|
if "down_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
|
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
|
||||||
)
|
)
|
||||||
@ -495,7 +494,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
art.save()
|
art.save()
|
||||||
next_art.save()
|
next_art.save()
|
||||||
self.quick_notif_list += ["qn_success"]
|
self.quick_notif_list += ["qn_success"]
|
||||||
if "add_article" in request.GET.keys():
|
if "add_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["add_article"], weekmail=None
|
WeekmailArticle, id=request.GET["add_article"], weekmail=None
|
||||||
)
|
)
|
||||||
@ -504,7 +503,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
|
|||||||
art.rank += 1
|
art.rank += 1
|
||||||
art.save()
|
art.save()
|
||||||
self.quick_notif_list += ["qn_success"]
|
self.quick_notif_list += ["qn_success"]
|
||||||
if "del_article" in request.GET.keys():
|
if "del_article" in request.GET:
|
||||||
art = get_object_or_404(
|
art = get_object_or_404(
|
||||||
WeekmailArticle, id=request.GET["del_article"], weekmail=self.object
|
WeekmailArticle, id=request.GET["del_article"], weekmail=self.object
|
||||||
)
|
)
|
||||||
@ -571,7 +570,7 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView):
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if form.is_valid() and not "preview" in request.POST.keys():
|
if form.is_valid() and "preview" not in request.POST:
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
@ -689,19 +688,13 @@ class PosterEditBaseView(UpdateView):
|
|||||||
template_name = "com/poster_edit.jinja"
|
template_name = "com/poster_edit.jinja"
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {}
|
return {
|
||||||
try:
|
"date_begin": self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
init["date_begin"] = self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S")
|
"date_end": self.object.date_end.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
except Exception:
|
}
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["date_end"] = self.object.date_end.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return init
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if "club_id" in kwargs and kwargs["club_id"]:
|
if kwargs.get("club_id"):
|
||||||
try:
|
try:
|
||||||
self.club = Club.objects.get(pk=kwargs["club_id"])
|
self.club = Club.objects.get(pk=kwargs["club_id"])
|
||||||
except Club.DoesNotExist as e:
|
except Club.DoesNotExist as e:
|
||||||
@ -737,7 +730,7 @@ class PosterDeleteBaseView(DeleteView):
|
|||||||
template_name = "core/delete_confirm.jinja"
|
template_name = "core/delete_confirm.jinja"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if "club_id" in kwargs and kwargs["club_id"]:
|
if kwargs.get("club_id"):
|
||||||
try:
|
try:
|
||||||
self.club = Club.objects.get(pk=kwargs["club_id"])
|
self.club = Club.objects.get(pk=kwargs["club_id"])
|
||||||
except Club.DoesNotExist as e:
|
except Club.DoesNotExist as e:
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
import re
|
|
||||||
from subprocess import PIPE, Popen, TimeoutExpired
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
# see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
|
||||||
# added "v?"
|
|
||||||
# Please note that this does not match the version of the three.js library.
|
|
||||||
# Hence, you shall have to check this one by yourself
|
|
||||||
semver_regex = re.compile(
|
|
||||||
r"^v?"
|
|
||||||
r"(?P<major>\d+)"
|
|
||||||
r"\.(?P<minor>\d+)"
|
|
||||||
r"\.(?P<patch>\d+)"
|
|
||||||
r"(?:-(?P<prerelease>(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
|
|
||||||
r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Checks the front dependencies are up to date."
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
deps = settings.SITH_FRONT_DEP_VERSIONS
|
|
||||||
|
|
||||||
processes = {
|
|
||||||
url: create_process(url)
|
|
||||||
for url in deps.keys()
|
|
||||||
if parse_semver(deps[url]) is not None
|
|
||||||
}
|
|
||||||
|
|
||||||
for url, process in processes.items():
|
|
||||||
try:
|
|
||||||
stdout, stderr = process.communicate(timeout=15)
|
|
||||||
except TimeoutExpired:
|
|
||||||
process.kill()
|
|
||||||
self.stderr.write(self.style.WARNING("{}: timeout".format(url)))
|
|
||||||
continue
|
|
||||||
# error, notice, warning
|
|
||||||
|
|
||||||
stdout = stdout.decode("utf-8")
|
|
||||||
stderr = stderr.decode("utf-8")
|
|
||||||
|
|
||||||
if stderr != "":
|
|
||||||
self.stderr.write(self.style.WARNING(stderr.strip()))
|
|
||||||
continue
|
|
||||||
|
|
||||||
# get all tags, parse them as semvers and find the biggest
|
|
||||||
tags = list_tags(stdout)
|
|
||||||
tags = map(parse_semver, tags)
|
|
||||||
tags = filter(lambda tag: tag is not None, tags)
|
|
||||||
latest_version = max(tags)
|
|
||||||
|
|
||||||
# cannot fail as those which fail are filtered in the processes dict creation
|
|
||||||
current_version = parse_semver(deps[url])
|
|
||||||
assert current_version is not None
|
|
||||||
|
|
||||||
if latest_version == current_version:
|
|
||||||
msg = "{}: {}".format(url, semver_to_s(current_version))
|
|
||||||
self.stdout.write(self.style.SUCCESS(msg))
|
|
||||||
else:
|
|
||||||
msg = "{}: {} < {}".format(
|
|
||||||
url, semver_to_s(current_version), semver_to_s(latest_version)
|
|
||||||
)
|
|
||||||
self.stdout.write(self.style.ERROR(msg))
|
|
||||||
|
|
||||||
|
|
||||||
def create_process(url):
|
|
||||||
"""Spawn a "git ls-remote --tags" child process."""
|
|
||||||
return Popen(["git", "ls-remote", "--tags", url], stdout=PIPE, stderr=PIPE)
|
|
||||||
|
|
||||||
|
|
||||||
def list_tags(s):
|
|
||||||
"""Parses "git ls-remote --tags" output. Takes a string."""
|
|
||||||
tag_prefix = "refs/tags/"
|
|
||||||
|
|
||||||
for line in s.strip().split("\n"):
|
|
||||||
# an example line could be:
|
|
||||||
# "1f41e2293f9c3c1962d2d97afa666207b98a222a\trefs/tags/foo"
|
|
||||||
parts = line.split("\t")
|
|
||||||
|
|
||||||
# check we have a commit ID (SHA-1 hash) and a tag name
|
|
||||||
assert len(parts) == 2
|
|
||||||
assert len(parts[0]) == 40
|
|
||||||
assert parts[1].startswith(tag_prefix)
|
|
||||||
|
|
||||||
# avoid duplicates (a peeled tag will appear twice: as "name" and as "name^{}")
|
|
||||||
if not parts[1].endswith("^{}"):
|
|
||||||
yield parts[1][len(tag_prefix) :]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_semver(s) -> tuple[int, int, int] | None:
|
|
||||||
"""Parse a semver string.
|
|
||||||
|
|
||||||
See https://semver.org
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A tuple, if the parsing was successful, else None.
|
|
||||||
In the latter case, it must probably be a prerelease
|
|
||||||
or include build metadata.
|
|
||||||
"""
|
|
||||||
m = semver_regex.match(s)
|
|
||||||
|
|
||||||
if (
|
|
||||||
m is None
|
|
||||||
or m.group("prerelease") is not None
|
|
||||||
or m.group("buildmetadata") is not None
|
|
||||||
):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return int(m.group("major")), int(m.group("minor")), int(m.group("patch"))
|
|
||||||
|
|
||||||
|
|
||||||
def semver_to_s(t):
|
|
||||||
"""Expects a 3-tuple with ints and turns it into a string of type "1.2.3"."""
|
|
||||||
return "{}.{}.{}".format(t[0], t[1], t[2])
|
|
@ -67,5 +67,6 @@ class Command(BaseCommand):
|
|||||||
subprocess.run(
|
subprocess.run(
|
||||||
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
||||||
env=dict(os.environ),
|
env=dict(os.environ),
|
||||||
).check_returncode()
|
check=True,
|
||||||
|
)
|
||||||
self.stdout.write("Installation success")
|
self.stdout.write("Installation success")
|
||||||
|
@ -934,7 +934,7 @@ Welcome to the wiki page!
|
|||||||
# Adding subscription for sli
|
# Adding subscription for sli
|
||||||
s = Subscription(
|
s = Subscription(
|
||||||
member=User.objects.filter(pk=sli.pk).first(),
|
member=User.objects.filter(pk=sli.pk).first(),
|
||||||
subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
|
subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())),
|
||||||
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||||
)
|
)
|
||||||
s.subscription_start = s.compute_start()
|
s.subscription_start = s.compute_start()
|
||||||
@ -947,7 +947,7 @@ Welcome to the wiki page!
|
|||||||
# Adding subscription for Krophil
|
# Adding subscription for Krophil
|
||||||
s = Subscription(
|
s = Subscription(
|
||||||
member=User.objects.filter(pk=krophil.pk).first(),
|
member=User.objects.filter(pk=krophil.pk).first(),
|
||||||
subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
|
subscription_type=next(iter(settings.SITH_SUBSCRIPTIONS.keys())),
|
||||||
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||||
)
|
)
|
||||||
s.subscription_start = s.compute_start()
|
s.subscription_start = s.compute_start()
|
||||||
|
@ -217,9 +217,9 @@ class Command(BaseCommand):
|
|||||||
UV.objects.bulk_create(uvs, ignore_conflicts=True)
|
UV.objects.bulk_create(uvs, ignore_conflicts=True)
|
||||||
|
|
||||||
def create_products(self):
|
def create_products(self):
|
||||||
categories = []
|
categories = [
|
||||||
for _ in range(10):
|
ProductType(name=self.faker.text(max_nb_chars=30)) for _ in range(10)
|
||||||
categories.append(ProductType(name=self.faker.text(max_nb_chars=30)))
|
]
|
||||||
ProductType.objects.bulk_create(categories)
|
ProductType.objects.bulk_create(categories)
|
||||||
categories = list(
|
categories = list(
|
||||||
ProductType.objects.filter(name__in=[c.name for c in categories])
|
ProductType.objects.filter(name__in=[c.name for c in categories])
|
||||||
@ -254,16 +254,16 @@ class Command(BaseCommand):
|
|||||||
archived=bool(random.random() > 0.7),
|
archived=bool(random.random() > 0.7),
|
||||||
)
|
)
|
||||||
products.append(product)
|
products.append(product)
|
||||||
for group in random.sample(groups, k=random.randint(0, 3)):
|
# there will be products without buying groups
|
||||||
# there will be products without buying groups
|
# but there are also such products in the real database
|
||||||
# but there are also such products in the real database
|
buying_groups.extend(
|
||||||
buying_groups.append(
|
Product.buying_groups.through(product=product, group=group)
|
||||||
Product.buying_groups.through(product=product, group=group)
|
for group in random.sample(groups, k=random.randint(0, 3))
|
||||||
)
|
)
|
||||||
for counter in random.sample(counters, random.randint(0, 4)):
|
selling_places.extend(
|
||||||
selling_places.append(
|
Counter.products.through(counter=counter, product=product)
|
||||||
Counter.products.through(counter=counter, product=product)
|
for counter in random.sample(counters, random.randint(0, 4))
|
||||||
)
|
)
|
||||||
Product.objects.bulk_create(products)
|
Product.objects.bulk_create(products)
|
||||||
Product.buying_groups.through.objects.bulk_create(buying_groups)
|
Product.buying_groups.through.objects.bulk_create(buying_groups)
|
||||||
Counter.products.through.objects.bulk_create(selling_places)
|
Counter.products.through.objects.bulk_create(selling_places)
|
||||||
|
@ -174,7 +174,7 @@ def validate_promo(value: int) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_group(*, pk: int = None, name: str = None) -> Group | None:
|
def get_group(*, pk: int | None = None, name: str | None = None) -> Group | None:
|
||||||
"""Search for a group by its primary key or its name.
|
"""Search for a group by its primary key or its name.
|
||||||
Either one of the two must be set.
|
Either one of the two must be set.
|
||||||
|
|
||||||
@ -445,7 +445,7 @@ class User(AbstractBaseUser):
|
|||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
|
def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
|
||||||
"""Check if this user is in the given group.
|
"""Check if this user is in the given group.
|
||||||
Either a group id or a group name must be provided.
|
Either a group id or a group name must be provided.
|
||||||
If both are passed, only the id will be considered.
|
If both are passed, only the id will be considered.
|
||||||
@ -649,7 +649,7 @@ class User(AbstractBaseUser):
|
|||||||
continue
|
continue
|
||||||
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
|
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
|
||||||
res.extend(links)
|
res.extend(links)
|
||||||
for _ in range(1, depth):
|
for _ in range(1, depth): # noqa: F402 we don't care about gettext here
|
||||||
ids = [getattr(c, reverse_key) for c in links]
|
ids = [getattr(c, reverse_key) for c in links]
|
||||||
links = list(
|
links = list(
|
||||||
User.godfathers.through.objects.filter(
|
User.godfathers.through.objects.filter(
|
||||||
@ -703,9 +703,7 @@ class User(AbstractBaseUser):
|
|||||||
return True
|
return True
|
||||||
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
|
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
|
||||||
return True
|
return True
|
||||||
if self.is_root:
|
return self.is_root
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_edit(self, obj):
|
def can_edit(self, obj):
|
||||||
"""Determine if the object can be edited by the user."""
|
"""Determine if the object can be edited by the user."""
|
||||||
@ -717,9 +715,7 @@ class User(AbstractBaseUser):
|
|||||||
return True
|
return True
|
||||||
if isinstance(obj, User) and obj == self:
|
if isinstance(obj, User) and obj == self:
|
||||||
return True
|
return True
|
||||||
if self.is_owner(obj):
|
return self.is_owner(obj)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_view(self, obj):
|
def can_view(self, obj):
|
||||||
"""Determine if the object can be viewed by the user."""
|
"""Determine if the object can be viewed by the user."""
|
||||||
@ -729,9 +725,7 @@ class User(AbstractBaseUser):
|
|||||||
for pk in obj.view_groups.values_list("pk", flat=True):
|
for pk in obj.view_groups.values_list("pk", flat=True):
|
||||||
if self.is_in_group(pk=pk):
|
if self.is_in_group(pk=pk):
|
||||||
return True
|
return True
|
||||||
if self.can_edit(obj):
|
return self.can_edit(obj)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
return user.is_root or user.is_board_member
|
return user.is_root or user.is_board_member
|
||||||
@ -759,23 +753,17 @@ class User(AbstractBaseUser):
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def preferences(self):
|
def preferences(self):
|
||||||
try:
|
if hasattr(self, "_preferences"):
|
||||||
return self._preferences
|
return self._preferences
|
||||||
except:
|
return Preferences.objects.create(user=self)
|
||||||
prefs = Preferences(user=self)
|
|
||||||
prefs.save()
|
|
||||||
return prefs
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def forum_infos(self):
|
def forum_infos(self):
|
||||||
try:
|
if hasattr(self, "_forum_infos"):
|
||||||
return self._forum_infos
|
return self._forum_infos
|
||||||
except:
|
from forum.models import ForumUserInfo
|
||||||
from forum.models import ForumUserInfo
|
|
||||||
|
|
||||||
infos = ForumUserInfo(user=self)
|
return ForumUserInfo.objects.create(user=self)
|
||||||
infos.save()
|
|
||||||
return infos
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def clubs_with_rights(self) -> list[Club]:
|
def clubs_with_rights(self) -> list[Club]:
|
||||||
@ -840,7 +828,7 @@ class AnonymousUser(AuthAnonymousUser):
|
|||||||
def favorite_topics(self):
|
def favorite_topics(self):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
def is_in_group(self, *, pk: int = None, name: str = None) -> bool:
|
def is_in_group(self, *, pk: int | None = None, name: str | None = None) -> bool:
|
||||||
"""The anonymous user is only in the public group."""
|
"""The anonymous user is only in the public group."""
|
||||||
allowed_id = settings.SITH_GROUP_PUBLIC_ID
|
allowed_id = settings.SITH_GROUP_PUBLIC_ID
|
||||||
if pk is not None:
|
if pk is not None:
|
||||||
@ -867,9 +855,7 @@ class AnonymousUser(AuthAnonymousUser):
|
|||||||
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
|
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
return hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
return _("Visitor")
|
return _("Visitor")
|
||||||
@ -1070,7 +1056,7 @@ class SithFile(models.Model):
|
|||||||
]:
|
]:
|
||||||
self.file.delete()
|
self.file.delete()
|
||||||
self.file = None
|
self.file = None
|
||||||
except:
|
except: # noqa E722 I don't know the exception that can be raised
|
||||||
self.file = None
|
self.file = None
|
||||||
self.mime_type = "inode/directory"
|
self.mime_type = "inode/directory"
|
||||||
if self.is_file and (self.file is None or self.file == ""):
|
if self.is_file and (self.file is None or self.file == ""):
|
||||||
@ -1196,12 +1182,12 @@ class SithFile(models.Model):
|
|||||||
return Album.objects.filter(id=self.id).first()
|
return Album.objects.filter(id=self.id).first()
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
parents = []
|
||||||
p = self.parent
|
current = self.parent
|
||||||
while p is not None:
|
while current is not None:
|
||||||
l.append(p)
|
parents.append(current)
|
||||||
p = p.parent
|
current = current.parent
|
||||||
return l
|
return parents
|
||||||
|
|
||||||
def get_parent_path(self):
|
def get_parent_path(self):
|
||||||
return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]])
|
return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]])
|
||||||
@ -1359,22 +1345,18 @@ class Page(models.Model):
|
|||||||
if hasattr(self, "club") and self.club.can_be_edited_by(user):
|
if hasattr(self, "club") and self.club.can_be_edited_by(user):
|
||||||
# Override normal behavior for clubs
|
# Override normal behavior for clubs
|
||||||
return True
|
return True
|
||||||
if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member:
|
return self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
if self.is_club_page:
|
return self.is_club_page
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
parents = []
|
||||||
p = self.parent
|
current = self.parent
|
||||||
while p is not None:
|
while current is not None:
|
||||||
l.append(p)
|
parents.append(current)
|
||||||
p = p.parent
|
current = current.parent
|
||||||
return l
|
return parents
|
||||||
|
|
||||||
def is_locked(self):
|
def is_locked(self):
|
||||||
"""Is True if the page is locked, False otherwise.
|
"""Is True if the page is locked, False otherwise.
|
||||||
@ -1386,7 +1368,6 @@ class Page(models.Model):
|
|||||||
if self.lock_timeout and (
|
if self.lock_timeout and (
|
||||||
timezone.now() - self.lock_timeout > timedelta(minutes=5)
|
timezone.now() - self.lock_timeout > timedelta(minutes=5)
|
||||||
):
|
):
|
||||||
# print("Lock timed out")
|
|
||||||
self.unset_lock()
|
self.unset_lock()
|
||||||
return (
|
return (
|
||||||
self.lock_user
|
self.lock_user
|
||||||
@ -1401,7 +1382,6 @@ class Page(models.Model):
|
|||||||
self.lock_user = user
|
self.lock_user = user
|
||||||
self.lock_timeout = timezone.now()
|
self.lock_timeout = timezone.now()
|
||||||
super().save()
|
super().save()
|
||||||
# print("Locking page")
|
|
||||||
|
|
||||||
def set_lock_recursive(self, user):
|
def set_lock_recursive(self, user):
|
||||||
"""Locks recursively all the child pages for editing properties."""
|
"""Locks recursively all the child pages for editing properties."""
|
||||||
@ -1420,7 +1400,6 @@ class Page(models.Model):
|
|||||||
self.lock_user = None
|
self.lock_user = None
|
||||||
self.lock_timeout = None
|
self.lock_timeout = None
|
||||||
super().save()
|
super().save()
|
||||||
# print("Unlocking page")
|
|
||||||
|
|
||||||
def get_lock(self):
|
def get_lock(self):
|
||||||
"""Returns the page's mutex containing the time and the user in a dict."""
|
"""Returns the page's mutex containing the time and the user in a dict."""
|
||||||
@ -1435,13 +1414,11 @@ class Page(models.Model):
|
|||||||
"""
|
"""
|
||||||
if self.parent is None:
|
if self.parent is None:
|
||||||
return self.name
|
return self.name
|
||||||
return "/".join([self.parent.get_full_name(), self.name])
|
return f"{self.parent.get_full_name()}/{self.name}"
|
||||||
|
|
||||||
def get_display_name(self):
|
def get_display_name(self):
|
||||||
try:
|
rev = self.revisions.last()
|
||||||
return self.revisions.last().title
|
return rev.title if rev is not None else self.name
|
||||||
except:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_club_page(self):
|
def is_club_page(self):
|
||||||
|
@ -1,265 +0,0 @@
|
|||||||
/**
|
|
||||||
* Builders to use Select2 in our templates.
|
|
||||||
*
|
|
||||||
* This comes with two flavours : local data or remote data.
|
|
||||||
*
|
|
||||||
* # Local data source
|
|
||||||
*
|
|
||||||
* To use local data source, you must define an array
|
|
||||||
* in your JS code, having the fields `id` and `text`.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* const data = [
|
|
||||||
* {id: 1, text: "foo"},
|
|
||||||
* {id: 2, text: "bar"},
|
|
||||||
* ];
|
|
||||||
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
|
|
||||||
* element: document.getElementById("select2-input"),
|
|
||||||
* dataSource: localDataSource(data)
|
|
||||||
* }));
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* You can also define a callback that return ids to exclude :
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* const data = [
|
|
||||||
* {id: 1, text: "foo"},
|
|
||||||
* {id: 2, text: "bar"},
|
|
||||||
* {id: 3, text: "to exclude"},
|
|
||||||
* ];
|
|
||||||
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
|
|
||||||
* element: document.getElementById("select2-input"),
|
|
||||||
* dataSource: localDataSource(data, {
|
|
||||||
* excluded: () => data.filter((i) => i.text === "to exclude").map((i) => parseInt(i))
|
|
||||||
* })
|
|
||||||
* }));
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* # Remote data source
|
|
||||||
*
|
|
||||||
* Select2 with remote data sources are similar to those with local
|
|
||||||
* data, but with some more parameters, like `resultConverter`,
|
|
||||||
* which takes a callback that must return a `Select2Object`.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
|
|
||||||
* element: document.getElementById("select2-input"),
|
|
||||||
* dataSource: remoteDataSource("/api/user/search", {
|
|
||||||
* excluded: () => [1, 2], // exclude users 1 and 2 from the search
|
|
||||||
* resultConverter: (user) => Object({id: user.id, text: user.firstName})
|
|
||||||
* })
|
|
||||||
* }));
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* # Overrides
|
|
||||||
*
|
|
||||||
* Dealing with a select2 may be complex.
|
|
||||||
* That's why, when defining a select,
|
|
||||||
* you may add an override parameter,
|
|
||||||
* in which you can declare any parameter defined in the
|
|
||||||
* Select2 documentation.
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
|
|
||||||
* element: document.getElementById("select2-input"),
|
|
||||||
* dataSource: remoteDataSource("/api/user/search", {
|
|
||||||
* resultConverter: (user) => Object({id: user.id, text: user.firstName}),
|
|
||||||
* overrides: {
|
|
||||||
* delay: 500
|
|
||||||
* }
|
|
||||||
* })
|
|
||||||
* }));
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* # Caveats with exclude
|
|
||||||
*
|
|
||||||
* With local data source, select2 evaluates the data only once.
|
|
||||||
* Thus, modify the exclude after the initialisation is a no-op.
|
|
||||||
*
|
|
||||||
* With remote data source, the exclude list will be evaluated
|
|
||||||
* after each api response.
|
|
||||||
* It makes it possible to bind the data returned by the callback
|
|
||||||
* to some reactive data, thus making the exclude list dynamic.
|
|
||||||
*
|
|
||||||
* # Images
|
|
||||||
*
|
|
||||||
* Sometimes, you would like to display an image besides
|
|
||||||
* the text on the select items.
|
|
||||||
* In this case, fill the `pictureGetter` option :
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* document.addEventListener("DOMContentLoaded", () => sithSelect2({
|
|
||||||
* element: document.getElementById("select2-input"),
|
|
||||||
* dataSource: remoteDataSource("/api/user/search", {
|
|
||||||
* resultConverter: (user) => Object({id: user.id, text: user.firstName})
|
|
||||||
* })
|
|
||||||
* pictureGetter: (user) => user.profilePict,
|
|
||||||
* }));
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* # Binding with alpine
|
|
||||||
*
|
|
||||||
* You can declare your select2 component in an Alpine data.
|
|
||||||
*
|
|
||||||
* ```html
|
|
||||||
* <body>
|
|
||||||
* <div x-data="select2_test">
|
|
||||||
* <select x-ref="search" x-ref="select"></select>
|
|
||||||
* <p x-text="currentSelection.id"></p>
|
|
||||||
* <p x-text="currentSelection.text"></p>
|
|
||||||
* </div>
|
|
||||||
* </body>
|
|
||||||
*
|
|
||||||
* <script>
|
|
||||||
* document.addEventListener("alpine:init", () => {
|
|
||||||
* Alpine.data("select2_test", () => ({
|
|
||||||
* selector: undefined,
|
|
||||||
* currentSelect: {id: "", text: ""},
|
|
||||||
*
|
|
||||||
* init() {
|
|
||||||
* this.selector = sithSelect2({
|
|
||||||
* element: $(this.$refs.select),
|
|
||||||
* dataSource: localDataSource(
|
|
||||||
* [{id: 1, text: "foo"}, {id: 2, text: "bar"}]
|
|
||||||
* ),
|
|
||||||
* });
|
|
||||||
* this.selector.on("select2:select", (event) => {
|
|
||||||
* // select2 => Alpine signals here
|
|
||||||
* this.currentSelect = this.selector.select2("data")
|
|
||||||
* });
|
|
||||||
* this.$watch("currentSelected" (value) => {
|
|
||||||
* // Alpine => select2 signals here
|
|
||||||
* });
|
|
||||||
* },
|
|
||||||
* }));
|
|
||||||
* })
|
|
||||||
* </script>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef Select2Object
|
|
||||||
* @property {number} id
|
|
||||||
* @property {string} text
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef Select2Options
|
|
||||||
* @property {Element} element
|
|
||||||
* @property {Object} dataSource
|
|
||||||
* the data source, built with `localDataSource` or `remoteDataSource`
|
|
||||||
* @property {number[]} excluded A list of ids to exclude from search
|
|
||||||
* @property {undefined | function(Object): string} pictureGetter
|
|
||||||
* A callback to get the picture field from the API response
|
|
||||||
* @property {Object | undefined} overrides
|
|
||||||
* Any other select2 parameter to apply on the config
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Select2Options} options
|
|
||||||
*/
|
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
|
|
||||||
function sithSelect2(options) {
|
|
||||||
const elem = $(options.element);
|
|
||||||
return elem.select2({
|
|
||||||
theme: elem[0].multiple ? "classic" : "default",
|
|
||||||
minimumInputLength: 2,
|
|
||||||
templateResult: selectItemBuilder(options.pictureGetter),
|
|
||||||
...options.dataSource,
|
|
||||||
...(options.overrides || {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef LocalSourceOptions
|
|
||||||
* @property {undefined | function(): number[]} excluded
|
|
||||||
* A callback to the ids to exclude from the search
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a data source for a Select2 from a local array
|
|
||||||
* @param {Select2Object[]} source The array containing the data
|
|
||||||
* @param {RemoteSourceOptions} options
|
|
||||||
*/
|
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
|
|
||||||
function localDataSource(source, options) {
|
|
||||||
if (options.excluded) {
|
|
||||||
const ids = options.excluded();
|
|
||||||
return { data: source.filter((i) => !ids.includes(i.id)) };
|
|
||||||
}
|
|
||||||
return { data: source };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef RemoteSourceOptions
|
|
||||||
* @property {undefined | function(): number[]} excluded
|
|
||||||
* A callback to the ids to exclude from the search
|
|
||||||
* @property {undefined | function(): Select2Object} resultConverter
|
|
||||||
* A converter for a value coming from the remote api
|
|
||||||
* @property {undefined | Object} overrides
|
|
||||||
* Any other select2 parameter to apply on the config
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a data source for a Select2 from a remote url
|
|
||||||
* @param {string} source The url of the endpoint
|
|
||||||
* @param {RemoteSourceOptions} options
|
|
||||||
*/
|
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
|
|
||||||
function remoteDataSource(source, options) {
|
|
||||||
jQuery.ajaxSettings.traditional = true;
|
|
||||||
const params = {
|
|
||||||
url: source,
|
|
||||||
dataType: "json",
|
|
||||||
cache: true,
|
|
||||||
delay: 250,
|
|
||||||
data: function (params) {
|
|
||||||
return {
|
|
||||||
search: params.term,
|
|
||||||
exclude: [
|
|
||||||
...(this.val() || []).map((i) => Number.parseInt(i)),
|
|
||||||
...(options.excluded ? options.excluded() : []),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (options.resultConverter) {
|
|
||||||
params.processResults = (data) => ({
|
|
||||||
results: data.results.map(options.resultConverter),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (options.overrides) {
|
|
||||||
Object.assign(params, options.overrides);
|
|
||||||
}
|
|
||||||
return { ajax: params };
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUnusedVariables: used in other scripts
|
|
||||||
function itemFormatter(user) {
|
|
||||||
if (user.loading) {
|
|
||||||
return user.text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a function to display the results
|
|
||||||
* @param {null | function(Object):string} pictureGetter
|
|
||||||
* @return {function(string): jQuery|HTMLElement}
|
|
||||||
*/
|
|
||||||
function selectItemBuilder(pictureGetter) {
|
|
||||||
return (item) => {
|
|
||||||
const picture = typeof pictureGetter === "function" ? pictureGetter(item) : null;
|
|
||||||
const imgHtml = picture
|
|
||||||
? `<img
|
|
||||||
src="${pictureGetter(item)}"
|
|
||||||
alt="${item.text}"
|
|
||||||
onerror="this.src = '/static/core/img/unknown.jpg'"
|
|
||||||
/>`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return $(`<div class="select-item">
|
|
||||||
${imgHtml}
|
|
||||||
<span class="select-item-text">${item.text}</span>
|
|
||||||
</div>`);
|
|
||||||
};
|
|
||||||
}
|
|
@ -28,6 +28,7 @@ input[type="file"] {
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: black;
|
color: black;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: hsl(0, 0%, 83%);
|
background: hsl(0, 0%, 83%);
|
||||||
}
|
}
|
||||||
@ -63,6 +64,7 @@ textarea[type="text"],
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -72,6 +74,7 @@ textarea {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -85,9 +88,11 @@ select {
|
|||||||
a:not(.button) {
|
a:not(.button) {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: $primary-dark-color;
|
color: $primary-dark-color;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary-light-color;
|
color: $primary-light-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
}
|
}
|
||||||
@ -116,7 +121,9 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
100% { transform: rotate(360deg); }
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ib {
|
.ib {
|
||||||
@ -143,11 +150,13 @@ a:not(.button) {
|
|||||||
|
|
||||||
.collapse-header-icon {
|
.collapse-header-icon {
|
||||||
transition: all ease-in-out 150ms;
|
transition: all ease-in-out 150ms;
|
||||||
|
|
||||||
&.reverse {
|
&.reverse {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse-body {
|
.collapse-body {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
@ -202,9 +211,11 @@ a:not(.button) {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
border-radius: 0.6em;
|
border-radius: 0.6em;
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
font-family: FontAwesome;
|
font-family: FontAwesome;
|
||||||
font-size: 4em;
|
font-size: 4em;
|
||||||
@ -212,15 +223,19 @@ a:not(.button) {
|
|||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#info_box {
|
#info_box {
|
||||||
background: $primary-neutral-light-color;
|
background: $primary-neutral-light-color;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: "\f05a";
|
content: "\f05a";
|
||||||
color: hsl(210, 100%, 56%);
|
color: hsl(210, 100%, 56%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#alert_box {
|
#alert_box {
|
||||||
background: $second-color;
|
background: $second-color;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: "\f06a";
|
content: "\f06a";
|
||||||
color: $white-color;
|
color: $white-color;
|
||||||
@ -240,7 +255,8 @@ a:not(.button) {
|
|||||||
#page {
|
#page {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
margin: 20px auto 0;
|
margin: 20px auto 0;
|
||||||
/*---------------------------------NAV---------------------------------*/
|
|
||||||
|
/*---------------------------------NAV---------------------------------*/
|
||||||
.btn {
|
.btn {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -252,9 +268,11 @@ a:not(.button) {
|
|||||||
|
|
||||||
&.btn-blue {
|
&.btn-blue {
|
||||||
background-color: $deepblue;
|
background-color: $deepblue;
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
background-color: darken($deepblue, 10%);
|
background-color: darken($deepblue, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: rgba(70, 90, 126, 0.4);
|
background-color: rgba(70, 90, 126, 0.4);
|
||||||
}
|
}
|
||||||
@ -262,9 +280,11 @@ a:not(.button) {
|
|||||||
|
|
||||||
&.btn-grey {
|
&.btn-grey {
|
||||||
background-color: grey;
|
background-color: grey;
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
background-color: darken(gray, 15%);
|
background-color: darken(gray, 15%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: lighten(gray, 15%);
|
background-color: lighten(gray, 15%);
|
||||||
}
|
}
|
||||||
@ -273,9 +293,11 @@ a:not(.button) {
|
|||||||
&.btn-red {
|
&.btn-red {
|
||||||
background-color: #fc8181;
|
background-color: #fc8181;
|
||||||
color: black;
|
color: black;
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
background-color: darken(#fc8181, 15%);
|
background-color: darken(#fc8181, 15%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
background-color: lighten(#fc8181, 15%);
|
background-color: lighten(#fc8181, 15%);
|
||||||
color: grey;
|
color: grey;
|
||||||
@ -287,12 +309,13 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*--------------------------------CONTENT------------------------------*/
|
/*--------------------------------CONTENT------------------------------*/
|
||||||
#quick_notif {
|
#quick_notif {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
background: $second-color;
|
background: $second-color;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
@ -333,14 +356,24 @@ a:not(.button) {
|
|||||||
border: #fc8181 1px solid;
|
border: #fc8181 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-title {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.alert-main {
|
.alert-main {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-aside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool_bar {
|
.tool_bar {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|
||||||
.tools {
|
.tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -349,6 +382,7 @@ a:not(.button) {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -358,11 +392,13 @@ a:not(.button) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&.selected_tab {
|
&.selected_tab {
|
||||||
background: $primary-color;
|
background: $primary-color;
|
||||||
color: $white-color;
|
color: $white-color;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $primary-color;
|
background: $primary-color;
|
||||||
color: $white-color;
|
color: $white-color;
|
||||||
@ -372,7 +408,7 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*---------------------------------NEWS--------------------------------*/
|
/*---------------------------------NEWS--------------------------------*/
|
||||||
#news {
|
#news {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@ -385,17 +421,21 @@ a:not(.button) {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
#news_admin {
|
#news_admin {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#right_column {
|
#right_column {
|
||||||
flex: 20%;
|
flex: 20%;
|
||||||
float: right;
|
float: right;
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#left_column {
|
#left_column {
|
||||||
flex: 79%;
|
flex: 79%;
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
background: $second-color;
|
background: $second-color;
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
@ -403,19 +443,22 @@ a:not(.button) {
|
|||||||
margin: 0 0 0.5em 0;
|
margin: 0 0 0.5em 0;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
|
||||||
&:not(:first-of-type) {
|
&:not(:first-of-type) {
|
||||||
margin: 2em 0 1em 0;
|
margin: 2em 0 1em 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $small-devices) {
|
@media screen and (max-width: $small-devices) {
|
||||||
|
|
||||||
#left_column,
|
#left_column,
|
||||||
#right_column {
|
#right_column {
|
||||||
flex: 100%;
|
flex: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AGENDA/BIRTHDAYS */
|
/* AGENDA/BIRTHDAYS */
|
||||||
#agenda,
|
#agenda,
|
||||||
#birthdays {
|
#birthdays {
|
||||||
display: block;
|
display: block;
|
||||||
@ -423,6 +466,7 @@ a:not(.button) {
|
|||||||
background: white;
|
background: white;
|
||||||
font-size: 70%;
|
font-size: 70%;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
#agenda_title,
|
#agenda_title,
|
||||||
#birthdays_title {
|
#birthdays_title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -435,39 +479,48 @@ a:not(.button) {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
background: $second-color;
|
background: $second-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
#agenda_content {
|
#agenda_content {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
height: 20em;
|
height: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#agenda_content,
|
#agenda_content,
|
||||||
#birthdays_content {
|
#birthdays_content {
|
||||||
.agenda_item {
|
.agenda_item {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
&:nth-of-type(even) {
|
&:nth-of-type(even) {
|
||||||
background: $secondary-neutral-light-color;
|
background: $secondary-neutral-light-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agenda_time {
|
.agenda_time {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agenda_item_content {
|
.agenda_item_content {
|
||||||
p {
|
p {
|
||||||
margin-top: 0.2em;
|
margin-top: 0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.birthdays_year {
|
ul.birthdays_year {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
> li {
|
|
||||||
|
>li {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
background: $secondary-neutral-light-color;
|
background: $secondary-neutral-light-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
@ -478,13 +531,15 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END AGENDA/BIRTHDAYS */
|
|
||||||
|
|
||||||
/* EVENTS TODAY AND NEXT FEW DAYS */
|
/* END AGENDA/BIRTHDAYS */
|
||||||
|
|
||||||
|
/* EVENTS TODAY AND NEXT FEW DAYS */
|
||||||
.news_events_group {
|
.news_events_group {
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
.news_events_group_date {
|
.news_events_group_date {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
padding: 0.6em;
|
padding: 0.6em;
|
||||||
@ -500,33 +555,42 @@ a:not(.button) {
|
|||||||
|
|
||||||
div {
|
div {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
.day {
|
.day {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_events_group_items {
|
.news_events_group_items {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.news_event:nth-of-type(odd) {
|
.news_event:nth-of-type(odd) {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_event:nth-of-type(even) {
|
.news_event:nth-of-type(even) {
|
||||||
background: $primary-neutral-light-color;
|
background: $primary-neutral-light-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_event {
|
.news_event {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0.4em;
|
padding: 0.4em;
|
||||||
|
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
border-bottom: 1px solid grey;
|
border-bottom: 1px solid grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.club_logo {
|
.club_logo {
|
||||||
float: left;
|
float: left;
|
||||||
min-width: 7em;
|
min-width: 7em;
|
||||||
@ -534,6 +598,7 @@ a:not(.button) {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
margin-top: 0.8em;
|
margin-top: 0.8em;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 6em;
|
max-height: 6em;
|
||||||
max-width: 8em;
|
max-width: 8em;
|
||||||
@ -541,16 +606,21 @@ a:not(.button) {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_date {
|
.news_date {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_content {
|
.news_content {
|
||||||
clear: left;
|
clear: left;
|
||||||
|
|
||||||
.button_bar {
|
.button_bar {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
.fb {
|
.fb {
|
||||||
color: $faceblue;
|
color: $faceblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.twitter {
|
.twitter {
|
||||||
color: $twitblue;
|
color: $twitblue;
|
||||||
}
|
}
|
||||||
@ -559,26 +629,30 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END EVENTS TODAY AND NEXT FEW DAYS */
|
|
||||||
|
|
||||||
/* COMING SOON */
|
/* END EVENTS TODAY AND NEXT FEW DAYS */
|
||||||
|
|
||||||
|
/* COMING SOON */
|
||||||
.news_coming_soon {
|
.news_coming_soon {
|
||||||
display: list-item;
|
display: list-item;
|
||||||
list-style-type: square;
|
list-style-type: square;
|
||||||
list-style-position: inside;
|
list-style-position: inside;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_date {
|
.news_date {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END COMING SOON */
|
|
||||||
|
|
||||||
/* NOTICES */
|
/* END COMING SOON */
|
||||||
|
|
||||||
|
/* NOTICES */
|
||||||
.news_notice {
|
.news_notice {
|
||||||
margin: 0 0 1em 1em;
|
margin: 0 0 1em 1em;
|
||||||
padding: 0.4em;
|
padding: 0.4em;
|
||||||
@ -586,16 +660,19 @@ a:not(.button) {
|
|||||||
background: $secondary-neutral-light-color;
|
background: $secondary-neutral-light-color;
|
||||||
box-shadow: $shadow-color 0 0 2px;
|
box-shadow: $shadow-color 0 0 2px;
|
||||||
border-radius: 18px 5px 18px 5px;
|
border-radius: 18px 5px 18px 5px;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_content {
|
.news_content {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END NOTICES */
|
|
||||||
|
|
||||||
/* CALLS */
|
/* END NOTICES */
|
||||||
|
|
||||||
|
/* CALLS */
|
||||||
.news_call {
|
.news_call {
|
||||||
margin: 0 0 1em 1em;
|
margin: 0 0 1em 1em;
|
||||||
padding: 0.4em;
|
padding: 0.4em;
|
||||||
@ -603,21 +680,26 @@ a:not(.button) {
|
|||||||
background: $secondary-neutral-light-color;
|
background: $secondary-neutral-light-color;
|
||||||
border: 1px solid grey;
|
border: 1px solid grey;
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_date {
|
.news_date {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_content {
|
.news_content {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* END CALLS */
|
|
||||||
|
/* END CALLS */
|
||||||
|
|
||||||
.news_empty {
|
.news_empty {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.news_date {
|
.news_date {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
@ -631,8 +713,8 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.select2 {
|
.tomselected {
|
||||||
margin: 10px 0!important;
|
margin: 10px 0 !important;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
|
|
||||||
@ -648,7 +730,9 @@ a:not(.button) {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.select2-results {
|
|
||||||
|
.ts-dropdown {
|
||||||
|
|
||||||
.select-item {
|
.select-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -664,16 +748,39 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ts-control {
|
||||||
|
|
||||||
|
.item {
|
||||||
|
.fa-times {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#news_details {
|
#news_details {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 0.4em;
|
padding: 0.4em;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
background: $white-color;
|
background: $white-color;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.club_logo {
|
.club_logo {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -681,6 +788,7 @@ a:not(.button) {
|
|||||||
float: left;
|
float: left;
|
||||||
min-width: 15em;
|
min-width: 15em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 15em;
|
max-height: 15em;
|
||||||
max-width: 12em;
|
max-width: 12em;
|
||||||
@ -689,6 +797,7 @@ a:not(.button) {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.share_button {
|
.share_button {
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
@ -700,6 +809,7 @@ a:not(.button) {
|
|||||||
float: right;
|
float: right;
|
||||||
display: block;
|
display: block;
|
||||||
margin-left: 0.3em;
|
margin-left: 0.3em;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
}
|
}
|
||||||
@ -731,26 +841,32 @@ a:not(.button) {
|
|||||||
#poster_edit,
|
#poster_edit,
|
||||||
#screen_edit {
|
#screen_edit {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
#title {
|
#title {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-bottom: 2px solid black;
|
border-bottom: 2px solid black;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#links {
|
#links {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
|
|
||||||
&.left {
|
&.left {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.right {
|
&.right {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
@ -759,27 +875,32 @@ a:not(.button) {
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: hsl(40, 100%, 50%);
|
background-color: hsl(40, 100%, 50%);
|
||||||
color: black;
|
color: black;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: black;
|
color: black;
|
||||||
background-color: hsl(40, 58%, 50%);
|
background-color: hsl(40, 58%, 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.delete {
|
&.delete {
|
||||||
background-color: hsl(0, 100%, 40%);
|
background-color: hsl(0, 100%, 40%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#posters,
|
#posters,
|
||||||
#screens {
|
#screens {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
#no-posters,
|
#no-posters,
|
||||||
#no-screens {
|
#no-screens {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poster,
|
.poster,
|
||||||
.screen {
|
.screen {
|
||||||
min-width: 10%;
|
min-width: 10%;
|
||||||
@ -791,26 +912,31 @@ a:not(.button) {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background-color: lightgrey;
|
background-color: lightgrey;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
border-bottom: 1px solid whitesmoke;
|
border-bottom: 1px solid whitesmoke;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
border-bottom: 1px solid whitesmoke;
|
border-bottom: 1px solid whitesmoke;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 20vw;
|
max-height: 20vw;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -829,10 +955,12 @@ a:not(.button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dates {
|
.dates {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
border-bottom: 1px solid whitesmoke;
|
border-bottom: 1px solid whitesmoke;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -841,15 +969,18 @@ a:not(.button) {
|
|||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.begin,
|
.begin,
|
||||||
.end {
|
.end {
|
||||||
width: 48%;
|
width: 48%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.begin {
|
.begin {
|
||||||
border-right: 1px solid whitesmoke;
|
border-right: 1px solid whitesmoke;
|
||||||
padding-right: 2%;
|
padding-right: 2%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit,
|
.edit,
|
||||||
.moderate,
|
.moderate,
|
||||||
.slideshow {
|
.slideshow {
|
||||||
@ -857,15 +988,18 @@ a:not(.button) {
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: hsl(40, 100%, 50%);
|
background-color: hsl(40, 100%, 50%);
|
||||||
color: black;
|
color: black;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: black;
|
color: black;
|
||||||
background-color: hsl(40, 58%, 50%);
|
background-color: hsl(40, 58%, 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2n) {
|
&:nth-child(2n) {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
@ -876,23 +1010,28 @@ a:not(.button) {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: list-item;
|
display: list-item;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not_moderated {
|
&.not_moderated {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .tooltip {
|
&:hover .tooltip {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#view {
|
#view {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@ -906,9 +1045,11 @@ a:not(.button) {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
background-color: rgba(10, 10, 10, 0.9);
|
background-color: rgba(10, 10, 10, 0.9);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
#placeholder {
|
#placeholder {
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
@ -917,6 +1058,7 @@ a:not(.button) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
@ -931,14 +1073,17 @@ a:not(.button) {
|
|||||||
tbody {
|
tbody {
|
||||||
.neg-amount {
|
.neg-amount {
|
||||||
color: red;
|
color: red;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
font-family: FontAwesome;
|
font-family: FontAwesome;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
content: "\f063";
|
content: "\f063";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pos-amount {
|
.pos-amount {
|
||||||
color: green;
|
color: green;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
font-family: FontAwesome;
|
font-family: FontAwesome;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@ -1005,6 +1150,7 @@ dt {
|
|||||||
.edit-bar {
|
.edit-bar {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 4px;
|
margin: 4px;
|
||||||
@ -1044,7 +1190,8 @@ th {
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
> ul {
|
|
||||||
|
>ul {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1055,7 +1202,8 @@ td {
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
> ul {
|
|
||||||
|
>ul {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1071,15 +1219,17 @@ thead {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody > tr {
|
tbody>tr {
|
||||||
&:nth-child(even):not(.highlight) {
|
&:nth-child(even):not(.highlight) {
|
||||||
background: $primary-neutral-light-color;
|
background: $primary-neutral-light-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.clickable:hover {
|
&.clickable:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: $secondary-neutral-light-color;
|
background: $secondary-neutral-light-color;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.highlight {
|
&.highlight {
|
||||||
color: $primary-dark-color;
|
color: $primary-dark-color;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@ -1139,9 +1289,11 @@ u,
|
|||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: $secondary-neutral-light-color;
|
background: $secondary-neutral-light-color;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
@ -1153,10 +1305,12 @@ u,
|
|||||||
.user_mini_profile {
|
.user_mini_profile {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_mini_profile_infos {
|
.user_mini_profile_infos {
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
height: 20%;
|
height: 20%;
|
||||||
@ -1164,16 +1318,20 @@ u,
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_mini_profile_infos_text {
|
.user_mini_profile_infos_text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.user_mini_profile_nick {
|
.user_mini_profile_nick {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user_mini_profile_picture {
|
.user_mini_profile_picture {
|
||||||
height: 80%;
|
height: 80%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1185,14 +1343,17 @@ u,
|
|||||||
.mini_profile_link {
|
.mini_profile_link {
|
||||||
display: block;
|
display: block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
em {
|
em {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 40px;
|
max-width: 40px;
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
@ -1214,6 +1375,7 @@ u,
|
|||||||
border: solid 1px red;
|
border: solid 1px red;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
}
|
}
|
||||||
@ -1223,6 +1385,7 @@ u,
|
|||||||
.matmat_results {
|
.matmat_results {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.matmat_user {
|
.matmat_user {
|
||||||
flex-basis: 14em;
|
flex-basis: 14em;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
@ -1231,10 +1394,12 @@ u,
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
box-shadow: $shadow-color 1px 1px 1px;
|
box-shadow: $shadow-color 1px 1px 1px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 1px 1px 5px $second-color;
|
box-shadow: 1px 1px 5px $second-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.matmat_user a {
|
.matmat_user a {
|
||||||
color: $primary-neutral-dark-color;
|
color: $primary-neutral-dark-color;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -1274,6 +1439,7 @@ footer {
|
|||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
margin: 0.6em 0;
|
margin: 0.6em 0;
|
||||||
color: $white-color;
|
color: $white-color;
|
||||||
@ -1283,18 +1449,20 @@ footer {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: $primary-neutral-dark-color;
|
background-color: $primary-neutral-dark-color;
|
||||||
box-shadow: $shadow-color 0 0 15px;
|
box-shadow: $shadow-color 0 0 15px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
padding: 0.8em;
|
padding: 0.8em;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $white-color !important;
|
color: $white-color !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary-dark-color;
|
color: $primary-dark-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .version {
|
>.version {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
color: rgba(0, 0, 0, 0.3);
|
color: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
@ -1326,6 +1494,7 @@ label {
|
|||||||
* {
|
* {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
}
|
}
|
||||||
@ -1342,19 +1511,23 @@ label {
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 50px;
|
max-width: 50px;
|
||||||
max-height: 50px;
|
max-height: 50px;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
@ -1371,6 +1544,7 @@ a.ui-button:active,
|
|||||||
background: $primary-color;
|
background: $primary-color;
|
||||||
border-color: $primary-color;
|
border-color: $primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-corner-all,
|
.ui-corner-all,
|
||||||
.ui-corner-bottom,
|
.ui-corner-bottom,
|
||||||
.ui-corner-right,
|
.ui-corner-right,
|
||||||
@ -1382,6 +1556,7 @@ a.ui-button:active,
|
|||||||
#club_detail {
|
#club_detail {
|
||||||
.club_logo {
|
.club_logo {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 10em;
|
max-height: 10em;
|
||||||
|
16
core/static/vendored/chart/Chart.bundle.min.js
vendored
16
core/static/vendored/chart/Chart.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
1
core/static/vendored/select2/select2.min.css
vendored
1
core/static/vendored/select2/select2.min.css
vendored
File diff suppressed because one or more lines are too long
2
core/static/vendored/select2/select2.min.js
vendored
2
core/static/vendored/select2/select2.min.js
vendored
File diff suppressed because one or more lines are too long
1
core/static/vendored/sentry/bundle.min.js
vendored
1
core/static/vendored/sentry/bundle.min.js
vendored
@ -1 +0,0 @@
|
|||||||
!function(n,e,r,t,i,o,a,c,s){for(var u=s,f=0;f<document.scripts.length;f++)if(document.scripts[f].src.indexOf(o)>-1){u&&"no"===document.scripts[f].getAttribute("data-lazy")&&(u=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[];function y(n){u&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&m(),v.push(n)}function g(){y({e:[].slice.call(arguments)})}function h(n){y({p:n})}function E(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,g),n.removeEventListener(t,h);var a=c;for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(a[s]=i[s]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&(e.browserTracingIntegration?r.push(e.browserTracingIntegration({enableInp:!0})):e.BrowserTracing&&r.push(new e.BrowserTracing));(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&(e.replayIntegration?r.push(e.replayIntegration()):e.Replay&&r.push(new e.Replay));n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{"function"==typeof n.sentryOnLoad&&(n.sentryOnLoad(),n.sentryOnLoad=void 0);for(var r=0;r<p.length;r++)"function"==typeof p[r]&&p[r]();p.splice(0);for(r=0;r<v.length;r++){_(o=v[r])&&"init"===o.f&&e.init.apply(e,o.a)}L()||e.init();var t=n.onerror,i=n.onunhandledrejection;for(r=0;r<v.length;r++){var o;if(_(o=v[r])){if("init"===o.f)continue;e[o.f].apply(e,o.a)}else l(o)&&t?t.apply(n,o.e):d(o)&&i&&i.apply(n,[o.p])}}catch(n){console.error(n)}}(e)}))}catch(n){console.error(n)}}var O=!1;function m(){if(!O){O=!0;var n=e.scripts[0],r=e.createElement("script");r.src=a,r.crossOrigin="anonymous",r.addEventListener("load",E,{once:!0,passive:!0}),n.parentNode.insertBefore(r,n)}}function L(){var e=n.__SENTRY__,r=void 0!==e&&e.version;return r?!!e[r]:!(void 0===e||!e.hub||!e.hub.getClient())}n[i]=n[i]||{},n[i].onLoad=function(n){L()?n():p.push(n)},n[i].forceLoad=function(){setTimeout((function(){m()}))},["init","addBreadcrumb","captureMessage","captureException","captureEvent","configureScope","withScope","showReportDialog"].forEach((function(e){n[i][e]=function(){y({f:e,a:arguments})}})),n.addEventListener(r,g),n.addEventListener(t,h),u||setTimeout((function(){m()}))}(window,document,"error","unhandledrejection","Sentry",'ab63c6820882cab2883218a4b9deba4d','https://browser.sentry-cdn.com/8.26.0/bundle.min.js',{"dsn":"https://ab63c6820882cab2883218a4b9deba4d@o4505360748642304.ingest.us.sentry.io/4507633486266368"},true);
|
|
93
core/static/webpack/ajax-select-index.ts
Normal file
93
core/static/webpack/ajax-select-index.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import "tom-select/dist/css/tom-select.css";
|
||||||
|
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
|
||||||
|
import TomSelect from "tom-select";
|
||||||
|
import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types";
|
||||||
|
import type { escape_html } from "tom-select/dist/types/utils";
|
||||||
|
import { type UserProfileSchema, userSearchUsers } from "#openapi";
|
||||||
|
|
||||||
|
@registerComponent("ajax-select")
|
||||||
|
export class AjaxSelect extends inheritHtmlElement("select") {
|
||||||
|
public widget: TomSelect;
|
||||||
|
public filter?: <T>(items: T[]) => T[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
this.loadTomSelect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTomSelect() {
|
||||||
|
const minCharNumberForSearch = 2;
|
||||||
|
let maxItems = 1;
|
||||||
|
|
||||||
|
if (this.node.multiple) {
|
||||||
|
maxItems = Number.parseInt(this.node.dataset.max) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.widget = new TomSelect(this.node, {
|
||||||
|
hideSelected: true,
|
||||||
|
diacritics: true,
|
||||||
|
duplicates: false,
|
||||||
|
maxItems: maxItems,
|
||||||
|
loadThrottle: Number.parseInt(this.node.dataset.delay) ?? null,
|
||||||
|
valueField: "id",
|
||||||
|
labelField: "display_name",
|
||||||
|
searchField: ["display_name", "nick_name", "first_name", "last_name"],
|
||||||
|
placeholder: this.node.dataset.placeholder ?? "",
|
||||||
|
shouldLoad: (query: string) => {
|
||||||
|
return query.length >= minCharNumberForSearch; // Avoid launching search with less than 2 characters
|
||||||
|
},
|
||||||
|
load: (query: string, callback: TomLoadCallback) => {
|
||||||
|
userSearchUsers({
|
||||||
|
query: {
|
||||||
|
search: query,
|
||||||
|
},
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.data) {
|
||||||
|
if (this.filter) {
|
||||||
|
callback(this.filter(response.data.results), []);
|
||||||
|
} else {
|
||||||
|
callback(response.data.results, []);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback([], []);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render: {
|
||||||
|
option: (item: UserProfileSchema, sanitize: typeof escape_html) => {
|
||||||
|
return `<div class="select-item">
|
||||||
|
<img
|
||||||
|
src="${sanitize(item.profile_pict)}"
|
||||||
|
alt="${sanitize(item.display_name)}"
|
||||||
|
onerror="this.src = '/static/core/img/unknown.jpg'"
|
||||||
|
/>
|
||||||
|
<span class="select-item-text">${sanitize(item.display_name)}</span>
|
||||||
|
</div>`;
|
||||||
|
},
|
||||||
|
item: (item: UserProfileSchema, sanitize: typeof escape_html) => {
|
||||||
|
return `<span><i class="fa fa-times"></i>${sanitize(item.display_name)}</span>`;
|
||||||
|
},
|
||||||
|
// biome-ignore lint/style/useNamingConvention: that's how it's defined
|
||||||
|
not_loading: (data: TomOption, _sanitize: typeof escape_html) => {
|
||||||
|
return `<div class="no-results">${interpolate(gettext("You need to type %(number)s more characters"), { number: minCharNumberForSearch - data.input.length }, true)}</div>`;
|
||||||
|
},
|
||||||
|
// biome-ignore lint/style/useNamingConvention: that's how it's defined
|
||||||
|
no_results: (_data: TomOption, _sanitize: typeof escape_html) => {
|
||||||
|
return `<div class="no-results">${gettext("No results found")}</div>`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow removing selected items by clicking on them
|
||||||
|
this.widget.on("item_select", (item: TomItem) => {
|
||||||
|
this.widget.removeItem(item);
|
||||||
|
});
|
||||||
|
// Remove typed text once an item has been selected
|
||||||
|
this.widget.on("item_add", () => {
|
||||||
|
this.widget.setTextboxValue("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +1,57 @@
|
|||||||
// biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde
|
// biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde
|
||||||
import "codemirror/lib/codemirror.css";
|
import "codemirror/lib/codemirror.css";
|
||||||
import "easymde/src/css/easymde.css";
|
import "easymde/src/css/easymde.css";
|
||||||
import easyMde from "easymde";
|
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
|
||||||
|
// biome-ignore lint/correctness/noUndeclaredDependencies: Imported by EasyMDE
|
||||||
|
import type CodeMirror from "codemirror";
|
||||||
|
// biome-ignore lint/style/useNamingConvention: This is how they called their namespace
|
||||||
|
import EasyMDE from "easymde";
|
||||||
import { markdownRenderMarkdown } from "#openapi";
|
import { markdownRenderMarkdown } from "#openapi";
|
||||||
|
|
||||||
/**
|
const loadEasyMde = (textarea: HTMLTextAreaElement) => {
|
||||||
* Create a new easymde based textarea
|
new EasyMDE({
|
||||||
* @param {HTMLTextAreaElement} textarea to use
|
|
||||||
**/
|
|
||||||
window.easymdeFactory = (textarea) => {
|
|
||||||
const easymde = new easyMde({
|
|
||||||
element: textarea,
|
element: textarea,
|
||||||
spellChecker: false,
|
spellChecker: false,
|
||||||
autoDownloadFontAwesome: false,
|
autoDownloadFontAwesome: false,
|
||||||
previewRender: Alpine.debounce(async (plainText, preview) => {
|
previewRender: Alpine.debounce((plainText: string, preview: MarkdownInput) => {
|
||||||
preview.innerHTML = (
|
const func = async (plainText: string, preview: MarkdownInput): Promise<null> => {
|
||||||
await markdownRenderMarkdown({ body: { text: plainText } })
|
preview.innerHTML = (
|
||||||
).data;
|
await markdownRenderMarkdown({ body: { text: plainText } })
|
||||||
|
).data as string;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
func(plainText, preview);
|
||||||
return null;
|
return null;
|
||||||
}, 300),
|
}, 300),
|
||||||
forceSync: true, // Avoid validation error on generic create view
|
forceSync: true, // Avoid validation error on generic create view
|
||||||
toolbar: [
|
toolbar: [
|
||||||
{
|
{
|
||||||
name: "heading-smaller",
|
name: "heading-smaller",
|
||||||
action: easyMde.toggleHeadingSmaller,
|
action: EasyMDE.toggleHeadingSmaller,
|
||||||
className: "fa fa-header",
|
className: "fa fa-header",
|
||||||
title: gettext("Heading"),
|
title: gettext("Heading"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "italic",
|
name: "italic",
|
||||||
action: easyMde.toggleItalic,
|
action: EasyMDE.toggleItalic,
|
||||||
className: "fa fa-italic",
|
className: "fa fa-italic",
|
||||||
title: gettext("Italic"),
|
title: gettext("Italic"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bold",
|
name: "bold",
|
||||||
action: easyMde.toggleBold,
|
action: EasyMDE.toggleBold,
|
||||||
className: "fa fa-bold",
|
className: "fa fa-bold",
|
||||||
title: gettext("Bold"),
|
title: gettext("Bold"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "strikethrough",
|
name: "strikethrough",
|
||||||
action: easyMde.toggleStrikethrough,
|
action: EasyMDE.toggleStrikethrough,
|
||||||
className: "fa fa-strikethrough",
|
className: "fa fa-strikethrough",
|
||||||
title: gettext("Strikethrough"),
|
title: gettext("Strikethrough"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "underline",
|
name: "underline",
|
||||||
action: function customFunction(editor) {
|
action: function customFunction(editor: { codemirror: CodeMirror.Editor }) {
|
||||||
const cm = editor.codemirror;
|
const cm = editor.codemirror;
|
||||||
cm.replaceSelection(`__${cm.getSelection()}__`);
|
cm.replaceSelection(`__${cm.getSelection()}__`);
|
||||||
},
|
},
|
||||||
@ -56,7 +60,7 @@ window.easymdeFactory = (textarea) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "superscript",
|
name: "superscript",
|
||||||
action: function customFunction(editor) {
|
action: function customFunction(editor: { codemirror: CodeMirror.Editor }) {
|
||||||
const cm = editor.codemirror;
|
const cm = editor.codemirror;
|
||||||
cm.replaceSelection(`^${cm.getSelection()}^`);
|
cm.replaceSelection(`^${cm.getSelection()}^`);
|
||||||
},
|
},
|
||||||
@ -65,7 +69,7 @@ window.easymdeFactory = (textarea) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "subscript",
|
name: "subscript",
|
||||||
action: function customFunction(editor) {
|
action: function customFunction(editor: { codemirror: CodeMirror.Editor }) {
|
||||||
const cm = editor.codemirror;
|
const cm = editor.codemirror;
|
||||||
cm.replaceSelection(`~${cm.getSelection()}~`);
|
cm.replaceSelection(`~${cm.getSelection()}~`);
|
||||||
},
|
},
|
||||||
@ -74,71 +78,71 @@ window.easymdeFactory = (textarea) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "code",
|
name: "code",
|
||||||
action: easyMde.toggleCodeBlock,
|
action: EasyMDE.toggleCodeBlock,
|
||||||
className: "fa fa-code",
|
className: "fa fa-code",
|
||||||
title: gettext("Code"),
|
title: gettext("Code"),
|
||||||
},
|
},
|
||||||
"|",
|
"|",
|
||||||
{
|
{
|
||||||
name: "quote",
|
name: "quote",
|
||||||
action: easyMde.toggleBlockquote,
|
action: EasyMDE.toggleBlockquote,
|
||||||
className: "fa fa-quote-left",
|
className: "fa fa-quote-left",
|
||||||
title: gettext("Quote"),
|
title: gettext("Quote"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unordered-list",
|
name: "unordered-list",
|
||||||
action: easyMde.toggleUnorderedList,
|
action: EasyMDE.toggleUnorderedList,
|
||||||
className: "fa fa-list-ul",
|
className: "fa fa-list-ul",
|
||||||
title: gettext("Unordered list"),
|
title: gettext("Unordered list"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ordered-list",
|
name: "ordered-list",
|
||||||
action: easyMde.toggleOrderedList,
|
action: EasyMDE.toggleOrderedList,
|
||||||
className: "fa fa-list-ol",
|
className: "fa fa-list-ol",
|
||||||
title: gettext("Ordered list"),
|
title: gettext("Ordered list"),
|
||||||
},
|
},
|
||||||
"|",
|
"|",
|
||||||
{
|
{
|
||||||
name: "link",
|
name: "link",
|
||||||
action: easyMde.drawLink,
|
action: EasyMDE.drawLink,
|
||||||
className: "fa fa-link",
|
className: "fa fa-link",
|
||||||
title: gettext("Insert link"),
|
title: gettext("Insert link"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
action: easyMde.drawImage,
|
action: EasyMDE.drawImage,
|
||||||
className: "fa-regular fa-image",
|
className: "fa-regular fa-image",
|
||||||
title: gettext("Insert image"),
|
title: gettext("Insert image"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "table",
|
name: "table",
|
||||||
action: easyMde.drawTable,
|
action: EasyMDE.drawTable,
|
||||||
className: "fa fa-table",
|
className: "fa fa-table",
|
||||||
title: gettext("Insert table"),
|
title: gettext("Insert table"),
|
||||||
},
|
},
|
||||||
"|",
|
"|",
|
||||||
{
|
{
|
||||||
name: "clean-block",
|
name: "clean-block",
|
||||||
action: easyMde.cleanBlock,
|
action: EasyMDE.cleanBlock,
|
||||||
className: "fa fa-eraser fa-clean-block",
|
className: "fa fa-eraser fa-clean-block",
|
||||||
title: gettext("Clean block"),
|
title: gettext("Clean block"),
|
||||||
},
|
},
|
||||||
"|",
|
"|",
|
||||||
{
|
{
|
||||||
name: "preview",
|
name: "preview",
|
||||||
action: easyMde.togglePreview,
|
action: EasyMDE.togglePreview,
|
||||||
className: "fa fa-eye no-disable",
|
className: "fa fa-eye no-disable",
|
||||||
title: gettext("Toggle preview"),
|
title: gettext("Toggle preview"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "side-by-side",
|
name: "side-by-side",
|
||||||
action: easyMde.toggleSideBySide,
|
action: EasyMDE.toggleSideBySide,
|
||||||
className: "fa fa-columns no-disable no-mobile",
|
className: "fa fa-columns no-disable no-mobile",
|
||||||
title: gettext("Toggle side by side"),
|
title: gettext("Toggle side by side"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fullscreen",
|
name: "fullscreen",
|
||||||
action: easyMde.toggleFullScreen,
|
action: EasyMDE.toggleFullScreen,
|
||||||
className: "fa fa-expand no-mobile",
|
className: "fa fa-expand no-mobile",
|
||||||
title: gettext("Toggle fullscreen"),
|
title: gettext("Toggle fullscreen"),
|
||||||
},
|
},
|
||||||
@ -152,27 +156,25 @@ window.easymdeFactory = (textarea) => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const submits = textarea.closest("form").querySelectorAll('input[type="submit"]');
|
const submits: HTMLInputElement[] = Array.from(
|
||||||
const parentDiv = textarea.parentElement;
|
textarea.closest("form").querySelectorAll('input[type="submit"]'),
|
||||||
let submitPressed = false;
|
);
|
||||||
|
const parentDiv = textarea.parentElement.parentElement;
|
||||||
|
|
||||||
function checkMarkdownInput() {
|
function checkMarkdownInput(event: Event) {
|
||||||
// an attribute is null if it does not exist, else a string
|
// an attribute is null if it does not exist, else a string
|
||||||
const required = textarea.getAttribute("required") != null;
|
const required = textarea.getAttribute("required") != null;
|
||||||
const length = textarea.value.trim().length;
|
const length = textarea.value.trim().length;
|
||||||
|
|
||||||
if (required && length === 0) {
|
if (required && length === 0) {
|
||||||
parentDiv.style.boxShadow = "red 0px 0px 1.5px 1px";
|
parentDiv.style.boxShadow = "red 0px 0px 1.5px 1px";
|
||||||
|
event.preventDefault();
|
||||||
} else {
|
} else {
|
||||||
parentDiv.style.boxShadow = "";
|
parentDiv.style.boxShadow = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmitClick(e) {
|
function onSubmitClick(e: Event) {
|
||||||
if (!submitPressed) {
|
|
||||||
easymde.codemirror.on("change", checkMarkdownInput);
|
|
||||||
}
|
|
||||||
submitPressed = true;
|
|
||||||
checkMarkdownInput(e);
|
checkMarkdownInput(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,3 +182,11 @@ window.easymdeFactory = (textarea) => {
|
|||||||
submit.addEventListener("click", onSubmitClick);
|
submit.addEventListener("click", onSubmitClick);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@registerComponent("markdown-input")
|
||||||
|
class MarkdownInput extends inheritHtmlElement("textarea") {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
window.addEventListener("DOMContentLoaded", () => loadEasyMde(this.node));
|
||||||
|
}
|
||||||
|
}
|
24
core/static/webpack/sentry-popup-index.ts
Normal file
24
core/static/webpack/sentry-popup-index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { exportToHtml } from "#core:utils/globals";
|
||||||
|
// biome-ignore lint/style/noNamespaceImport: this is the recommended way from the documentation
|
||||||
|
import * as Sentry from "@sentry/browser";
|
||||||
|
|
||||||
|
interface LoggedUser {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SentryOptions {
|
||||||
|
dsn: string;
|
||||||
|
eventId: string;
|
||||||
|
user?: LoggedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportToHtml("loadSentryPopup", (options: SentryOptions) => {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: options.dsn,
|
||||||
|
});
|
||||||
|
Sentry.showReportDialog({
|
||||||
|
eventId: options.eventId,
|
||||||
|
...(options.user ?? {}),
|
||||||
|
});
|
||||||
|
});
|
21
core/static/webpack/utils/globals.ts
Normal file
21
core/static/webpack/utils/globals.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { Alpine as AlpineType } from "alpinejs";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
const Alpine: AlpineType;
|
||||||
|
const gettext: (text: string) => string;
|
||||||
|
const interpolate: <T>(fmt: string, args: string[] | T, isNamed?: boolean) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to export typescript functions to regular html and jinja files
|
||||||
|
* Without it, you either have to use the any keyword and suppress warnings or do a
|
||||||
|
* very painful type conversion workaround which is only here to please the linter
|
||||||
|
*
|
||||||
|
* This is only useful if you're using typescript, this is equivalent to doing
|
||||||
|
* window.yourFunction = yourFunction
|
||||||
|
**/
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Avoid strange tricks to export functions
|
||||||
|
export function exportToHtml(name: string, func: any) {
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: Avoid strange tricks to export functions
|
||||||
|
(window as any)[name] = func;
|
||||||
|
}
|
50
core/static/webpack/utils/web-components.ts
Normal file
50
core/static/webpack/utils/web-components.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Class decorator to register components easily
|
||||||
|
* It's a wrapper around window.customElements.define
|
||||||
|
* What's nice about it is that you don't separate the component registration
|
||||||
|
* and the class definition
|
||||||
|
**/
|
||||||
|
export function registerComponent(name: string, options?: ElementDefinitionOptions) {
|
||||||
|
return (component: CustomElementConstructor) => {
|
||||||
|
window.customElements.define(name, component, options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safari doesn't support inheriting from HTML tags on web components
|
||||||
|
* The technique is to:
|
||||||
|
* create a new web component
|
||||||
|
* create the desired type inside
|
||||||
|
* pass all attributes to the child component
|
||||||
|
* store is at as `node` inside the parent
|
||||||
|
*
|
||||||
|
* Since we can't use the generic type to instantiate the node, we create a generator function
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* class MyClass extends inheritHtmlElement("select") {
|
||||||
|
* // do whatever
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
**/
|
||||||
|
export function inheritHtmlElement<K extends keyof HTMLElementTagNameMap>(tagName: K) {
|
||||||
|
return class Inherited extends HTMLElement {
|
||||||
|
protected node: HTMLElementTagNameMap[K];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.node = document.createElement(tagName);
|
||||||
|
const attributes: Attr[] = []; // We need to make a copy to delete while iterating
|
||||||
|
for (const attr of this.attributes) {
|
||||||
|
if (attr.name in this.node) {
|
||||||
|
attributes.push(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attr of attributes) {
|
||||||
|
this.removeAttributeNode(attr);
|
||||||
|
this.node.setAttributeNode(attr);
|
||||||
|
}
|
||||||
|
this.appendChild(this.node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -1,26 +1,26 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
{% block head %}
|
{% block additional_js %}
|
||||||
{{ super() }}
|
{% if settings.SENTRY_DSN %}
|
||||||
<script
|
<script src="{{ static('webpack/sentry-popup-index.ts') }}" defer ></script>
|
||||||
src="{{ static('vendored/sentry/bundle.min.js') }}"
|
{% endif %}
|
||||||
crossorigin="anonymous"
|
{% endblock additional_js %}
|
||||||
></script>
|
|
||||||
{% endblock head %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h3>{% trans %}500, Server Error{% endtrans %}</h3>
|
<h3>{% trans %}500, Server Error{% endtrans %}</h3>
|
||||||
{% if settings.SENTRY_DSN %}
|
{% if settings.SENTRY_DSN %}
|
||||||
<script>
|
<script>
|
||||||
Sentry.init({ dsn: '{{ settings.SENTRY_DSN }}' });
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
Sentry.showReportDialog({
|
loadSentryPopup({
|
||||||
eventId: '{{ request.sentry_last_event_id() }}',
|
dsn: "{{ settings.SENTRY_DSN }}",
|
||||||
{% if user.is_authenticated %}
|
eventId: "{{ request.sentry_last_event_id() }}",
|
||||||
user: {
|
{% if user.is_authenticated %}
|
||||||
'name': '{{user.first_name}} {{user.last_name}}',
|
user: {
|
||||||
'email': '{{user.email}}'
|
name: '{{user.first_name}} {{user.last_name}}',
|
||||||
}
|
email: '{{user.email}}'
|
||||||
{% endif %}
|
}
|
||||||
})
|
{% endif %}
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM</title>
|
<title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}">
|
||||||
|
<link rel="stylesheet" href="{{ static('user/user_stats.scss') }}">
|
||||||
<link rel="stylesheet" href="{{ static('core/base.css') }}">
|
<link rel="stylesheet" href="{{ static('core/base.css') }}">
|
||||||
<link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}">
|
<link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}">
|
||||||
<link rel="stylesheet" href="{{ static('core/style.scss') }}">
|
<link rel="stylesheet" href="{{ static('core/style.scss') }}">
|
||||||
@ -12,7 +13,6 @@
|
|||||||
<link rel="stylesheet" href="{{ static('core/header.scss') }}">
|
<link rel="stylesheet" href="{{ static('core/header.scss') }}">
|
||||||
<link rel="stylesheet" href="{{ static('core/navbar.scss') }}">
|
<link rel="stylesheet" href="{{ static('core/navbar.scss') }}">
|
||||||
<link rel="stylesheet" href="{{ static('core/pagination.scss') }}">
|
<link rel="stylesheet" href="{{ static('core/pagination.scss') }}">
|
||||||
<link rel="stylesheet" href="{{ static('vendored/select2/select2.min.css') }}">
|
|
||||||
|
|
||||||
{% block jquery_css %}
|
{% block jquery_css %}
|
||||||
{# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #}
|
{# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #}
|
||||||
@ -26,8 +26,6 @@
|
|||||||
<script src="{{ static('webpack/jquery-index.js') }}"></script>
|
<script src="{{ static('webpack/jquery-index.js') }}"></script>
|
||||||
<!-- Put here to always have access to those functions on django widgets -->
|
<!-- Put here to always have access to those functions on django widgets -->
|
||||||
<script src="{{ static('core/js/script.js') }}"></script>
|
<script src="{{ static('core/js/script.js') }}"></script>
|
||||||
<script defer src="{{ static('vendored/select2/select2.min.js') }}"></script>
|
|
||||||
<script defer src="{{ static('core/js/sith-select2.js') }}"></script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
|
<markdown-input name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</markdown-input>
|
||||||
|
|
||||||
{# The easymde script can be included twice, it's safe in the code #}
|
{# The easymde script can be included twice, it's safe in the code #}
|
||||||
<script src="{{ statics.js }}" defer> </script>
|
<script src="{{ statics.js }}" defer> </script>
|
||||||
<link rel="stylesheet" type="text/css" href="{{ statics.css }}" defer>
|
<link rel="stylesheet" type="text/css" href="{{ statics.css }}" defer>
|
||||||
<script type="text/javascript">
|
|
||||||
addEventListener("DOMContentLoaded", (event) => {
|
|
||||||
easymdeFactory(
|
|
||||||
document.getElementById("{{ widget.attrs.id }}"));
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +50,7 @@ def phonenumber(
|
|||||||
try:
|
try:
|
||||||
parsed = phonenumbers.parse(value, country)
|
parsed = phonenumbers.parse(value, country)
|
||||||
return phonenumbers.format_number(parsed, number_format)
|
return phonenumbers.format_number(parsed, number_format)
|
||||||
except phonenumbers.NumberParseException as e:
|
except phonenumbers.NumberParseException:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -343,7 +343,7 @@ class TestUserTools:
|
|||||||
response = client.get(reverse("core:user_tools"))
|
response = client.get(reverse("core:user_tools"))
|
||||||
assertRedirects(
|
assertRedirects(
|
||||||
response,
|
response,
|
||||||
expected_url=f"/login?next=%2Fuser%2Ftools%2F",
|
expected_url="/login?next=%2Fuser%2Ftools%2F",
|
||||||
target_status_code=301,
|
target_status_code=301,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=0&godchildren_depth=0"
|
+ "?godfathers_depth=0&godchildren_depth=0"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [self.main_user.id]
|
assert [u["id"] for u in response.json()["users"]] == [self.main_user.id]
|
||||||
@ -91,7 +91,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=10&godchildren_depth=10"
|
+ "?godfathers_depth=10&godchildren_depth=10"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [
|
assert [u["id"] for u in response.json()["users"]] == [
|
||||||
@ -126,7 +126,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=1&godchildren_depth=1"
|
+ "?godfathers_depth=1&godchildren_depth=1"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [
|
assert [u["id"] for u in response.json()["users"]] == [
|
||||||
@ -150,7 +150,7 @@ class TestFetchFamilyApi(TestCase):
|
|||||||
self.client.force_login(self.main_user)
|
self.client.force_login(self.main_user)
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("api:family_graph", args=[self.main_user.id])
|
reverse("api:family_graph", args=[self.main_user.id])
|
||||||
+ f"?godfathers_depth=10&godchildren_depth=0"
|
+ "?godfathers_depth=10&godchildren_depth=0"
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert [u["id"] for u in response.json()["users"]] == [
|
assert [u["id"] for u in response.json()["users"]] == [
|
||||||
|
@ -158,10 +158,11 @@ def test_user_invoice_with_multiple_items():
|
|||||||
item_recipe = Recipe(InvoiceItem, invoice=foreign_key(Recipe(Invoice, user=user)))
|
item_recipe = Recipe(InvoiceItem, invoice=foreign_key(Recipe(Invoice, user=user)))
|
||||||
item_recipe.make(_quantity=3, quantity=1, product_unit_price=5)
|
item_recipe.make(_quantity=3, quantity=1, product_unit_price=5)
|
||||||
item_recipe.make(_quantity=1, quantity=1, product_unit_price=5)
|
item_recipe.make(_quantity=1, quantity=1, product_unit_price=5)
|
||||||
|
item_recipe.make(_quantity=2, quantity=1, product_unit_price=iter([5, 8]))
|
||||||
res = list(
|
res = list(
|
||||||
Invoice.objects.filter(user=user)
|
Invoice.objects.filter(user=user)
|
||||||
.annotate_total()
|
.annotate_total()
|
||||||
.order_by("-total")
|
.order_by("-total")
|
||||||
.values_list("total", flat=True)
|
.values_list("total", flat=True)
|
||||||
)
|
)
|
||||||
assert res == [15, 5]
|
assert res == [15, 13, 5]
|
||||||
|
136
core/urls.py
136
core/urls.py
@ -29,13 +29,67 @@ from core.converters import (
|
|||||||
FourDigitYearConverter,
|
FourDigitYearConverter,
|
||||||
TwoDigitMonthConverter,
|
TwoDigitMonthConverter,
|
||||||
)
|
)
|
||||||
from core.views import *
|
from core.views import (
|
||||||
|
FileDeleteView,
|
||||||
|
FileEditPropView,
|
||||||
|
FileEditView,
|
||||||
|
FileListView,
|
||||||
|
FileModerateView,
|
||||||
|
FileModerationView,
|
||||||
|
FileView,
|
||||||
|
GiftCreateView,
|
||||||
|
GiftDeleteView,
|
||||||
|
GroupCreateView,
|
||||||
|
GroupDeleteView,
|
||||||
|
GroupEditView,
|
||||||
|
GroupListView,
|
||||||
|
GroupTemplateView,
|
||||||
|
NotificationList,
|
||||||
|
PageCreateView,
|
||||||
|
PageDeleteView,
|
||||||
|
PageEditView,
|
||||||
|
PageHistView,
|
||||||
|
PageListView,
|
||||||
|
PagePropView,
|
||||||
|
PageRevView,
|
||||||
|
PageView,
|
||||||
|
SithLoginView,
|
||||||
|
SithPasswordChangeDoneView,
|
||||||
|
SithPasswordChangeView,
|
||||||
|
SithPasswordResetCompleteView,
|
||||||
|
SithPasswordResetConfirmView,
|
||||||
|
SithPasswordResetDoneView,
|
||||||
|
SithPasswordResetView,
|
||||||
|
UserAccountDetailView,
|
||||||
|
UserAccountView,
|
||||||
|
UserClubView,
|
||||||
|
UserCreationView,
|
||||||
|
UserGodfathersTreeView,
|
||||||
|
UserGodfathersView,
|
||||||
|
UserListView,
|
||||||
|
UserMiniView,
|
||||||
|
UserPicturesView,
|
||||||
|
UserPreferencesView,
|
||||||
|
UserStatsView,
|
||||||
|
UserToolsView,
|
||||||
|
UserUpdateGroupView,
|
||||||
|
UserUpdateProfileView,
|
||||||
|
UserView,
|
||||||
|
delete_user_godfather,
|
||||||
|
index,
|
||||||
|
logout,
|
||||||
|
notification,
|
||||||
|
password_root_change,
|
||||||
|
search_json,
|
||||||
|
search_user_json,
|
||||||
|
search_view,
|
||||||
|
send_file,
|
||||||
|
)
|
||||||
|
|
||||||
register_converter(FourDigitYearConverter, "yyyy")
|
register_converter(FourDigitYearConverter, "yyyy")
|
||||||
register_converter(TwoDigitMonthConverter, "mm")
|
register_converter(TwoDigitMonthConverter, "mm")
|
||||||
register_converter(BooleanStringConverter, "bool")
|
register_converter(BooleanStringConverter, "bool")
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", index, name="index"),
|
path("", index, name="index"),
|
||||||
path("notifications/", NotificationList.as_view(), name="notification_list"),
|
path("notifications/", NotificationList.as_view(), name="notification_list"),
|
||||||
@ -80,27 +134,17 @@ urlpatterns = [
|
|||||||
path("group/new/", GroupCreateView.as_view(), name="group_new"),
|
path("group/new/", GroupCreateView.as_view(), name="group_new"),
|
||||||
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
|
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
|
||||||
path(
|
path(
|
||||||
"group/<int:group_id>/delete/",
|
"group/<int:group_id>/delete/", GroupDeleteView.as_view(), name="group_delete"
|
||||||
GroupDeleteView.as_view(),
|
|
||||||
name="group_delete",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"group/<int:group_id>/detail/",
|
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
||||||
GroupTemplateView.as_view(),
|
|
||||||
name="group_detail",
|
|
||||||
),
|
),
|
||||||
# User views
|
# User views
|
||||||
path("user/", UserListView.as_view(), name="user_list"),
|
path("user/", UserListView.as_view(), name="user_list"),
|
||||||
path(
|
path("user/<int:user_id>/mini/", UserMiniView.as_view(), name="user_profile_mini"),
|
||||||
"user/<int:user_id>/mini/",
|
|
||||||
UserMiniView.as_view(),
|
|
||||||
name="user_profile_mini",
|
|
||||||
),
|
|
||||||
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
|
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/pictures/",
|
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
|
||||||
UserPicturesView.as_view(),
|
|
||||||
name="user_pictures",
|
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/godfathers/",
|
"user/<int:user_id>/godfathers/",
|
||||||
@ -117,28 +161,14 @@ urlpatterns = [
|
|||||||
delete_user_godfather,
|
delete_user_godfather,
|
||||||
name="user_godfathers_delete",
|
name="user_godfathers_delete",
|
||||||
),
|
),
|
||||||
path(
|
path("user/<int:user_id>/edit/", UserUpdateProfileView.as_view(), name="user_edit"),
|
||||||
"user/<int:user_id>/edit/",
|
|
||||||
UserUpdateProfileView.as_view(),
|
|
||||||
name="user_edit",
|
|
||||||
),
|
|
||||||
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
|
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
|
||||||
|
path("user/<int:user_id>/prefs/", UserPreferencesView.as_view(), name="user_prefs"),
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/prefs/",
|
"user/<int:user_id>/groups/", UserUpdateGroupView.as_view(), name="user_groups"
|
||||||
UserPreferencesView.as_view(),
|
|
||||||
name="user_prefs",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"user/<int:user_id>/groups/",
|
|
||||||
UserUpdateGroupView.as_view(),
|
|
||||||
name="user_groups",
|
|
||||||
),
|
),
|
||||||
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
|
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
|
||||||
path(
|
path("user/<int:user_id>/account/", UserAccountView.as_view(), name="user_account"),
|
||||||
"user/<int:user_id>/account/",
|
|
||||||
UserAccountView.as_view(),
|
|
||||||
name="user_account",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
|
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
|
||||||
UserAccountDetailView.as_view(),
|
UserAccountDetailView.as_view(),
|
||||||
@ -179,42 +209,18 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
|
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
|
||||||
path(
|
path(
|
||||||
"file/<int:file_id>/moderate/",
|
"file/<int:file_id>/moderate/", FileModerateView.as_view(), name="file_moderate"
|
||||||
FileModerateView.as_view(),
|
|
||||||
name="file_moderate",
|
|
||||||
),
|
),
|
||||||
path("file/<int:file_id>/download/", send_file, name="download"),
|
path("file/<int:file_id>/download/", send_file, name="download"),
|
||||||
# Page views
|
# Page views
|
||||||
path("page/", PageListView.as_view(), name="page_list"),
|
path("page/", PageListView.as_view(), name="page_list"),
|
||||||
path("page/create/", PageCreateView.as_view(), name="page_new"),
|
path("page/create/", PageCreateView.as_view(), name="page_new"),
|
||||||
|
path("page/<int:page_id>/delete/", PageDeleteView.as_view(), name="page_delete"),
|
||||||
|
path("page/<path:page_name>/edit/", PageEditView.as_view(), name="page_edit"),
|
||||||
|
path("page/<path:page_name>/prop/", PagePropView.as_view(), name="page_prop"),
|
||||||
|
path("page/<path:page_name>/hist/", PageHistView.as_view(), name="page_hist"),
|
||||||
path(
|
path(
|
||||||
"page/<int:page_id>/delete/",
|
"page/<path:page_name>/rev/<int:rev>/", PageRevView.as_view(), name="page_rev"
|
||||||
PageDeleteView.as_view(),
|
|
||||||
name="page_delete",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/edit/",
|
|
||||||
PageEditView.as_view(),
|
|
||||||
name="page_edit",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/prop/",
|
|
||||||
PagePropView.as_view(),
|
|
||||||
name="page_prop",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/hist/",
|
|
||||||
PageHistView.as_view(),
|
|
||||||
name="page_hist",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/rev/<int:rev>/",
|
|
||||||
PageRevView.as_view(),
|
|
||||||
name="page_rev",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"page/<path:page_name>/",
|
|
||||||
PageView.as_view(),
|
|
||||||
name="page",
|
|
||||||
),
|
),
|
||||||
|
path("page/<path:page_name>/", PageView.as_view(), name="page"),
|
||||||
]
|
]
|
||||||
|
@ -127,7 +127,7 @@ def resize_image_explicit(
|
|||||||
|
|
||||||
|
|
||||||
def exif_auto_rotate(image):
|
def exif_auto_rotate(image):
|
||||||
for orientation in ExifTags.TAGS.keys():
|
for orientation in ExifTags.TAGS:
|
||||||
if ExifTags.TAGS[orientation] == "Orientation":
|
if ExifTags.TAGS[orientation] == "Orientation":
|
||||||
break
|
break
|
||||||
exif = dict(image._getexif().items())
|
exif = dict(image._getexif().items())
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
import types
|
import types
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import AccessMixin
|
from django.contrib.auth.mixins import AccessMixin
|
||||||
from django.core.exceptions import (
|
from django.core.exceptions import (
|
||||||
ImproperlyConfigured,
|
ImproperlyConfigured,
|
||||||
@ -35,6 +36,7 @@ from django.http import (
|
|||||||
HttpResponseNotFound,
|
HttpResponseNotFound,
|
||||||
HttpResponseServerError,
|
HttpResponseServerError,
|
||||||
)
|
)
|
||||||
|
from django.shortcuts import render
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.views.generic.base import View
|
from django.views.generic.base import View
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
@ -79,9 +81,7 @@ def can_edit_prop(obj: Any, user: User) -> bool:
|
|||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
if obj is None or user.is_owner(obj):
|
return obj is None or user.is_owner(obj)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def can_edit(obj: Any, user: User) -> bool:
|
def can_edit(obj: Any, user: User) -> bool:
|
||||||
@ -232,7 +232,9 @@ class UserIsRootMixin(GenericContentPermissionMixinBuilder):
|
|||||||
PermissionDenied: if the user isn't root
|
PermissionDenied: if the user isn't root
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_function = lambda obj, user: user.is_root
|
@staticmethod
|
||||||
|
def permission_function(obj: Any, user: User):
|
||||||
|
return user.is_root
|
||||||
|
|
||||||
|
|
||||||
class FormerSubscriberMixin(AccessMixin):
|
class FormerSubscriberMixin(AccessMixin):
|
||||||
@ -304,10 +306,10 @@ class QuickNotifMixin:
|
|||||||
kwargs["quick_notifs"] = []
|
kwargs["quick_notifs"] = []
|
||||||
for n in self.quick_notif_list:
|
for n in self.quick_notif_list:
|
||||||
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
|
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
|
||||||
for k, v in settings.SITH_QUICK_NOTIF.items():
|
for key, val in settings.SITH_QUICK_NOTIF.items():
|
||||||
for gk in self.request.GET.keys():
|
for gk in self.request.GET:
|
||||||
if k == gk:
|
if key == gk:
|
||||||
kwargs["quick_notifs"].append(v)
|
kwargs["quick_notifs"].append(val)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
@ -324,8 +326,10 @@ class DetailFormView(SingleObjectMixin, FormView):
|
|||||||
return super().get_object()
|
return super().get_object()
|
||||||
|
|
||||||
|
|
||||||
from .files import *
|
# F403: those star-imports would be hellish to refactor
|
||||||
from .group import *
|
# E402: putting those import at the top of the file would also be difficult
|
||||||
from .page import *
|
from .files import * # noqa: F403 E402
|
||||||
from .site import *
|
from .group import * # noqa: F403 E402
|
||||||
from .user import *
|
from .page import * # noqa: F403 E402
|
||||||
|
from .site import * # noqa: F403 E402
|
||||||
|
from .user import * # noqa: F403 E402
|
||||||
|
@ -193,7 +193,7 @@ class FileEditView(CanEditMixin, UpdateView):
|
|||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
fields = ["name", "is_moderated"]
|
fields = ["name", "is_moderated"]
|
||||||
if self.object.is_file:
|
if self.object.is_file:
|
||||||
fields = ["file"] + fields
|
fields = ["file", *fields]
|
||||||
return modelform_factory(SithFile, fields=fields)
|
return modelform_factory(SithFile, fields=fields)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -283,38 +283,38 @@ class FileView(CanViewMixin, DetailView, FormMixin):
|
|||||||
`obj` is the SithFile object you want to put in the clipboard, or
|
`obj` is the SithFile object you want to put in the clipboard, or
|
||||||
where you want to paste the clipboard
|
where you want to paste the clipboard
|
||||||
"""
|
"""
|
||||||
if "delete" in request.POST.keys():
|
if "delete" in request.POST:
|
||||||
for f_id in request.POST.getlist("file_list"):
|
for f_id in request.POST.getlist("file_list"):
|
||||||
sf = SithFile.objects.filter(id=f_id).first()
|
file = SithFile.objects.filter(id=f_id).first()
|
||||||
if sf:
|
if file:
|
||||||
sf.delete()
|
file.delete()
|
||||||
if "clear" in request.POST.keys():
|
if "clear" in request.POST:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
if "cut" in request.POST.keys():
|
if "cut" in request.POST:
|
||||||
for f_id in request.POST.getlist("file_list"):
|
for f_id_str in request.POST.getlist("file_list"):
|
||||||
f_id = int(f_id)
|
f_id = int(f_id_str)
|
||||||
if (
|
if (
|
||||||
f_id in [c.id for c in obj.children.all()]
|
f_id in [c.id for c in obj.children.all()]
|
||||||
and f_id not in request.session["clipboard"]
|
and f_id not in request.session["clipboard"]
|
||||||
):
|
):
|
||||||
request.session["clipboard"].append(f_id)
|
request.session["clipboard"].append(f_id)
|
||||||
if "paste" in request.POST.keys():
|
if "paste" in request.POST:
|
||||||
for f_id in request.session["clipboard"]:
|
for f_id in request.session["clipboard"]:
|
||||||
sf = SithFile.objects.filter(id=f_id).first()
|
file = SithFile.objects.filter(id=f_id).first()
|
||||||
if sf:
|
if file:
|
||||||
sf.move_to(obj)
|
file.move_to(obj)
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
request.session.modified = True
|
request.session.modified = True
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.form = self.get_form()
|
self.form = self.get_form()
|
||||||
if "clipboard" not in request.session.keys():
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "clipboard" not in request.session.keys():
|
if "clipboard" not in request.session:
|
||||||
request.session["clipboard"] = []
|
request.session["clipboard"] = []
|
||||||
if request.user.can_edit(self.object):
|
if request.user.can_edit(self.object):
|
||||||
# XXX this call can fail!
|
# XXX this call can fail!
|
||||||
@ -398,6 +398,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
|
|||||||
self.object.is_moderated = True
|
self.object.is_moderated = True
|
||||||
self.object.moderator = request.user
|
self.object.moderator = request.user
|
||||||
self.object.save()
|
self.object.save()
|
||||||
if "next" in self.request.GET.keys():
|
if "next" in self.request.GET:
|
||||||
return redirect(self.request.GET["next"])
|
return redirect(self.request.GET["next"])
|
||||||
return redirect("core:file_moderation")
|
return redirect("core:file_moderation")
|
||||||
|
@ -29,6 +29,7 @@ from captcha.fields import CaptchaField
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||||
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
@ -38,7 +39,6 @@ from django.forms import (
|
|||||||
Textarea,
|
Textarea,
|
||||||
TextInput,
|
TextInput,
|
||||||
)
|
)
|
||||||
from django.templatetags.static import static
|
|
||||||
from django.utils.translation import gettext
|
from django.utils.translation import gettext
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
||||||
@ -72,8 +72,8 @@ class MarkdownInput(Textarea):
|
|||||||
context = super().get_context(name, value, attrs)
|
context = super().get_context(name, value, attrs)
|
||||||
|
|
||||||
context["statics"] = {
|
context["statics"] = {
|
||||||
"js": static("webpack/easymde-index.js"),
|
"js": staticfiles_storage.url("webpack/easymde-index.ts"),
|
||||||
"css": static("webpack/easymde-index.css"),
|
"css": staticfiles_storage.url("webpack/easymde-index.css"),
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ class SelectUser(TextInput):
|
|||||||
|
|
||||||
class LoginForm(AuthenticationForm):
|
class LoginForm(AuthenticationForm):
|
||||||
def __init__(self, *arg, **kwargs):
|
def __init__(self, *arg, **kwargs):
|
||||||
if "data" in kwargs.keys():
|
if "data" in kwargs:
|
||||||
from counter.models import Customer
|
from counter.models import Customer
|
||||||
|
|
||||||
data = kwargs["data"].copy()
|
data = kwargs["data"].copy()
|
||||||
@ -157,7 +157,7 @@ class LoginForm(AuthenticationForm):
|
|||||||
else:
|
else:
|
||||||
user = User.objects.filter(username=data["username"]).first()
|
user = User.objects.filter(username=data["username"]).first()
|
||||||
data["username"] = user.username
|
data["username"] = user.username
|
||||||
except:
|
except: # noqa E722 I don't know what error is supposed to be raised here
|
||||||
pass
|
pass
|
||||||
kwargs["data"] = data
|
kwargs["data"] = data
|
||||||
super().__init__(*arg, **kwargs)
|
super().__init__(*arg, **kwargs)
|
||||||
|
@ -55,7 +55,7 @@ class PageView(CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if "page" not in context.keys():
|
if "page" not in context:
|
||||||
context["new_page"] = self.kwargs["page_name"]
|
context["new_page"] = self.kwargs["page_name"]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -92,22 +92,16 @@ class PageRevView(CanViewMixin, DetailView):
|
|||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self, *args, **kwargs):
|
||||||
self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
|
self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
|
||||||
return self.page
|
return self.page
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if self.page is not None:
|
if not self.page:
|
||||||
context["page"] = self.page
|
return context | {"new_page": self.kwargs["page_name"]}
|
||||||
try:
|
context["page"] = self.page
|
||||||
rev = self.page.revisions.get(id=self.kwargs["rev"])
|
context["rev"] = self.page.revisions.filter(id=self.kwargs["rev"]).first()
|
||||||
context["rev"] = rev
|
|
||||||
except:
|
|
||||||
# By passing, the template will just display the normal page without taking revision into account
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
context["new_page"] = self.kwargs["page_name"]
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@ -118,7 +112,7 @@ class PageCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {}
|
init = {}
|
||||||
if "page" in self.request.GET.keys():
|
if "page" in self.request.GET:
|
||||||
page_name = self.request.GET["page"]
|
page_name = self.request.GET["page"]
|
||||||
parent_name = "/".join(page_name.split("/")[:-1])
|
parent_name = "/".join(page_name.split("/")[:-1])
|
||||||
parent = Page.get_page_by_full_name(parent_name)
|
parent = Page.get_page_by_full_name(parent_name)
|
||||||
@ -145,18 +139,8 @@ class PagePropView(CanEditPagePropMixin, UpdateView):
|
|||||||
slug_field = "_full_name"
|
slug_field = "_full_name"
|
||||||
slug_url_kwarg = "page_name"
|
slug_url_kwarg = "page_name"
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self, queryset=None):
|
||||||
o = super().get_object()
|
self.page = super().get_object()
|
||||||
# Create the page if it does not exists
|
|
||||||
# if p == None:
|
|
||||||
# parent_name = '/'.join(page_name.split('/')[:-1])
|
|
||||||
# name = page_name.split('/')[-1]
|
|
||||||
# if parent_name == "":
|
|
||||||
# p = Page(name=name)
|
|
||||||
# else:
|
|
||||||
# parent = Page.get_page_by_full_name(parent_name)
|
|
||||||
# p = Page(name=name, parent=parent)
|
|
||||||
self.page = o
|
|
||||||
try:
|
try:
|
||||||
self.page.set_lock_recursive(self.request.user)
|
self.page.set_lock_recursive(self.request.user)
|
||||||
except LockError as e:
|
except LockError as e:
|
||||||
|
@ -53,11 +53,8 @@ class NotificationList(ListView):
|
|||||||
if self.request.user.is_anonymous:
|
if self.request.user.is_anonymous:
|
||||||
return Notification.objects.none()
|
return Notification.objects.none()
|
||||||
# TODO: Bulk update in django 2.2
|
# TODO: Bulk update in django 2.2
|
||||||
if "see_all" in self.request.GET.keys():
|
if "see_all" in self.request.GET:
|
||||||
for n in self.request.user.notifications.filter(viewed=False):
|
self.request.user.notifications.filter(viewed=False).update(viewed=True)
|
||||||
n.viewed = True
|
|
||||||
n.save()
|
|
||||||
|
|
||||||
return self.request.user.notifications.order_by("-date")[:20]
|
return self.request.user.notifications.order_by("-date")[:20]
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,9 +21,11 @@
|
|||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
import itertools
|
||||||
|
|
||||||
# This file contains all the views that concern the user model
|
# This file contains all the views that concern the user model
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
from operator import itemgetter
|
||||||
from smtplib import SMTPException
|
from smtplib import SMTPException
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -253,8 +255,10 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
"name": _("Groups"),
|
"name": _("Groups"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
try:
|
if (
|
||||||
if user.customer and (
|
hasattr(user, "customer")
|
||||||
|
and user.customer
|
||||||
|
and (
|
||||||
user == self.request.user
|
user == self.request.user
|
||||||
or self.request.user.is_in_group(
|
or self.request.user.is_in_group(
|
||||||
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
||||||
@ -264,25 +268,22 @@ class UserTabsMixin(TabedViewMixin):
|
|||||||
+ settings.SITH_BOARD_SUFFIX
|
+ settings.SITH_BOARD_SUFFIX
|
||||||
)
|
)
|
||||||
or self.request.user.is_root
|
or self.request.user.is_root
|
||||||
):
|
)
|
||||||
tab_list.append(
|
):
|
||||||
{
|
tab_list.append(
|
||||||
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
{
|
||||||
"slug": "stats",
|
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
|
||||||
"name": _("Stats"),
|
"slug": "stats",
|
||||||
}
|
"name": _("Stats"),
|
||||||
)
|
}
|
||||||
tab_list.append(
|
)
|
||||||
{
|
tab_list.append(
|
||||||
"url": reverse(
|
{
|
||||||
"core:user_account", kwargs={"user_id": user.id}
|
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
|
||||||
),
|
"slug": "account",
|
||||||
"slug": "account",
|
"name": _("Account") + " (%s €)" % user.customer.amount,
|
||||||
"name": _("Account") + " (%s €)" % user.customer.amount,
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return tab_list
|
return tab_list
|
||||||
|
|
||||||
|
|
||||||
@ -665,9 +666,15 @@ class UserAccountView(UserAccountBase):
|
|||||||
kwargs["refilling_month"] = self.expense_by_month(
|
kwargs["refilling_month"] = self.expense_by_month(
|
||||||
Refilling.objects.filter(customer=self.object.customer)
|
Refilling.objects.filter(customer=self.object.customer)
|
||||||
)
|
)
|
||||||
kwargs["invoices_month"] = self.expense_by_month(
|
kwargs["invoices_month"] = [
|
||||||
Invoice.objects.filter(user=self.object)
|
# the django ORM removes the `group by` clause in this query,
|
||||||
)
|
# so a little of post-processing is needed
|
||||||
|
{"grouped_date": key, "total": sum(i["total"] for i in group)}
|
||||||
|
for key, group in itertools.groupby(
|
||||||
|
self.expense_by_month(Invoice.objects.filter(user=self.object)),
|
||||||
|
key=itemgetter("grouped_date"),
|
||||||
|
)
|
||||||
|
]
|
||||||
kwargs["etickets"] = self.object.customer.buyings.exclude(product__eticket=None)
|
kwargs["etickets"] = self.object.customer.buyings.exclude(product__eticket=None)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@ -15,7 +15,19 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
from counter.models import *
|
from counter.models import (
|
||||||
|
AccountDump,
|
||||||
|
BillingInfo,
|
||||||
|
CashRegisterSummary,
|
||||||
|
Counter,
|
||||||
|
Customer,
|
||||||
|
Eticket,
|
||||||
|
Permanency,
|
||||||
|
Product,
|
||||||
|
ProductType,
|
||||||
|
Refilling,
|
||||||
|
Selling,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Product)
|
@admin.register(Product)
|
||||||
|
@ -154,7 +154,7 @@ class Customer(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_full_url(self):
|
def get_full_url(self):
|
||||||
return "".join(["https://", settings.SITH_URL, self.get_absolute_url()])
|
return f"https://{settings.SITH_URL}{self.get_absolute_url()}"
|
||||||
|
|
||||||
|
|
||||||
class BillingInfo(models.Model):
|
class BillingInfo(models.Model):
|
||||||
@ -287,9 +287,7 @@ class ProductType(models.Model):
|
|||||||
"""Method to see if that object can be edited by the given user."""
|
"""Method to see if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
@ -346,21 +344,19 @@ class Product(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_record_product(self):
|
def is_record_product(self):
|
||||||
return settings.SITH_ECOCUP_CONS == self.id
|
return self.id == settings.SITH_ECOCUP_CONS
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_unrecord_product(self):
|
def is_unrecord_product(self):
|
||||||
return settings.SITH_ECOCUP_DECO == self.id
|
return self.id == settings.SITH_ECOCUP_DECO
|
||||||
|
|
||||||
def is_owned_by(self, user):
|
def is_owned_by(self, user):
|
||||||
"""Method to see if that object can be edited by the given user."""
|
"""Method to see if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(
|
return user.is_in_group(
|
||||||
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
|
||||||
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
|
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_sold_to(self, user: User) -> bool:
|
def can_be_sold_to(self, user: User) -> bool:
|
||||||
"""Check if whether the user given in parameter has the right to buy
|
"""Check if whether the user given in parameter has the right to buy
|
||||||
@ -392,10 +388,7 @@ class Product(models.Model):
|
|||||||
buying_groups = list(self.buying_groups.all())
|
buying_groups = list(self.buying_groups.all())
|
||||||
if not buying_groups:
|
if not buying_groups:
|
||||||
return True
|
return True
|
||||||
for group in buying_groups:
|
return any(user.is_in_group(pk=group.id) for group in buying_groups)
|
||||||
if user.is_in_group(pk=group.id):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def profit(self):
|
def profit(self):
|
||||||
@ -887,27 +880,19 @@ class Selling(models.Model):
|
|||||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
||||||
) % {
|
) % {
|
||||||
"event": event,
|
"event": event,
|
||||||
"url": "".join(
|
"url": (
|
||||||
(
|
f'<a href="{self.customer.get_full_url()}">'
|
||||||
'<a href="',
|
f"{self.customer.get_full_url()}</a>"
|
||||||
self.customer.get_full_url(),
|
|
||||||
'">',
|
|
||||||
self.customer.get_full_url(),
|
|
||||||
"</a>",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
"eticket": "".join(
|
"eticket": (
|
||||||
(
|
f'<a href="{self.get_eticket_full_url()}">'
|
||||||
'<a href="',
|
f"{self.get_eticket_full_url()}</a>"
|
||||||
self.get_eticket_full_url(),
|
|
||||||
'">',
|
|
||||||
self.get_eticket_full_url(),
|
|
||||||
"</a>",
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
message_txt = _(
|
message_txt = _(
|
||||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
"You bought an eticket for the event %(event)s.\n"
|
||||||
|
"You can download it directly from this link %(eticket)s.\n"
|
||||||
|
"You can also retrieve all your e-tickets on your account page %(url)s."
|
||||||
) % {
|
) % {
|
||||||
"event": event,
|
"event": event,
|
||||||
"url": self.customer.get_full_url(),
|
"url": self.customer.get_full_url(),
|
||||||
@ -919,7 +904,7 @@ class Selling(models.Model):
|
|||||||
|
|
||||||
def get_eticket_full_url(self):
|
def get_eticket_full_url(self):
|
||||||
eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id})
|
eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id})
|
||||||
return "".join(["https://", settings.SITH_URL, eticket_url])
|
return f"https://{settings.SITH_URL}{eticket_url}"
|
||||||
|
|
||||||
|
|
||||||
class Permanency(models.Model):
|
class Permanency(models.Model):
|
||||||
@ -1019,15 +1004,15 @@ class CashRegisterSummary(models.Model):
|
|||||||
elif name == "hundred_euros":
|
elif name == "hundred_euros":
|
||||||
return self.items.filter(value=100, is_check=False).first()
|
return self.items.filter(value=100, is_check=False).first()
|
||||||
elif name == "check_1":
|
elif name == "check_1":
|
||||||
return checks[0] if 0 < len(checks) else None
|
return checks[0] if len(checks) > 0 else None
|
||||||
elif name == "check_2":
|
elif name == "check_2":
|
||||||
return checks[1] if 1 < len(checks) else None
|
return checks[1] if len(checks) > 1 else None
|
||||||
elif name == "check_3":
|
elif name == "check_3":
|
||||||
return checks[2] if 2 < len(checks) else None
|
return checks[2] if len(checks) > 2 else None
|
||||||
elif name == "check_4":
|
elif name == "check_4":
|
||||||
return checks[3] if 3 < len(checks) else None
|
return checks[3] if len(checks) > 3 else None
|
||||||
elif name == "check_5":
|
elif name == "check_5":
|
||||||
return checks[4] if 4 < len(checks) else None
|
return checks[4] if len(checks) > 4 else None
|
||||||
else:
|
else:
|
||||||
return object.__getattribute__(self, name)
|
return object.__getattribute__(self, name)
|
||||||
|
|
||||||
@ -1035,9 +1020,7 @@ class CashRegisterSummary(models.Model):
|
|||||||
"""Method to see if that object can be edited by the given user."""
|
"""Method to see if that object can be edited by the given user."""
|
||||||
if user.is_anonymous:
|
if user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
|
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_total(self):
|
def get_total(self):
|
||||||
t = 0
|
t = 0
|
||||||
|
@ -51,7 +51,7 @@ def write_log(instance, operation_type):
|
|||||||
# Return None by default
|
# Return None by default
|
||||||
return None
|
return None
|
||||||
|
|
||||||
log = OperationLog(
|
OperationLog(
|
||||||
label=str(instance),
|
label=str(instance),
|
||||||
operator=get_user(),
|
operator=get_user(),
|
||||||
operation_type=operation_type,
|
operation_type=operation_type,
|
||||||
|
@ -8,25 +8,17 @@
|
|||||||
{% if current_tab == "products" %}
|
{% if current_tab == "products" %}
|
||||||
<p><a href="{{ url('counter:new_product') }}">{% trans %}New product{% endtrans %}</a></p>
|
<p><a href="{{ url('counter:new_product') }}">{% trans %}New product{% endtrans %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if product_list %}
|
<h3>{% trans %}Product list{% endtrans %}</h3>
|
||||||
<h3>{% trans %}Product list{% endtrans %}</h3>
|
{%- for product_type, products in object_list -%}
|
||||||
{% for t in ProductType.objects.all().order_by('name') %}
|
<h4>{{ product_type or _("Uncategorized") }}</h4>
|
||||||
<h4>{{ t }}</h4>
|
|
||||||
<ul>
|
|
||||||
{% for p in product_list.filter(product_type=t).all().order_by('name') %}
|
|
||||||
<li><a href="{{ url('counter:product_edit', product_id=p.id) }}">{{ p }} ({{ p.code }})</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endfor %}
|
|
||||||
<h4>{% trans %}Uncategorized{% endtrans %}</h4>
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for p in product_list.filter(product_type=None).all().order_by('name') %}
|
{%- for product in products -%}
|
||||||
<li><a href="{{ url('counter:product_edit', product_id=p.id) }}">{{ p }} ({{ p.code }})</a></li>
|
<li><a href="{{ url('counter:product_edit', product_id=product.id) }}">{{ product }} ({{ product.code }})</a></li>
|
||||||
{% endfor %}
|
{%- endfor -%}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{%- else -%}
|
||||||
{% trans %}There is no products in this website.{% endtrans %}
|
{% trans %}There is no products in this website.{% endtrans %}
|
||||||
{% endif %}
|
{%- endfor -%}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ class TestBarmanConnection(TestCase):
|
|||||||
)
|
)
|
||||||
response = self.client.get(reverse("counter:activity", args=[self.counter.id]))
|
response = self.client.get(reverse("counter:activity", args=[self.counter.id]))
|
||||||
|
|
||||||
assert not '<li><a href="/user/1/">S' Kia</a></li>' in str(response.content)
|
assert '<li><a href="/user/1/">S' Kia</a></li>' not in str(response.content)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@ -853,7 +853,7 @@ class TestCustomerAccountId(TestCase):
|
|||||||
number = account_id[:-1]
|
number = account_id[:-1]
|
||||||
assert created is True
|
assert created is True
|
||||||
assert number == "12346"
|
assert number == "12346"
|
||||||
assert 6 == len(account_id)
|
assert len(account_id) == 6
|
||||||
assert account_id[-1] in string.ascii_lowercase
|
assert account_id[-1] in string.ascii_lowercase
|
||||||
assert customer.amount == 0
|
assert customer.amount == 0
|
||||||
|
|
||||||
|
@ -15,15 +15,44 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from counter.views import *
|
from counter.views import (
|
||||||
|
ActiveProductListView,
|
||||||
|
ArchivedProductListView,
|
||||||
|
CashSummaryEditView,
|
||||||
|
CashSummaryListView,
|
||||||
|
CounterActivityView,
|
||||||
|
CounterCashSummaryView,
|
||||||
|
CounterClick,
|
||||||
|
CounterCreateView,
|
||||||
|
CounterDeleteView,
|
||||||
|
CounterEditPropView,
|
||||||
|
CounterEditView,
|
||||||
|
CounterLastOperationsView,
|
||||||
|
CounterListView,
|
||||||
|
CounterMain,
|
||||||
|
CounterRefillingListView,
|
||||||
|
CounterStatView,
|
||||||
|
EticketCreateView,
|
||||||
|
EticketEditView,
|
||||||
|
EticketListView,
|
||||||
|
EticketPDFView,
|
||||||
|
InvoiceCallView,
|
||||||
|
ProductCreateView,
|
||||||
|
ProductEditView,
|
||||||
|
ProductTypeCreateView,
|
||||||
|
ProductTypeEditView,
|
||||||
|
ProductTypeListView,
|
||||||
|
RefillingDeleteView,
|
||||||
|
SellingDeleteView,
|
||||||
|
StudentCardDeleteView,
|
||||||
|
StudentCardFormView,
|
||||||
|
counter_login,
|
||||||
|
counter_logout,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<int:counter_id>/", CounterMain.as_view(), name="details"),
|
path("<int:counter_id>/", CounterMain.as_view(), name="details"),
|
||||||
path(
|
path("<int:counter_id>/click/<int:user_id>/", CounterClick.as_view(), name="click"),
|
||||||
"<int:counter_id>/click/<int:user_id>/",
|
|
||||||
CounterClick.as_view(),
|
|
||||||
name="click",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"<int:counter_id>/last_ops/",
|
"<int:counter_id>/last_ops/",
|
||||||
CounterLastOperationsView.as_view(),
|
CounterLastOperationsView.as_view(),
|
||||||
@ -34,19 +63,11 @@ urlpatterns = [
|
|||||||
CounterCashSummaryView.as_view(),
|
CounterCashSummaryView.as_view(),
|
||||||
name="cash_summary",
|
name="cash_summary",
|
||||||
),
|
),
|
||||||
path(
|
path("<int:counter_id>/activity/", CounterActivityView.as_view(), name="activity"),
|
||||||
"<int:counter_id>/activity/",
|
|
||||||
CounterActivityView.as_view(),
|
|
||||||
name="activity",
|
|
||||||
),
|
|
||||||
path("<int:counter_id>/stats/", CounterStatView.as_view(), name="stats"),
|
path("<int:counter_id>/stats/", CounterStatView.as_view(), name="stats"),
|
||||||
path("<int:counter_id>/login/", counter_login, name="login"),
|
path("<int:counter_id>/login/", counter_login, name="login"),
|
||||||
path("<int:counter_id>/logout/", counter_logout, name="logout"),
|
path("<int:counter_id>/logout/", counter_logout, name="logout"),
|
||||||
path(
|
path("eticket/<int:selling_id>/pdf/", EticketPDFView.as_view(), name="eticket_pdf"),
|
||||||
"eticket/<int:selling_id>/pdf/",
|
|
||||||
EticketPDFView.as_view(),
|
|
||||||
name="eticket_pdf",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"customer/<int:customer_id>/card/add/",
|
"customer/<int:customer_id>/card/add/",
|
||||||
StudentCardFormView.as_view(),
|
StudentCardFormView.as_view(),
|
||||||
@ -59,17 +80,11 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("admin/<int:counter_id>/", CounterEditView.as_view(), name="admin"),
|
path("admin/<int:counter_id>/", CounterEditView.as_view(), name="admin"),
|
||||||
path(
|
path(
|
||||||
"admin/<int:counter_id>/prop/",
|
"admin/<int:counter_id>/prop/", CounterEditPropView.as_view(), name="prop_admin"
|
||||||
CounterEditPropView.as_view(),
|
|
||||||
name="prop_admin",
|
|
||||||
),
|
),
|
||||||
path("admin/", CounterListView.as_view(), name="admin_list"),
|
path("admin/", CounterListView.as_view(), name="admin_list"),
|
||||||
path("admin/new/", CounterCreateView.as_view(), name="new"),
|
path("admin/new/", CounterCreateView.as_view(), name="new"),
|
||||||
path(
|
path("admin/delete/<int:counter_id>/", CounterDeleteView.as_view(), name="delete"),
|
||||||
"admin/delete/<int:counter_id>/",
|
|
||||||
CounterDeleteView.as_view(),
|
|
||||||
name="delete",
|
|
||||||
),
|
|
||||||
path("admin/invoices_call/", InvoiceCallView.as_view(), name="invoices_call"),
|
path("admin/invoices_call/", InvoiceCallView.as_view(), name="invoices_call"),
|
||||||
path(
|
path(
|
||||||
"admin/cash_summary/list/",
|
"admin/cash_summary/list/",
|
||||||
@ -81,10 +96,10 @@ urlpatterns = [
|
|||||||
CashSummaryEditView.as_view(),
|
CashSummaryEditView.as_view(),
|
||||||
name="cash_summary_edit",
|
name="cash_summary_edit",
|
||||||
),
|
),
|
||||||
path("admin/product/list/", ProductListView.as_view(), name="product_list"),
|
path("admin/product/list/", ActiveProductListView.as_view(), name="product_list"),
|
||||||
path(
|
path(
|
||||||
"admin/product/list_archived/",
|
"admin/product/list_archived/",
|
||||||
ProductArchivedListView.as_view(),
|
ArchivedProductListView.as_view(),
|
||||||
name="product_list_archived",
|
name="product_list_archived",
|
||||||
),
|
),
|
||||||
path("admin/product/create/", ProductCreateView.as_view(), name="new_product"),
|
path("admin/product/create/", ProductCreateView.as_view(), name="new_product"),
|
||||||
|
@ -12,10 +12,12 @@
|
|||||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
import itertools
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from datetime import timezone as tz
|
from datetime import timezone as tz
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from operator import attrgetter
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
@ -89,16 +91,10 @@ class CounterAdminMixin(View):
|
|||||||
edit_club = []
|
edit_club = []
|
||||||
|
|
||||||
def _test_group(self, user):
|
def _test_group(self, user):
|
||||||
for grp_id in self.edit_group:
|
return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
|
||||||
if user.is_in_group(pk=grp_id):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _test_club(self, user):
|
def _test_club(self, user):
|
||||||
for c in self.edit_club:
|
return any(c.can_be_edited_by(user) for c in self.edit_club)
|
||||||
if c.can_be_edited_by(user):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not (
|
if not (
|
||||||
@ -179,7 +175,7 @@ class CounterMain(
|
|||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if self.object.type == "BAR" and not (
|
if self.object.type == "BAR" and not (
|
||||||
"counter_token" in self.request.session.keys()
|
"counter_token" in self.request.session
|
||||||
and self.request.session["counter_token"] == self.object.token
|
and self.request.session["counter_token"] == self.object.token
|
||||||
): # Check the token to avoid the bar to be stolen
|
): # Check the token to avoid the bar to be stolen
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
@ -217,7 +213,7 @@ class CounterMain(
|
|||||||
kwargs["barmen"] = self.object.barmen_list
|
kwargs["barmen"] = self.object.barmen_list
|
||||||
elif self.request.user.is_authenticated:
|
elif self.request.user.is_authenticated:
|
||||||
kwargs["barmen"] = [self.request.user]
|
kwargs["barmen"] = [self.request.user]
|
||||||
if "last_basket" in self.request.session.keys():
|
if "last_basket" in self.request.session:
|
||||||
kwargs["last_basket"] = self.request.session.pop("last_basket")
|
kwargs["last_basket"] = self.request.session.pop("last_basket")
|
||||||
kwargs["last_customer"] = self.request.session.pop("last_customer")
|
kwargs["last_customer"] = self.request.session.pop("last_customer")
|
||||||
kwargs["last_total"] = self.request.session.pop("last_total")
|
kwargs["last_total"] = self.request.session.pop("last_total")
|
||||||
@ -292,7 +288,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Simple get view."""
|
"""Simple get view."""
|
||||||
if "basket" not in request.session.keys(): # Init the basket session entry
|
if "basket" not in request.session: # Init the basket session entry
|
||||||
request.session["basket"] = {}
|
request.session["basket"] = {}
|
||||||
request.session["basket_total"] = 0
|
request.session["basket_total"] = 0
|
||||||
request.session["not_enough"] = False # Reset every variable
|
request.session["not_enough"] = False # Reset every variable
|
||||||
@ -316,7 +312,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
): # Check that at least one barman is logged in
|
): # Check that at least one barman is logged in
|
||||||
return self.cancel(request)
|
return self.cancel(request)
|
||||||
if self.object.type == "BAR" and not (
|
if self.object.type == "BAR" and not (
|
||||||
"counter_token" in self.request.session.keys()
|
"counter_token" in self.request.session
|
||||||
and self.request.session["counter_token"] == self.object.token
|
and self.request.session["counter_token"] == self.object.token
|
||||||
): # Also check the token to avoid the bar to be stolen
|
): # Also check the token to avoid the bar to be stolen
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
@ -327,7 +323,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
)
|
)
|
||||||
+ "?bad_location"
|
+ "?bad_location"
|
||||||
)
|
)
|
||||||
if "basket" not in request.session.keys():
|
if "basket" not in request.session:
|
||||||
request.session["basket"] = {}
|
request.session["basket"] = {}
|
||||||
request.session["basket_total"] = 0
|
request.session["basket_total"] = 0
|
||||||
request.session["not_enough"] = False # Reset every variable
|
request.session["not_enough"] = False # Reset every variable
|
||||||
@ -384,13 +380,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get_total_quantity_for_pid(self, request, pid):
|
def get_total_quantity_for_pid(self, request, pid):
|
||||||
pid = str(pid)
|
pid = str(pid)
|
||||||
try:
|
if pid not in request.session["basket"]:
|
||||||
return (
|
|
||||||
request.session["basket"][pid]["qty"]
|
|
||||||
+ request.session["basket"][pid]["bonus_qty"]
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
return 0
|
return 0
|
||||||
|
return (
|
||||||
|
request.session["basket"][pid]["qty"]
|
||||||
|
+ request.session["basket"][pid]["bonus_qty"]
|
||||||
|
)
|
||||||
|
|
||||||
def compute_record_product(self, request, product=None):
|
def compute_record_product(self, request, product=None):
|
||||||
recorded = 0
|
recorded = 0
|
||||||
@ -804,25 +799,41 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
|
|||||||
current_tab = "products"
|
current_tab = "products"
|
||||||
|
|
||||||
|
|
||||||
class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
|
class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
|
||||||
|
model = Product
|
||||||
|
queryset = Product.objects.annotate(type_name=F("product_type__name"))
|
||||||
|
template_name = "counter/product_list.jinja"
|
||||||
|
ordering = [
|
||||||
|
F("product_type__priority").desc(nulls_last=True),
|
||||||
|
"product_type",
|
||||||
|
"name",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
res = super().get_context_data(**kwargs)
|
||||||
|
res["object_list"] = itertools.groupby(
|
||||||
|
res["object_list"], key=attrgetter("type_name")
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ArchivedProductListView(ProductListView):
|
||||||
"""A list view for the admins."""
|
"""A list view for the admins."""
|
||||||
|
|
||||||
model = Product
|
|
||||||
template_name = "counter/product_list.jinja"
|
|
||||||
queryset = Product.objects.filter(archived=True)
|
|
||||||
ordering = ["name"]
|
|
||||||
current_tab = "archive"
|
current_tab = "archive"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(archived=True)
|
||||||
|
|
||||||
class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
|
|
||||||
|
class ActiveProductListView(ProductListView):
|
||||||
"""A list view for the admins."""
|
"""A list view for the admins."""
|
||||||
|
|
||||||
model = Product
|
|
||||||
template_name = "counter/product_list.jinja"
|
|
||||||
queryset = Product.objects.filter(archived=False)
|
|
||||||
ordering = ["name"]
|
|
||||||
current_tab = "products"
|
current_tab = "products"
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(archived=False)
|
||||||
|
|
||||||
|
|
||||||
class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
|
class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
|
||||||
"""A create view for the admins."""
|
"""A create view for the admins."""
|
||||||
|
@ -24,6 +24,12 @@ Si le mot apparaît dans le template Jinja :
|
|||||||
{% trans %}Hello{% endtrans %}
|
{% trans %}Hello{% endtrans %}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Si on est dans un fichier javascript ou typescript :
|
||||||
|
|
||||||
|
```js
|
||||||
|
gettext("Hello");
|
||||||
|
```
|
||||||
|
|
||||||
## Générer le fichier django.po
|
## Générer le fichier django.po
|
||||||
|
|
||||||
La traduction se fait en trois étapes.
|
La traduction se fait en trois étapes.
|
||||||
@ -32,7 +38,7 @@ l'éditer et enfin le compiler au format binaire pour qu'il soit lu par le serve
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
./manage.py makemessages --locale=fr -e py,jinja --ignore=node_modules # Pour le backend
|
./manage.py makemessages --locale=fr -e py,jinja --ignore=node_modules # Pour le backend
|
||||||
./manage.py makemessages --locale=fr -d djangojs --ignore=node_modules # Pour le frontend
|
./manage.py makemessages --locale=fr -d djangojs -e js,ts --ignore=node_modules # Pour le frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
## Éditer le fichier django.po
|
## Éditer le fichier django.po
|
||||||
|
@ -190,6 +190,10 @@ que sont VsCode et Sublime Text.
|
|||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -246,15 +246,3 @@ pytest core/tests/tests_core.py::TestUserRegistration
|
|||||||
tous les tests avant de push un commit.
|
tous les tests avant de push un commit.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Vérifier les dépendances Javascript
|
|
||||||
|
|
||||||
Une commande a été écrite pour vérifier les éventuelles mises
|
|
||||||
à jour à faire sur les librairies Javascript utilisées.
|
|
||||||
N'oubliez pas de mettre à jour à la fois le fichier
|
|
||||||
de la librairie, mais également sa version dans `sith/settings.py`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Vérifier les mises à jour
|
|
||||||
python manage.py check_front
|
|
||||||
```
|
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.db.models import F, Sum
|
||||||
|
|
||||||
from eboutic.models import *
|
from eboutic.models import Basket, BasketItem, Invoice, InvoiceItem
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Basket)
|
@admin.register(Basket)
|
||||||
|
@ -117,9 +117,7 @@ class BasketForm:
|
|||||||
"""
|
"""
|
||||||
if not self.error_messages and not self.correct_items:
|
if not self.error_messages and not self.correct_items:
|
||||||
self.clean()
|
self.clean()
|
||||||
if self.error_messages:
|
return not self.error_messages
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def errors(self) -> list[str]:
|
def errors(self) -> list[str]:
|
||||||
|
@ -173,9 +173,8 @@ class InvoiceQueryset(models.QuerySet):
|
|||||||
return self.annotate(
|
return self.annotate(
|
||||||
total=Subquery(
|
total=Subquery(
|
||||||
InvoiceItem.objects.filter(invoice_id=OuterRef("pk"))
|
InvoiceItem.objects.filter(invoice_id=OuterRef("pk"))
|
||||||
.annotate(item_amount=F("product_unit_price") * F("quantity"))
|
.values("invoice_id")
|
||||||
.values("item_amount")
|
.annotate(total=Sum(F("product_unit_price") * F("quantity")))
|
||||||
.annotate(total=Sum("item_amount"))
|
|
||||||
.values("total")
|
.values("total")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -2,8 +2,6 @@ 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 pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
|
||||||
|
|
||||||
from counter.models import BillingInfo
|
from counter.models import BillingInfo
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
|
|
||||||
from cryptography.hazmat.primitives.hashes import SHA1
|
from cryptography.hazmat.primitives.hashes import SHA1
|
||||||
from cryptography.hazmat.primitives.serialization import (
|
from cryptography.hazmat.primitives.serialization import (
|
||||||
load_pem_private_key,
|
load_pem_private_key,
|
||||||
@ -19,6 +19,12 @@ from cryptography.hazmat.primitives.serialization import (
|
|||||||
)
|
)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import (
|
||||||
|
RSAPrivateKey,
|
||||||
|
RSAPublicKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_signature_valid():
|
def test_signature_valid():
|
||||||
"""Test that data sent to the bank is correctly signed."""
|
"""Test that data sent to the bank is correctly signed."""
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import urllib
|
import urllib
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
|
||||||
from cryptography.hazmat.primitives.hashes import SHA1
|
from cryptography.hazmat.primitives.hashes import SHA1
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -38,6 +38,9 @@ from core.models import User
|
|||||||
from counter.models import Counter, Customer, Product, Selling
|
from counter.models import Counter, Customer, Product, Selling
|
||||||
from eboutic.models import Basket, BasketItem
|
from eboutic.models import Basket, BasketItem
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||||
|
|
||||||
|
|
||||||
class TestEboutic(TestCase):
|
class TestEboutic(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -25,7 +25,14 @@
|
|||||||
from django.urls import path, register_converter
|
from django.urls import path, register_converter
|
||||||
|
|
||||||
from eboutic.converters import PaymentResultConverter
|
from eboutic.converters import PaymentResultConverter
|
||||||
from eboutic.views import *
|
from eboutic.views import (
|
||||||
|
EbouticCommand,
|
||||||
|
EtransactionAutoAnswer,
|
||||||
|
e_transaction_data,
|
||||||
|
eboutic_main,
|
||||||
|
pay_with_sith,
|
||||||
|
payment_result,
|
||||||
|
)
|
||||||
|
|
||||||
register_converter(PaymentResultConverter, "res")
|
register_converter(PaymentResultConverter, "res")
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@ import base64
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
|
||||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
|
||||||
from cryptography.hazmat.primitives.hashes import SHA1
|
from cryptography.hazmat.primitives.hashes import SHA1
|
||||||
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -47,6 +47,9 @@ from eboutic.models import (
|
|||||||
)
|
)
|
||||||
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_GET
|
@require_GET
|
||||||
@ -221,7 +224,7 @@ class EtransactionAutoAnswer(View):
|
|||||||
# Payment authorized:
|
# Payment authorized:
|
||||||
# * 'Error' is '00000'
|
# * 'Error' is '00000'
|
||||||
# * 'Auto' is in the request
|
# * 'Auto' is in the request
|
||||||
if request.GET["Error"] == "00000" and "Auto" in request.GET.keys():
|
if request.GET["Error"] == "00000" and "Auto" in request.GET:
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
b = (
|
b = (
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from election.views import *
|
from election.views import (
|
||||||
|
CandidatureCreateView,
|
||||||
|
CandidatureDeleteView,
|
||||||
|
CandidatureUpdateView,
|
||||||
|
ElectionCreateView,
|
||||||
|
ElectionDeleteView,
|
||||||
|
ElectionDetailView,
|
||||||
|
ElectionListArchivedView,
|
||||||
|
ElectionListCreateView,
|
||||||
|
ElectionListDeleteView,
|
||||||
|
ElectionsListView,
|
||||||
|
ElectionUpdateView,
|
||||||
|
RoleCreateView,
|
||||||
|
RoleDeleteView,
|
||||||
|
RoleUpdateView,
|
||||||
|
VoteFormView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", ElectionsListView.as_view(), name="list"),
|
path("", ElectionsListView.as_view(), name="list"),
|
||||||
@ -19,16 +35,10 @@ urlpatterns = [
|
|||||||
name="delete_list",
|
name="delete_list",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<int:election_id>/role/create/",
|
"<int:election_id>/role/create/", RoleCreateView.as_view(), name="create_role"
|
||||||
RoleCreateView.as_view(),
|
|
||||||
name="create_role",
|
|
||||||
),
|
),
|
||||||
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
|
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
|
||||||
path(
|
path("<int:role_id>/role/delete/", RoleDeleteView.as_view(), name="delete_role"),
|
||||||
"<int:role_id>/role/delete/",
|
|
||||||
RoleDeleteView.as_view(),
|
|
||||||
name="delete_role",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"<int:election_id>/candidate/add/",
|
"<int:election_id>/candidate/add/",
|
||||||
CandidatureCreateView.as_view(),
|
CandidatureCreateView.as_view(),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ajax_select import make_ajax_field
|
from ajax_select import make_ajax_field
|
||||||
from ajax_select.fields import AutoCompleteSelectField
|
from ajax_select.fields import AutoCompleteSelectField
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -10,11 +12,14 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||||
|
|
||||||
from core.models import User
|
|
||||||
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
|
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
|
||||||
from core.views.forms import MarkdownInput, SelectDateTime
|
from core.views.forms import MarkdownInput, SelectDateTime
|
||||||
from election.models import Candidature, Election, ElectionList, Role, Vote
|
from election.models import Candidature, Election, ElectionList, Role, Vote
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
# Custom form field
|
# Custom form field
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +28,6 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
|
|||||||
|
|
||||||
def __init__(self, queryset, max_choice, **kwargs):
|
def __init__(self, queryset, max_choice, **kwargs):
|
||||||
self.max_choice = max_choice
|
self.max_choice = max_choice
|
||||||
widget = forms.CheckboxSelectMultiple()
|
|
||||||
super().__init__(queryset, **kwargs)
|
super().__init__(queryset, **kwargs)
|
||||||
|
|
||||||
def clean(self, value):
|
def clean(self, value):
|
||||||
@ -251,7 +255,7 @@ class VoteFormView(CanCreateMixin, FormView):
|
|||||||
|
|
||||||
def vote(self, election_data):
|
def vote(self, election_data):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for role_title in election_data.keys():
|
for role_title in election_data:
|
||||||
# If we have a multiple choice field
|
# If we have a multiple choice field
|
||||||
if isinstance(election_data[role_title], QuerySet):
|
if isinstance(election_data[role_title], QuerySet):
|
||||||
if election_data[role_title].count() > 0:
|
if election_data[role_title].count() > 0:
|
||||||
@ -444,28 +448,16 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
|
|||||||
pk_url_kwarg = "election_id"
|
pk_url_kwarg = "election_id"
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = {}
|
return {
|
||||||
try:
|
"start_date": self.object.start_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S")
|
"end_date": self.object.end_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
except Exception:
|
"start_candidature": self.object.start_candidature.strftime(
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["end_date"] = self.object.end_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["start_candidature"] = self.object.start_candidature.strftime(
|
|
||||||
"%Y-%m-%d %H:%M:%S"
|
"%Y-%m-%d %H:%M:%S"
|
||||||
)
|
),
|
||||||
except Exception:
|
"end_candidature": self.object.end_candidature.strftime(
|
||||||
pass
|
|
||||||
try:
|
|
||||||
init["end_candidature"] = self.object.end_candidature.strftime(
|
|
||||||
"%Y-%m-%d %H:%M:%S"
|
"%Y-%m-%d %H:%M:%S"
|
||||||
)
|
),
|
||||||
except Exception:
|
}
|
||||||
pass
|
|
||||||
return init
|
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
def get_success_url(self, **kwargs):
|
||||||
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
|
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
from forum.models import *
|
from forum.models import Forum, ForumMessage, ForumTopic
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Forum)
|
@admin.register(Forum)
|
||||||
|
@ -25,6 +25,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timezone as tz
|
from datetime import timezone as tz
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -207,12 +208,12 @@ class Forum(models.Model):
|
|||||||
return self.get_parent_list()
|
return self.get_parent_list()
|
||||||
|
|
||||||
def get_parent_list(self):
|
def get_parent_list(self):
|
||||||
l = []
|
parents = []
|
||||||
p = self.parent
|
current = self.parent
|
||||||
while p is not None:
|
while current is not None:
|
||||||
l.append(p)
|
parents.append(current)
|
||||||
p = p.parent
|
current = current.parent
|
||||||
return l
|
return parents
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def topic_number(self):
|
def topic_number(self):
|
||||||
@ -228,12 +229,12 @@ class Forum(models.Model):
|
|||||||
def last_message(self):
|
def last_message(self):
|
||||||
return self._last_message
|
return self._last_message
|
||||||
|
|
||||||
def get_children_list(self):
|
def get_children_list(self) -> list[Self]:
|
||||||
l = [self.id]
|
children = [self.id]
|
||||||
for c in self.children.all():
|
for c in self.children.all():
|
||||||
l.append(c.id)
|
children.append(c.id)
|
||||||
l += c.get_children_list()
|
children.extend(c.get_children_list())
|
||||||
return l
|
return children
|
||||||
|
|
||||||
|
|
||||||
class ForumTopic(models.Model):
|
class ForumTopic(models.Model):
|
||||||
|
@ -23,7 +23,26 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from forum.views import *
|
from forum.views import (
|
||||||
|
ForumCreateView,
|
||||||
|
ForumDeleteView,
|
||||||
|
ForumDetailView,
|
||||||
|
ForumEditView,
|
||||||
|
ForumFavoriteTopics,
|
||||||
|
ForumLastUnread,
|
||||||
|
ForumMainView,
|
||||||
|
ForumMarkAllAsRead,
|
||||||
|
ForumMessageCreateView,
|
||||||
|
ForumMessageDeleteView,
|
||||||
|
ForumMessageEditView,
|
||||||
|
ForumMessageUndeleteView,
|
||||||
|
ForumMessageView,
|
||||||
|
ForumSearchView,
|
||||||
|
ForumTopicCreateView,
|
||||||
|
ForumTopicDetailView,
|
||||||
|
ForumTopicEditView,
|
||||||
|
ForumTopicSubscribeView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", ForumMainView.as_view(), name="main"),
|
path("", ForumMainView.as_view(), name="main"),
|
||||||
@ -35,21 +54,9 @@ urlpatterns = [
|
|||||||
path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"),
|
path("<int:forum_id>/", ForumDetailView.as_view(), name="view_forum"),
|
||||||
path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"),
|
path("<int:forum_id>/edit/", ForumEditView.as_view(), name="edit_forum"),
|
||||||
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
|
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
|
||||||
path(
|
path("<int:forum_id>/new_topic/", ForumTopicCreateView.as_view(), name="new_topic"),
|
||||||
"<int:forum_id>/new_topic/",
|
path("topic/<int:topic_id>/", ForumTopicDetailView.as_view(), name="view_topic"),
|
||||||
ForumTopicCreateView.as_view(),
|
path("topic/<int:topic_id>/edit/", ForumTopicEditView.as_view(), name="edit_topic"),
|
||||||
name="new_topic",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"topic/<int:topic_id>/",
|
|
||||||
ForumTopicDetailView.as_view(),
|
|
||||||
name="view_topic",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"topic/<int:topic_id>/edit/",
|
|
||||||
ForumTopicEditView.as_view(),
|
|
||||||
name="edit_topic",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"topic/<int:topic_id>/new_message/",
|
"topic/<int:topic_id>/new_message/",
|
||||||
ForumMessageCreateView.as_view(),
|
ForumMessageCreateView.as_view(),
|
||||||
@ -60,11 +67,7 @@ urlpatterns = [
|
|||||||
ForumTopicSubscribeView.as_view(),
|
ForumTopicSubscribeView.as_view(),
|
||||||
name="toggle_subscribe_topic",
|
name="toggle_subscribe_topic",
|
||||||
),
|
),
|
||||||
path(
|
path("message/<int:message_id>/", ForumMessageView.as_view(), name="view_message"),
|
||||||
"message/<int:message_id>/",
|
|
||||||
ForumMessageView.as_view(),
|
|
||||||
name="view_message",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"message/<int:message_id>/edit/",
|
"message/<int:message_id>/edit/",
|
||||||
ForumMessageEditView.as_view(),
|
ForumMessageEditView.as_view(),
|
||||||
|
@ -71,7 +71,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
self.logger = logging.getLogger("main")
|
self.logger = logging.getLogger("main")
|
||||||
if options["verbosity"] < 0 or 2 < options["verbosity"]:
|
if not 0 <= options["verbosity"] <= 2:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"verbosity level should be between 0 and 2 included", stacklevel=2
|
"verbosity level should be between 0 and 2 included", stacklevel=2
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
logger = logging.getLogger("main")
|
logger = logging.getLogger("main")
|
||||||
if options["verbosity"] < 0 or 2 < options["verbosity"]:
|
if not 0 <= options["verbosity"] <= 2:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"verbosity level should be between 0 and 2 included", stacklevel=2
|
"verbosity level should be between 0 and 2 included", stacklevel=2
|
||||||
)
|
)
|
||||||
|
File diff suppressed because one or more lines are too long
2
galaxy/static/galaxy/js/d3-force-3d.min.js
vendored
2
galaxy/static/galaxy/js/d3-force-3d.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
galaxy/static/galaxy/js/three.min.js
vendored
6
galaxy/static/galaxy/js/three.min.js
vendored
File diff suppressed because one or more lines are too long
138
galaxy/static/webpack/galaxy/galaxy-index.js
Normal file
138
galaxy/static/webpack/galaxy/galaxy-index.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { default as ForceGraph3D } from "3d-force-graph";
|
||||||
|
import { forceX, forceY, forceZ } from "d3-force-3d";
|
||||||
|
// biome-ignore lint/style/noNamespaceImport: This is how it should be imported
|
||||||
|
import * as Three from "three";
|
||||||
|
import SpriteText from "three-spritetext";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef GalaxyConfig
|
||||||
|
* @property {number} nodeId id of the current user node
|
||||||
|
* @property {string} dataUrl url to fetch the galaxy data from
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the galaxy of an user
|
||||||
|
* @param {GalaxyConfig} config
|
||||||
|
**/
|
||||||
|
window.loadGalaxy = (config) => {
|
||||||
|
window.getNodeFromId = (id) => {
|
||||||
|
return Graph.graphData().nodes.find((n) => n.id === id);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.getLinksFromNodeId = (id) => {
|
||||||
|
return Graph.graphData().links.filter(
|
||||||
|
(l) => l.source.id === id || l.target.id === id,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.focusNode = (node) => {
|
||||||
|
highlightNodes.clear();
|
||||||
|
highlightLinks.clear();
|
||||||
|
|
||||||
|
hoverNode = node || null;
|
||||||
|
if (node) {
|
||||||
|
// collect neighbors and links for highlighting
|
||||||
|
for (const link of window.getLinksFromNodeId(node.id)) {
|
||||||
|
highlightLinks.add(link);
|
||||||
|
highlightNodes.add(link.source);
|
||||||
|
highlightNodes.add(link.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh node and link display
|
||||||
|
Graph.nodeThreeObject(Graph.nodeThreeObject())
|
||||||
|
.linkWidth(Graph.linkWidth())
|
||||||
|
.linkDirectionalParticles(Graph.linkDirectionalParticles());
|
||||||
|
|
||||||
|
// Aim at node from outside it
|
||||||
|
const distance = 42;
|
||||||
|
const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
|
||||||
|
|
||||||
|
const newPos =
|
||||||
|
node.x || node.y || node.z
|
||||||
|
? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }
|
||||||
|
: { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0)
|
||||||
|
|
||||||
|
Graph.cameraPosition(
|
||||||
|
newPos, // new position
|
||||||
|
node, // lookAt ({ x, y, z })
|
||||||
|
3000, // ms transition duration
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const highlightNodes = new Set();
|
||||||
|
const highlightLinks = new Set();
|
||||||
|
let hoverNode = null;
|
||||||
|
|
||||||
|
const grpahDiv = document.getElementById("3d-graph");
|
||||||
|
const Graph = ForceGraph3D();
|
||||||
|
Graph(grpahDiv);
|
||||||
|
Graph.jsonUrl(config.dataUrl)
|
||||||
|
.width(
|
||||||
|
grpahDiv.parentElement.clientWidth > 1200
|
||||||
|
? 1200
|
||||||
|
: grpahDiv.parentElement.clientWidth,
|
||||||
|
) // Not perfect at all. JS-fu master from the future, please fix this :-)
|
||||||
|
.height(1000)
|
||||||
|
.enableNodeDrag(false) // allow easier navigation
|
||||||
|
.onNodeClick((node) => {
|
||||||
|
const camera = Graph.cameraPosition();
|
||||||
|
const distance = Math.sqrt(
|
||||||
|
(node.x - camera.x) ** 2 + (node.y - camera.y) ** 2 + (node.z - camera.z) ** 2,
|
||||||
|
);
|
||||||
|
if (distance < 120 || highlightNodes.has(node)) {
|
||||||
|
window.focusNode(node);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.linkWidth((link) => (highlightLinks.has(link) ? 0.4 : 0.0))
|
||||||
|
.linkColor((link) =>
|
||||||
|
highlightLinks.has(link) ? "rgba(255,160,0,1)" : "rgba(128,255,255,0.6)",
|
||||||
|
)
|
||||||
|
.linkVisibility((link) => highlightLinks.has(link))
|
||||||
|
.nodeVisibility((node) => highlightNodes.has(node) || node.mass > 4)
|
||||||
|
// .linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 1) // kinda buggy for now, and slows this a bit, but would be great to help visualize lanes
|
||||||
|
.linkDirectionalParticleWidth(0.2)
|
||||||
|
.linkDirectionalParticleSpeed(-0.006)
|
||||||
|
.nodeThreeObject((node) => {
|
||||||
|
const sprite = new SpriteText(node.name);
|
||||||
|
sprite.material.depthWrite = false; // make sprite background transparent
|
||||||
|
sprite.color = highlightNodes.has(node)
|
||||||
|
? node === hoverNode
|
||||||
|
? "rgba(200,0,0,1)"
|
||||||
|
: "rgba(255,160,0,0.8)"
|
||||||
|
: "rgba(0,255,255,0.2)";
|
||||||
|
sprite.textHeight = 2;
|
||||||
|
sprite.center = new Three.Vector2(1.2, 0.5);
|
||||||
|
return sprite;
|
||||||
|
})
|
||||||
|
.onEngineStop(() => {
|
||||||
|
window.focusNode(window.getNodeFromId(config.nodeId));
|
||||||
|
Graph.onEngineStop(() => {
|
||||||
|
/* nope */
|
||||||
|
}); // don't call ourselves in a loop while moving the focus
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set distance between stars
|
||||||
|
Graph.d3Force("link").distance((link) => link.value);
|
||||||
|
|
||||||
|
// Set high masses nearer the center of the galaxy
|
||||||
|
// TODO: quick and dirty strength computation, this will need tuning.
|
||||||
|
Graph.d3Force(
|
||||||
|
"positionX",
|
||||||
|
forceX().strength((node) => {
|
||||||
|
return 1 - 1 / node.mass;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
Graph.d3Force(
|
||||||
|
"positionY",
|
||||||
|
forceY().strength((node) => {
|
||||||
|
return 1 - 1 / node.mass;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
Graph.d3Force(
|
||||||
|
"positionZ",
|
||||||
|
forceZ().strength((node) => {
|
||||||
|
return 1 - 1 / node.mass;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
@ -4,13 +4,18 @@
|
|||||||
{% trans user_name=object.get_display_name() %}{{ user_name }}'s Galaxy{% endtrans %}
|
{% trans user_name=object.get_display_name() %}{{ user_name }}'s Galaxy{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_js %}
|
||||||
|
<script src="{{ static('webpack/galaxy/galaxy-index.js') }}" defer></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if object.current_star %}
|
{% if object.current_star %}
|
||||||
<div style="display: flex; flex-wrap: wrap;">
|
<div style="display: flex; flex-wrap: wrap;">
|
||||||
<div id="3d-graph"></div>
|
<div id="3d-graph"></div>
|
||||||
|
|
||||||
<div style="margin: 1em;">
|
<div style="margin: 1em;">
|
||||||
<p><a onclick="focus_node(get_node_from_id({{ object.id }}))">Reset on {{ object.get_display_name() }}</a></p>
|
<p><a onclick="window.focusNode(window.getNodeFromId({{ object.id }}))">Reset on {{ object.get_display_name() }}</a></p>
|
||||||
<p>Self score: {{ object.current_star.mass }}</p>
|
<p>Self score: {{ object.current_star.mass }}</p>
|
||||||
<table style="width: initial;">
|
<table style="width: initial;">
|
||||||
<tr>
|
<tr>
|
||||||
@ -24,7 +29,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% for lane in lanes %}
|
{% for lane in lanes %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a onclick="focus_node(get_node_from_id({{ lane.other_star_id }}))">Locate</a></td>
|
<td><a onclick="window.focusNode(window.getNodeFromId({{ lane.other_star_id }}))">Locate</a></td>
|
||||||
<td><a href="{{ url("galaxy:user", user_id=lane.other_star_id) }}">{{ lane.other_star_name }}</a></td>
|
<td><a href="{{ url("galaxy:user", user_id=lane.other_star_id) }}">{{ lane.other_star_name }}</a></td>
|
||||||
<td>{{ lane.other_star_mass }}</td>
|
<td>{{ lane.other_star_mass }}</td>
|
||||||
<td>{{ lane.distance }}</td>
|
<td>{{ lane.distance }}</td>
|
||||||
@ -45,106 +50,13 @@
|
|||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
<script src="{{ static('galaxy/js/three.min.js') }}" defer></script>
|
|
||||||
<script src="{{ static('galaxy/js/three-spritetext.min.js') }}" defer></script>
|
|
||||||
<script src="{{ static('galaxy/js/3d-force-graph.min.js') }}" defer></script>
|
|
||||||
<script src="{{ static('galaxy/js/d3-force-3d.min.js') }}" defer></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var Graph;
|
|
||||||
|
|
||||||
function get_node_from_id(id) {
|
|
||||||
return Graph.graphData().nodes.find(n => n.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_links_from_node_id(id) {
|
|
||||||
return Graph.graphData().links.filter(l => l.source.id === id || l.target.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focus_node(node) {
|
|
||||||
highlightNodes.clear();
|
|
||||||
highlightLinks.clear();
|
|
||||||
|
|
||||||
hoverNode = node || null;
|
|
||||||
if (node) { // collect neighbors and links for highlighting
|
|
||||||
get_links_from_node_id(node.id).forEach(link => {
|
|
||||||
highlightLinks.add(link);
|
|
||||||
highlightNodes.add(link.source);
|
|
||||||
highlightNodes.add(link.target);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh node and link display
|
|
||||||
Graph
|
|
||||||
.nodeThreeObject(Graph.nodeThreeObject())
|
|
||||||
.linkWidth(Graph.linkWidth())
|
|
||||||
.linkDirectionalParticles(Graph.linkDirectionalParticles());
|
|
||||||
|
|
||||||
// Aim at node from outside it
|
|
||||||
const distance = 42;
|
|
||||||
const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
|
|
||||||
|
|
||||||
const newPos = node.x || node.y || node.z
|
|
||||||
? { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }
|
|
||||||
: { x: 0, y: 0, z: distance }; // special case if node is in (0,0,0)
|
|
||||||
|
|
||||||
Graph.cameraPosition(
|
|
||||||
newPos, // new position
|
|
||||||
node, // lookAt ({ x, y, z })
|
|
||||||
3000 // ms transition duration
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const highlightNodes = new Set();
|
|
||||||
const highlightLinks = new Set();
|
|
||||||
let hoverNode = null;
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
var graph_div = document.getElementById('3d-graph');
|
window.loadGalaxy({
|
||||||
Graph = ForceGraph3D();
|
nodeId: {{ object.id }},
|
||||||
Graph(graph_div);
|
dataUrl: '{{ url("galaxy:data") }}',
|
||||||
Graph
|
});
|
||||||
.jsonUrl('{{ url("galaxy:data") }}')
|
});
|
||||||
.width(graph_div.parentElement.clientWidth > 1200 ? 1200 : graph_div.parentElement.clientWidth) // Not perfect at all. JS-fu master from the future, please fix this :-)
|
|
||||||
.height(1000)
|
|
||||||
.enableNodeDrag(false) // allow easier navigation
|
|
||||||
.onNodeClick(node => {
|
|
||||||
camera = Graph.cameraPosition();
|
|
||||||
var distance = Math.sqrt(Math.pow(node.x - camera.x, 2) + Math.pow(node.y - camera.y, 2) + Math.pow(node.z - camera.z, 2))
|
|
||||||
if (distance < 120 || highlightNodes.has(node)) {
|
|
||||||
focus_node(node);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.linkWidth(link => highlightLinks.has(link) ? 0.4 : 0.0)
|
|
||||||
.linkColor(link => highlightLinks.has(link) ? 'rgba(255,160,0,1)' : 'rgba(128,255,255,0.6)')
|
|
||||||
.linkVisibility(link => highlightLinks.has(link))
|
|
||||||
.nodeVisibility(node => highlightNodes.has(node) || node.mass > 4)
|
|
||||||
// .linkDirectionalParticles(link => highlightLinks.has(link) ? 3 : 1) // kinda buggy for now, and slows this a bit, but would be great to help visualize lanes
|
|
||||||
.linkDirectionalParticleWidth(0.2)
|
|
||||||
.linkDirectionalParticleSpeed(-0.006)
|
|
||||||
.nodeThreeObject(node => {
|
|
||||||
const sprite = new SpriteText(node.name);
|
|
||||||
sprite.material.depthWrite = false; // make sprite background transparent
|
|
||||||
sprite.color = highlightNodes.has(node) ? node === hoverNode ? 'rgba(200,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.2)';
|
|
||||||
sprite.textHeight = 2;
|
|
||||||
sprite.center = new THREE.Vector2(1.2, 0.5);
|
|
||||||
return sprite;
|
|
||||||
})
|
|
||||||
.onEngineStop( () => {
|
|
||||||
focus_node(get_node_from_id({{ object.id }}));
|
|
||||||
Graph.onEngineStop(() => {}); // don't call ourselves in a loop while moving the focus
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set distance between stars
|
|
||||||
Graph.d3Force('link').distance(link => link.value);
|
|
||||||
|
|
||||||
// Set high masses nearer the center of the galaxy
|
|
||||||
// TODO: quick and dirty strength computation, this will need tuning.
|
|
||||||
Graph.d3Force('positionX', d3.forceX().strength(node => { return 1 - (1 / node.mass); }));
|
|
||||||
Graph.d3Force('positionY', d3.forceY().strength(node => { return 1 - (1 / node.mass); }));
|
|
||||||
Graph.d3Force('positionZ', d3.forceZ().strength(node => { return 1 - (1 / node.mass); }));
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ class TestGalaxyView(TestCase):
|
|||||||
response = self.client.get(reverse("galaxy:user", args=[user.id]))
|
response = self.client.get(reverse("galaxy:user", args=[user.id]))
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
f'<a onclick="focus_node(get_node_from_id({user.id}))">Reset on {user}</a>',
|
f'<a onclick="window.focusNode(window.getNodeFromId({user.id}))">Reset on {user}</a>',
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,17 +23,9 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from galaxy.views import *
|
from galaxy.views import GalaxyDataView, GalaxyUserView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path("<int:user_id>/", GalaxyUserView.as_view(), name="user"),
|
||||||
"<int:user_id>/",
|
path("data.json", GalaxyDataView.as_view(), name="data"),
|
||||||
GalaxyUserView.as_view(),
|
|
||||||
name="user",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"data.json",
|
|
||||||
GalaxyDataView.as_view(),
|
|
||||||
name="data",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from launderette.models import *
|
from launderette.models import Launderette, Machine, Slot, Token
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Launderette)
|
@admin.register(Launderette)
|
||||||
|
@ -51,18 +51,14 @@ class Launderette(models.Model):
|
|||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 9:
|
return bool(m and m.role >= 9)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
launderette_club = Club.objects.filter(
|
launderette_club = Club.objects.filter(
|
||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 2:
|
return bool(m and m.role >= 2)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_be_viewed_by(self, user):
|
def can_be_viewed_by(self, user):
|
||||||
return user.is_subscribed
|
return user.is_subscribed
|
||||||
@ -113,9 +109,7 @@ class Machine(models.Model):
|
|||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 9:
|
return bool(m and m.role >= 9)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Token(models.Model):
|
class Token(models.Model):
|
||||||
@ -164,15 +158,7 @@ class Token(models.Model):
|
|||||||
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
|
||||||
).first()
|
).first()
|
||||||
m = launderette_club.get_membership_for(user)
|
m = launderette_club.get_membership_for(user)
|
||||||
if m and m.role >= 9:
|
return bool(m and m.role >= 9)
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_avaliable(self):
|
|
||||||
if not self.borrow_date and not self.user:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class Slot(models.Model):
|
class Slot(models.Model):
|
||||||
|
@ -15,22 +15,28 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from launderette.views import *
|
from launderette.views import (
|
||||||
|
LaunderetteAdminView,
|
||||||
|
LaunderetteBookMainView,
|
||||||
|
LaunderetteBookView,
|
||||||
|
LaunderetteClickView,
|
||||||
|
LaunderetteCreateView,
|
||||||
|
LaunderetteEditView,
|
||||||
|
LaunderetteListView,
|
||||||
|
LaunderetteMainClickView,
|
||||||
|
LaunderetteMainView,
|
||||||
|
MachineCreateView,
|
||||||
|
MachineDeleteView,
|
||||||
|
MachineEditView,
|
||||||
|
SlotDeleteView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# views
|
# views
|
||||||
path("", LaunderetteMainView.as_view(), name="launderette_main"),
|
path("", LaunderetteMainView.as_view(), name="launderette_main"),
|
||||||
path(
|
path("slot/<int:slot_id>/delete/", SlotDeleteView.as_view(), name="delete_slot"),
|
||||||
"slot/<int:slot_id>/delete/",
|
|
||||||
SlotDeleteView.as_view(),
|
|
||||||
name="delete_slot",
|
|
||||||
),
|
|
||||||
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
|
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
|
||||||
path(
|
path("book/<int:launderette_id>/", LaunderetteBookView.as_view(), name="book_slot"),
|
||||||
"book/<int:launderette_id>/",
|
|
||||||
LaunderetteBookView.as_view(),
|
|
||||||
name="book_slot",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"<int:launderette_id>/click/",
|
"<int:launderette_id>/click/",
|
||||||
LaunderetteMainClickView.as_view(),
|
LaunderetteMainClickView.as_view(),
|
||||||
|
@ -19,7 +19,7 @@ from datetime import timezone as tz
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import DataError, transaction
|
from django.db import transaction
|
||||||
from django.template import defaultfilters
|
from django.template import defaultfilters
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import dateparse, timezone
|
from django.utils import dateparse, timezone
|
||||||
@ -73,15 +73,15 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
|||||||
self.machines = {}
|
self.machines = {}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if "slot_type" in request.POST.keys():
|
if "slot_type" in request.POST:
|
||||||
self.slot_type = request.POST["slot_type"]
|
self.slot_type = request.POST["slot_type"]
|
||||||
if "slot" in request.POST.keys() and request.user.is_authenticated:
|
if "slot" in request.POST and request.user.is_authenticated:
|
||||||
self.subscriber = request.user
|
self.subscriber = request.user
|
||||||
if self.subscriber.is_subscribed:
|
if self.subscriber.is_subscribed:
|
||||||
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
|
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
|
||||||
tzinfo=tz.utc
|
tzinfo=tz.utc
|
||||||
)
|
)
|
||||||
if self.slot_type == "WASHING":
|
if self.slot_type in ["WASHING", "DRYING"]:
|
||||||
if self.check_slot(self.slot_type):
|
if self.check_slot(self.slot_type):
|
||||||
Slot(
|
Slot(
|
||||||
user=self.subscriber,
|
user=self.subscriber,
|
||||||
@ -89,30 +89,21 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
|||||||
machine=self.machines[self.slot_type],
|
machine=self.machines[self.slot_type],
|
||||||
type=self.slot_type,
|
type=self.slot_type,
|
||||||
).save()
|
).save()
|
||||||
elif self.slot_type == "DRYING":
|
elif self.check_slot("WASHING") and self.check_slot(
|
||||||
if self.check_slot(self.slot_type):
|
"DRYING", self.date + timedelta(hours=1)
|
||||||
Slot(
|
):
|
||||||
user=self.subscriber,
|
Slot(
|
||||||
start_date=self.date,
|
user=self.subscriber,
|
||||||
machine=self.machines[self.slot_type],
|
start_date=self.date,
|
||||||
type=self.slot_type,
|
machine=self.machines["WASHING"],
|
||||||
).save()
|
type="WASHING",
|
||||||
else:
|
).save()
|
||||||
if self.check_slot("WASHING") and self.check_slot(
|
Slot(
|
||||||
"DRYING", self.date + timedelta(hours=1)
|
user=self.subscriber,
|
||||||
):
|
start_date=self.date + timedelta(hours=1),
|
||||||
Slot(
|
machine=self.machines["DRYING"],
|
||||||
user=self.subscriber,
|
type="DRYING",
|
||||||
start_date=self.date,
|
).save()
|
||||||
machine=self.machines["WASHING"],
|
|
||||||
type="WASHING",
|
|
||||||
).save()
|
|
||||||
Slot(
|
|
||||||
user=self.subscriber,
|
|
||||||
start_date=self.date + timedelta(hours=1),
|
|
||||||
machine=self.machines["DRYING"],
|
|
||||||
type="DRYING",
|
|
||||||
).save()
|
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def check_slot(self, machine_type, date=None):
|
def check_slot(self, machine_type, date=None):
|
||||||
@ -149,15 +140,17 @@ class LaunderetteBookView(CanViewMixin, DetailView):
|
|||||||
):
|
):
|
||||||
free = False
|
free = False
|
||||||
if (
|
if (
|
||||||
self.slot_type == "BOTH"
|
(
|
||||||
|
self.slot_type == "BOTH"
|
||||||
|
and self.check_slot("WASHING", h)
|
||||||
|
and self.check_slot("DRYING", h + timedelta(hours=1))
|
||||||
|
)
|
||||||
|
or self.slot_type == "WASHING"
|
||||||
and self.check_slot("WASHING", h)
|
and self.check_slot("WASHING", h)
|
||||||
and self.check_slot("DRYING", h + timedelta(hours=1))
|
or self.slot_type == "DRYING"
|
||||||
|
and self.check_slot("DRYING", h)
|
||||||
):
|
):
|
||||||
free = True
|
free = True
|
||||||
elif self.slot_type == "WASHING" and self.check_slot("WASHING", h):
|
|
||||||
free = True
|
|
||||||
elif self.slot_type == "DRYING" and self.check_slot("DRYING", h):
|
|
||||||
free = True
|
|
||||||
if free and datetime.now().replace(tzinfo=tz.utc) < h:
|
if free and datetime.now().replace(tzinfo=tz.utc) < h:
|
||||||
kwargs["planning"][date].append(h)
|
kwargs["planning"][date].append(h)
|
||||||
else:
|
else:
|
||||||
@ -236,42 +229,39 @@ class ManageTokenForm(forms.Form):
|
|||||||
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
|
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
|
||||||
token_type = cleaned_data["token_type"]
|
token_type = cleaned_data["token_type"]
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
|
if cleaned_data["action"] not in ["BACK", "ADD", "DEL"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
tokens = list(
|
||||||
|
Token.objects.filter(
|
||||||
|
launderette=launderette, type=token_type, name__in=token_list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_names = {t.name for t in tokens}
|
||||||
|
if cleaned_data["action"] in ["BACK", "DEL"]:
|
||||||
|
for t in set(token_list) - existing_names:
|
||||||
|
self.add_error(
|
||||||
|
None,
|
||||||
|
_("Token %(token_name)s does not exists") % {"token_name": t},
|
||||||
|
)
|
||||||
if cleaned_data["action"] == "BACK":
|
if cleaned_data["action"] == "BACK":
|
||||||
for t in token_list:
|
Token.objects.filter(id__in=[t.id for t in tokens]).update(
|
||||||
try:
|
borrow_date=None, user=None
|
||||||
tok = Token.objects.filter(
|
)
|
||||||
launderette=launderette, type=token_type, name=t
|
|
||||||
).first()
|
|
||||||
tok.borrow_date = None
|
|
||||||
tok.user = None
|
|
||||||
tok.save()
|
|
||||||
except:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Token %(token_name)s does not exists") % {"token_name": t},
|
|
||||||
)
|
|
||||||
elif cleaned_data["action"] == "ADD":
|
|
||||||
for t in token_list:
|
|
||||||
try:
|
|
||||||
Token(launderette=launderette, type=token_type, name=t).save()
|
|
||||||
except DataError as e:
|
|
||||||
self.add_error(None, e)
|
|
||||||
except:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Token %(token_name)s already exists") % {"token_name": t},
|
|
||||||
)
|
|
||||||
elif cleaned_data["action"] == "DEL":
|
elif cleaned_data["action"] == "DEL":
|
||||||
|
Token.objects.filter(id__in=[t.id for t in tokens]).delete()
|
||||||
|
elif cleaned_data["action"] == "ADD":
|
||||||
|
for name in existing_names:
|
||||||
|
self.add_error(
|
||||||
|
None,
|
||||||
|
_("Token %(token_name)s already exists") % {"token_name": name},
|
||||||
|
)
|
||||||
for t in token_list:
|
for t in token_list:
|
||||||
try:
|
if t == "":
|
||||||
Token.objects.filter(
|
self.add_error(None, _("Token name can not be blank"))
|
||||||
launderette=launderette, type=token_type, name=t
|
else:
|
||||||
).delete()
|
Token(launderette=launderette, type=token_type, name=t).save()
|
||||||
except:
|
|
||||||
self.add_error(
|
|
||||||
None,
|
|
||||||
_("Token %(token_name)s does not exists") % {"token_name": t},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
||||||
@ -288,13 +278,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
form = self.get_form()
|
|
||||||
return super().post(request, *args, **kwargs)
|
return super().post(request, *args, **kwargs)
|
||||||
form.launderette = self.object
|
|
||||||
if form.is_valid():
|
|
||||||
return self.form_valid(form)
|
|
||||||
else:
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""We handle here the redirection, passing the user id of the asked customer."""
|
"""We handle here the redirection, passing the user id of the asked customer."""
|
||||||
@ -353,7 +337,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView):
|
|||||||
kwargs["counter"] = self.object.counter
|
kwargs["counter"] = self.object.counter
|
||||||
kwargs["form"] = self.get_form()
|
kwargs["form"] = self.get_form()
|
||||||
kwargs["barmen"] = [self.request.user]
|
kwargs["barmen"] = [self.request.user]
|
||||||
if "last_basket" in self.request.session.keys():
|
if "last_basket" in self.request.session:
|
||||||
kwargs["last_basket"] = self.request.session.pop("last_basket", None)
|
kwargs["last_basket"] = self.request.session.pop("last_basket", None)
|
||||||
kwargs["last_customer"] = self.request.session.pop("last_customer", None)
|
kwargs["last_customer"] = self.request.session.pop("last_customer", None)
|
||||||
kwargs["last_total"] = self.request.session.pop("last_total", None)
|
kwargs["last_total"] = self.request.session.pop("last_total", None)
|
||||||
@ -479,7 +463,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""We handle here the login form for the barman."""
|
"""We handle here the login form for the barman."""
|
||||||
kwargs = super().get_context_data(**kwargs)
|
kwargs = super().get_context_data(**kwargs)
|
||||||
if "form" not in kwargs.keys():
|
if "form" not in kwargs:
|
||||||
kwargs["form"] = self.get_form()
|
kwargs["form"] = self.get_form()
|
||||||
kwargs["counter"] = self.object.counter
|
kwargs["counter"] = self.object.counter
|
||||||
kwargs["customer"] = self.customer
|
kwargs["customer"] = self.customer
|
||||||
@ -519,7 +503,7 @@ class MachineCreateView(CanCreateMixin, CreateView):
|
|||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
ret = super().get_initial()
|
ret = super().get_initial()
|
||||||
if "launderette" in self.request.GET.keys():
|
if "launderette" in self.request.GET:
|
||||||
obj = Launderette.objects.filter(
|
obj = Launderette.objects.filter(
|
||||||
id=int(self.request.GET["launderette"])
|
id=int(self.request.GET["launderette"])
|
||||||
).first()
|
).first()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-10-09 11:50+0200\n"
|
"POT-Creation-Date: 2024-10-16 02:19+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"
|
||||||
@ -22,87 +22,95 @@ msgstr ""
|
|||||||
msgid "captured.%s"
|
msgid "captured.%s"
|
||||||
msgstr "capture.%s"
|
msgstr "capture.%s"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:32
|
#: core/static/webpack/ajax-select-index.ts:73
|
||||||
|
msgid "You need to type %(number)s more characters"
|
||||||
|
msgstr "Vous devez taper %(number)s caractères de plus"
|
||||||
|
|
||||||
|
#: core/static/webpack/ajax-select-index.ts:76
|
||||||
|
msgid "No results found"
|
||||||
|
msgstr "Aucun résultat trouvé"
|
||||||
|
|
||||||
|
#: core/static/webpack/easymde-index.ts:31
|
||||||
msgid "Heading"
|
msgid "Heading"
|
||||||
msgstr "Titre"
|
msgstr "Titre"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:38
|
#: core/static/webpack/easymde-index.ts:37
|
||||||
msgid "Italic"
|
msgid "Italic"
|
||||||
msgstr "Italique"
|
msgstr "Italique"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:44
|
#: core/static/webpack/easymde-index.ts:43
|
||||||
msgid "Bold"
|
msgid "Bold"
|
||||||
msgstr "Gras"
|
msgstr "Gras"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:50
|
#: core/static/webpack/easymde-index.ts:49
|
||||||
msgid "Strikethrough"
|
msgid "Strikethrough"
|
||||||
msgstr "Barré"
|
msgstr "Barré"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:59
|
#: core/static/webpack/easymde-index.ts:58
|
||||||
msgid "Underline"
|
msgid "Underline"
|
||||||
msgstr "Souligné"
|
msgstr "Souligné"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:68
|
#: core/static/webpack/easymde-index.ts:67
|
||||||
msgid "Superscript"
|
msgid "Superscript"
|
||||||
msgstr "Exposant"
|
msgstr "Exposant"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:77
|
#: core/static/webpack/easymde-index.ts:76
|
||||||
msgid "Subscript"
|
msgid "Subscript"
|
||||||
msgstr "Indice"
|
msgstr "Indice"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:83
|
#: core/static/webpack/easymde-index.ts:82
|
||||||
msgid "Code"
|
msgid "Code"
|
||||||
msgstr "Code"
|
msgstr "Code"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:90
|
#: core/static/webpack/easymde-index.ts:89
|
||||||
msgid "Quote"
|
msgid "Quote"
|
||||||
msgstr "Citation"
|
msgstr "Citation"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:96
|
#: core/static/webpack/easymde-index.ts:95
|
||||||
msgid "Unordered list"
|
msgid "Unordered list"
|
||||||
msgstr "Liste non ordonnée"
|
msgstr "Liste non ordonnée"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:102
|
#: core/static/webpack/easymde-index.ts:101
|
||||||
msgid "Ordered list"
|
msgid "Ordered list"
|
||||||
msgstr "Liste ordonnée"
|
msgstr "Liste ordonnée"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:109
|
#: core/static/webpack/easymde-index.ts:108
|
||||||
msgid "Insert link"
|
msgid "Insert link"
|
||||||
msgstr "Insérer lien"
|
msgstr "Insérer lien"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:115
|
#: core/static/webpack/easymde-index.ts:114
|
||||||
msgid "Insert image"
|
msgid "Insert image"
|
||||||
msgstr "Insérer image"
|
msgstr "Insérer image"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:121
|
#: core/static/webpack/easymde-index.ts:120
|
||||||
msgid "Insert table"
|
msgid "Insert table"
|
||||||
msgstr "Insérer tableau"
|
msgstr "Insérer tableau"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:128
|
#: core/static/webpack/easymde-index.ts:127
|
||||||
msgid "Clean block"
|
msgid "Clean block"
|
||||||
msgstr "Nettoyer bloc"
|
msgstr "Nettoyer bloc"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:135
|
#: core/static/webpack/easymde-index.ts:134
|
||||||
msgid "Toggle preview"
|
msgid "Toggle preview"
|
||||||
msgstr "Activer la prévisualisation"
|
msgstr "Activer la prévisualisation"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:141
|
#: core/static/webpack/easymde-index.ts:140
|
||||||
msgid "Toggle side by side"
|
msgid "Toggle side by side"
|
||||||
msgstr "Activer la vue côte à côte"
|
msgstr "Activer la vue côte à côte"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:147
|
#: core/static/webpack/easymde-index.ts:146
|
||||||
msgid "Toggle fullscreen"
|
msgid "Toggle fullscreen"
|
||||||
msgstr "Activer le plein écran"
|
msgstr "Activer le plein écran"
|
||||||
|
|
||||||
#: core/static/webpack/easymde-index.js:154
|
#: core/static/webpack/easymde-index.ts:153
|
||||||
msgid "Markdown guide"
|
msgid "Markdown guide"
|
||||||
msgstr "Guide markdown"
|
msgstr "Guide markdown"
|
||||||
|
|
||||||
#: core/static/webpack/user/family-graph-index.js:222
|
#: core/static/webpack/user/family-graph-index.js:233
|
||||||
msgid "family_tree.%(extension)s"
|
msgid "family_tree.%(extension)s"
|
||||||
msgstr "arbre_genealogique.%(extension)s"
|
msgstr "arbre_genealogique.%(extension)s"
|
||||||
|
|
||||||
#: core/static/webpack/user/pictures-index.js:67
|
#: core/static/webpack/user/pictures-index.js:76
|
||||||
msgid "pictures.%(extension)s"
|
msgid "pictures.%(extension)s"
|
||||||
msgstr "photos.%(extension)s"
|
msgstr "photos.%(extension)s"
|
||||||
|
|
||||||
@ -110,10 +118,10 @@ msgstr "photos.%(extension)s"
|
|||||||
msgid "Incorrect value"
|
msgid "Incorrect value"
|
||||||
msgstr "Valeur incorrecte"
|
msgstr "Valeur incorrecte"
|
||||||
|
|
||||||
#: sas/static/sas/js/viewer.js:205
|
#: sas/static/webpack/sas/viewer-index.ts:271
|
||||||
msgid "Couldn't moderate picture"
|
msgid "Couldn't moderate picture"
|
||||||
msgstr "Il n'a pas été possible de modérer l'image"
|
msgstr "Il n'a pas été possible de modérer l'image"
|
||||||
|
|
||||||
#: sas/static/sas/js/viewer.js:217
|
#: sas/static/webpack/sas/viewer-index.ts:284
|
||||||
msgid "Couldn't delete picture"
|
msgid "Couldn't delete picture"
|
||||||
msgstr "Il n'a pas été possible de supprimer l'image"
|
msgstr "Il n'a pas été possible de supprimer l'image"
|
||||||
|
@ -23,7 +23,12 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from matmat.views import *
|
from matmat.views import (
|
||||||
|
SearchClearFormView,
|
||||||
|
SearchNormalFormView,
|
||||||
|
SearchQuickFormView,
|
||||||
|
SearchReverseFormView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", SearchNormalFormView.as_view(), name="search"),
|
path("", SearchNormalFormView.as_view(), name="search"),
|
||||||
|
@ -71,15 +71,15 @@ class SearchForm(forms.ModelForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for key in self.fields.keys():
|
for key in self.fields:
|
||||||
self.fields[key].required = False
|
self.fields[key].required = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cleaned_data_json(self):
|
def cleaned_data_json(self):
|
||||||
data = self.cleaned_data
|
data = self.cleaned_data
|
||||||
for key in data.keys():
|
for key, val in data.items():
|
||||||
if key in ("date_of_birth", "phone") and data[key] is not None:
|
if key in ("date_of_birth", "phone") and val is not None:
|
||||||
data[key] = str(data[key])
|
data[key] = str(val)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -98,10 +98,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView):
|
|||||||
self.session = request.session
|
self.session = request.session
|
||||||
self.last_search = self.session.get("matmat_search_result", str([]))
|
self.last_search = self.session.get("matmat_search_result", str([]))
|
||||||
self.last_search = literal_eval(self.last_search)
|
self.last_search = literal_eval(self.last_search)
|
||||||
if "valid_form" in kwargs.keys():
|
self.valid_form = kwargs.get("valid_form")
|
||||||
self.valid_form = kwargs["valid_form"]
|
|
||||||
else:
|
|
||||||
self.valid_form = None
|
|
||||||
|
|
||||||
self.init_query = self.model.objects
|
self.init_query = self.model.objects
|
||||||
self.can_see_hidden = True
|
self.can_see_hidden = True
|
||||||
@ -202,8 +199,8 @@ class SearchClearFormView(FormerSubscriberMixin, View):
|
|||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
super().dispatch(request, *args, **kwargs)
|
super().dispatch(request, *args, **kwargs)
|
||||||
if "matmat_search_form" in request.session.keys():
|
if "matmat_search_form" in request.session:
|
||||||
request.session.pop("matmat_search_form")
|
request.session.pop("matmat_search_form")
|
||||||
if "matmat_search_result" in request.session.keys():
|
if "matmat_search_result" in request.session:
|
||||||
request.session.pop("matmat_search_result")
|
request.session.pop("matmat_search_result")
|
||||||
return HttpResponseRedirect(reverse("matmat:search"))
|
return HttpResponseRedirect(reverse("matmat:search"))
|
||||||
|
656
package-lock.json
generated
656
package-lock.json
generated
@ -11,23 +11,32 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@hey-api/client-fetch": "^0.4.0",
|
"@hey-api/client-fetch": "^0.4.0",
|
||||||
|
"@sentry/browser": "^8.34.0",
|
||||||
"@zip.js/zip.js": "^2.7.52",
|
"@zip.js/zip.js": "^2.7.52",
|
||||||
|
"3d-force-graph": "^1.73.4",
|
||||||
"alpinejs": "^3.14.1",
|
"alpinejs": "^3.14.1",
|
||||||
|
"chart.js": "^4.4.4",
|
||||||
"cytoscape": "^3.30.2",
|
"cytoscape": "^3.30.2",
|
||||||
"cytoscape-cxtmenu": "^3.5.0",
|
"cytoscape-cxtmenu": "^3.5.0",
|
||||||
"cytoscape-klay": "^3.1.4",
|
"cytoscape-klay": "^3.1.4",
|
||||||
|
"d3-force-3d": "^3.0.5",
|
||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-ui": "^1.14.0",
|
"jquery-ui": "^1.14.0",
|
||||||
"jquery.shorten": "^1.0.0",
|
"jquery.shorten": "^1.0.0",
|
||||||
"native-file-system-adapter": "^3.0.1"
|
"native-file-system-adapter": "^3.0.1",
|
||||||
|
"three": "^0.169.0",
|
||||||
|
"three-spritetext": "^1.9.0",
|
||||||
|
"tom-select": "^2.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.25.2",
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@biomejs/biome": "1.9.3",
|
"@biomejs/biome": "1.9.3",
|
||||||
"@hey-api/openapi-ts": "^0.53.8",
|
"@hey-api/openapi-ts": "^0.53.8",
|
||||||
|
"@types/alpinejs": "^3.13.10",
|
||||||
|
"@types/jquery": "^3.5.31",
|
||||||
"babel-loader": "^9.2.1",
|
"babel-loader": "^9.2.1",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||||
@ -38,6 +47,7 @@
|
|||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"webpack": "^5.94.0",
|
"webpack": "^5.94.0",
|
||||||
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1790,7 +1800,6 @@
|
|||||||
"version": "7.25.6",
|
"version": "7.25.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
||||||
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
@ -2160,6 +2169,24 @@
|
|||||||
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
|
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
|
||||||
|
},
|
||||||
|
"node_modules/@orchidjs/sifter": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-zCZbwKegHytfsPm8Amcfh7v/4vHqTAaOu6xFswBYcn8nznBOuseu6COB2ON7ez0tFV0mKL0nRNnCiZZA+lU9/g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@orchidjs/unicode-variants": "^1.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@orchidjs/unicode-variants": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@orchidjs/unicode-variants/-/unicode-variants-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-NvVBRnZNE+dugiXERFsET1JlKZfM5lJDEpSMilKW4bToYJ7pxf0Zne78xyXB2ny2c2aHfJ6WLnz1AaTNHAmQeQ=="
|
||||||
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
@ -2169,6 +2196,114 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@polka/url": {
|
||||||
|
"version": "1.0.0-next.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
|
||||||
|
"integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/browser-utils": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "8.34.0",
|
||||||
|
"@sentry/types": "8.34.0",
|
||||||
|
"@sentry/utils": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/feedback": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "8.34.0",
|
||||||
|
"@sentry/types": "8.34.0",
|
||||||
|
"@sentry/utils": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/browser-utils": "8.34.0",
|
||||||
|
"@sentry/core": "8.34.0",
|
||||||
|
"@sentry/types": "8.34.0",
|
||||||
|
"@sentry/utils": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry-internal/replay-canvas": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/replay": "8.34.0",
|
||||||
|
"@sentry/core": "8.34.0",
|
||||||
|
"@sentry/types": "8.34.0",
|
||||||
|
"@sentry/utils": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/browser": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/browser-utils": "8.34.0",
|
||||||
|
"@sentry-internal/feedback": "8.34.0",
|
||||||
|
"@sentry-internal/replay": "8.34.0",
|
||||||
|
"@sentry-internal/replay-canvas": "8.34.0",
|
||||||
|
"@sentry/core": "8.34.0",
|
||||||
|
"@sentry/types": "8.34.0",
|
||||||
|
"@sentry/utils": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/core": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "8.34.0",
|
||||||
|
"@sentry/utils": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/types": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/utils": {
|
||||||
|
"version": "8.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.34.0.tgz",
|
||||||
|
"integrity": "sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "8.34.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
@ -2184,6 +2319,17 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tweenjs/tween.js": {
|
||||||
|
"version": "25.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz",
|
||||||
|
"integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/alpinejs": {
|
||||||
|
"version": "3.13.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/alpinejs/-/alpinejs-3.13.10.tgz",
|
||||||
|
"integrity": "sha512-ah53tF6mWuuwerpDE7EHwbZErNDJQlsLISPqJhYj2RZ9nuTYbRknSkqebUd3igkhLIZKkPa7IiXjSn9qsU9O2w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/codemirror": {
|
"node_modules/@types/codemirror": {
|
||||||
"version": "5.60.15",
|
"version": "5.60.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz",
|
||||||
@ -2221,6 +2367,15 @@
|
|||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jquery": {
|
||||||
|
"version": "3.5.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.31.tgz",
|
||||||
|
"integrity": "sha512-rf/iB+cPJ/YZfMwr+FVuQbm7IaWC4y3FVYfVDxRGqmUCFjjPII0HWaP0vTPJGp6m4o13AXySCcMbWfrWtBFAKw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/sizzle": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@ -2241,6 +2396,12 @@
|
|||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/sizzle": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/tern": {
|
"node_modules/@types/tern": {
|
||||||
"version": "0.23.9",
|
"version": "0.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||||
@ -2489,6 +2650,29 @@
|
|||||||
"node": ">=16.5.0"
|
"node": ">=16.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/3d-force-graph": {
|
||||||
|
"version": "1.73.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/3d-force-graph/-/3d-force-graph-1.73.4.tgz",
|
||||||
|
"integrity": "sha512-eMHZ1LVzh9APLv+An0AXz2dVPwasJlqAnJ61ABlb1qaO6DYuqIUTTErh0DN/24nIWJu1jCim2WiVujzz7slnWQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"accessor-fn": "1",
|
||||||
|
"kapsule": "1",
|
||||||
|
"three": ">=0.118 <1",
|
||||||
|
"three-forcegraph": "1",
|
||||||
|
"three-render-objects": "^1.29"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/accessor-fn": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-zZpFYBqIL1Aqg+f2qmYHJ8+yIZF7/tP6PUGx2/QM0uGPSO5UegpinmkNwDohxWtOj586BpMPVRUjce2HI6xB3A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.12.1",
|
"version": "8.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||||
@ -2510,6 +2694,18 @@
|
|||||||
"acorn": "^8"
|
"acorn": "^8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn-walk": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^8.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@ -2900,6 +3096,17 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "4.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz",
|
||||||
|
"integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
||||||
@ -3419,6 +3626,159 @@
|
|||||||
"cytoscape": "^3.2.0"
|
"cytoscape": "^3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-binarytree": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw=="
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-dispatch": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-force-3d": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-tdwhAhoTYZY/a6eo9nR7HP3xSW/C6XvJTbeRpR92nlPzH6OiE+4MliN9feuSFd0tPtEUo+191qOhCTWx3NYifg==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-binarytree": "1",
|
||||||
|
"d3-dispatch": "1 - 3",
|
||||||
|
"d3-octree": "1",
|
||||||
|
"d3-quadtree": "1 - 3",
|
||||||
|
"d3-timer": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-octree": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Qxg4oirJrNXauiuC94uKMbgxwnhdda9xRLl9ihq45srlJ4Ga3CSgqGcAL8iW7N5CIv4Oz8x3E734ulxyvHPvwA=="
|
||||||
|
},
|
||||||
|
"node_modules/d3-quadtree": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale-chromatic": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3",
|
||||||
|
"d3-interpolate": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/data-joint": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/data-joint/-/data-joint-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-tMK0m4OVGqiA3zkn8JmO6YAqD8UwJqIAx4AAwFl1SKTtKAqcXePuT+n2aayiX9uITtlN3DFtKKTOxJRUc2+HvQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"index-array-by": "^1.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/debounce": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
@ -3515,6 +3875,12 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/duplexer": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@ -4026,6 +4392,21 @@
|
|||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/gzip-size": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"duplexer": "^0.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/handlebars": {
|
"node_modules/handlebars": {
|
||||||
"version": "4.7.8",
|
"version": "4.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||||
@ -4068,6 +4449,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-escaper": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||||
@ -4120,6 +4507,22 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/index-array-by": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/internmap": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/interpret": {
|
"node_modules/interpret": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
|
||||||
@ -4328,6 +4731,17 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kapsule": {
|
||||||
|
"version": "1.14.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.14.6.tgz",
|
||||||
|
"integrity": "sha512-wSi6tHNOfXrIK2Pvv6BhZ9ukzhbp+XZlOOPWSVGUbqfFsnnli4Eq8FN6TaWJv2e17sY5+fKYVxa4DP2oPGlKhg==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash-es": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kind-of": {
|
"node_modules/kind-of": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
@ -4375,6 +4789,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash-es": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||||
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
@ -4629,6 +5048,15 @@
|
|||||||
"ufo": "^1.5.4"
|
"ufo": "^1.5.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mrmime": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@ -4680,6 +5108,39 @@
|
|||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/ngraph.events": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ=="
|
||||||
|
},
|
||||||
|
"node_modules/ngraph.forcelayout": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==",
|
||||||
|
"dependencies": {
|
||||||
|
"ngraph.events": "^1.0.0",
|
||||||
|
"ngraph.merge": "^1.0.0",
|
||||||
|
"ngraph.random": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ngraph.graph": {
|
||||||
|
"version": "20.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-20.0.1.tgz",
|
||||||
|
"integrity": "sha512-VFsQ+EMkT+7lcJO1QP8Ik3w64WbHJl27Q53EO9hiFU9CRyxJ8HfcXtfWz/U8okuoYKDctbciL6pX3vG5dt1rYA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ngraph.events": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ngraph.merge": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg=="
|
||||||
|
},
|
||||||
|
"node_modules/ngraph.random": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-h25UdUN/g8U7y29TzQtRm/GvGr70lK37yQPvPKXXuVfs7gCm82WipYFZcksQfeKumtOemAzBIcT7lzzyK/edLw=="
|
||||||
|
},
|
||||||
"node_modules/node-domexception": {
|
"node_modules/node-domexception": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||||
@ -4791,6 +5252,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/opener": {
|
||||||
|
"version": "1.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||||
|
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"opener": "bin/opener-bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
@ -4923,6 +5393,17 @@
|
|||||||
"pathe": "^1.1.2"
|
"pathe": "^1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/polished": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.17.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.47",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
@ -5514,8 +5995,7 @@
|
|||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.14.1",
|
"version": "0.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/regenerator-transform": {
|
"node_modules/regenerator-transform": {
|
||||||
"version": "0.15.2",
|
"version": "0.15.2",
|
||||||
@ -5718,6 +6198,20 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sirv": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@polka/url": "^1.0.0-next.24",
|
||||||
|
"mrmime": "^2.0.0",
|
||||||
|
"totalist": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
@ -6036,6 +6530,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/three": {
|
||||||
|
"version": "0.169.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three/-/three-0.169.0.tgz",
|
||||||
|
"integrity": "sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w=="
|
||||||
|
},
|
||||||
|
"node_modules/three-forcegraph": {
|
||||||
|
"version": "1.41.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/three-forcegraph/-/three-forcegraph-1.41.15.tgz",
|
||||||
|
"integrity": "sha512-E1j6bKt7lWg9t/ERdEiuxYfPbAioTCd9RG2bgqyC0yM3rwkBqn5VZN3fvb7umaOuTB1Tqpq6m07iVfJSfzTnCQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"accessor-fn": "1",
|
||||||
|
"d3-array": "1 - 3",
|
||||||
|
"d3-force-3d": "2 - 3",
|
||||||
|
"d3-scale": "1 - 4",
|
||||||
|
"d3-scale-chromatic": "1 - 3",
|
||||||
|
"data-joint": "1",
|
||||||
|
"kapsule": "1",
|
||||||
|
"ngraph.forcelayout": "3",
|
||||||
|
"ngraph.graph": "20",
|
||||||
|
"tinycolor2": "1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">=0.118.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/three-render-objects": {
|
||||||
|
"version": "1.29.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/three-render-objects/-/three-render-objects-1.29.5.tgz",
|
||||||
|
"integrity": "sha512-OLtETrjF184NuaaI/vpRlIP9FxVNAgBBCgWYXhGFUDnPdl/2iX8rialUPGA1gEXvOTiKyepArVgm1LUkJw15rQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@tweenjs/tween.js": "18 - 25",
|
||||||
|
"accessor-fn": "1",
|
||||||
|
"kapsule": "1",
|
||||||
|
"polished": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/three-spritetext": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three-spritetext/-/three-spritetext-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-+dMrxBsxTu5OviykIg5jTMry5TQ8u5yuS9zKH0mWElyldoFGdegEkIm71kDk34bxBp/NQhRLW+iom1b/GMTioA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"three": ">=0.86.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinycolor2": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
|
||||||
|
},
|
||||||
"node_modules/to-fast-properties": {
|
"node_modules/to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||||
@ -6057,6 +6612,31 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tom-select": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tom-select/-/tom-select-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-QS4vnOcB6StNGqX4sGboGXL2fkhBF2gIBB+8Hwv30FZXYPn0CyYO8kkdATRvwfCTThxiR4WcXwKJZ3cOmtI9eg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@orchidjs/sifter": "^1.0.3",
|
||||||
|
"@orchidjs/unicode-variants": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tom-select"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/totalist": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ts-loader": {
|
"node_modules/ts-loader": {
|
||||||
"version": "9.5.1",
|
"version": "9.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz",
|
||||||
@ -6282,6 +6862,53 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webpack-bundle-analyzer": {
|
||||||
|
"version": "4.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz",
|
||||||
|
"integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@discoveryjs/json-ext": "0.5.7",
|
||||||
|
"acorn": "^8.0.4",
|
||||||
|
"acorn-walk": "^8.0.0",
|
||||||
|
"commander": "^7.2.0",
|
||||||
|
"debounce": "^1.2.1",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"gzip-size": "^6.0.0",
|
||||||
|
"html-escaper": "^2.0.2",
|
||||||
|
"opener": "^1.5.2",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"sirv": "^2.0.3",
|
||||||
|
"ws": "^7.3.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"webpack-bundle-analyzer": "lib/bin/analyzer.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webpack-bundle-analyzer/node_modules/commander": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webpack-cli": {
|
"node_modules/webpack-cli": {
|
||||||
"version": "5.1.4",
|
"version": "5.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
|
||||||
@ -6469,6 +7096,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "7.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||||
|
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
14
package.json
14
package.json
@ -7,6 +7,8 @@
|
|||||||
"compile": "webpack --mode production",
|
"compile": "webpack --mode production",
|
||||||
"compile-dev": "webpack --mode development",
|
"compile-dev": "webpack --mode development",
|
||||||
"serve": "webpack --mode development --watch",
|
"serve": "webpack --mode development --watch",
|
||||||
|
"analyse-dev": "webpack --config webpack.analyze.config.js --mode development",
|
||||||
|
"analyse-prod": "webpack --config webpack.analyze.config.js --mode production",
|
||||||
"check": "biome check --write"
|
"check": "biome check --write"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@ -23,6 +25,8 @@
|
|||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.25.4",
|
||||||
"@biomejs/biome": "1.9.3",
|
"@biomejs/biome": "1.9.3",
|
||||||
"@hey-api/openapi-ts": "^0.53.8",
|
"@hey-api/openapi-ts": "^0.53.8",
|
||||||
|
"@types/alpinejs": "^3.13.10",
|
||||||
|
"@types/jquery": "^3.5.31",
|
||||||
"babel-loader": "^9.2.1",
|
"babel-loader": "^9.2.1",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||||
@ -33,21 +37,29 @@
|
|||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"webpack": "^5.94.0",
|
"webpack": "^5.94.0",
|
||||||
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@hey-api/client-fetch": "^0.4.0",
|
"@hey-api/client-fetch": "^0.4.0",
|
||||||
|
"@sentry/browser": "^8.34.0",
|
||||||
"@zip.js/zip.js": "^2.7.52",
|
"@zip.js/zip.js": "^2.7.52",
|
||||||
|
"3d-force-graph": "^1.73.4",
|
||||||
"alpinejs": "^3.14.1",
|
"alpinejs": "^3.14.1",
|
||||||
|
"chart.js": "^4.4.4",
|
||||||
"cytoscape": "^3.30.2",
|
"cytoscape": "^3.30.2",
|
||||||
"cytoscape-cxtmenu": "^3.5.0",
|
"cytoscape-cxtmenu": "^3.5.0",
|
||||||
"cytoscape-klay": "^3.1.4",
|
"cytoscape-klay": "^3.1.4",
|
||||||
|
"d3-force-3d": "^3.0.5",
|
||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-ui": "^1.14.0",
|
"jquery-ui": "^1.14.0",
|
||||||
"jquery.shorten": "^1.0.0",
|
"jquery.shorten": "^1.0.0",
|
||||||
"native-file-system-adapter": "^3.0.1"
|
"native-file-system-adapter": "^3.0.1",
|
||||||
|
"three": "^0.169.0",
|
||||||
|
"three-spritetext": "^1.9.0",
|
||||||
|
"tom-select": "^2.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,10 @@ class Migration(migrations.Migration):
|
|||||||
unique=True,
|
unique=True,
|
||||||
validators=[
|
validators=[
|
||||||
django.core.validators.RegexValidator(
|
django.core.validators.RegexValidator(
|
||||||
message="The code of an UV must only contains uppercase characters without accent and numbers",
|
message=(
|
||||||
|
"The code of an UV must only contains "
|
||||||
|
"uppercase characters without accent and numbers"
|
||||||
|
),
|
||||||
regex="([A-Z0-9]+)",
|
regex="([A-Z0-9]+)",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -45,7 +45,8 @@ class UV(models.Model):
|
|||||||
validators.RegexValidator(
|
validators.RegexValidator(
|
||||||
regex="([A-Z0-9]+)",
|
regex="([A-Z0-9]+)",
|
||||||
message=_(
|
message=_(
|
||||||
"The code of an UV must only contains uppercase characters without accent and numbers"
|
"The code of an UV must only contains "
|
||||||
|
"uppercase characters without accent and numbers"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -27,7 +27,10 @@ class TestUVSearch(TestCase):
|
|||||||
semester="AUTUMN",
|
semester="AUTUMN",
|
||||||
department="GI",
|
department="GI",
|
||||||
manager="francky",
|
manager="francky",
|
||||||
title="Programmation Orientée Objet: Concepts fondamentaux et mise en pratique avec le langage C++",
|
title=(
|
||||||
|
"Programmation Orientée Objet: "
|
||||||
|
"Concepts fondamentaux et mise en pratique avec le langage C++"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
uv_recipe.prepare(
|
uv_recipe.prepare(
|
||||||
code="MT01",
|
code="MT01",
|
||||||
@ -118,7 +121,7 @@ class TestUVSearch(TestCase):
|
|||||||
("M", {"MT01", "MT10"}),
|
("M", {"MT01", "MT10"}),
|
||||||
("mt", {"MT01", "MT10"}),
|
("mt", {"MT01", "MT10"}),
|
||||||
("MT", {"MT01", "MT10"}),
|
("MT", {"MT01", "MT10"}),
|
||||||
("algèbre", {"MT01"}), # Title search case insensitive
|
("algèbre", {"MT01"}), # Title search case insensitive
|
||||||
# Manager search
|
# Manager search
|
||||||
("moss", {"TNEV"}),
|
("moss", {"TNEV"}),
|
||||||
("francky", {"DA50", "AP4A"}),
|
("francky", {"DA50", "AP4A"}),
|
||||||
|
@ -381,7 +381,9 @@ class TestUVCommentCreationAndDisplay(TestCase):
|
|||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
_(
|
_(
|
||||||
"You already posted a comment on this UV. If you want to comment again, please modify or delete your previous comment."
|
"You already posted a comment on this UV. "
|
||||||
|
"If you want to comment again, "
|
||||||
|
"please modify or delete your previous comment."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +23,17 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from pedagogy.views import *
|
from pedagogy.views import (
|
||||||
|
UVCommentDeleteView,
|
||||||
|
UVCommentReportCreateView,
|
||||||
|
UVCommentUpdateView,
|
||||||
|
UVCreateView,
|
||||||
|
UVDeleteView,
|
||||||
|
UVDetailFormView,
|
||||||
|
UVGuideView,
|
||||||
|
UVModerationFormView,
|
||||||
|
UVUpdateView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Urls displaying the actual application for visitors
|
# Urls displaying the actual application for visitors
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
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.views.generic import (
|
from django.views.generic import (
|
||||||
@ -193,18 +193,12 @@ class UVModerationFormView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form_clean = form.clean()
|
form_clean = form.clean()
|
||||||
for report in form_clean.get("accepted_reports", []):
|
accepted = form_clean.get("accepted_reports", [])
|
||||||
try:
|
if len(accepted) > 0: # delete the reported comments
|
||||||
report.comment.delete() # Delete the related comment
|
UVComment.objects.filter(reports__in=accepted).delete()
|
||||||
except ObjectDoesNotExist:
|
denied = form_clean.get("denied_reports", [])
|
||||||
# To avoid errors when two reports points the same comment
|
if len(denied) > 0: # delete the comments themselves
|
||||||
pass
|
UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
|
||||||
for report in form_clean.get("denied_reports", []):
|
|
||||||
try:
|
|
||||||
report.delete() # Delete the report itself
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
# To avoid errors when two reports points the same comment
|
|
||||||
pass
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
284
poetry.lock
generated
284
poetry.lock
generated
@ -343,73 +343,73 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.6.2"
|
version = "7.6.3"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "coverage-7.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9df1950fb92d49970cce38100d7e7293c84ed3606eaa16ea0b6bc27175bb667"},
|
{file = "coverage-7.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6da42bbcec130b188169107ecb6ee7bd7b4c849d24c9370a0c884cf728d8e976"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:24500f4b0e03aab60ce575c85365beab64b44d4db837021e08339f61d1fbfe52"},
|
{file = "coverage-7.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c222958f59b0ae091f4535851cbb24eb57fc0baea07ba675af718fb5302dddb2"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a663b180b6669c400b4630a24cc776f23a992d38ce7ae72ede2a397ce6b0f170"},
|
{file = "coverage-7.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab84a8b698ad5a6c365b08061920138e7a7dd9a04b6feb09ba1bfae68346ce6d"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfde025e2793a22efe8c21f807d276bd1d6a4bcc5ba6f19dbdfc4e7a12160909"},
|
{file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70a6756ce66cd6fe8486c775b30889f0dc4cb20c157aa8c35b45fd7868255c5c"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087932079c065d7b8ebadd3a0160656c55954144af6439886c8bcf78bbbcde7f"},
|
{file = "coverage-7.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c2e6fa98032fec8282f6b27e3f3986c6e05702828380618776ad794e938f53a"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9c6b0c1cafd96213a0327cf680acb39f70e452caf8e9a25aeb05316db9c07f89"},
|
{file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:921fbe13492caf6a69528f09d5d7c7d518c8d0e7b9f6701b7719715f29a71e6e"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6e85830eed5b5263ffa0c62428e43cb844296f3b4461f09e4bdb0d44ec190bc2"},
|
{file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6d99198203f0b9cb0b5d1c0393859555bc26b548223a769baf7e321a627ed4fc"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62ab4231c01e156ece1b3a187c87173f31cbeee83a5e1f6dff17f288dca93345"},
|
{file = "coverage-7.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87cd2e29067ea397a47e352efb13f976eb1b03e18c999270bb50589323294c6e"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-win32.whl", hash = "sha256:7b80fbb0da3aebde102a37ef0138aeedff45997e22f8962e5f16ae1742852676"},
|
{file = "coverage-7.6.3-cp310-cp310-win32.whl", hash = "sha256:a3328c3e64ea4ab12b85999eb0779e6139295bbf5485f69d42cf794309e3d007"},
|
||||||
{file = "coverage-7.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:d20c3d1f31f14d6962a4e2f549c21d31e670b90f777ef4171be540fb7fb70f02"},
|
{file = "coverage-7.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bca4c8abc50d38f9773c1ec80d43f3768df2e8576807d1656016b9d3eeaa96fd"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bb21bac7783c1bf6f4bbe68b1e0ff0d20e7e7732cfb7995bc8d96e23aa90fc7b"},
|
{file = "coverage-7.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c51ef82302386d686feea1c44dbeef744585da16fcf97deea2a8d6c1556f519b"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7b2e437fbd8fae5bc7716b9c7ff97aecc95f0b4d56e4ca08b3c8d8adcaadb84"},
|
{file = "coverage-7.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ca37993206402c6c35dc717f90d4c8f53568a8b80f0bf1a1b2b334f4d488fba"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:536f77f2bf5797983652d1d55f1a7272a29afcc89e3ae51caa99b2db4e89d658"},
|
{file = "coverage-7.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c77326300b839c44c3e5a8fe26c15b7e87b2f32dfd2fc9fee1d13604347c9b38"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f361296ca7054f0936b02525646b2731b32c8074ba6defab524b79b2b7eeac72"},
|
{file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e484e479860e00da1f005cd19d1c5d4a813324e5951319ac3f3eefb497cc549"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7926d8d034e06b479797c199747dd774d5e86179f2ce44294423327a88d66ca7"},
|
{file = "coverage-7.6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c6c0f4d53ef603397fc894a895b960ecd7d44c727df42a8d500031716d4e8d2"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0bbae11c138585c89fb4e991faefb174a80112e1a7557d507aaa07675c62e66b"},
|
{file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37be7b5ea3ff5b7c4a9db16074dc94523b5f10dd1f3b362a827af66a55198175"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fcad7d5d2bbfeae1026b395036a8aa5abf67e8038ae7e6a25c7d0f88b10a8e6a"},
|
{file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:43b32a06c47539fe275106b376658638b418c7cfdfff0e0259fbf877e845f14b"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f01e53575f27097d75d42de33b1b289c74b16891ce576d767ad8c48d17aeb5e0"},
|
{file = "coverage-7.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee77c7bef0724165e795b6b7bf9c4c22a9b8468a6bdb9c6b4281293c6b22a90f"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-win32.whl", hash = "sha256:7781f4f70c9b0b39e1b129b10c7d43a4e0c91f90c60435e6da8288efc2b73438"},
|
{file = "coverage-7.6.3-cp311-cp311-win32.whl", hash = "sha256:43517e1f6b19f610a93d8227e47790722c8bf7422e46b365e0469fc3d3563d97"},
|
||||||
{file = "coverage-7.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:9bcd51eeca35a80e76dc5794a9dd7cb04b97f0e8af620d54711793bfc1fbba4b"},
|
{file = "coverage-7.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:04f2189716e85ec9192df307f7c255f90e78b6e9863a03223c3b998d24a3c6c6"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ebc94fadbd4a3f4215993326a6a00e47d79889391f5659bf310f55fe5d9f581c"},
|
{file = "coverage-7.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27bd5f18d8f2879e45724b0ce74f61811639a846ff0e5c0395b7818fae87aec6"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9681516288e3dcf0aa7c26231178cc0be6cac9705cac06709f2353c5b406cfea"},
|
{file = "coverage-7.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d546cfa78844b8b9c1c0533de1851569a13f87449897bbc95d698d1d3cb2a30f"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9c5d13927d77af4fbe453953810db766f75401e764727e73a6ee4f82527b3e"},
|
{file = "coverage-7.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9975442f2e7a5cfcf87299c26b5a45266ab0696348420049b9b94b2ad3d40234"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92f9ca04b3e719d69b02dc4a69debb795af84cb7afd09c5eb5d54b4a1ae2191"},
|
{file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:583049c63106c0555e3ae3931edab5669668bbef84c15861421b94e121878d3f"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ff2ef83d6d0b527b5c9dad73819b24a2f76fdddcfd6c4e7a4d7e73ecb0656b4"},
|
{file = "coverage-7.6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2341a78ae3a5ed454d524206a3fcb3cec408c2a0c7c2752cd78b606a2ff15af4"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47ccb6e99a3031ffbbd6e7cc041e70770b4fe405370c66a54dbf26a500ded80b"},
|
{file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4fb91d5f72b7e06a14ff4ae5be625a81cd7e5f869d7a54578fc271d08d58ae3"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a867d26f06bcd047ef716175b2696b315cb7571ccb951006d61ca80bbc356e9e"},
|
{file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e279f3db904e3b55f520f11f983cc8dc8a4ce9b65f11692d4718ed021ec58b83"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cdfcf2e914e2ba653101157458afd0ad92a16731eeba9a611b5cbb3e7124e74b"},
|
{file = "coverage-7.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa23ce39661a3e90eea5f99ec59b763b7d655c2cada10729ed920a38bfc2b167"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-win32.whl", hash = "sha256:f9035695dadfb397bee9eeaf1dc7fbeda483bf7664a7397a629846800ce6e276"},
|
{file = "coverage-7.6.3-cp312-cp312-win32.whl", hash = "sha256:52ac29cc72ee7e25ace7807249638f94c9b6a862c56b1df015d2b2e388e51dbd"},
|
||||||
{file = "coverage-7.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:5ed69befa9a9fc796fe015a7040c9398722d6b97df73a6b608e9e275fa0932b0"},
|
{file = "coverage-7.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:40e8b1983080439d4802d80b951f4a93d991ef3261f69e81095a66f86cf3c3c6"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eea60c79d36a8f39475b1af887663bc3ae4f31289cd216f514ce18d5938df40"},
|
{file = "coverage-7.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9134032f5aa445ae591c2ba6991d10136a1f533b1d2fa8f8c21126468c5025c6"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa68a6cdbe1bc6793a9dbfc38302c11599bbe1837392ae9b1d238b9ef3dafcf1"},
|
{file = "coverage-7.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99670790f21a96665a35849990b1df447993880bb6463a0a1d757897f30da929"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ec528ae69f0a139690fad6deac8a7d33629fa61ccce693fdd07ddf7e9931fba"},
|
{file = "coverage-7.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc7d6b380ca76f5e817ac9eef0c3686e7834c8346bef30b041a4ad286449990"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed5ac02126f74d190fa2cc14a9eb2a5d9837d5863920fa472b02eb1595cdc925"},
|
{file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b26757b22faf88fcf232f5f0e62f6e0fd9e22a8a5d0d5016888cdfe1f6c1c4"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21c0ea0d4db8a36b275cb6fb2437a3715697a4ba3cb7b918d3525cc75f726304"},
|
{file = "coverage-7.6.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c59d6a4a4633fad297f943c03d0d2569867bd5372eb5684befdff8df8522e39"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:35a51598f29b2a19e26d0908bd196f771a9b1c5d9a07bf20be0adf28f1ad4f77"},
|
{file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f263b18692f8ed52c8de7f40a0751e79015983dbd77b16906e5b310a39d3ca21"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c9192925acc33e146864b8cf037e2ed32a91fdf7644ae875f5d46cd2ef086a5f"},
|
{file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:79644f68a6ff23b251cae1c82b01a0b51bc40c8468ca9585c6c4b1aeee570e0b"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf4eeecc9e10f5403ec06138978235af79c9a79af494eb6b1d60a50b49ed2869"},
|
{file = "coverage-7.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71967c35828c9ff94e8c7d405469a1fb68257f686bca7c1ed85ed34e7c2529c4"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-win32.whl", hash = "sha256:e4ee15b267d2dad3e8759ca441ad450c334f3733304c55210c2a44516e8d5530"},
|
{file = "coverage-7.6.3-cp313-cp313-win32.whl", hash = "sha256:e266af4da2c1a4cbc6135a570c64577fd3e6eb204607eaff99d8e9b710003c6f"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313-win_amd64.whl", hash = "sha256:c71965d1ced48bf97aab79fad56df82c566b4c498ffc09c2094605727c4b7e36"},
|
{file = "coverage-7.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:ea52bd218d4ba260399a8ae4bb6b577d82adfc4518b93566ce1fddd4a49d1dce"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7571e8bbecc6ac066256f9de40365ff833553e2e0c0c004f4482facb131820ef"},
|
{file = "coverage-7.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8d4c6ea0f498c7c79111033a290d060c517853a7bcb2f46516f591dab628ddd3"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:078a87519057dacb5d77e333f740708ec2a8f768655f1db07f8dfd28d7a005f0"},
|
{file = "coverage-7.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:331b200ad03dbaa44151d74daeb7da2cf382db424ab923574f6ecca7d3b30de3"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5e92e3e84a8718d2de36cd8387459cba9a4508337b8c5f450ce42b87a9e760"},
|
{file = "coverage-7.6.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54356a76b67cf8a3085818026bb556545ebb8353951923b88292556dfa9f812d"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebabdf1c76593a09ee18c1a06cd3022919861365219ea3aca0247ededf6facd6"},
|
{file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebec65f5068e7df2d49466aab9128510c4867e532e07cb6960075b27658dca38"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12179eb0575b8900912711688e45474f04ab3934aaa7b624dea7b3c511ecc90f"},
|
{file = "coverage-7.6.3-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d33a785ea8354c480515e781554d3be582a86297e41ccbea627a5c632647f2cd"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:39d3b964abfe1519b9d313ab28abf1d02faea26cd14b27f5283849bf59479ff5"},
|
{file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f7ddb920106bbbbcaf2a274d56f46956bf56ecbde210d88061824a95bdd94e92"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:84c4315577f7cd511d6250ffd0f695c825efe729f4205c0340f7004eda51191f"},
|
{file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:70d24936ca6c15a3bbc91ee9c7fc661132c6f4c9d42a23b31b6686c05073bde5"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff797320dcbff57caa6b2301c3913784a010e13b1f6cf4ab3f563f3c5e7919db"},
|
{file = "coverage-7.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c30e42ea11badb147f0d2e387115b15e2bd8205a5ad70d6ad79cf37f6ac08c91"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-win32.whl", hash = "sha256:2b636a301e53964550e2f3094484fa5a96e699db318d65398cfba438c5c92171"},
|
{file = "coverage-7.6.3-cp313-cp313t-win32.whl", hash = "sha256:365defc257c687ce3e7d275f39738dcd230777424117a6c76043459db131dd43"},
|
||||||
{file = "coverage-7.6.2-cp313-cp313t-win_amd64.whl", hash = "sha256:d03a060ac1a08e10589c27d509bbdb35b65f2d7f3f8d81cf2fa199877c7bc58a"},
|
{file = "coverage-7.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:23bb63ae3f4c645d2d82fa22697364b0046fbafb6261b258a58587441c5f7bd0"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c37faddc8acd826cfc5e2392531aba734b229741d3daec7f4c777a8f0d4993e5"},
|
{file = "coverage-7.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da29ceabe3025a1e5a5aeeb331c5b1af686daab4ff0fb4f83df18b1180ea83e2"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab31fdd643f162c467cfe6a86e9cb5f1965b632e5e65c072d90854ff486d02cf"},
|
{file = "coverage-7.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df8c05a0f574d480947cba11b947dc41b1265d721c3777881da2fb8d3a1ddfba"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97df87e1a20deb75ac7d920c812e9326096aa00a9a4b6d07679b4f1f14b06c90"},
|
{file = "coverage-7.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec1e3b40b82236d100d259854840555469fad4db64f669ab817279eb95cd535c"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:343056c5e0737487a5291f5691f4dfeb25b3e3c8699b4d36b92bb0e586219d14"},
|
{file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4adeb878a374126f1e5cf03b87f66279f479e01af0e9a654cf6d1509af46c40"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4ef1c56b47b6b9024b939d503ab487231df1f722065a48f4fc61832130b90e"},
|
{file = "coverage-7.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43d6a66e33b1455b98fc7312b124296dad97a2e191c80320587234a77b1b736e"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fca4a92c8a7a73dee6946471bce6d1443d94155694b893b79e19ca2a540d86e"},
|
{file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1990b1f4e2c402beb317840030bb9f1b6a363f86e14e21b4212e618acdfce7f6"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69f251804e052fc46d29d0e7348cdc5fcbfc4861dc4a1ebedef7e78d241ad39e"},
|
{file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:12f9515d875859faedb4144fd38694a761cd2a61ef9603bf887b13956d0bbfbb"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e8ea055b3ea046c0f66217af65bc193bbbeca1c8661dc5fd42698db5795d2627"},
|
{file = "coverage-7.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99ded130555c021d99729fabd4ddb91a6f4cc0707df4b1daf912c7850c373b13"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-win32.whl", hash = "sha256:6c2ba1e0c24d8fae8f2cf0aeb2fc0a2a7f69b6d20bd8d3749fd6b36ecef5edf0"},
|
{file = "coverage-7.6.3-cp39-cp39-win32.whl", hash = "sha256:c3a79f56dee9136084cf84a6c7c4341427ef36e05ae6415bf7d787c96ff5eaa3"},
|
||||||
{file = "coverage-7.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:2186369a654a15628e9c1c9921409a6b3eda833e4b91f3ca2a7d9f77abb4987c"},
|
{file = "coverage-7.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:aac7501ae73d4a02f4b7ac8fcb9dc55342ca98ffb9ed9f2dfb8a25d53eda0e4d"},
|
||||||
{file = "coverage-7.6.2-pp39.pp310-none-any.whl", hash = "sha256:667952739daafe9616db19fbedbdb87917eee253ac4f31d70c7587f7ab531b4e"},
|
{file = "coverage-7.6.3-pp39.pp310-none-any.whl", hash = "sha256:b9853509b4bf57ba7b1f99b9d866c422c9c5248799ab20e652bbb8a184a38181"},
|
||||||
{file = "coverage-7.6.2.tar.gz", hash = "sha256:a5f81e68aa62bc0cfca04f7b19eaa8f9c826b53fc82ab9e2121976dc74f131f3"},
|
{file = "coverage-7.6.3.tar.gz", hash = "sha256:bb7d5fe92bd0dc235f63ebe9f8c6e0884f7360f88f3411bfed1350c872ef2054"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -491,13 +491,13 @@ tests = ["noseofyeti[black] (==2.4.9)", "pytest (==8.3.2)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "distlib"
|
name = "distlib"
|
||||||
version = "0.3.8"
|
version = "0.3.9"
|
||||||
description = "Distribution utilities"
|
description = "Distribution utilities"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
|
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
|
||||||
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -814,13 +814,13 @@ dev = ["flake8", "markdown", "twine", "wheel"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "griffe"
|
name = "griffe"
|
||||||
version = "1.3.2"
|
version = "1.4.1"
|
||||||
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "griffe-1.3.2-py3-none-any.whl", hash = "sha256:2e34b5e46507d615915c8e6288bb1a2234bd35dee44d01e40a2bc2f25bd4d10c"},
|
{file = "griffe-1.4.1-py3-none-any.whl", hash = "sha256:84295ee0b27743bd880aea75632830ef02ded65d16124025e4c263bb826ab645"},
|
||||||
{file = "griffe-1.3.2.tar.gz", hash = "sha256:1ec50335aa507ed2445f2dd45a15c9fa3a45f52c9527e880571dfc61912fd60c"},
|
{file = "griffe-1.4.1.tar.gz", hash = "sha256:911a201b01dc92e08c0e84c38a301e9da5ec067f00e7d9f2e39bc24dbfa3c176"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1283,13 +1283,13 @@ cache = ["platformdirs"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "9.5.39"
|
version = "9.5.40"
|
||||||
description = "Documentation that simply works"
|
description = "Documentation that simply works"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocs_material-9.5.39-py3-none-any.whl", hash = "sha256:0f2f68c8db89523cb4a59705cd01b4acd62b2f71218ccb67e1e004e560410d2b"},
|
{file = "mkdocs_material-9.5.40-py3-none-any.whl", hash = "sha256:8e7a16ada34e79a7b6459ff2602584222f522c738b6a023d1bea853d5049da6f"},
|
||||||
{file = "mkdocs_material-9.5.39.tar.gz", hash = "sha256:25faa06142afa38549d2b781d475a86fb61de93189f532b88e69bf11e5e5c3be"},
|
{file = "mkdocs_material-9.5.40.tar.gz", hash = "sha256:b69d70e667ec51fc41f65e006a3184dd00d95b2439d982cb1586e4c018943156"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1323,13 +1323,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocstrings"
|
name = "mkdocstrings"
|
||||||
version = "0.26.1"
|
version = "0.26.2"
|
||||||
description = "Automatic documentation from sources, for MkDocs."
|
description = "Automatic documentation from sources, for MkDocs."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocstrings-0.26.1-py3-none-any.whl", hash = "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf"},
|
{file = "mkdocstrings-0.26.2-py3-none-any.whl", hash = "sha256:1248f3228464f3b8d1a15bd91249ce1701fe3104ac517a5f167a0e01ca850ba5"},
|
||||||
{file = "mkdocstrings-0.26.1.tar.gz", hash = "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33"},
|
{file = "mkdocstrings-0.26.2.tar.gz", hash = "sha256:34a8b50f1e6cfd29546c6c09fbe02154adfb0b361bb758834bf56aa284ba876e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1349,13 +1349,13 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocstrings-python"
|
name = "mkdocstrings-python"
|
||||||
version = "1.11.1"
|
version = "1.12.1"
|
||||||
description = "A Python handler for mkdocstrings."
|
description = "A Python handler for mkdocstrings."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocstrings_python-1.11.1-py3-none-any.whl", hash = "sha256:a21a1c05acef129a618517bb5aae3e33114f569b11588b1e7af3e9d4061a71af"},
|
{file = "mkdocstrings_python-1.12.1-py3-none-any.whl", hash = "sha256:205244488199c9aa2a39787ad6a0c862d39b74078ea9aa2be817bc972399563f"},
|
||||||
{file = "mkdocstrings_python-1.11.1.tar.gz", hash = "sha256:8824b115c5359304ab0b5378a91f6202324a849e1da907a3485b59208b797322"},
|
{file = "mkdocstrings_python-1.12.1.tar.gz", hash = "sha256:60d6a5ca912c9af4ad431db6d0111ce9f79c6c48d33377dde6a05a8f5f48d792"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1365,13 +1365,13 @@ mkdocstrings = ">=0.26"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "model-bakery"
|
name = "model-bakery"
|
||||||
version = "1.19.5"
|
version = "1.20.0"
|
||||||
description = "Smart object creation facility for Django."
|
description = "Smart object creation facility for Django."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "model_bakery-1.19.5-py3-none-any.whl", hash = "sha256:09ecbbf124d32614339581b642c82ac4a73147442f598c7bad23eece24187e5c"},
|
{file = "model_bakery-1.20.0-py3-none-any.whl", hash = "sha256:875326466f5982ee8f0281abdfa774d78893d5473562575dfd5a9304ac7c5b8c"},
|
||||||
{file = "model_bakery-1.19.5.tar.gz", hash = "sha256:37cece544a33f8899ed8f0488cd6a9d2b0b6925e7b478a4ff2786dece8c63745"},
|
{file = "model_bakery-1.20.0.tar.gz", hash = "sha256:ec9dc846b9a00b20f92df38fac310263323ab61b59b6eeebf77a4aefb0412724"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1599,13 +1599,13 @@ testing = ["pytest", "pytest-benchmark"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pre-commit"
|
name = "pre-commit"
|
||||||
version = "3.8.0"
|
version = "4.0.1"
|
||||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"},
|
{file = "pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878"},
|
||||||
{file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"},
|
{file = "pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -2192,58 +2192,70 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rjsmin"
|
name = "rjsmin"
|
||||||
version = "1.2.2"
|
version = "1.2.3"
|
||||||
description = "Javascript Minifier"
|
description = "Javascript Minifier"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "rjsmin-1.2.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4420107304ba7a00b5b9b56cdcd166b9876b34e626829fc4552c85d8fdc3737a"},
|
{file = "rjsmin-1.2.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:86e4257875d89b0f7968af9e7c0292e72454f6c75031d1818997782b2e8425a8"},
|
||||||
{file = "rjsmin-1.2.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:155a2f3312c1f8c6cec7b5080581cafc761dc0e41d64bfb5d46a772c5230ded8"},
|
{file = "rjsmin-1.2.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2ab72093591127e627b13c1243d4fef40c10593c733517999682f7f2ebf47ee"},
|
||||||
{file = "rjsmin-1.2.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:88fcb58d65f88cbfa752d51c1ebe5845553f9706def6d9671e98283411575e3e"},
|
{file = "rjsmin-1.2.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:448b9eb9fd7b6a70beb5c728a41bc23561dd011f0b8fcf7ed9855b6be198c9a2"},
|
||||||
{file = "rjsmin-1.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6eae13608b88f4ce32e0557c8fdef58e69bb4d293182202a03e800f0d33b5268"},
|
{file = "rjsmin-1.2.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ebd3948f9bc912525bab93f61c694b11410296b5fd0806e988d42378ef302b8e"},
|
||||||
{file = "rjsmin-1.2.2-cp310-cp310-manylinux1_i686.whl", hash = "sha256:81f92fb855fb613ebd04a6d6d46483e71fe3c4f22042dc30dcc938fbd748e59c"},
|
{file = "rjsmin-1.2.3-cp310-cp310-manylinux1_i686.whl", hash = "sha256:823f856b40681328157e5dffc0a588dddefb4b6ce49f79de994dfca6084617be"},
|
||||||
{file = "rjsmin-1.2.2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:897db9bf25538047e9388951d532dc291a629b5d041180a8a1a8c102e9d44b90"},
|
{file = "rjsmin-1.2.3-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:15e3019f0823a003741ddb93e0c70c5d22567acd0757a7edacc40face1517029"},
|
||||||
{file = "rjsmin-1.2.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:5938af8c46734f92f74fdc4d0b6324137c0e09f0a8c3825c83e4cfca1b532e40"},
|
{file = "rjsmin-1.2.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:dece04e309e29879c12dca8af166ea5d77c497ec932cf82e4a1eb24d1489c398"},
|
||||||
{file = "rjsmin-1.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0424a7b9096fa2b0ab577c4dc7acd683e6cfb5c718ad39a9fb293cb6cbaba95b"},
|
{file = "rjsmin-1.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dd4a1e527568c3a9711ff1d5251763645c14df02d52a45aec089836600b664ea"},
|
||||||
{file = "rjsmin-1.2.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1714ed93c2bd40c5f970905d2eeda4a6844e09087ae11277d4d43b3e68c32a47"},
|
{file = "rjsmin-1.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:78aaa3b79a244a4e21164ce355ce22a5a0d7f2d7841a10343009406a3d34d9bb"},
|
||||||
{file = "rjsmin-1.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:35596fa6d2d44a5471715c464657123995da78aa6f79bccfbb4b8d6ff7d0a4b4"},
|
{file = "rjsmin-1.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ea4617618cbf78d98756878a292309f6f54fb4ea1b1ea406f79e88eda4d5d50"},
|
||||||
{file = "rjsmin-1.2.2-cp311-cp311-manylinux1_i686.whl", hash = "sha256:3968667158948355b9a62e9641497aac7ac069c076a595e93199d0fe3a40217a"},
|
{file = "rjsmin-1.2.3-cp311-cp311-manylinux1_i686.whl", hash = "sha256:85957171184ef2dee1957cef5e4adb93a7e2702c12c30bd74420ebace1756e89"},
|
||||||
{file = "rjsmin-1.2.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:d07d14354694f6a47f572f2aa2a1ad74b76723e62a0d2b6df796138b71888247"},
|
{file = "rjsmin-1.2.3-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:b6485014e9cbec9a41fb4a7b96ce511ab45a5db8c54ca57ad610f53747e7bab1"},
|
||||||
{file = "rjsmin-1.2.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:a78dfa6009235b902454ac53264252b7b94f1e43e3a9e97c4cadae88e409b882"},
|
{file = "rjsmin-1.2.3-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:64ac6ef8753c56179a53e237ea4d2b3ccdef88b8b51141618311d48e31013207"},
|
||||||
{file = "rjsmin-1.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9b7a45001e58243a455d11d2de925cadb8c2a0dc737001de646a0f4d90cf0034"},
|
{file = "rjsmin-1.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dbd5f653b5ebcd4920793009ffa210ad5523c523e39e45ee1a0770e4323126dc"},
|
||||||
{file = "rjsmin-1.2.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:86c5e657b74b6c9482bb96f18a79d61750f4e8204759cce179f7eb17d395c683"},
|
{file = "rjsmin-1.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b0174d7786dcebab808485d1c27f049c74b97590cddcd62f6ed54796a2c6503b"},
|
||||||
{file = "rjsmin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8c2c30b86c7232443a4a726e1bbee34f800556e581e95fc07194ecbf8e02d1d2"},
|
{file = "rjsmin-1.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6cf24720ea214cbffa0ed96ba0dc24a5cf3ff3cbf59d44a1018292424b48452a"},
|
||||||
{file = "rjsmin-1.2.2-cp312-cp312-manylinux1_i686.whl", hash = "sha256:8982c3ef27fac26dd6b7d0c55ae98fa550fee72da2db010b87211e4b5dd78a67"},
|
{file = "rjsmin-1.2.3-cp312-cp312-manylinux1_i686.whl", hash = "sha256:ac911d1a12a6d7879ba52e08c56b0ad1a74377bae52610ea74f0f9d936d41785"},
|
||||||
{file = "rjsmin-1.2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:3fc27ae4ece99e2c994cd79df2f0d3f7ac650249f632d19aa8ce85118e33bf0f"},
|
{file = "rjsmin-1.2.3-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:57a0b2f13402623e4ec44eb7ad8846387b2d5605aa8732a05ebefb2289c24b96"},
|
||||||
{file = "rjsmin-1.2.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:41113d8d6cae7f7406b30143cc49cc045bbb3fadc2f28df398cea30e1daa60b1"},
|
{file = "rjsmin-1.2.3-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:e28610cca3ab03e43113eadad4f7dd9ea235ddc29a8dc5462bb161a80e5d251f"},
|
||||||
{file = "rjsmin-1.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3aa09a89b2b7aa2b9251329fe0c3e36c2dc2f10f78b8811e5be92a072596348b"},
|
{file = "rjsmin-1.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d4afb4fc3624dc44a7fbae4e41c0b5dc5d861a7f5de865ad463041ec1b5d835c"},
|
||||||
{file = "rjsmin-1.2.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5abb8d1241f4ea97950b872fa97a422ba8413fe02358f64128ff0cf745017f07"},
|
{file = "rjsmin-1.2.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ca26b80c7e63cf0788b41571a4bd08d175df7719364e0dd9a3cf7b6cb1ab834c"},
|
||||||
{file = "rjsmin-1.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5abc686a9ef7eaf208f9ad1fb5fb949556ecb7cc1fee27290eb7f194e01d97bd"},
|
{file = "rjsmin-1.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fcc22001588b84d34bbf2c77afa519894244150c4b0754a6e573298ffac24666"},
|
||||||
{file = "rjsmin-1.2.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:076adcf04c34f712c9427fd9ba6a75bbf7aab975650dfc78cbdd0fbdbe49ca63"},
|
{file = "rjsmin-1.2.3-cp313-cp313-manylinux1_i686.whl", hash = "sha256:624d1a0a35122f3f8955d160a39305cf6f786a5b346ee34c516b391cb153a106"},
|
||||||
{file = "rjsmin-1.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8cb8947ddd250fce58261b0357846cd5d55419419c0f7dfb131dc4b733579a26"},
|
{file = "rjsmin-1.2.3-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:72bd04b7db6190339d8214a5fd289ca31fc1ed30a240f8b0ca13acb9ce3a88af"},
|
||||||
{file = "rjsmin-1.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9069c48b6508b9c5b05435e2c6042c2a0e2f97b35d7b9c27ceaea5fd377ffdc5"},
|
{file = "rjsmin-1.2.3-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:7559f59f4749519b92d72bb68e33b68463f479a82a2a739f1b28a853067aa0e7"},
|
||||||
{file = "rjsmin-1.2.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:02b61cf9b6bc518fdac667f3ca3dab051cb8bd1bf4cba28b6d29153ec27990ad"},
|
{file = "rjsmin-1.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:aa8bdecf278f754d1a133ab51119a63a4d38500557912bb0930ae0fd61437ec6"},
|
||||||
{file = "rjsmin-1.2.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:09eca8581797244587916e5e07e36c4c86d54a4b7e5c7697484a95b75803515d"},
|
{file = "rjsmin-1.2.3-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:2078acc2d02a005ef122eb330e941462c8c3102cf798ad49f1c5ec18ac714240"},
|
||||||
{file = "rjsmin-1.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c52b9dd45c837f1c5c2e8d40776f9e63257f8dbd5f79b85f648cc70da6c1e4e9"},
|
{file = "rjsmin-1.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fa40584fddb4f1d2236119505f6c2fe2b57a1ebaf6eaee2bb2eaac33d2a4ca73"},
|
||||||
{file = "rjsmin-1.2.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4fe4ce990412c053a6bcd47d55133927e22fd3d100233d73355f60f9053054c5"},
|
{file = "rjsmin-1.2.3-cp313-cp313t-manylinux1_i686.whl", hash = "sha256:bbe5d8340878b38dd4f7b879ed7728f6fc3d7524ad81a5cfbe4eb8ae63951407"},
|
||||||
{file = "rjsmin-1.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:aa883b9363b5134239066060879d5eb422a0d4ccf24ccf871f65a5b34c64926f"},
|
{file = "rjsmin-1.2.3-cp313-cp313t-manylinux1_x86_64.whl", hash = "sha256:c298c93f5633cf894325907cf49fc7fb010c0f75dc9cda90b0fc1684ad19e5a3"},
|
||||||
{file = "rjsmin-1.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:6f4e95c5ac95b4cbb519917b3aa1d3d92fc6939c371637674c4a42b67b2b3f44"},
|
{file = "rjsmin-1.2.3-cp313-cp313t-manylinux2014_aarch64.whl", hash = "sha256:35f18cffe3f1bf6d96bcfd977199378ebfd641d823b08e235d1e0bb0fbaa5532"},
|
||||||
{file = "rjsmin-1.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ae3cd64e18e62aa330b24dd6f7b9809ce0a694afd1f01fe99c21f9acd1cb0ea6"},
|
{file = "rjsmin-1.2.3-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9aeadf4dd5f941bebf110fe83960a4bafdac176647537819bb7662f5e9a37aaa"},
|
||||||
{file = "rjsmin-1.2.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7999d797fcf805844d2d91598651785497249f592f31674da0964e794b3be019"},
|
{file = "rjsmin-1.2.3-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c3219e6e22897b31c8598cb412ed56bc12a722c1d4f88a71710c16efe8c07d0c"},
|
||||||
{file = "rjsmin-1.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e733fea039a7b5ad7c06cc8bf215ee7afac81d462e273b3ab55c1ccc906cf127"},
|
{file = "rjsmin-1.2.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:bceccb06b118be890fe735fc09ee256851f4993708cb3647f6c71dd0151cce89"},
|
||||||
{file = "rjsmin-1.2.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ccca74461bd53a99ff3304fcf299ea861df89846be3207329cb82d717ce47ea6"},
|
{file = "rjsmin-1.2.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f3620271f00b8ba3c7c5134ca1d99cde5fd1bf1e84aa96aa65c177ee634122f7"},
|
||||||
{file = "rjsmin-1.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:88f59ad24f91bf9c25d5c2ca3c84a72eed0028f57a98e3b85a915ece5c25be1e"},
|
{file = "rjsmin-1.2.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f3d86f70fcca5f68b65eabbce365d07d80404ecd6aa9c55ba9e9f1042a3514c7"},
|
||||||
{file = "rjsmin-1.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7a8b56fbd64adcc4402637f0e07b90b441e9981d720a10eb6265118018b42682"},
|
{file = "rjsmin-1.2.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1dae9230eae6d7eb2820a511cb640ca6f2e5b91ff78805d71332e8a65a898ea1"},
|
||||||
{file = "rjsmin-1.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2c24686cfdf86e55692183f7867e72c9e982add479c244eda7b8390f96db2c6c"},
|
{file = "rjsmin-1.2.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b788c3ec9d68d8fda2240eb7831bdfb2cc0c88d5fb38c9ed6e0fd090eb5d1490"},
|
||||||
{file = "rjsmin-1.2.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6c0d9f9ea8d9cd48cbcdc74a1c2e85d4d588af12bb8f0b672070ae7c9b6e6306"},
|
{file = "rjsmin-1.2.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4763efbfad7fbf3240a33f08f64991bf0db07453caf283eea51ade84053e9bb7"},
|
||||||
{file = "rjsmin-1.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:27abd32c9f5b6e0c0a3bcad43e8e24108c6d6c13a4e6c50c97497ea2b4614bb4"},
|
{file = "rjsmin-1.2.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e1379e448da75e2426205c756e79d7b9ba1b7ed616fe97122d72c3fe054e8cac"},
|
||||||
{file = "rjsmin-1.2.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:e0e009f6f8460901f5144b34ac2948f94af2f9b8c9b5425da705dbc8152c36c2"},
|
{file = "rjsmin-1.2.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:27e134f4d91a5986cba6dced5cb539947a3ec61544ab5ef31b74b384ddc03931"},
|
||||||
{file = "rjsmin-1.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:41e6013cb37a5b3563c19aa35f8e659fa536aa4197a0e3b6a57a381638294a15"},
|
{file = "rjsmin-1.2.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2674fcad70d0fab4c1c71e4ac1d4d67935f67e6ecc3924de0dd1264c80a9f9a2"},
|
||||||
{file = "rjsmin-1.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:62cbd38c9f5090f0a6378a45c415b4f96ae871216cedab0dfa21965620c0be4c"},
|
{file = "rjsmin-1.2.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b1f2540bd0ce7eda326df7b3bfa360f6edd526bfcb959b5d136afdbccddf0765"},
|
||||||
{file = "rjsmin-1.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2fd5254d36f10a17564b63e8bf9ac579c7b5f211364e11e9753ff5b562843c67"},
|
{file = "rjsmin-1.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:23f3b4adde995a0d0b7835558840dd4673adf99d2473b6d40474d30801d6c57b"},
|
||||||
{file = "rjsmin-1.2.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6cf0309d001a0d45d731dbaab1afd0c23d135c9e029fe56c935c1798094686fc"},
|
{file = "rjsmin-1.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c5fb0574eb541d374a2751e9c0ae019fdd86c9e3eb2e7cf893756886e7b3923f"},
|
||||||
{file = "rjsmin-1.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfbe333dab8d23f0a71da90e2d8e8b762a739cbd55a6f948b2dfda089b6d5853"},
|
{file = "rjsmin-1.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18d6a3229d1ed511a0b0a9a7271ef58ff3b02ba408b92b426857b33b137e7f15"},
|
||||||
{file = "rjsmin-1.2.2.tar.gz", hash = "sha256:8c1bcd821143fecf23242012b55e13610840a839cd467b358f16359010d62dae"},
|
{file = "rjsmin-1.2.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7fe1181452fca7713f377cb6c43cd139638a9edc8c8c29c67119626df164b317"},
|
||||||
|
{file = "rjsmin-1.2.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:57708a4e637aac571c578424a7092d3ec64afb1eabbb73e0c71659457eac9ee4"},
|
||||||
|
{file = "rjsmin-1.2.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:4c1b5a888d43063a22e2e2c2b4db4d6139dfa6e0d2903ae9bb050ed63a340f40"},
|
||||||
|
{file = "rjsmin-1.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e4ac3f85df88d636a9680432fbbf5d3fe1f171821688106a6710738f06575fc2"},
|
||||||
|
{file = "rjsmin-1.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9dff6b14f92ca7a9f6fbf13548358715e47c5e69576aa5dd8b0ad5048fdc967f"},
|
||||||
|
{file = "rjsmin-1.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07c4f1efbbbcd16a645ada1f012595f3eb3e5d5933395effb6104d3731de2d96"},
|
||||||
|
{file = "rjsmin-1.2.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:37a73f6ff49dd8c662399575a249a2a028d098c1fa940c6e88aa9082beb44eca"},
|
||||||
|
{file = "rjsmin-1.2.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:73357ec91465cf69173d637ccde7b46ed3a8001161c9650325fa305a486e89a3"},
|
||||||
|
{file = "rjsmin-1.2.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:236c792fbe18c3b18d4e0ad5ff1b1145f1fbe02126aee9f21bca757b00b63b7e"},
|
||||||
|
{file = "rjsmin-1.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a630a3131a4e63e10665a0ea7cfe0784a3e1e1c854edf79a8ac0654e3756648"},
|
||||||
|
{file = "rjsmin-1.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a1c98f60ca57adbae023cf989eec91d052f0601df63ddc52a0a48303b21a7f9e"},
|
||||||
|
{file = "rjsmin-1.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:32a0174efac83ac72a681edcb9acf5e1c87c5b6aae65ed3424468b5945a90f9d"},
|
||||||
|
{file = "rjsmin-1.2.3.tar.gz", hash = "sha256:1388b52493a4c04fbc970a2d757c301fa05a3c37640314c2ce9dfc8d8a730cc6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2679,4 +2691,4 @@ filelock = ">=3.4"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "730b020b335ea67342069c40591f6959f1e5f01eef40806c95777def2f39eb37"
|
content-hash = "cb47f6409e629d8369a19d82f44a57dbe9414c79e6e72bd88a6bcb34d78f0bc0"
|
||||||
|
@ -65,7 +65,7 @@ optional = true
|
|||||||
# deps used for development purposes, but unneeded in prod
|
# deps used for development purposes, but unneeded in prod
|
||||||
django-debug-toolbar = "^4.4.6"
|
django-debug-toolbar = "^4.4.6"
|
||||||
ipython = "^8.26.0"
|
ipython = "^8.26.0"
|
||||||
pre-commit = "^3.8.0"
|
pre-commit = "^4.0.1"
|
||||||
ruff = "^0.6.9" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml
|
ruff = "^0.6.9" # Version used in pipeline is controlled by pre-commit hooks in .pre-commit.config.yaml
|
||||||
djhtml = "^3.0.6"
|
djhtml = "^3.0.6"
|
||||||
faker = "^30.3.0"
|
faker = "^30.3.0"
|
||||||
@ -77,14 +77,14 @@ freezegun = "^1.5.1" # used to test time-dependent code
|
|||||||
pytest = "^8.3.2"
|
pytest = "^8.3.2"
|
||||||
pytest-cov = "^5.0.0"
|
pytest-cov = "^5.0.0"
|
||||||
pytest-django = "^4.9.0"
|
pytest-django = "^4.9.0"
|
||||||
model-bakery = "^1.19.5"
|
model-bakery = "^1.20.0"
|
||||||
|
|
||||||
[tool.poetry.group.docs.dependencies]
|
[tool.poetry.group.docs.dependencies]
|
||||||
# deps used to work on the documentation
|
# deps used to work on the documentation
|
||||||
mkdocs = "^1.6.1"
|
mkdocs = "^1.6.1"
|
||||||
mkdocs-material = "^9.5.39"
|
mkdocs-material = "^9.5.40"
|
||||||
mkdocstrings = "^0.26.1"
|
mkdocstrings = "^0.26.2"
|
||||||
mkdocstrings-python = "^1.11.1"
|
mkdocstrings-python = "^1.12.0"
|
||||||
mkdocs-include-markdown-plugin = "^6.2.2"
|
mkdocs-include-markdown-plugin = "^6.2.2"
|
||||||
|
|
||||||
[tool.poetry.group.docs]
|
[tool.poetry.group.docs]
|
||||||
@ -101,17 +101,30 @@ select = [
|
|||||||
"A", # shadowing of Python builtins
|
"A", # shadowing of Python builtins
|
||||||
"B",
|
"B",
|
||||||
"C4", # use comprehensions when possible
|
"C4", # use comprehensions when possible
|
||||||
"I", # isort
|
|
||||||
"DJ", # django-specific rules,
|
"DJ", # django-specific rules,
|
||||||
"F401", # unused import
|
"E", # pycodestyle (https://docs.astral.sh/ruff/rules/#pycodestyle-e-w)
|
||||||
|
"ERA", # commented code
|
||||||
|
"F", # pyflakes (https://docs.astral.sh/ruff/rules/#pyflakes-f)
|
||||||
"FBT", # boolean trap
|
"FBT", # boolean trap
|
||||||
|
"FLY", # f-string instead of str.join
|
||||||
|
"FURB", # https://docs.astral.sh/ruff/rules/#refurb-furb
|
||||||
|
"I", # isort
|
||||||
|
"INT", # gettext
|
||||||
|
"PERF", # performance
|
||||||
|
"PLW", # pylint warnings (https://docs.astral.sh/ruff/rules/#pylint-pl)
|
||||||
|
"RUF", # Ruff specific rules
|
||||||
|
"SIM", # simplify (https://docs.astral.sh/ruff/rules/#flake8-simplify-sim)
|
||||||
|
"T100", # breakpoint()
|
||||||
|
"T2", # print statements
|
||||||
|
"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
|
||||||
"T2", # print statements
|
|
||||||
]
|
]
|
||||||
|
|
||||||
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
|
||||||
|
"RUF012" # mutable class attributes. This rule doesn't integrate well with django
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.pydocstyle]
|
[tool.ruff.lint.pydocstyle]
|
||||||
|
@ -44,8 +44,8 @@ class Command(BaseCommand):
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
confirm = input(
|
confirm = input(
|
||||||
"User selected: %s\nDo you really want to delete all message from this user ? [y/N] "
|
"User selected: %s\nDo you really want "
|
||||||
% (user,)
|
"to delete all message from this user ? [y/N] " % (user,)
|
||||||
)
|
)
|
||||||
|
|
||||||
if not confirm.lower().startswith("y"):
|
if not confirm.lower().startswith("y"):
|
||||||
|
@ -66,11 +66,11 @@ class TestMergeUser(TestCase):
|
|||||||
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
||||||
# fields of to_delete should be assigned to to_keep
|
# fields of to_delete should be assigned to to_keep
|
||||||
# if they were not set beforehand
|
# if they were not set beforehand
|
||||||
assert "Biggus" == self.to_keep.first_name
|
assert self.to_keep.first_name == "Biggus"
|
||||||
assert "Dickus" == self.to_keep.last_name
|
assert self.to_keep.last_name == "Dickus"
|
||||||
assert "B'ian" == self.to_keep.nick_name
|
assert self.to_keep.nick_name == "B'ian"
|
||||||
assert "Jerusalem" == self.to_keep.address
|
assert self.to_keep.address == "Jerusalem"
|
||||||
assert "Rome" == self.to_keep.parent_address
|
assert self.to_keep.parent_address == "Rome"
|
||||||
assert self.to_keep.groups.count() == 3
|
assert self.to_keep.groups.count() == 3
|
||||||
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
|
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
|
||||||
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
|
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
|
||||||
|
@ -24,7 +24,11 @@
|
|||||||
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from rootplace.views import *
|
from rootplace.views import (
|
||||||
|
DeleteAllForumUserMessagesView,
|
||||||
|
MergeUsersView,
|
||||||
|
OperationLogListView,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("merge/", MergeUsersView.as_view(), name="merge"),
|
path("merge/", MergeUsersView.as_view(), name="merge"),
|
||||||
|
@ -48,7 +48,8 @@ def __merge_subscriptions(u1: User, u2: User):
|
|||||||
Some examples :
|
Some examples :
|
||||||
- if u1 is not subscribed, his subscription end date become the one of u2
|
- if u1 is not subscribed, his subscription end date become the one of u2
|
||||||
- if u1 is subscribed but not u2, nothing happen
|
- if u1 is subscribed but not u2, nothing happen
|
||||||
- if u1 is subscribed for, let's say, 2 remaining months and u2 is subscribed for 3 remaining months,
|
- if u1 is subscribed for, let's say,
|
||||||
|
2 remaining months and u2 is subscribed for 3 remaining months,
|
||||||
he shall then be subscribed for 5 months
|
he shall then be subscribed for 5 months
|
||||||
"""
|
"""
|
||||||
last_subscription = (
|
last_subscription = (
|
||||||
|
15
sas/admin.py
15
sas/admin.py
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from sas.models import Album, PeoplePictureRelation, Picture
|
from sas.models import Album, PeoplePictureRelation, Picture, PictureModerationRequest
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Picture)
|
@admin.register(Picture)
|
||||||
@ -31,4 +31,15 @@ class PeoplePictureRelationAdmin(admin.ModelAdmin):
|
|||||||
autocomplete_fields = ("picture", "user")
|
autocomplete_fields = ("picture", "user")
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Album)
|
@admin.register(Album)
|
||||||
|
class AlbumAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("name", "parent", "date", "owner", "is_moderated")
|
||||||
|
search_fields = ("name",)
|
||||||
|
autocomplete_fields = ("owner", "parent", "edit_groups", "view_groups")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(PictureModerationRequest)
|
||||||
|
class PictureModerationRequestAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("author", "picture", "created_at")
|
||||||
|
search_fields = ("author", "picture")
|
||||||
|
autocomplete_fields = ("author", "picture")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user