Compare commits

...

5 commits

Author SHA1 Message Date
Xavier Meunier
483ec463e5 Merge branch 'refactor/make-aggregated-values-computed-on-the-fly-instead-of-recorded-in-DB' into 'develop'
Make aggregated values computed when called instead of stored in DB

See merge request la-chariotte/la-chariotte!123
2024-11-05 11:01:03 +00:00
xmeunier
686a31b96a Make aggregated values computed when called instead of stored in DB
- GroupedOrder.total_price
- Order.articles_nb
- Order.price
- Item.ordered_nb
2024-11-05 11:01:02 +00:00
3d38bb56c0 Populate the changelog and bump the version to 1.3.0 2024-11-05 11:00:21 +00:00
Ploc
99db86153d refactor: change license file from text to markdown
Change license file from text to markdown as markdown adds some markup that makes it easier to read than text.
2024-11-02 15:04:01 +00:00
Ploc
8741d6d9b6 fix: license text
Copy license text from https://www.gnu.org/licenses/agpl-3.0.txt
2024-11-02 15:04:01 +00:00
10 changed files with 406 additions and 1039 deletions

View file

@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.3.0 (2024-11-02)
This is a small release, with a few features and bugfixes. The code is now
hosted on https://framagit.org rather than https://gitlab.com.
### Added
- Changed hour and date format in the generated CSV files
- Added the date in the grouped order dashboard
- Changed the display of the last possible date and hour to place an order
- Added the total number of ordered items in the grouped orders overview
- Removed header line and column for grouped order name in the CSV export for emails
### Fixed
- The "reset password" form is now fixed, it was broken since the last release.
## 1.2.0 (2024-07-12) ## 1.2.0 (2024-07-12)
A small release, with a few features, a new footer and a `/stats` endpoint. A small release, with a few features, a new footer and a `/stats` endpoint.

1092
LICENSE

File diff suppressed because it is too large Load diff

View file

@ -22,22 +22,21 @@ classDiagram
name name
deadline : DateTime deadline : DateTime
delivery_date : Date delivery_date : Date
delivery_slot
place place
description description
orga : CustomUser orga : CustomUser
total_price
} }
class Item{ class Item{
name name
grouped_order : GroupedOrder grouped_order : GroupedOrder
ordered_nb ordered_nb
total_price price
max_limit max_limit
} }
class Order{ class Order{
grouped_order : GroupedOrder grouped_order : GroupedOrder
author : OrderAuthor author : OrderAuthor
price
created_date created_date
note note
} }

View file

@ -1 +1 @@
__version__ = "1.2.0" __version__ = "1.3.0"

View file

@ -0,0 +1,36 @@
# Generated by Django 4.2.16 on 2024-10-31 21:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("order", "0026_groupedorder_delivery_slot"),
]
operations = [
migrations.RemoveField(
model_name="groupedorder",
name="total_price",
),
migrations.RemoveField(
model_name="item",
name="ordered_nb",
),
migrations.RemoveField(
model_name="order",
name="articles_nb",
),
migrations.RemoveField(
model_name="order",
name="price",
),
migrations.AlterField(
model_name="order",
name="created_date",
field=models.DateTimeField(
auto_now_add=True, verbose_name="Date et heure de commande"
),
),
]

View file

@ -25,7 +25,6 @@ class GroupedOrder(models.Model):
max_length=100, null=True, blank=True, verbose_name="Lieu de livraison" max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
) )
description = models.TextField("Description", null=True, blank=True) description = models.TextField("Description", null=True, blank=True)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
code = models.CharField(auto_created=True) code = models.CharField(auto_created=True)
def create_code_from_pk(self): def create_code_from_pk(self):
@ -52,16 +51,12 @@ class GroupedOrder(models.Model):
) )
self.code = f"{base_36_pk}{random_string}"[:code_length] self.code = f"{base_36_pk}{random_string}"[:code_length]
def compute_total_price(self): @property
def total_price(self):
price = 0 price = 0
for order in self.order_set.all(): for order in self.order_set.all():
price += order.price price += order.price
self.total_price = price return price
self.save()
def compute_items_ordered_nb(self):
for item in self.item_set.all():
item.compute_ordered_nb()
def get_total_ordered_items(self): def get_total_ordered_items(self):
total_nb = 0 total_nb = 0
@ -123,26 +118,24 @@ class Order(models.Model):
GroupedOrder, on_delete=models.CASCADE, related_name="order_set" GroupedOrder, on_delete=models.CASCADE, related_name="order_set"
) )
author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE) author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE)
articles_nb = models.PositiveIntegerField(default=0)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True) created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True)
note = models.TextField(max_length=200, null=True, blank=True) note = models.TextField(max_length=200, null=True, blank=True)
def compute_order_articles_nb(self): @property
def articles_nb(self):
"""Computes the number of articles in this order""" """Computes the number of articles in this order"""
articles_nb = 0 articles_nb = 0
for ord_item in self.ordered_items.all(): for ord_item in self.ordered_items.all():
articles_nb += ord_item.nb articles_nb += ord_item.nb
self.articles_nb = articles_nb return articles_nb
self.save()
def compute_order_price(self): @property
def price(self):
"""Computes the total price of the order""" """Computes the total price of the order"""
price = 0 price = 0
for ord_item in self.ordered_items.all(): for ord_item in self.ordered_items.all():
price += ord_item.get_price() price += ord_item.get_price()
self.price = price return price
self.save()
def __str__(self): # pragma: no cover def __str__(self): # pragma: no cover
return ( return (
@ -157,15 +150,13 @@ class Item(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2) price = models.DecimalField(max_digits=10, decimal_places=2)
max_limit = models.PositiveSmallIntegerField(null=True, blank=True) max_limit = models.PositiveSmallIntegerField(null=True, blank=True)
ordered_nb = models.IntegerField(default=0) @property
def ordered_nb(self):
def compute_ordered_nb(self):
"""Computes the number of times this item has been ordered""" """Computes the number of times this item has been ordered"""
ordered_nb = 0 ordered_nb = 0
for order in self.orders.all(): for order in self.orders.all():
ordered_nb += order.nb ordered_nb += order.nb
self.ordered_nb = ordered_nb return ordered_nb
self.save()
def get_total_price(self): def get_total_price(self):
"""Returns the total price of all orders on this item""" """Returns the total price of all orders on this item"""

View file

@ -150,12 +150,6 @@ class GroupedOrderOverview(UserIsOrgaMixin, generic.DetailView):
# Staff can see but not edit grouped orders # Staff can see but not edit grouped orders
return super().test_func() or self.request.user.is_staff return super().test_func() or self.request.user.is_staff
def get(self, request, *args, **kwargs):
# Compute grouped order total price before display
self.get_object().compute_total_price()
self.get_object().compute_items_ordered_nb()
return super().get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GroupedOrderOverview, self).get_context_data(**kwargs) context = super(GroupedOrderOverview, self).get_context_data(**kwargs)
# Add share link to context # Add share link to context
@ -228,7 +222,6 @@ class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
# duplicate each item and add it to new_grouped_order # duplicate each item and add it to new_grouped_order
for item in initial_grouped_order.item_set.all(): for item in initial_grouped_order.item_set.all():
item.pk = None item.pk = None
item.ordered_nb = 0
item.save() item.save()
new_grouped_order.item_set.add(item) new_grouped_order.item_set.add(item)
@ -300,7 +293,11 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
context = super(GroupedOrderExportView, self).get_context_data(**kwargs) context = super(GroupedOrderExportView, self).get_context_data(**kwargs)
grouped_order = self.get_object() grouped_order = self.get_object()
items = grouped_order.item_set.filter(ordered_nb__gt=0).order_by("name") items = [
item
for item in grouped_order.item_set.all().order_by("name")
if item.ordered_nb > 0
]
orders = grouped_order.order_set.all().order_by( orders = grouped_order.order_set.all().order_by(
"author__last_name", "author__first_name" "author__last_name", "author__first_name"
) )

View file

@ -72,7 +72,6 @@ def place_order(request, code):
if error_message: if error_message:
order.delete() order.delete()
author.delete() author.delete()
grouped_order.compute_items_ordered_nb()
return render( return render(
request, request,
"order/grouped_order_detail.html", "order/grouped_order_detail.html",
@ -103,8 +102,6 @@ def place_order(request, code):
) )
# Send confirmation mail and redirect to confirmation page # Send confirmation mail and redirect to confirmation page
order.compute_order_price()
grouped_order.compute_items_ordered_nb()
send_order_confirmation_mail(order) send_order_confirmation_mail(order)
# Redirect to prevent data from being posted twice when the user hits the Back # Redirect to prevent data from being posted twice when the user hits the Back
@ -123,7 +120,6 @@ def validate_item_ordered_nb(item, ordered_nb):
def validate_articles_ordered_nb(order): def validate_articles_ordered_nb(order):
"""Return an error if no items are ordered""" """Return an error if no items are ordered"""
order.compute_order_articles_nb()
if order.articles_nb == 0: if order.articles_nb == 0:
return "Veuillez commander au moins un produit" return "Veuillez commander au moins un produit"
return None return None

View file

@ -1396,7 +1396,7 @@ class TestGroupedOrderSheetView:
response = client_log.get(generate_sheet_url) response = client_log.get(generate_sheet_url)
assert response.status_code == 200 assert response.status_code == 200
assert response.context["grouped_order"] == grouped_order assert response.context["grouped_order"] == grouped_order
assert response.context["items"].count() == 0 assert len(response.context["items"]) == 0
assert len(response.context["orders_dict"]) == 0 assert len(response.context["orders_dict"]) == 0
# we order some items in the grouped order # we order some items in the grouped order
@ -1404,7 +1404,7 @@ class TestGroupedOrderSheetView:
response = client_log.get(generate_sheet_url) response = client_log.get(generate_sheet_url)
assert response.status_code == 200 assert response.status_code == 200
assert response.context["grouped_order"] == grouped_order assert response.context["grouped_order"] == grouped_order
assert response.context["items"].count() == 2 assert len(response.context["items"]) == 2
assert response.context["orders_dict"][order] == [3, 2] assert response.context["orders_dict"][order] == [3, 2]
assert response.context["grouped_order"].total_price == 35 assert response.context["grouped_order"].total_price == 35

View file

@ -44,11 +44,4 @@ def order_items_in_grouped_order(grouped_order):
models.OrderedItem.objects.create(order=order, item=item_2, nb=2) models.OrderedItem.objects.create(order=order, item=item_2, nb=2)
models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1) models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1)
models.OrderedItem.objects.create(order=order_3, item=item_2, nb=1) models.OrderedItem.objects.create(order=order_3, item=item_2, nb=1)
item_1.compute_ordered_nb()
item_2.compute_ordered_nb()
order.compute_order_price()
order_2.compute_order_price()
order_3.compute_order_price()
grouped_order.compute_total_price()
grouped_order.save()
return order return order