Add more Ruff rules (#891)

* ruff: apply rule F

* ruff: apply rule E

* ruff: apply rule SIM

* ruff: apply rule TCH

* ruff: apply rule ERA

* ruff: apply rule PLW

* ruff: apply rule FLY

* ruff: apply rule PERF

* ruff: apply rules FURB & RUF
This commit is contained in:
thomas girod 2024-10-15 11:36:26 +02:00 committed by GitHub
parent d114b01bcc
commit d16a207a83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
82 changed files with 836 additions and 748 deletions

View File

@ -15,7 +15,16 @@
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(ClubAccount)

View File

@ -82,9 +82,7 @@ class Company(models.Model):
def is_owned_by(self, 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 True
return False
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
def can_be_edited_by(self, 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):
return True
m = self.club.get_membership_for(user)
if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True
return False
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
class ClubAccount(models.Model):
@ -161,29 +157,20 @@ class ClubAccount(models.Model):
"""Check if that object can be edited by the given user."""
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
def can_be_edited_by(self, user):
"""Check if that object can be edited by the given user."""
m = self.club.get_membership_for(user)
if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True
return False
return m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
def can_be_viewed_by(self, user):
"""Check if that object can be viewed by the given user."""
m = self.club.get_membership_for(user)
if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]:
return True
return False
return m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
def has_open_journal(self):
for j in self.journals.all():
if not j.closed:
return True
return False
return self.journals.filter(closed=False).exists()
def get_open_journal(self):
return self.journals.filter(closed=False).first()
@ -228,17 +215,13 @@ class GeneralJournal(models.Model):
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
if self.club_account.can_be_edited_by(user):
return True
return False
return self.club_account.can_be_edited_by(user)
def can_be_edited_by(self, 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 True
if self.club_account.can_be_edited_by(user):
return True
return False
return self.club_account.can_be_edited_by(user)
def can_be_viewed_by(self, user):
return self.club_account.can_be_viewed_by(user)
@ -416,9 +399,7 @@ class Operation(models.Model):
if self.journal.closed:
return False
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 True
return False
return m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]
def can_be_edited_by(self, user):
"""Check if that object can be edited by the given user."""
@ -427,9 +408,7 @@ class Operation(models.Model):
if self.journal.closed:
return False
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 True
return False
return m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]
class AccountingType(models.Model):
@ -472,9 +451,7 @@ class AccountingType(models.Model):
"""Check if that object can be edited by the given user."""
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
class SimplifiedAccountingType(models.Model):

View File

@ -102,7 +102,7 @@ class TestOperation(TestCase):
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
)
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.op1 = Operation(
journal=self.journal,
@ -111,7 +111,7 @@ class TestOperation(TestCase):
remark="Test bilan",
mode="CASH",
done=True,
label=l,
label=label,
accounting_type=at,
target_type="USER",
target_id=self.skia.id,
@ -124,7 +124,7 @@ class TestOperation(TestCase):
remark="Test bilan",
mode="CASH",
done=True,
label=l,
label=label,
accounting_type=at,
target_type="USER",
target_id=self.skia.id,

View File

@ -15,7 +15,41 @@
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 = [
# Accounting types

View File

@ -182,7 +182,7 @@ class ClubAccountCreateView(CanCreateMixin, CreateView):
def get_initial(self):
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()
if obj is not None:
ret["bank_account"] = obj.id
@ -264,7 +264,7 @@ class JournalCreateView(CanCreateMixin, CreateView):
def get_initial(self):
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()
if obj is not None:
ret["club_account"] = obj.id
@ -362,7 +362,7 @@ class OperationForm(forms.ModelForm):
def clean(self):
self.cleaned_data = super().clean()
if "target_type" in self.cleaned_data.keys():
if "target_type" in self.cleaned_data:
if (
self.cleaned_data.get("user") is None
and self.cleaned_data.get("club") is None
@ -633,19 +633,17 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
ret = collections.OrderedDict()
statement = collections.OrderedDict()
total_sum = 0
for sat in [None] + list(
SimplifiedAccountingType.objects.order_by("label").all()
):
for sat in [
None,
*list(SimplifiedAccountingType.objects.order_by("label")),
]:
amount = queryset.filter(
accounting_type__movement_type=movement_type, simpleaccounting_type=sat
).aggregate(amount_sum=Sum("amount"))["amount_sum"]
if sat:
sat = sat.label
else:
sat = ""
label = sat.label if sat is not None else ""
if amount:
total_sum += amount
statement[sat] = amount
statement[label] = amount
ret[movement_type] = statement
ret[movement_type + "_sum"] = total_sum
return ret
@ -668,15 +666,12 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView):
self.statement(self.object.operations.filter(label=None).all(), "DEBIT")
)
statement[_("No label operations")] = no_label_statement
for l in labels:
for label in labels:
l_stmt = collections.OrderedDict()
l_stmt.update(
self.statement(self.object.operations.filter(label=l).all(), "CREDIT")
)
l_stmt.update(
self.statement(self.object.operations.filter(label=l).all(), "DEBIT")
)
statement[l] = l_stmt
journals = self.object.operations.filter(label=label).all()
l_stmt.update(self.statement(journals, "CREDIT"))
l_stmt.update(self.statement(journals, "DEBIT"))
statement[label] = l_stmt
return statement
def get_context_data(self, **kwargs):
@ -798,7 +793,7 @@ class LabelCreateView(
def get_initial(self):
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()
if obj is not None:
ret["club_account"] = obj.id

View File

@ -111,8 +111,8 @@ class MailingForm(forms.Form):
"""Convert given users into real users and check their validity."""
cleaned_data = super().clean()
users = []
for user in cleaned_data["subscription_users"]:
user = User.objects.filter(id=user).first()
for user_id in cleaned_data["subscription_users"]:
user = User.objects.filter(id=user_id).first()
if not user:
raise forms.ValidationError(
_("One of the selected users doesn't exist"), code="invalid"
@ -128,7 +128,7 @@ class MailingForm(forms.Form):
def clean(self):
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
raise forms.ValidationError(_("An action is required"), code="invalid")

View File

@ -389,9 +389,7 @@ class Membership(models.Model):
if user.is_root or user.is_board_member:
return True
membership = self.club.get_membership_for(user)
if membership is not None and membership.role >= self.role:
return True
return False
return membership is not None and membership.role >= self.role
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)

View File

@ -24,7 +24,32 @@
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 = [
path("", ClubListView.as_view(), name="club_list"),
@ -32,32 +57,20 @@ urlpatterns = [
path("stats/", ClubStatView.as_view(), name="club_stats"),
path("<int:club_id>/", ClubView.as_view(), name="club_view"),
path(
"<int:club_id>/rev/<int:rev_id>/",
ClubRevView.as_view(),
name="club_view_rev",
"<int:club_id>/rev/<int:rev_id>/", ClubRevView.as_view(), name="club_view_rev"
),
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/page/",
ClubPageEditView.as_view(),
name="club_edit_page",
),
path("<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>/elderlies/",
ClubOldMembersView.as_view(),
name="club_old_members",
),
path("<int:club_id>/sellings/", ClubSellingView.as_view(), name="club_sellings"),
path(
"<int:club_id>/sellings/",
ClubSellingView.as_view(),
name="club_sellings",
),
path(
"<int:club_id>/sellings/csv/",
ClubSellingCSVView.as_view(),
name="sellings_csv",
"<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>/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/create/",
PosterCreateView.as_view(),
name="poster_create",
"<int:club_id>/poster/create/", PosterCreateView.as_view(), name="poster_create"
),
path(
"<int:club_id>/poster/<int:poster_id>/edit/",

View File

@ -397,7 +397,8 @@ class ClubSellingCSVView(ClubSellingView):
row.append(selling.customer.user.get_display_name())
else:
row.append("")
row = row + [
row = [
*row,
selling.label,
selling.quantity,
selling.quantity * selling.unit_price,
@ -408,7 +409,7 @@ class ClubSellingCSVView(ClubSellingView):
row.append(selling.product.purchase_price)
row.append(selling.product.selling_price - selling.product.purchase_price)
else:
row = row + ["", "", ""]
row = [*row, "", "", ""]
return row
def get(self, request, *args, **kwargs):
@ -622,9 +623,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
def remove_subscription(self, cleaned_data):
"""Remove specified users from a mailing list."""
fields = [
cleaned_data[key]
for key in cleaned_data.keys()
if key.startswith("removal_")
val for key, val in cleaned_data.items() if key.startswith("removal_")
]
for field in fields:
for sub in field:

View File

@ -15,7 +15,7 @@
from django.contrib import admin
from haystack.admin import SearchModelAdmin
from com.models import *
from com.models import News, Poster, Screen, Sith, Weekmail
@admin.register(News)

View File

@ -16,7 +16,36 @@
from django.urls import path
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 = [
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/admin/", NewsAdminListView.as_view(), name="news_admin_list"),
path("news/create/", NewsCreateView.as_view(), name="news_new"),
path("news/<int:news_id>/delete/", NewsDeleteView.as_view(), name="news_delete"),
path(
"news/<int:news_id>/delete/",
NewsDeleteView.as_view(),
name="news_delete",
),
path(
"news/<int:news_id>/moderate/",
NewsModerateView.as_view(),
name="news_moderate",
"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>/", NewsDetailView.as_view(), name="news_detail"),
@ -71,11 +94,7 @@ urlpatterns = [
),
path("poster/", PosterListView.as_view(), name="poster_list"),
path("poster/create/", PosterCreateView.as_view(), name="poster_create"),
path(
"poster/<int:poster_id>/edit/",
PosterEditView.as_view(),
name="poster_edit",
),
path("poster/<int:poster_id>/edit/", PosterEditView.as_view(), name="poster_edit"),
path(
"poster/<int:poster_id>/delete/",
PosterDeleteView.as_view(),
@ -98,11 +117,7 @@ urlpatterns = [
ScreenSlideshowView.as_view(),
name="screen_slideshow",
),
path(
"screen/<int:screen_id>/edit/",
ScreenEditView.as_view(),
name="screen_edit",
),
path("screen/<int:screen_id>/edit/", ScreenEditView.as_view(), name="screen_edit"),
path(
"screen/<int:screen_id>/delete/",
ScreenDeleteView.as_view(),

View File

@ -86,12 +86,11 @@ class PosterForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
super().__init__(*args, **kwargs)
if self.user:
if not self.user.is_com_admin:
self.fields["club"].queryset = Club.objects.filter(
id__in=self.user.clubs_with_rights
)
self.fields.pop("display_time")
if self.user and not self.user.is_com_admin:
self.fields["club"].queryset = Club.objects.filter(
id__in=self.user.clubs_with_rights
)
self.fields.pop("display_time")
class ComTabsMixin(TabedViewMixin):
@ -312,7 +311,7 @@ class NewsCreateView(CanCreateMixin, CreateView):
def post(self, request, *args, **kwargs):
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)
else:
self.object = form.instance
@ -354,13 +353,13 @@ class NewsModerateView(CanEditMixin, SingleObjectMixin):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if "remove" in request.GET.keys():
if "remove" in request.GET:
self.object.is_moderated = False
else:
self.object.is_moderated = True
self.object.moderator = request.user
self.object.save()
if "next" in self.request.GET.keys():
if "next" in self.request.GET:
return redirect(self.request.GET["next"])
return redirect("com:news_admin_list")
@ -424,7 +423,7 @@ class WeekmailPreviewView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, Detai
try:
self.object.send() # This should fail
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:
u.preferences.receive_weekmail = False
u.preferences.save()
@ -471,7 +470,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if "up_article" in request.GET.keys():
if "up_article" in request.GET:
art = get_object_or_404(
WeekmailArticle, id=request.GET["up_article"], weekmail=self.object
)
@ -483,7 +482,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
art.save()
prev_art.save()
self.quick_notif_list += ["qn_success"]
if "down_article" in request.GET.keys():
if "down_article" in request.GET:
art = get_object_or_404(
WeekmailArticle, id=request.GET["down_article"], weekmail=self.object
)
@ -495,7 +494,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
art.save()
next_art.save()
self.quick_notif_list += ["qn_success"]
if "add_article" in request.GET.keys():
if "add_article" in request.GET:
art = get_object_or_404(
WeekmailArticle, id=request.GET["add_article"], weekmail=None
)
@ -504,7 +503,7 @@ class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateVi
art.rank += 1
art.save()
self.quick_notif_list += ["qn_success"]
if "del_article" in request.GET.keys():
if "del_article" in request.GET:
art = get_object_or_404(
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)
else:
return self.form_invalid(form)
@ -689,19 +688,13 @@ class PosterEditBaseView(UpdateView):
template_name = "com/poster_edit.jinja"
def get_initial(self):
init = {}
try:
init["date_begin"] = self.object.date_begin.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
return {
"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"),
}
def dispatch(self, request, *args, **kwargs):
if "club_id" in kwargs and kwargs["club_id"]:
if kwargs.get("club_id"):
try:
self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist as e:
@ -737,7 +730,7 @@ class PosterDeleteBaseView(DeleteView):
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
if "club_id" in kwargs and kwargs["club_id"]:
if kwargs.get("club_id"):
try:
self.club = Club.objects.get(pk=kwargs["club_id"])
except Club.DoesNotExist as e:

View File

@ -67,5 +67,6 @@ class Command(BaseCommand):
subprocess.run(
[str(Path(__file__).parent / "install_xapian.sh"), desired],
env=dict(os.environ),
).check_returncode()
check=True,
)
self.stdout.write("Installation success")

View File

@ -934,7 +934,7 @@ Welcome to the wiki page!
# Adding subscription for sli
s = Subscription(
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],
)
s.subscription_start = s.compute_start()
@ -947,7 +947,7 @@ Welcome to the wiki page!
# Adding subscription for Krophil
s = Subscription(
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],
)
s.subscription_start = s.compute_start()

View File

@ -217,9 +217,9 @@ class Command(BaseCommand):
UV.objects.bulk_create(uvs, ignore_conflicts=True)
def create_products(self):
categories = []
for _ in range(10):
categories.append(ProductType(name=self.faker.text(max_nb_chars=30)))
categories = [
ProductType(name=self.faker.text(max_nb_chars=30)) for _ in range(10)
]
ProductType.objects.bulk_create(categories)
categories = list(
ProductType.objects.filter(name__in=[c.name for c in categories])
@ -254,16 +254,16 @@ class Command(BaseCommand):
archived=bool(random.random() > 0.7),
)
products.append(product)
for group in random.sample(groups, k=random.randint(0, 3)):
# there will be products without buying groups
# but there are also such products in the real database
buying_groups.append(
Product.buying_groups.through(product=product, group=group)
)
for counter in random.sample(counters, random.randint(0, 4)):
selling_places.append(
Counter.products.through(counter=counter, product=product)
)
# there will be products without buying groups
# but there are also such products in the real database
buying_groups.extend(
Product.buying_groups.through(product=product, group=group)
for group in random.sample(groups, k=random.randint(0, 3))
)
selling_places.extend(
Counter.products.through(counter=counter, product=product)
for counter in random.sample(counters, random.randint(0, 4))
)
Product.objects.bulk_create(products)
Product.buying_groups.through.objects.bulk_create(buying_groups)
Counter.products.through.objects.bulk_create(selling_places)

View File

@ -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.
Either one of the two must be set.
@ -445,7 +445,7 @@ class User(AbstractBaseUser):
else:
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.
Either a group id or a group name must be provided.
If both are passed, only the id will be considered.
@ -649,7 +649,7 @@ class User(AbstractBaseUser):
continue
links = list(User.godfathers.through.objects.filter(**{key: self.id}))
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]
links = list(
User.godfathers.through.objects.filter(
@ -703,9 +703,7 @@ class User(AbstractBaseUser):
return True
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
return True
if self.is_root:
return True
return False
return self.is_root
def can_edit(self, obj):
"""Determine if the object can be edited by the user."""
@ -717,9 +715,7 @@ class User(AbstractBaseUser):
return True
if isinstance(obj, User) and obj == self:
return True
if self.is_owner(obj):
return True
return False
return self.is_owner(obj)
def can_view(self, obj):
"""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):
if self.is_in_group(pk=pk):
return True
if self.can_edit(obj):
return True
return False
return self.can_edit(obj)
def can_be_edited_by(self, user):
return user.is_root or user.is_board_member
@ -759,23 +753,17 @@ class User(AbstractBaseUser):
@cached_property
def preferences(self):
try:
if hasattr(self, "_preferences"):
return self._preferences
except:
prefs = Preferences(user=self)
prefs.save()
return prefs
return Preferences.objects.create(user=self)
@cached_property
def forum_infos(self):
try:
if hasattr(self, "_forum_infos"):
return self._forum_infos
except:
from forum.models import ForumUserInfo
from forum.models import ForumUserInfo
infos = ForumUserInfo(user=self)
infos.save()
return infos
return ForumUserInfo.objects.create(user=self)
@cached_property
def clubs_with_rights(self) -> list[Club]:
@ -840,7 +828,7 @@ class AnonymousUser(AuthAnonymousUser):
def favorite_topics(self):
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."""
allowed_id = settings.SITH_GROUP_PUBLIC_ID
if pk is not None:
@ -867,9 +855,7 @@ class AnonymousUser(AuthAnonymousUser):
and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists()
):
return True
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
return True
return False
return hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self)
def get_display_name(self):
return _("Visitor")
@ -1070,7 +1056,7 @@ class SithFile(models.Model):
]:
self.file.delete()
self.file = None
except:
except: # noqa E722 I don't know the exception that can be raised
self.file = None
self.mime_type = "inode/directory"
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()
def get_parent_list(self):
l = []
p = self.parent
while p is not None:
l.append(p)
p = p.parent
return l
parents = []
current = self.parent
while current is not None:
parents.append(current)
current = current.parent
return parents
def get_parent_path(self):
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):
# Override normal behavior for clubs
return True
if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member:
return True
return False
return self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member
def can_be_viewed_by(self, user):
if self.is_club_page:
return True
return False
return self.is_club_page
def get_parent_list(self):
l = []
p = self.parent
while p is not None:
l.append(p)
p = p.parent
return l
parents = []
current = self.parent
while current is not None:
parents.append(current)
current = current.parent
return parents
def is_locked(self):
"""Is True if the page is locked, False otherwise.
@ -1386,7 +1368,6 @@ class Page(models.Model):
if self.lock_timeout and (
timezone.now() - self.lock_timeout > timedelta(minutes=5)
):
# print("Lock timed out")
self.unset_lock()
return (
self.lock_user
@ -1401,7 +1382,6 @@ class Page(models.Model):
self.lock_user = user
self.lock_timeout = timezone.now()
super().save()
# print("Locking page")
def set_lock_recursive(self, user):
"""Locks recursively all the child pages for editing properties."""
@ -1420,7 +1400,6 @@ class Page(models.Model):
self.lock_user = None
self.lock_timeout = None
super().save()
# print("Unlocking page")
def get_lock(self):
"""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:
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):
try:
return self.revisions.last().title
except:
return self.name
rev = self.revisions.last()
return rev.title if rev is not None else self.name
@cached_property
def is_club_page(self):

View File

@ -50,7 +50,7 @@ def phonenumber(
try:
parsed = phonenumbers.parse(value, country)
return phonenumbers.format_number(parsed, number_format)
except phonenumbers.NumberParseException as e:
except phonenumbers.NumberParseException:
return value

View File

@ -343,7 +343,7 @@ class TestUserTools:
response = client.get(reverse("core:user_tools"))
assertRedirects(
response,
expected_url=f"/login?next=%2Fuser%2Ftools%2F",
expected_url="/login?next=%2Fuser%2Ftools%2F",
target_status_code=301,
)

View File

@ -73,7 +73,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user)
response = self.client.get(
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 [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)
response = self.client.get(
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 [u["id"] for u in response.json()["users"]] == [
@ -126,7 +126,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user)
response = self.client.get(
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 [u["id"] for u in response.json()["users"]] == [
@ -150,7 +150,7 @@ class TestFetchFamilyApi(TestCase):
self.client.force_login(self.main_user)
response = self.client.get(
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 [u["id"] for u in response.json()["users"]] == [

View File

@ -29,13 +29,67 @@ from core.converters import (
FourDigitYearConverter,
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(TwoDigitMonthConverter, "mm")
register_converter(BooleanStringConverter, "bool")
urlpatterns = [
path("", index, name="index"),
path("notifications/", NotificationList.as_view(), name="notification_list"),
@ -80,27 +134,17 @@ urlpatterns = [
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>/delete/",
GroupDeleteView.as_view(),
name="group_delete",
"group/<int:group_id>/delete/", GroupDeleteView.as_view(), name="group_delete"
),
path(
"group/<int:group_id>/detail/",
GroupTemplateView.as_view(),
name="group_detail",
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
),
# User views
path("user/", UserListView.as_view(), name="user_list"),
path(
"user/<int:user_id>/mini/",
UserMiniView.as_view(),
name="user_profile_mini",
),
path("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>/pictures/",
UserPicturesView.as_view(),
name="user_pictures",
"user/<int:user_id>/pictures/", UserPicturesView.as_view(), name="user_pictures"
),
path(
"user/<int:user_id>/godfathers/",
@ -117,28 +161,14 @@ urlpatterns = [
delete_user_godfather,
name="user_godfathers_delete",
),
path(
"user/<int:user_id>/edit/",
UserUpdateProfileView.as_view(),
name="user_edit",
),
path("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>/prefs/", UserPreferencesView.as_view(), name="user_prefs"),
path(
"user/<int:user_id>/prefs/",
UserPreferencesView.as_view(),
name="user_prefs",
),
path(
"user/<int:user_id>/groups/",
UserUpdateGroupView.as_view(),
name="user_groups",
"user/<int:user_id>/groups/", UserUpdateGroupView.as_view(), name="user_groups"
),
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
path(
"user/<int:user_id>/account/",
UserAccountView.as_view(),
name="user_account",
),
path("user/<int:user_id>/account/", UserAccountView.as_view(), name="user_account"),
path(
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
UserAccountDetailView.as_view(),
@ -179,42 +209,18 @@ urlpatterns = [
),
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
path(
"file/<int:file_id>/moderate/",
FileModerateView.as_view(),
name="file_moderate",
"file/<int:file_id>/moderate/", FileModerateView.as_view(), name="file_moderate"
),
path("file/<int:file_id>/download/", send_file, name="download"),
# Page views
path("page/", PageListView.as_view(), name="page_list"),
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(
"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(
"page/<path:page_name>/rev/<int:rev>/",
PageRevView.as_view(),
name="page_rev",
),
path(
"page/<path:page_name>/",
PageView.as_view(),
name="page",
"page/<path:page_name>/rev/<int:rev>/", PageRevView.as_view(), name="page_rev"
),
path("page/<path:page_name>/", PageView.as_view(), name="page"),
]

View File

@ -127,7 +127,7 @@ def resize_image_explicit(
def exif_auto_rotate(image):
for orientation in ExifTags.TAGS.keys():
for orientation in ExifTags.TAGS:
if ExifTags.TAGS[orientation] == "Orientation":
break
exif = dict(image._getexif().items())

View File

@ -25,6 +25,7 @@
import types
from typing import Any
from django.conf import settings
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import (
ImproperlyConfigured,
@ -35,6 +36,7 @@ from django.http import (
HttpResponseNotFound,
HttpResponseServerError,
)
from django.shortcuts import render
from django.utils.functional import cached_property
from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin
@ -79,9 +81,7 @@ def can_edit_prop(obj: Any, user: User) -> bool:
raise PermissionDenied
```
"""
if obj is None or user.is_owner(obj):
return True
return False
return obj is None or user.is_owner(obj)
def can_edit(obj: Any, user: User) -> bool:
@ -232,7 +232,9 @@ class UserIsRootMixin(GenericContentPermissionMixinBuilder):
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):
@ -304,10 +306,10 @@ class QuickNotifMixin:
kwargs["quick_notifs"] = []
for n in self.quick_notif_list:
kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n])
for k, v in settings.SITH_QUICK_NOTIF.items():
for gk in self.request.GET.keys():
if k == gk:
kwargs["quick_notifs"].append(v)
for key, val in settings.SITH_QUICK_NOTIF.items():
for gk in self.request.GET:
if key == gk:
kwargs["quick_notifs"].append(val)
return kwargs
@ -324,8 +326,10 @@ class DetailFormView(SingleObjectMixin, FormView):
return super().get_object()
from .files import *
from .group import *
from .page import *
from .site import *
from .user import *
# F403: those star-imports would be hellish to refactor
# E402: putting those import at the top of the file would also be difficult
from .files import * # noqa: F403 E402
from .group import * # noqa: F403 E402
from .page import * # noqa: F403 E402
from .site import * # noqa: F403 E402
from .user import * # noqa: F403 E402

View File

@ -193,7 +193,7 @@ class FileEditView(CanEditMixin, UpdateView):
def get_form_class(self):
fields = ["name", "is_moderated"]
if self.object.is_file:
fields = ["file"] + fields
fields = ["file", *fields]
return modelform_factory(SithFile, fields=fields)
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
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"):
sf = SithFile.objects.filter(id=f_id).first()
if sf:
sf.delete()
if "clear" in request.POST.keys():
file = SithFile.objects.filter(id=f_id).first()
if file:
file.delete()
if "clear" in request.POST:
request.session["clipboard"] = []
if "cut" in request.POST.keys():
for f_id in request.POST.getlist("file_list"):
f_id = int(f_id)
if "cut" in request.POST:
for f_id_str in request.POST.getlist("file_list"):
f_id = int(f_id_str)
if (
f_id in [c.id for c in obj.children.all()]
and f_id not in request.session["clipboard"]
):
request.session["clipboard"].append(f_id)
if "paste" in request.POST.keys():
if "paste" in request.POST:
for f_id in request.session["clipboard"]:
sf = SithFile.objects.filter(id=f_id).first()
if sf:
sf.move_to(obj)
file = SithFile.objects.filter(id=f_id).first()
if file:
file.move_to(obj)
request.session["clipboard"] = []
request.session.modified = True
def get(self, request, *args, **kwargs):
self.form = self.get_form()
if "clipboard" not in request.session.keys():
if "clipboard" not in request.session:
request.session["clipboard"] = []
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if "clipboard" not in request.session.keys():
if "clipboard" not in request.session:
request.session["clipboard"] = []
if request.user.can_edit(self.object):
# XXX this call can fail!
@ -398,6 +398,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
self.object.is_moderated = True
self.object.moderator = request.user
self.object.save()
if "next" in self.request.GET.keys():
if "next" in self.request.GET:
return redirect(self.request.GET["next"])
return redirect("core:file_moderation")

View File

@ -140,7 +140,7 @@ class SelectUser(TextInput):
class LoginForm(AuthenticationForm):
def __init__(self, *arg, **kwargs):
if "data" in kwargs.keys():
if "data" in kwargs:
from counter.models import Customer
data = kwargs["data"].copy()
@ -157,7 +157,7 @@ class LoginForm(AuthenticationForm):
else:
user = User.objects.filter(username=data["username"]).first()
data["username"] = user.username
except:
except: # noqa E722 I don't know what error is supposed to be raised here
pass
kwargs["data"] = data
super().__init__(*arg, **kwargs)

View File

@ -55,7 +55,7 @@ class PageView(CanViewMixin, DetailView):
def get_context_data(self, **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"]
return context
@ -92,22 +92,16 @@ class PageRevView(CanViewMixin, DetailView):
)
return res
def get_object(self):
def get_object(self, *args, **kwargs):
self.page = Page.get_page_by_full_name(self.kwargs["page_name"])
return self.page
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.page is not None:
context["page"] = self.page
try:
rev = self.page.revisions.get(id=self.kwargs["rev"])
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"]
if not self.page:
return context | {"new_page": self.kwargs["page_name"]}
context["page"] = self.page
context["rev"] = self.page.revisions.filter(id=self.kwargs["rev"]).first()
return context
@ -118,7 +112,7 @@ class PageCreateView(CanCreateMixin, CreateView):
def get_initial(self):
init = {}
if "page" in self.request.GET.keys():
if "page" in self.request.GET:
page_name = self.request.GET["page"]
parent_name = "/".join(page_name.split("/")[:-1])
parent = Page.get_page_by_full_name(parent_name)
@ -145,18 +139,8 @@ class PagePropView(CanEditPagePropMixin, UpdateView):
slug_field = "_full_name"
slug_url_kwarg = "page_name"
def get_object(self):
o = 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
def get_object(self, queryset=None):
self.page = super().get_object()
try:
self.page.set_lock_recursive(self.request.user)
except LockError as e:

View File

@ -53,11 +53,8 @@ class NotificationList(ListView):
if self.request.user.is_anonymous:
return Notification.objects.none()
# TODO: Bulk update in django 2.2
if "see_all" in self.request.GET.keys():
for n in self.request.user.notifications.filter(viewed=False):
n.viewed = True
n.save()
if "see_all" in self.request.GET:
self.request.user.notifications.filter(viewed=False).update(viewed=True)
return self.request.user.notifications.order_by("-date")[:20]

View File

@ -255,8 +255,10 @@ class UserTabsMixin(TabedViewMixin):
"name": _("Groups"),
}
)
try:
if user.customer and (
if (
hasattr(user, "customer")
and user.customer
and (
user == self.request.user
or self.request.user.is_in_group(
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
@ -266,25 +268,22 @@ class UserTabsMixin(TabedViewMixin):
+ settings.SITH_BOARD_SUFFIX
)
or self.request.user.is_root
):
tab_list.append(
{
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
"slug": "stats",
"name": _("Stats"),
}
)
tab_list.append(
{
"url": reverse(
"core:user_account", kwargs={"user_id": user.id}
),
"slug": "account",
"name": _("Account") + " (%s €)" % user.customer.amount,
}
)
except:
pass
)
):
tab_list.append(
{
"url": reverse("core:user_stats", kwargs={"user_id": user.id}),
"slug": "stats",
"name": _("Stats"),
}
)
tab_list.append(
{
"url": reverse("core:user_account", kwargs={"user_id": user.id}),
"slug": "account",
"name": _("Account") + " (%s €)" % user.customer.amount,
}
)
return tab_list

View File

@ -15,7 +15,19 @@
from django.contrib import admin
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)

View File

@ -154,7 +154,7 @@ class Customer(models.Model):
self.save()
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):
@ -287,9 +287,7 @@ class ProductType(models.Model):
"""Method to see if that object can be edited by the given user."""
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
return user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
class Product(models.Model):
@ -346,21 +344,19 @@ class Product(models.Model):
@property
def is_record_product(self):
return settings.SITH_ECOCUP_CONS == self.id
return self.id == settings.SITH_ECOCUP_CONS
@property
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):
"""Method to see if that object can be edited by the given user."""
if user.is_anonymous:
return False
if user.is_in_group(
return user.is_in_group(
pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
return True
return False
) or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
def can_be_sold_to(self, user: User) -> bool:
"""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())
if not buying_groups:
return True
for group in buying_groups:
if user.is_in_group(pk=group.id):
return True
return False
return any(user.is_in_group(pk=group.id) for group in buying_groups)
@property
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."
) % {
"event": event,
"url": "".join(
(
'<a href="',
self.customer.get_full_url(),
'">',
self.customer.get_full_url(),
"</a>",
)
"url": (
f'<a href="{self.customer.get_full_url()}">'
f"{self.customer.get_full_url()}</a>"
),
"eticket": "".join(
(
'<a href="',
self.get_eticket_full_url(),
'">',
self.get_eticket_full_url(),
"</a>",
)
"eticket": (
f'<a href="{self.get_eticket_full_url()}">'
f"{self.get_eticket_full_url()}</a>"
),
}
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,
"url": self.customer.get_full_url(),
@ -919,7 +904,7 @@ class Selling(models.Model):
def get_eticket_full_url(self):
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):
@ -1019,15 +1004,15 @@ class CashRegisterSummary(models.Model):
elif name == "hundred_euros":
return self.items.filter(value=100, is_check=False).first()
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":
return checks[1] if 1 < len(checks) else None
return checks[1] if len(checks) > 1 else None
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":
return checks[3] if 3 < len(checks) else None
return checks[3] if len(checks) > 3 else None
elif name == "check_5":
return checks[4] if 4 < len(checks) else None
return checks[4] if len(checks) > 4 else None
else:
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."""
if user.is_anonymous:
return False
if user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID):
return True
return False
return user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID)
def get_total(self):
t = 0

View File

@ -51,7 +51,7 @@ def write_log(instance, operation_type):
# Return None by default
return None
log = OperationLog(
OperationLog(
label=str(instance),
operator=get_user(),
operation_type=operation_type,

View File

@ -503,7 +503,7 @@ class TestBarmanConnection(TestCase):
)
response = self.client.get(reverse("counter:activity", args=[self.counter.id]))
assert not '<li><a href="/user/1/">S&#39; Kia</a></li>' in str(response.content)
assert '<li><a href="/user/1/">S&#39; Kia</a></li>' not in str(response.content)
@pytest.mark.django_db
@ -853,7 +853,7 @@ class TestCustomerAccountId(TestCase):
number = account_id[:-1]
assert created is True
assert number == "12346"
assert 6 == len(account_id)
assert len(account_id) == 6
assert account_id[-1] in string.ascii_lowercase
assert customer.amount == 0

View File

@ -15,7 +15,40 @@
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 = [
path("<int:counter_id>/", CounterMain.as_view(), name="details"),

View File

@ -91,16 +91,10 @@ class CounterAdminMixin(View):
edit_club = []
def _test_group(self, user):
for grp_id in self.edit_group:
if user.is_in_group(pk=grp_id):
return True
return False
return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
def _test_club(self, user):
for c in self.edit_club:
if c.can_be_edited_by(user):
return True
return False
return any(c.can_be_edited_by(user) for c in self.edit_club)
def dispatch(self, request, *args, **kwargs):
if not (
@ -181,7 +175,7 @@ class CounterMain(
def post(self, request, *args, **kwargs):
self.object = self.get_object()
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
): # Check the token to avoid the bar to be stolen
return HttpResponseRedirect(
@ -219,7 +213,7 @@ class CounterMain(
kwargs["barmen"] = self.object.barmen_list
elif self.request.user.is_authenticated:
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_customer"] = self.request.session.pop("last_customer")
kwargs["last_total"] = self.request.session.pop("last_total")
@ -294,7 +288,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def get(self, request, *args, **kwargs):
"""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_total"] = 0
request.session["not_enough"] = False # Reset every variable
@ -318,7 +312,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
): # Check that at least one barman is logged in
return self.cancel(request)
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
): # Also check the token to avoid the bar to be stolen
return HttpResponseRedirect(
@ -329,7 +323,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
)
+ "?bad_location"
)
if "basket" not in request.session.keys():
if "basket" not in request.session:
request.session["basket"] = {}
request.session["basket_total"] = 0
request.session["not_enough"] = False # Reset every variable
@ -386,13 +380,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView):
def get_total_quantity_for_pid(self, request, pid):
pid = str(pid)
try:
return (
request.session["basket"][pid]["qty"]
+ request.session["basket"][pid]["bonus_qty"]
)
except:
if pid not in request.session["basket"]:
return 0
return (
request.session["basket"][pid]["qty"]
+ request.session["basket"][pid]["bonus_qty"]
)
def compute_record_product(self, request, product=None):
recorded = 0

View File

@ -13,8 +13,9 @@
#
#
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)

View File

@ -117,9 +117,7 @@ class BasketForm:
"""
if not self.error_messages and not self.correct_items:
self.clean()
if self.error_messages:
return False
return True
return not self.error_messages
@cached_property
def errors(self) -> list[str]:

View File

@ -2,8 +2,6 @@ from typing import Annotated
from ninja import ModelSchema, Schema
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
# from phonenumber_field.phonenumber import PhoneNumber
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
from counter.models import BillingInfo

View File

@ -7,11 +7,11 @@
import base64
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from cryptography.exceptions import InvalidSignature
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.serialization import (
load_pem_private_key,
@ -19,6 +19,12 @@ from cryptography.hazmat.primitives.serialization import (
)
from django.conf import settings
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import (
RSAPrivateKey,
RSAPublicKey,
)
def test_signature_valid():
"""Test that data sent to the bank is correctly signed."""

View File

@ -24,9 +24,9 @@
import base64
import json
import urllib
from typing import TYPE_CHECKING
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.serialization import load_pem_private_key
from django.conf import settings
@ -38,6 +38,9 @@ from core.models import User
from counter.models import Counter, Customer, Product, Selling
from eboutic.models import Basket, BasketItem
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
class TestEboutic(TestCase):
@classmethod

View File

@ -25,7 +25,14 @@
from django.urls import path, register_converter
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")

View File

@ -17,11 +17,11 @@ import base64
import json
from datetime import datetime
from enum import Enum
from typing import TYPE_CHECKING
import sentry_sdk
from cryptography.exceptions import InvalidSignature
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.serialization import load_pem_public_key
from django.conf import settings
@ -47,6 +47,9 @@ from eboutic.models import (
)
from eboutic.schemas import PurchaseItemList, PurchaseItemSchema
if TYPE_CHECKING:
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
@login_required
@require_GET
@ -221,7 +224,7 @@ class EtransactionAutoAnswer(View):
# Payment authorized:
# * 'Error' is '00000'
# * '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:
with transaction.atomic():
b = (

View File

@ -1,6 +1,22 @@
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 = [
path("", ElectionsListView.as_view(), name="list"),
@ -19,16 +35,10 @@ urlpatterns = [
name="delete_list",
),
path(
"<int:election_id>/role/create/",
RoleCreateView.as_view(),
name="create_role",
"<int:election_id>/role/create/", RoleCreateView.as_view(), name="create_role"
),
path("<int:role_id>/role/edit/", RoleUpdateView.as_view(), name="update_role"),
path(
"<int:role_id>/role/delete/",
RoleDeleteView.as_view(),
name="delete_role",
),
path("<int:role_id>/role/delete/", RoleDeleteView.as_view(), name="delete_role"),
path(
"<int:election_id>/candidate/add/",
CandidatureCreateView.as_view(),

View File

@ -1,3 +1,5 @@
from typing import TYPE_CHECKING
from ajax_select import make_ajax_field
from ajax_select.fields import AutoCompleteSelectField
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.edit import CreateView, DeleteView, FormView, UpdateView
from core.models import User
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
from core.views.forms import MarkdownInput, SelectDateTime
from election.models import Candidature, Election, ElectionList, Role, Vote
if TYPE_CHECKING:
from core.models import User
# Custom form field
@ -23,7 +28,6 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField):
def __init__(self, queryset, max_choice, **kwargs):
self.max_choice = max_choice
widget = forms.CheckboxSelectMultiple()
super().__init__(queryset, **kwargs)
def clean(self, value):
@ -251,7 +255,7 @@ class VoteFormView(CanCreateMixin, FormView):
def vote(self, election_data):
with transaction.atomic():
for role_title in election_data.keys():
for role_title in election_data:
# If we have a multiple choice field
if isinstance(election_data[role_title], QuerySet):
if election_data[role_title].count() > 0:
@ -444,28 +448,16 @@ class ElectionUpdateView(CanEditMixin, UpdateView):
pk_url_kwarg = "election_id"
def get_initial(self):
init = {}
try:
init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
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(
return {
"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"),
"start_candidature": self.object.start_candidature.strftime(
"%Y-%m-%d %H:%M:%S"
)
except Exception:
pass
try:
init["end_candidature"] = self.object.end_candidature.strftime(
),
"end_candidature": self.object.end_candidature.strftime(
"%Y-%m-%d %H:%M:%S"
)
except Exception:
pass
return init
),
}
def get_success_url(self, **kwargs):
return reverse_lazy("election:detail", kwargs={"election_id": self.object.id})

View File

@ -16,7 +16,7 @@
from django.contrib import admin
from haystack.admin import SearchModelAdmin
from forum.models import *
from forum.models import Forum, ForumMessage, ForumTopic
@admin.register(Forum)

View File

@ -25,6 +25,7 @@ from __future__ import annotations
from datetime import datetime
from datetime import timezone as tz
from itertools import chain
from typing import Self
from django.conf import settings
from django.core.exceptions import ValidationError
@ -207,12 +208,12 @@ class Forum(models.Model):
return self.get_parent_list()
def get_parent_list(self):
l = []
p = self.parent
while p is not None:
l.append(p)
p = p.parent
return l
parents = []
current = self.parent
while current is not None:
parents.append(current)
current = current.parent
return parents
@property
def topic_number(self):
@ -228,12 +229,12 @@ class Forum(models.Model):
def last_message(self):
return self._last_message
def get_children_list(self):
l = [self.id]
def get_children_list(self) -> list[Self]:
children = [self.id]
for c in self.children.all():
l.append(c.id)
l += c.get_children_list()
return l
children.append(c.id)
children.extend(c.get_children_list())
return children
class ForumTopic(models.Model):

View File

@ -23,7 +23,26 @@
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 = [
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>/edit/", ForumEditView.as_view(), name="edit_forum"),
path("<int:forum_id>/delete/", ForumDeleteView.as_view(), name="delete_forum"),
path(
"<int:forum_id>/new_topic/",
ForumTopicCreateView.as_view(),
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("<int:forum_id>/new_topic/", ForumTopicCreateView.as_view(), 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(
"topic/<int:topic_id>/new_message/",
ForumMessageCreateView.as_view(),
@ -60,11 +67,7 @@ urlpatterns = [
ForumTopicSubscribeView.as_view(),
name="toggle_subscribe_topic",
),
path(
"message/<int:message_id>/",
ForumMessageView.as_view(),
name="view_message",
),
path("message/<int:message_id>/", ForumMessageView.as_view(), name="view_message"),
path(
"message/<int:message_id>/edit/",
ForumMessageEditView.as_view(),

View File

@ -71,7 +71,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
self.logger = logging.getLogger("main")
if options["verbosity"] < 0 or 2 < options["verbosity"]:
if not 0 <= options["verbosity"] <= 2:
warnings.warn(
"verbosity level should be between 0 and 2 included", stacklevel=2
)

View File

@ -40,7 +40,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
logger = logging.getLogger("main")
if options["verbosity"] < 0 or 2 < options["verbosity"]:
if not 0 <= options["verbosity"] <= 2:
warnings.warn(
"verbosity level should be between 0 and 2 included", stacklevel=2
)

View File

@ -23,17 +23,9 @@
from django.urls import path
from galaxy.views import *
from galaxy.views import GalaxyDataView, GalaxyUserView
urlpatterns = [
path(
"<int:user_id>/",
GalaxyUserView.as_view(),
name="user",
),
path(
"data.json",
GalaxyDataView.as_view(),
name="data",
),
path("<int:user_id>/", GalaxyUserView.as_view(), name="user"),
path("data.json", GalaxyDataView.as_view(), name="data"),
]

View File

@ -14,7 +14,7 @@
#
from django.contrib import admin
from launderette.models import *
from launderette.models import Launderette, Machine, Slot, Token
@admin.register(Launderette)

View File

@ -51,18 +51,14 @@ class Launderette(models.Model):
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user)
if m and m.role >= 9:
return True
return False
return bool(m and m.role >= 9)
def can_be_edited_by(self, user):
launderette_club = Club.objects.filter(
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user)
if m and m.role >= 2:
return True
return False
return bool(m and m.role >= 2)
def can_be_viewed_by(self, user):
return user.is_subscribed
@ -113,9 +109,7 @@ class Machine(models.Model):
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user)
if m and m.role >= 9:
return True
return False
return bool(m and m.role >= 9)
class Token(models.Model):
@ -164,15 +158,7 @@ class Token(models.Model):
unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"]
).first()
m = launderette_club.get_membership_for(user)
if 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
return bool(m and m.role >= 9)
class Slot(models.Model):

View File

@ -15,22 +15,28 @@
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 = [
# views
path("", LaunderetteMainView.as_view(), name="launderette_main"),
path(
"slot/<int:slot_id>/delete/",
SlotDeleteView.as_view(),
name="delete_slot",
),
path("slot/<int:slot_id>/delete/", SlotDeleteView.as_view(), name="delete_slot"),
path("book/", LaunderetteBookMainView.as_view(), name="book_main"),
path(
"book/<int:launderette_id>/",
LaunderetteBookView.as_view(),
name="book_slot",
),
path("book/<int:launderette_id>/", LaunderetteBookView.as_view(), name="book_slot"),
path(
"<int:launderette_id>/click/",
LaunderetteMainClickView.as_view(),

View File

@ -19,7 +19,7 @@ from datetime import timezone as tz
from django import forms
from django.conf import settings
from django.db import DataError, transaction
from django.db import transaction
from django.template import defaultfilters
from django.urls import reverse_lazy
from django.utils import dateparse, timezone
@ -73,15 +73,15 @@ class LaunderetteBookView(CanViewMixin, DetailView):
self.machines = {}
with transaction.atomic():
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"]
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
if self.subscriber.is_subscribed:
self.date = dateparse.parse_datetime(request.POST["slot"]).replace(
tzinfo=tz.utc
)
if self.slot_type == "WASHING":
if self.slot_type in ["WASHING", "DRYING"]:
if self.check_slot(self.slot_type):
Slot(
user=self.subscriber,
@ -89,30 +89,21 @@ class LaunderetteBookView(CanViewMixin, DetailView):
machine=self.machines[self.slot_type],
type=self.slot_type,
).save()
elif self.slot_type == "DRYING":
if self.check_slot(self.slot_type):
Slot(
user=self.subscriber,
start_date=self.date,
machine=self.machines[self.slot_type],
type=self.slot_type,
).save()
else:
if self.check_slot("WASHING") and self.check_slot(
"DRYING", self.date + timedelta(hours=1)
):
Slot(
user=self.subscriber,
start_date=self.date,
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()
elif self.check_slot("WASHING") and self.check_slot(
"DRYING", self.date + timedelta(hours=1)
):
Slot(
user=self.subscriber,
start_date=self.date,
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)
def check_slot(self, machine_type, date=None):
@ -149,15 +140,17 @@ class LaunderetteBookView(CanViewMixin, DetailView):
):
free = False
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("DRYING", h + timedelta(hours=1))
or self.slot_type == "DRYING"
and self.check_slot("DRYING", h)
):
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:
kwargs["planning"][date].append(h)
else:
@ -236,42 +229,39 @@ class ManageTokenForm(forms.Form):
token_list = cleaned_data["tokens"].strip(" \n\r").split(" ")
token_type = cleaned_data["token_type"]
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":
for t in token_list:
try:
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},
)
Token.objects.filter(id__in=[t.id for t in tokens]).update(
borrow_date=None, user=None
)
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:
try:
Token.objects.filter(
launderette=launderette, type=token_type, name=t
).delete()
except:
self.add_error(
None,
_("Token %(token_name)s does not exists") % {"token_name": t},
)
if t == "":
self.add_error(None, _("Token name can not be blank"))
else:
Token(launderette=launderette, type=token_type, name=t).save()
class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
@ -288,13 +278,7 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView):
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
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):
"""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["form"] = self.get_form()
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_customer"] = self.request.session.pop("last_customer", 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):
"""We handle here the login form for the barman."""
kwargs = super().get_context_data(**kwargs)
if "form" not in kwargs.keys():
if "form" not in kwargs:
kwargs["form"] = self.get_form()
kwargs["counter"] = self.object.counter
kwargs["customer"] = self.customer
@ -519,7 +503,7 @@ class MachineCreateView(CanCreateMixin, CreateView):
def get_initial(self):
ret = super().get_initial()
if "launderette" in self.request.GET.keys():
if "launderette" in self.request.GET:
obj = Launderette.objects.filter(
id=int(self.request.GET["launderette"])
).first()

View File

@ -23,7 +23,12 @@
from django.urls import path
from matmat.views import *
from matmat.views import (
SearchClearFormView,
SearchNormalFormView,
SearchQuickFormView,
SearchReverseFormView,
)
urlpatterns = [
path("", SearchNormalFormView.as_view(), name="search"),

View File

@ -71,15 +71,15 @@ class SearchForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key in self.fields.keys():
for key in self.fields:
self.fields[key].required = False
@property
def cleaned_data_json(self):
data = self.cleaned_data
for key in data.keys():
if key in ("date_of_birth", "phone") and data[key] is not None:
data[key] = str(data[key])
for key, val in data.items():
if key in ("date_of_birth", "phone") and val is not None:
data[key] = str(val)
return data
@ -98,10 +98,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView):
self.session = request.session
self.last_search = self.session.get("matmat_search_result", str([]))
self.last_search = literal_eval(self.last_search)
if "valid_form" in kwargs.keys():
self.valid_form = kwargs["valid_form"]
else:
self.valid_form = None
self.valid_form = kwargs.get("valid_form")
self.init_query = self.model.objects
self.can_see_hidden = True
@ -202,8 +199,8 @@ class SearchClearFormView(FormerSubscriberMixin, View):
def dispatch(self, 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")
if "matmat_search_result" in request.session.keys():
if "matmat_search_result" in request.session:
request.session.pop("matmat_search_result")
return HttpResponseRedirect(reverse("matmat:search"))

View File

@ -32,7 +32,10 @@ class Migration(migrations.Migration):
unique=True,
validators=[
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]+)",
)
],

View File

@ -45,7 +45,8 @@ class UV(models.Model):
validators.RegexValidator(
regex="([A-Z0-9]+)",
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"
),
)
],

View File

@ -27,7 +27,10 @@ class TestUVSearch(TestCase):
semester="AUTUMN",
department="GI",
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(
code="MT01",
@ -118,7 +121,7 @@ class TestUVSearch(TestCase):
("M", {"MT01", "MT10"}),
("mt", {"MT01", "MT10"}),
("MT", {"MT01", "MT10"}),
("algèbre", {"MT01"}), # Title search case insensitive
("algèbre", {"MT01"}), # Title search case insensitive
# Manager search
("moss", {"TNEV"}),
("francky", {"DA50", "AP4A"}),

View File

@ -381,7 +381,9 @@ class TestUVCommentCreationAndDisplay(TestCase):
self.assertContains(
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."
),
)

View File

@ -23,7 +23,17 @@
from django.urls import path
from pedagogy.views import *
from pedagogy.views import (
UVCommentDeleteView,
UVCommentReportCreateView,
UVCommentUpdateView,
UVCreateView,
UVDeleteView,
UVDetailFormView,
UVGuideView,
UVModerationFormView,
UVUpdateView,
)
urlpatterns = [
# Urls displaying the actual application for visitors

View File

@ -23,7 +23,7 @@
from django.conf import settings
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.urls import reverse, reverse_lazy
from django.views.generic import (
@ -193,18 +193,12 @@ class UVModerationFormView(FormView):
def form_valid(self, form):
form_clean = form.clean()
for report in form_clean.get("accepted_reports", []):
try:
report.comment.delete() # Delete the related comment
except ObjectDoesNotExist:
# To avoid errors when two reports points the same comment
pass
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
accepted = form_clean.get("accepted_reports", [])
if len(accepted) > 0: # delete the reported comments
UVComment.objects.filter(reports__in=accepted).delete()
denied = form_clean.get("denied_reports", [])
if len(denied) > 0: # delete the comments themselves
UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
return super().form_valid(form)
def get_success_url(self):

View File

@ -101,18 +101,30 @@ select = [
"A", # shadowing of Python builtins
"B",
"C4", # use comprehensions when possible
"I", # isort
"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
"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)
"UP009", # utf-8 encoding declaration is unnecessary
"T2", # print statements
"T100", # breakpoint()
]
ignore = [
"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]

View File

@ -44,8 +44,8 @@ class Command(BaseCommand):
exit(1)
confirm = input(
"User selected: %s\nDo you really want to delete all message from this user ? [y/N] "
% (user,)
"User selected: %s\nDo you really want "
"to delete all message from this user ? [y/N] " % (user,)
)
if not confirm.lower().startswith("y"):

View File

@ -66,11 +66,11 @@ class TestMergeUser(TestCase):
self.to_keep = User.objects.get(pk=self.to_keep.pk)
# fields of to_delete should be assigned to to_keep
# if they were not set beforehand
assert "Biggus" == self.to_keep.first_name
assert "Dickus" == self.to_keep.last_name
assert "B'ian" == self.to_keep.nick_name
assert "Jerusalem" == self.to_keep.address
assert "Rome" == self.to_keep.parent_address
assert self.to_keep.first_name == "Biggus"
assert self.to_keep.last_name == "Dickus"
assert self.to_keep.nick_name == "B'ian"
assert self.to_keep.address == "Jerusalem"
assert self.to_keep.parent_address == "Rome"
assert self.to_keep.groups.count() == 3
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)

View File

@ -24,7 +24,11 @@
from django.urls import path
from rootplace.views import *
from rootplace.views import (
DeleteAllForumUserMessagesView,
MergeUsersView,
OperationLogListView,
)
urlpatterns = [
path("merge/", MergeUsersView.as_view(), name="merge"),

View File

@ -48,7 +48,8 @@ def __merge_subscriptions(u1: User, u2: User):
Some examples :
- 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 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
"""
last_subscription = (

View File

@ -15,6 +15,7 @@
from __future__ import annotations
import contextlib
from io import BytesIO
from pathlib import Path
from typing import ClassVar, Self
@ -108,10 +109,8 @@ class Picture(SasFile):
def generate_thumbnails(self, *, overwrite=False):
im = Image.open(BytesIO(self.file.read()))
try:
with contextlib.suppress(Exception):
im = exif_auto_rotate(im)
except:
pass
# convert the compressed image and the thumbnail into webp
# The original image keeps its original type, because it's not
# meant to be shown on the website, but rather to keep the real image

View File

@ -15,24 +15,33 @@
from django.urls import path
from sas.views import *
from sas.views import (
AlbumEditView,
AlbumUploadView,
AlbumView,
ModerationView,
PictureAskRemovalView,
PictureEditView,
PictureView,
SASMainView,
send_album,
send_compressed,
send_pict,
send_thumb,
)
urlpatterns = [
path("", SASMainView.as_view(), name="main"),
path("moderation/", ModerationView.as_view(), name="moderation"),
path("album/<int:album_id>/", AlbumView.as_view(), name="album"),
path(
"album/<int:album_id>/upload/",
AlbumUploadView.as_view(),
name="album_upload",
"album/<int:album_id>/upload/", AlbumUploadView.as_view(), name="album_upload"
),
path("album/<int:album_id>/edit/", AlbumEditView.as_view(), name="album_edit"),
path("album/<int:album_id>/preview/", send_album, name="album_preview"),
path("picture/<int:picture_id>/", PictureView.as_view(), name="picture"),
path(
"picture/<int:picture_id>/edit/",
PictureEditView.as_view(),
name="picture_edit",
"picture/<int:picture_id>/edit/", PictureEditView.as_view(), name="picture_edit"
),
path(
"picture/<int:picture_id>/report",
@ -45,9 +54,5 @@ urlpatterns = [
send_compressed,
name="download_compressed",
),
path(
"picture/<int:picture_id>/download/thumb/",
send_thumb,
name="download_thumb",
),
path("picture/<int:picture_id>/download/thumb/", send_thumb, name="download_thumb"),
]

View File

@ -115,19 +115,18 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin):
self.form = self.get_form()
parent = SithFile.objects.filter(id=self.object.id).first()
files = request.FILES.getlist("images")
if request.user.is_authenticated and request.user.is_subscribed:
if request.user.is_subscribed and self.form.is_valid():
self.form.process(
parent=parent,
owner=request.user,
files=files,
automodere=(
request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
or request.user.is_root
),
)
if self.form.is_valid():
self.form.process(
parent=parent,
owner=request.user,
files=files,
automodere=(
request.user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID)
or request.user.is_root
),
)
if self.form.is_valid():
return HttpResponse(str(self.form.errors), status=200)
return HttpResponse(str(self.form.errors), status=200)
return HttpResponse(str(self.form.errors), status=500)
@ -146,7 +145,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
def get(self, request, *args, **kwargs):
self.form = self.get_form()
if "clipboard" not in request.session.keys():
if "clipboard" not in request.session:
request.session["clipboard"] = []
return super().get(request, *args, **kwargs)
@ -155,7 +154,7 @@ class AlbumView(CanViewMixin, DetailView, FormMixin):
if not self.object.file:
self.object.generate_thumbnail()
self.form = self.get_form()
if "clipboard" not in request.session.keys():
if "clipboard" not in request.session:
request.session["clipboard"] = []
if request.user.can_edit(self.object): # Handle the copy-paste functions
FileView.handle_clipboard(request, self.object)

View File

@ -345,8 +345,8 @@ SITH_LAUNDERETTE_MANAGER = {
# Main root for club pages
SITH_CLUB_ROOT_PAGE = "clubs"
# Define the date in the year serving as reference for the subscriptions calendar
# (month, day)
# Define the date in the year serving as
# reference for the subscriptions calendar (month, day)
SITH_SEMESTER_START_AUTUMN = (8, 15) # 15 August
SITH_SEMESTER_START_SPRING = (2, 15) # 15 February
@ -509,10 +509,12 @@ SITH_ACCOUNT_INACTIVITY_DELTA = relativedelta(years=2)
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
"""timedelta between the warning mail and the actual account dump"""
# Defines which product type is the refilling type, and thus increases the account amount
# Defines which product type is the refilling type,
# and thus increases the account amount
SITH_COUNTER_PRODUCTTYPE_REFILLING = 3
# Defines which product is the one year subscription and which one is the six month subscription
# Defines which product is the one year subscription
# and which one is the six month subscription
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2
SITH_PRODUCTTYPE_SUBSCRIPTION = 2
@ -700,15 +702,15 @@ TOXIC_DOMAINS_PROVIDERS = [
]
try:
from .settings_custom import *
from .settings_custom import * # noqa F403 (this star-import is actually useful)
logging.getLogger("django").info("Custom settings imported")
except:
except ImportError:
logging.getLogger("django").warning("Custom settings failed")
if DEBUG:
INSTALLED_APPS += ("debug_toolbar",)
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware",) + MIDDLEWARE
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)
DEBUG_TOOLBAR_PANELS = [
"debug_toolbar.panels.versions.VersionsPanel",
"debug_toolbar.panels.timer.TimerPanel",

View File

@ -71,20 +71,20 @@ if settings.DEBUG:
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
if settings.SENTRY_ENV == "development":
if settings.SENTRY_ENV == "development" and settings.SENTRY_DSN:
"""Sentry debug endpoint
This function always crash and allows us to test
the sentry configuration and the modal popup
displayed to users on production
The error will be displayed on Sentry
inside the "development" environment
NOTE : you need to specify the SENTRY_DSN setting in settings_custom.py
"""
def raise_exception(request):
division_by_zero = 1 / 0
_division_by_zero = 1 / 0
urlpatterns += [path("sentry-debug/", raise_exception)]

View File

@ -14,9 +14,12 @@ IGNORE_PATTERNS = [
]
# We override the original staticfiles app according to https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list
# However, this is buggy and requires us to have an exact naming of the class like this to be detected
# Also, it requires to create all commands in management/commands again or they don't get detected by django
# We override the original staticfiles app according to
# https://docs.djangoproject.com/en/4.2/ref/contrib/staticfiles/#customizing-the-ignored-pattern-list
# However, this is buggy and requires us
# to have an exact naming of the class like this to be detected
# Also, it requires to create all commands in management/commands again
# or they don't get detected by django
# Workaround originates from https://stackoverflow.com/a/78724835/12640533
class StaticFilesConfig(StaticFilesConfig):
"""

View File

@ -28,10 +28,10 @@ class Command(CollectStatic):
def collect_scss(self) -> list[Scss.CompileArg]:
files: list[Scss.CompileArg] = []
for finder in get_finders():
for path, storage in finder.list(
for path_str, storage in finder.list(
set(self.ignore_patterns) - set(IGNORE_PATTERNS_SCSS)
):
path = Path(path)
path = Path(path_str)
if path.suffix != ".scss":
continue
files.append(

View File

@ -10,7 +10,7 @@ from staticfiles.processors import OpenApi, Webpack
class Command(Runserver):
"""Light wrapper around the statics runserver that integrates webpack auto bundling"""
"""Light wrapper around default runserver that integrates webpack auto bundling."""
def run(self, **options):
# OpenApi generation needs to be before webpack

View File

@ -83,7 +83,7 @@ class OpenApi:
@classmethod
def compile(cls):
"""Compile a typescript client for the sith API. Only generates it if it changed"""
"""Compile a TS client for the sith API. Only generates it if it changed."""
logging.getLogger("django").info("Compiling open api typescript client")
out = cls.OPENAPI_DIR / "schema.json"
cls.OPENAPI_DIR.mkdir(parents=True, exist_ok=True)
@ -110,4 +110,4 @@ class OpenApi:
with open(out, "w") as f:
_ = f.write(schema)
subprocess.run(["npx", "openapi-ts"]).check_returncode()
subprocess.run(["npx", "openapi-ts"], check=True)

View File

@ -125,7 +125,10 @@ class Migration(migrations.Migration):
"minimal_quantity",
models.IntegerField(
verbose_name="minimal quantity",
help_text="if the effective quantity is less than the minimal, item is added to the shopping list",
help_text=(
"if the effective quantity is less than the minimal, "
"item is added to the shopping list"
),
default=1,
),
),

View File

@ -30,7 +30,7 @@ from core.utils import get_start_of_semester
def validate_type(value):
if value not in settings.SITH_SUBSCRIPTIONS.keys():
if value not in settings.SITH_SUBSCRIPTIONS:
raise ValidationError(_("Bad subscription type"))
@ -107,7 +107,9 @@ class Subscription(models.Model):
)
@staticmethod
def compute_start(d: date = None, duration: int = 1, user: User = None) -> date:
def compute_start(
d: date | None = None, duration: int = 1, user: User | None = None
) -> date:
"""Computes the start date of the subscription.
The computation is done with respect to the given date (default is today)
@ -129,7 +131,9 @@ class Subscription(models.Model):
return get_start_of_semester(d)
@staticmethod
def compute_end(duration: int, start: date = None, user: User = None) -> date:
def compute_end(
duration: int, start: date | None = None, user: User | None = None
) -> date:
"""Compute the end date of the subscription.
Args:

View File

@ -15,7 +15,7 @@
from django.urls import path
from subscription.views import *
from subscription.views import NewSubscription, SubscriptionsStatsView
urlpatterns = [
# Subscription views

View File

@ -94,11 +94,13 @@ class SubscriptionForm(forms.ModelForm):
self.errors.pop("email", None)
self.errors.pop("date_of_birth", None)
if cleaned_data.get("member") is None:
# This should be handled here, but it is done in the Subscription model's clean method
# This should be handled here,
# but it is done in the Subscription model's clean method
# TODO investigate why!
raise ValidationError(
_(
"You must either choose an existing user or create a new one properly"
"You must either choose an existing "
"user or create a new one properly"
)
)
return cleaned_data
@ -114,7 +116,7 @@ class NewSubscription(CreateView):
raise PermissionDenied
def get_initial(self):
if "member" in self.request.GET.keys():
if "member" in self.request.GET:
return {
"member": self.request.GET["member"],
"subscription_type": "deux-semestres",

View File

@ -30,7 +30,12 @@ class Migration(migrations.Migration):
"subscription_deadline",
models.DateField(
default=datetime.date.today,
help_text="Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.",
help_text=(
"Before this date, users are allowed "
"to subscribe to this Trombi. "
"After this date, users subscribed will "
"be allowed to comment on each other."
),
verbose_name="subscription deadline",
),
),
@ -38,7 +43,10 @@ class Migration(migrations.Migration):
"comments_deadline",
models.DateField(
default=datetime.date.today,
help_text="After this date, users won't be able to make comments anymore.",
help_text=(
"After this date, users won't be able "
"to make comments anymore."
),
verbose_name="comments deadline",
),
),
@ -92,7 +100,10 @@ class Migration(migrations.Migration):
models.ImageField(
upload_to="trombi",
blank=True,
help_text="The profile picture you want in the trombi (warning: this picture may be published)",
help_text=(
"The profile picture you want in the trombi "
"(warning: this picture may be published)"
),
verbose_name="profile pict",
null=True,
),
@ -102,7 +113,10 @@ class Migration(migrations.Migration):
models.ImageField(
upload_to="trombi",
blank=True,
help_text="The scrub picture you want in the trombi (warning: this picture may be published)",
help_text=(
"The scrub picture you want in the trombi "
"(warning: this picture may be published)"
),
verbose_name="scrub pict",
null=True,
),

View File

@ -55,9 +55,9 @@ class Trombi(models.Model):
_("subscription deadline"),
default=date.today,
help_text=_(
"Before this date, users are "
"allowed to subscribe to this Trombi. "
"After this date, users subscribed will be allowed to comment on each other."
"Before this date, users are allowed to subscribe to this Trombi. "
"After this date, users subscribed will"
" be allowed to comment on each other."
),
)
comments_deadline = models.DateField(
@ -131,7 +131,8 @@ class TrombiUser(models.Model):
null=True,
blank=True,
help_text=_(
"The profile picture you want in the trombi (warning: this picture may be published)"
"The profile picture you want in the trombi "
"(warning: this picture may be published)"
),
)
scrub_pict = models.ImageField(
@ -140,7 +141,8 @@ class TrombiUser(models.Model):
null=True,
blank=True,
help_text=_(
"The scrub picture you want in the trombi (warning: this picture may be published)"
"The scrub picture you want in the trombi "
"(warning: this picture may be published)"
),
)
@ -158,10 +160,7 @@ class TrombiUser(models.Model):
role = str(settings.SITH_CLUB_ROLES[m.role])
if m.description:
role += " (%s)" % m.description
if m.end_date:
end_date = get_semester_code(m.end_date)
else:
end_date = ""
end_date = get_semester_code(m.end_date) if m.end_date else ""
TrombiClubMembership(
user=self,
club=str(m.club),

View File

@ -24,7 +24,25 @@
from django.urls import path
from trombi.views import *
from trombi.views import (
TrombiCommentCreateView,
TrombiCommentEditView,
TrombiCreateView,
TrombiDeleteUserView,
TrombiDetailView,
TrombiEditView,
TrombiExportView,
TrombiModerateCommentsView,
TrombiModerateCommentView,
UserTrombiAddMembershipView,
UserTrombiDeleteMembershipView,
UserTrombiEditMembershipView,
UserTrombiEditPicturesView,
UserTrombiEditProfileView,
UserTrombiProfileView,
UserTrombiResetClubMembershipsView,
UserTrombiToolsView,
)
urlpatterns = [
path("<int:club_id>/new/", TrombiCreateView.as_view(), name="create"),
@ -41,9 +59,7 @@ urlpatterns = [
name="moderate_comment",
),
path(
"user/<int:user_id>/delete/",
TrombiDeleteUserView.as_view(),
name="delete_user",
"user/<int:user_id>/delete/", TrombiDeleteUserView.as_view(), name="delete_user"
),
path("<int:trombi_id>/", TrombiDetailView.as_view(), name="detail"),
path(
@ -52,9 +68,7 @@ urlpatterns = [
name="new_comment",
),
path(
"<int:user_id>/profile/",
UserTrombiProfileView.as_view(),
name="user_profile",
"<int:user_id>/profile/", UserTrombiProfileView.as_view(), name="user_profile"
),
path(
"comment/<int:comment_id>/edit/",

View File

@ -29,6 +29,7 @@ from django import forms
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.forms.models import modelform_factory
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
@ -75,7 +76,10 @@ class TrombiTabsMixin(TabedViewMixin):
"name": _("My pictures"),
}
)
try:
if (
hasattr(self.request.user, "trombi_user")
and self.request.user.trombi_user.trombi
):
trombi = self.request.user.trombi_user.trombi
if self.request.user.is_owner(trombi):
tab_list.append(
@ -87,8 +91,6 @@ class TrombiTabsMixin(TabedViewMixin):
"name": _("Admin tools"),
}
)
except:
pass
return tab_list
@ -163,7 +165,7 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie
try:
TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save()
self.quick_notif_list.append("qn_success")
except: # We don't care about duplicate keys
except IntegrityError: # We don't care about duplicate keys
self.quick_notif_list.append("qn_fail")
return super().get(request, *args, **kwargs)
@ -239,12 +241,12 @@ class TrombiModerateCommentView(DetailView):
)
elif request.POST["action"] == "reject":
return super().get(request, *args, **kwargs)
elif request.POST["action"] == "delete" and "reason" in request.POST.keys():
elif request.POST["action"] == "delete" and "reason" in request.POST:
self.object.author.user.email_user(
subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")),
message=_(
'Your comment to %(target)s on the Trombi "%(trombi)s" was rejected for the following '
"reason: %(reason)s\n\n"
'Your comment to %(target)s on the Trombi "%(trombi)s" '
"was rejected for the following reason: %(reason)s\n\n"
"Your comment was:\n\n%(content)s"
)
% {
@ -498,7 +500,7 @@ class TrombiCommentFormView(LoginRequiredMixin, View):
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
if "user_id" in self.kwargs.keys():
if "user_id" in self.kwargs:
kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"])
else:
kwargs["target"] = self.object.target