la-chariotte/la_chariotte/order/models.py

186 lines
7 KiB
Python

import random
import base36
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils import timezone
from la_chariotte.settings import AUTH_USER_MODEL
CODE_LENGTH = 6
class GroupedOrder(models.Model):
name = models.CharField(
max_length=100, null=True, verbose_name="Titre de la commande"
)
orga = models.ForeignKey(
AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Organisateur·ice"
)
delivery_date = models.DateField("Date de livraison")
deadline = models.DateTimeField("Date limite de commande")
place = models.CharField(
max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
)
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)
def create_code_from_pk(self):
"""When a grouped order is created, we compute a unique code that will be used in url path
How we generate this code :
1. The instance pk, written in base36 (max 5 digits for now - we assume that there will not be more than 60466175 grouped orders)
2. A random int written in base36 (5 digits long)
3. Only the 6 first digits of this string
The use of pk in the beginning of the string guarantees the uniqueness, and the random part makes that we cannot guess the url path.
"""
base_36_pk = base36.dumps(self.pk)
random_string = base36.dumps(
random.randint(pow(36, CODE_LENGTH - 2), pow(36, CODE_LENGTH - 1) - 1)
) # generates a 5 digits long string
self.code = f"{base_36_pk}{random_string}"[:CODE_LENGTH]
def compute_total_price(self):
price = 0
for order in self.order_set.all():
price += order.price
self.total_price = price
self.save()
def compute_items_ordered_nb(self):
"""Updates the ordered_nb of all items fo the grouped_order"""
for item in self.item_set.all():
item.compute_ordered_nb()
def is_ongoing(self):
"""Returns True if the grouped order is open for new Orders - False if it's too late"""
return self.deadline >= timezone.now()
def is_to_be_delivered(self):
"""Returns True if the grouped order has not been delivered yet - False if it's old"""
return self.delivery_date >= timezone.now().date()
def get_absolute_url(self):
return reverse("order:manage_items", kwargs={"pk": self.pk})
def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude)
if self.delivery_date < timezone.now().date() and not self.pk:
# if the grouped order is being created (not updated), it cannot be in the past
# if we are updating, it's ok
raise ValidationError("La date de livraison ne doit pas être dans le passé")
if self.delivery_date < self.deadline.date():
raise ValidationError(
"La date limite de commande doit être avant la date de livraison"
)
def __str__(self): # pragma: no cover
return (
self.name
if self.name
else f"Commande groupée {self.pk} du {self.date} organisée par {self.orga}"
)
class OrderAuthor(models.Model):
"""Created when a user orders without having an account - or when a user creates an account"""
# TODO faire le lien avec CustomUser (CustomUser hérite de OrderAuthor), pour ensuite préremplir quand on est connecté·e
first_name = models.CharField(verbose_name="Prénom")
last_name = models.CharField(verbose_name="Nom")
phone = models.CharField(
verbose_name="Numéro de téléphone",
help_text="Pour que l'organisateur·ice vous contacte en cas de besoin",
)
email = models.CharField(
verbose_name="Adresse mail",
help_text="Pour que l'organisateur·ice vous contacte en cas de besoin",
)
def __str__(self): # pragma: no cover
return f"{self.first_name} {self.last_name}"
class Order(models.Model):
grouped_order = models.ForeignKey(
GroupedOrder, on_delete=models.CASCADE, related_name="order_set"
)
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 de la commande", auto_now_add=True)
note = models.TextField(max_length=200, null=True, blank=True)
def compute_order_articles_nb(self):
"""Computes the number of articles in this order"""
articles_nb = 0
for ord_item in self.ordered_items.all():
articles_nb += ord_item.nb
self.articles_nb = articles_nb
self.save()
def compute_order_price(self):
"""Computes the total price of the order"""
price = 0
for ord_item in self.ordered_items.all():
price += ord_item.get_price()
self.price = price
self.save()
def __str__(self): # pragma: no cover
return f"Commande de {self.author} pour la commande groupée {self.grouped_order.pk}"
class Item(models.Model):
name = models.CharField(max_length=100)
grouped_order = models.ForeignKey(GroupedOrder, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
max_limit = models.PositiveSmallIntegerField(null=True, blank=True)
ordered_nb = models.IntegerField(default=0)
def compute_ordered_nb(self):
"""Computes the number of ordered articles for this item (in this grouped order)"""
ordered_nb = 0
for order in self.orders.all():
ordered_nb += order.nb
self.ordered_nb = ordered_nb
self.save()
def get_total_price(self):
"""Returns the price of all orders on this item"""
return self.price * self.ordered_nb
def get_remaining_nb(self):
"""Returns the number of remaining articles for this item in this grouped order"""
if self.max_limit is not None:
return self.max_limit - self.ordered_nb
else:
return None
def get_absolute_url(self):
return reverse("order:manage_items", kwargs={"pk": self.grouped_order.pk})
def __str__(self): # pragma: no cover
return f"{self.name} ({self.price} €)"
class Meta:
ordering = ["name"]
class OrderedItem(models.Model):
"""Item in one specific Order, and the number of articles"""
nb = models.PositiveSmallIntegerField(default=0) # works up to 32767
order = models.ForeignKey(
Order, on_delete=models.CASCADE, related_name="ordered_items"
)
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name="orders")
def get_price(self):
return self.nb * self.item.price
def __str__(self): # pragma: no cover
return f"{self.nb} {self.item}, dans la commande {self.order.pk}"