mirror of
https://github.com/ae-utbm/sith.git
synced 2026-06-05 07:39:21 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31d186237a | |||
| e811aeaecd | |||
| 549a778be0 | |||
| 78b24dc1e7 | |||
| ebf0196bef | |||
| 362b9eea06 | |||
| 3b3e33ed80 |
@@ -392,6 +392,30 @@ class ClubRoleForm(forms.ModelForm):
|
||||
self.instance.order = cleaned_data["ORDER"] - 1
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=True): # noqa: FBT002
|
||||
instance: ClubRole = super().save(commit=commit)
|
||||
if commit and "is_board" in self.changed_data:
|
||||
# if the role was moved from board to simple member,
|
||||
# remove all users with that role from the club board group.
|
||||
# If the role became a board role, add users with
|
||||
# that role to the club board group.
|
||||
group_id = instance.club.board_group_id
|
||||
if self.cleaned_data["is_board"]:
|
||||
User.groups.through.objects.bulk_create(
|
||||
[
|
||||
User.groups.through(user_id=u, group_id=group_id)
|
||||
for u in Membership.objects.ongoing()
|
||||
.filter(role=instance)
|
||||
.values_list("user_id", flat=True)
|
||||
],
|
||||
ignore_conflicts=True,
|
||||
)
|
||||
else:
|
||||
User.groups.through.objects.filter(
|
||||
user__memberships__role=instance, group_id=group_id
|
||||
).delete()
|
||||
return instance
|
||||
|
||||
|
||||
class ClubRoleCreateForm(forms.ModelForm):
|
||||
"""Form to create a club role.
|
||||
|
||||
@@ -4,6 +4,7 @@ import pytest
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from model_bakery import baker, seq
|
||||
from model_bakery.recipe import Recipe
|
||||
from pytest_django.asserts import assertRedirects
|
||||
@@ -239,7 +240,7 @@ class TestClubRoleUpdate(TestCase):
|
||||
|
||||
def test_president_moves_itself_out_of_the_presidency(self):
|
||||
"""Test that if the user moves its own role out of the presidency,
|
||||
then it's redirected to another page and loses access to the update page."""
|
||||
then it loses access to the update page."""
|
||||
self.payload["roles-0-is_presidency"] = False
|
||||
self.client.force_login(self.user)
|
||||
res = self.client.post(self.url, data=self.payload)
|
||||
@@ -251,3 +252,29 @@ class TestClubRoleUpdate(TestCase):
|
||||
|
||||
res = self.client.get(self.url)
|
||||
assert res.status_code == 403
|
||||
|
||||
def test_role_stops_being_board(self):
|
||||
"""Test that if a role stops being a board role,
|
||||
its users lose the club board group."""
|
||||
self.payload["roles-0-is_board"] = False
|
||||
self.payload["roles-0-is_presidency"] = False
|
||||
self.payload["roles-1-is_board"] = False
|
||||
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
|
||||
assert formset.is_valid()
|
||||
formset.save()
|
||||
assert not self.user.groups.contains(self.club.board_group)
|
||||
|
||||
def test_role_becomes_board(self):
|
||||
"""Test that if a role becomes a board role,
|
||||
its active users get the club board group"""
|
||||
members = [
|
||||
baker.make(Membership, club=self.club, role=self.roles[0], end_date=None),
|
||||
baker.make(Membership, club=self.club, role=self.roles[0], end_date=now()),
|
||||
]
|
||||
self.payload["roles-2-is_board"] = True
|
||||
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
|
||||
assert formset.is_valid()
|
||||
formset.save()
|
||||
# the second membership is finished, so its user shouldn't get the role
|
||||
assert members[0].user.groups.contains(self.club.board_group)
|
||||
assert not members[1].user.groups.contains(self.club.board_group)
|
||||
|
||||
@@ -46,6 +46,10 @@ details.accordion>.accordion-content {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
padding: .75em 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin animation($selector) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RecursivePartial, TomSettings } from "tom-select/dist/types/types";
|
||||
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base.ts";
|
||||
import { registerComponent } from "#core:utils/web-components.ts";
|
||||
import type { RecursivePartial, TomSettings } from "tom-select/src/types";
|
||||
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base";
|
||||
import { registerComponent } from "#core:utils/web-components";
|
||||
|
||||
const productParsingRegex = /^(\d+x)?(.*)/i;
|
||||
const codeParsingRegex = / \((\w+)\)$/;
|
||||
@@ -63,13 +63,6 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
this.widget.hook("after", "onOptionSelect", () => {
|
||||
/* Focus the next element if it's an input */
|
||||
if (this.nextElementSibling.nodeName === "INPUT") {
|
||||
(this.nextElementSibling as HTMLInputElement).focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
protected tomSelectSettings(): RecursivePartial<TomSettings> {
|
||||
/* We disable the dropdown on focus because we're going to always autofocus the widget */
|
||||
@@ -80,9 +73,7 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
|
||||
// We need to manually set weights or it results on an inconsistent
|
||||
// behavior between production and development environment
|
||||
searchField: [
|
||||
// @ts-expect-error documentation says it's fine, specified type is wrong
|
||||
{ field: "code", weight: 2 },
|
||||
// @ts-expect-error documentation says it's fine, specified type is wrong
|
||||
{ field: "text", weight: 0.5 },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -25,6 +25,9 @@ document.addEventListener("alpine:init", () => {
|
||||
}
|
||||
|
||||
this.codeField = this.$refs.codeField;
|
||||
this.codeField.widget.hook("after", "onOptionSelect", () => {
|
||||
this.handleCode();
|
||||
});
|
||||
this.codeField.widget.focus();
|
||||
|
||||
// It's quite tricky to manually apply attributes to the management part
|
||||
@@ -154,6 +157,7 @@ document.addEventListener("alpine:init", () => {
|
||||
this.addToBasket(code, quantity);
|
||||
}
|
||||
this.codeField.widget.clear();
|
||||
this.codeField.widget.setTextboxValue("");
|
||||
this.codeField.widget.focus();
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -42,7 +42,28 @@
|
||||
min-width: 350px;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5rem;
|
||||
margin-left: 0;
|
||||
|
||||
.basket-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
.product-name {
|
||||
flex: 1 2 0;
|
||||
min-width: 0;
|
||||
text-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,10 +56,15 @@
|
||||
<div class="accordion-content">
|
||||
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
|
||||
|
||||
<form method="post" action=""
|
||||
class="code_form" @submit.prevent="handleCode">
|
||||
<form method="post" action="" @submit.prevent="handleCode">
|
||||
|
||||
<counter-product-select name="code" x-ref="codeField" autofocus required placeholder="{% trans %}Select a product...{% endtrans %}">
|
||||
<counter-product-select
|
||||
name="code"
|
||||
x-ref="codeField"
|
||||
autofocus
|
||||
required
|
||||
placeholder="{% trans %}Select a product...{% endtrans %}"
|
||||
>
|
||||
<option value=""></option>
|
||||
<optgroup label="{% trans %}Operations{% endtrans %}">
|
||||
<option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option>
|
||||
@@ -68,13 +73,11 @@
|
||||
{%- for category, prices in categories.items() -%}
|
||||
<optgroup label="{{ category }}">
|
||||
{%- for price in prices -%}
|
||||
<option value="{{ price.id }}">{{ price.full_label }}</option>
|
||||
<option value="{{ price.id }}">{{ price.full_label }} ({{ price.product.code }})</option>
|
||||
{%- endfor -%}
|
||||
</optgroup>
|
||||
{%- endfor -%}
|
||||
</counter-product-select>
|
||||
|
||||
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
|
||||
</form>
|
||||
|
||||
{% for error in form.non_form_errors() %}
|
||||
@@ -102,7 +105,9 @@
|
||||
{{ form.management_form }}
|
||||
</div>
|
||||
<ul>
|
||||
<li x-show="getBasketSize() === 0">{% trans %}This basket is empty{% endtrans %}</li>
|
||||
<li x-show="getBasketSize() === 0">
|
||||
<em>{% trans %}This basket is empty{% endtrans %}</em>
|
||||
</li>
|
||||
<template x-for="(item, index) in Object.values(basket)" :key="item.product.price.id">
|
||||
<li>
|
||||
<template x-for="error in item.errors">
|
||||
@@ -110,19 +115,23 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
|
||||
<span class="quantity" x-text="item.quantity"></span>
|
||||
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
|
||||
<div class="basket-row">
|
||||
<div>
|
||||
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
|
||||
<span class="quantity" x-text="item.quantity"></span>
|
||||
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
|
||||
</div>
|
||||
|
||||
<span x-text="item.product.name"></span> :
|
||||
<span x-text="item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })">€</span>
|
||||
<span x-show="item.getBonusQuantity() > 0"
|
||||
x-text="`${item.getBonusQuantity()} x P`"></span>
|
||||
<span class="product-name" x-text="item.product.name"></span>
|
||||
<span x-text="`${item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })} €`"></span>
|
||||
<span x-show="item.getBonusQuantity() > 0"
|
||||
x-text="`${item.getBonusQuantity()} x P`"></span>
|
||||
|
||||
<button
|
||||
class="remove-item"
|
||||
@click.prevent="removeFromBasket(item.product.price.id)"
|
||||
><i class="fa fa-trash-can delete-action"></i></button>
|
||||
<button
|
||||
class="remove-item"
|
||||
@click.prevent="removeFromBasket(item.product.price.id)"
|
||||
><i class="fa fa-trash-can delete-action"></i></button>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ prod = [
|
||||
]
|
||||
dev = [
|
||||
"django-debug-toolbar>=6.3.0,<7",
|
||||
"ipython>=9.13.0,<10.0.0",
|
||||
"ipython>=9.14.0,<10.0.0",
|
||||
"pre-commit>=4.6.0,<5.0.0",
|
||||
"ruff>=0.15.13,<1.0.0",
|
||||
"djhtml>=3.0.11,<4.0.0",
|
||||
|
||||
Reference in New Issue
Block a user