7 Commits

Author SHA1 Message Date
thomas girod
71ed7cdf7d Merge pull request #1289 from ae-utbm/product_history
Product history
2026-02-17 22:05:59 +01:00
imperosol
43768171a1 show creation date on Product update page 2026-02-17 22:05:34 +01:00
imperosol
0eccb4a5b5 Add created_at and updated_at to Product model 2026-02-17 22:05:19 +01:00
thomas girod
e7584c8c83 Merge pull request #1299 from ae-utbm/populate-more
add ban generation to populate_more
2026-02-17 22:04:18 +01:00
thomas girod
ac06de4f55 Merge pull request #1300 from ae-utbm/csv-typo
fix: typo
2026-02-17 12:30:35 +01:00
imperosol
e2fca3e6d2 fix: typo 2026-02-14 15:22:18 +01:00
imperosol
2138783bde add ban generation to populate_more 2026-02-14 15:14:45 +01:00
9 changed files with 125 additions and 19 deletions

View File

@@ -35,7 +35,7 @@ TODO : rewrite the pagination used in this template an Alpine one
{% csrf_token %}
{{ form }}
<p><input type="submit" value="{% trans %}Show{% endtrans %}" /></p>
<p><input type="submit" value="{% trans %}Download as cvs{% endtrans %}" formaction="{{ url('club:sellings_csv', club_id=object.id) }}"/></p>
<p><input type="submit" value="{% trans %}Download as CSV{% endtrans %}" formaction="{{ url('club:sellings_csv', club_id=object.id) }}"/></p>
</form>
<p>
{% trans %}Quantity: {% endtrans %}{{ total_quantity }} {% trans %}units{% endtrans %}<br/>

View File

@@ -12,7 +12,7 @@ from django.utils.timezone import localdate, make_aware, now
from faker import Faker
from club.models import Club, Membership
from core.models import Group, User
from core.models import Group, User, UserBan
from counter.models import (
Counter,
Customer,
@@ -40,6 +40,7 @@ class Command(BaseCommand):
self.stdout.write("Creating users...")
users = self.create_users()
self.create_bans(random.sample(users, k=len(users) // 200)) # 0.5% of users
subscribers = random.sample(users, k=int(0.8 * len(users)))
self.stdout.write("Creating subscriptions...")
self.create_subscriptions(subscribers)
@@ -88,6 +89,8 @@ class Command(BaseCommand):
self.stdout.write("Done")
def create_users(self) -> list[User]:
# Create a single password hash for all users to make it faster.
# It's insecure as hell, but it's ok since it's only for dev purposes.
password = make_password("plop")
users = [
User(
@@ -114,14 +117,33 @@ class Command(BaseCommand):
public_group.users.add(*users)
return users
def create_bans(self, users: list[User]):
ban_groups = [
settings.SITH_GROUP_BANNED_COUNTER_ID,
settings.SITH_GROUP_BANNED_SUBSCRIPTION_ID,
settings.SITH_GROUP_BANNED_ALCOHOL_ID,
]
UserBan.objects.bulk_create(
[
UserBan(
user=user,
ban_group_id=i,
reason=self.faker.sentence(),
expires_at=make_aware(self.faker.future_datetime("+1y")),
)
for user in users
for i in random.sample(ban_groups, k=random.randint(1, len(ban_groups)))
]
)
def create_subscriptions(self, users: list[User]):
def prepare_subscription(_user: User, start_date: date) -> Subscription:
payment_method = random.choice(settings.SITH_SUBSCRIPTION_PAYMENT_METHOD)[0]
duration = random.randint(1, 4)
sub = Subscription(member=_user, payment_method=payment_method)
sub.subscription_start = sub.compute_start(d=start_date, duration=duration)
sub.subscription_end = sub.compute_end(duration)
return sub
s = Subscription(member=_user, payment_method=payment_method)
s.subscription_start = s.compute_start(d=start_date, duration=duration)
s.subscription_end = s.compute_end(duration)
return s
subscriptions = []
customers = []

View File

@@ -39,8 +39,9 @@ class ProductAdmin(SearchModelAdmin):
"code",
"product_type",
"selling_price",
"profit",
"archived",
"created_at",
"updated_at",
)
list_select_related = ("product_type",)
search_fields = ("name", "code")

View File

@@ -0,0 +1,67 @@
# Generated by Django 5.2.8 on 2026-02-10 15:40
from operator import attrgetter
import django.utils.timezone
from django.db import migrations, models
from django.db.migrations.state import StateApps
from django.db.models import OuterRef, Subquery
from counter.models import Selling
def apply_product_history_dates(apps: StateApps, schema_editor):
"""Approximate a posteriori the value of created_at and updated_at."""
Product = apps.get_model("counter", "Product")
sales_subquery = Selling.objects.filter(product=OuterRef("pk")).values("date")
# for products that have an associated sale, we set the creation date
# to the one of the first sale, and the update date to the one of the last sale
products = list(
Product.objects.exclude(sellings=None)
.annotate(
new_created_at=Subquery(sales_subquery.order_by("date")[:1]),
new_updated_at=Subquery(sales_subquery.order_by("-date")[:1]),
)
.only("id")
)
for product in products:
product.created_at = product.new_created_at
product.updated_at = product.new_updated_at
# For the remaining products (those without sale),
# they are given the creation and update date of the previous product having sales.
products_without_sale = list(Product.objects.filter(sellings=None).only("id"))
for product in products_without_sale:
previous_product = max(
(p for p in products if p.id < product.id), key=attrgetter("id")
)
product.created_at = previous_product.created_at
product.updated_at = previous_product.updated_at
products.extend(products_without_sale)
Product.objects.bulk_update(products, fields=["created_at", "updated_at"])
class Migration(migrations.Migration):
dependencies = [("counter", "0035_remove_selling_is_validated_and_more")]
operations = [
migrations.AddField(
model_name="product",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="product",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.RunPython(
apply_product_history_dates, reverse_code=migrations.RunPython.noop
),
]

View File

@@ -399,6 +399,8 @@ class Product(models.Model):
Group, related_name="products", verbose_name=_("buying groups"), blank=True
)
archived = models.BooleanField(_("archived"), default=False)
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
class Meta:
verbose_name = _("product")

View File

@@ -3,6 +3,8 @@
{% block content %}
{% if object %}
<h2>{% trans name=object %}Edit product {{ name }}{% endtrans %}</h2>
<p><i>{% trans %}Creation date{% endtrans %} : {{ object.created_at|date }}</i></p>
<p><i>{% trans %}Last update{% endtrans %} : {{ object.updated_at|date }}</i></p>
{% else %}
<h2>{% trans %}Product creation{% endtrans %}</h2>
{% endif %}

View File

@@ -89,7 +89,7 @@
:disabled="csvLoading"
:aria-busy="csvLoading"
>
{% trans %}Download as cvs{% endtrans %} <i class="fa fa-file-arrow-down"></i>
{% trans %}Download as CSV{% endtrans %} <i class="fa fa-file-arrow-down"></i>
</button>
</div>

View File

@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-08 16:14+0100\n"
"POT-Creation-Date: 2026-02-14 15:21+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -388,7 +388,7 @@ msgstr "Montrer"
#: club/templates/club/club_sellings.jinja
#: counter/templates/counter/product_list.jinja
msgid "Download as cvs"
msgid "Download as CSV"
msgstr "Télécharger en CSV"
#: club/templates/club/club_sellings.jinja
@@ -1566,7 +1566,7 @@ msgstr "Visiteur"
msgid "ban type"
msgstr "type de ban"
#: core/models.py
#: core/models.py counter/models.py
msgid "created at"
msgstr "créé le"
@@ -3109,6 +3109,10 @@ msgstr "groupe d'achat"
msgid "archived"
msgstr "archivé"
#: counter/models.py
msgid "updated at"
msgstr "mis à jour le"
#: counter/models.py
msgid "product"
msgstr "produit"
@@ -3664,6 +3668,14 @@ msgstr ""
msgid "Edit product %(name)s"
msgstr "Édition du produit %(name)s"
#: counter/templates/counter/product_form.jinja
msgid "Creation date"
msgstr "Date de création"
#: counter/templates/counter/product_form.jinja
msgid "Last update"
msgstr "Dernière mise à jour"
#: counter/templates/counter/product_form.jinja
msgid "Product creation"
msgstr "Création de produit"
@@ -3951,8 +3963,8 @@ msgid ""
"inconvenience."
msgstr ""
"Les paiements par carte bancaire sont actuellement désactivés sur l'eboutic. "
"Vous pouvez cependant toujours recharger votre compte dans un des lieux de vie de l'AE. "
"Veuillez nous excuser pour le désagrément."
"Vous pouvez cependant toujours recharger votre compte dans un des lieux de "
"vie de l'AE. Veuillez nous excuser pour le désagrément."
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid ""
@@ -4121,8 +4133,8 @@ msgstr "Les candidatures sont fermées pour cette élection"
#: election/templates/election/election_detail.jinja
msgid "Candidate pictures won't display for privacy reasons."
msgstr ""
"La photo du candidat ne s'affiche pas pour "
"des raisons de respect de la vie privée."
"La photo du candidat ne s'affiche pas pour des raisons de respect de la vie "
"privée."
#: election/templates/election/election_detail.jinja
msgid "Polls close "

6
uv.lock generated
View File

@@ -2243,11 +2243,11 @@ wheels = [
[[package]]
name = "sqlparse"
version = "0.5.4"
version = "0.5.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/67/701f86b28d63b2086de47c942eccf8ca2208b3be69715a1119a4e384415a/sqlparse-0.5.4.tar.gz", hash = "sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e", size = 120112, upload-time = "2025-11-28T07:10:18.377Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/70/001ee337f7aa888fb2e3f5fd7592a6afc5283adb1ed44ce8df5764070f22/sqlparse-0.5.4-py3-none-any.whl", hash = "sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb", size = 45933, upload-time = "2025-11-28T07:10:19.73Z" },
{ url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" },
]
[[package]]