diff --git a/la_chariotte/mail/templates/mail/order_confirm_mail.html b/la_chariotte/mail/templates/mail/order_confirm_mail.html index 9398d3d..398ae48 100644 --- a/la_chariotte/mail/templates/mail/order_confirm_mail.html +++ b/la_chariotte/mail/templates/mail/order_confirm_mail.html @@ -22,7 +22,7 @@
Rendez-vous pour la distribution :
le {{ order.grouped_order.delivery_date }}{% if order.grouped_order.delivery_slot %}, {{ order.grouped_order.delivery_slot }}{% endif %}
- {% if order.grouped_order.place %}
Lieu : {{ order.grouped_order.place }}{% endif %}
+ {% if order.place %}
Lieu : {{ order.place }}{% endif %}
Une question sur cette commande groupée ?
Vous pouvez contacter l'organisateur·ice de la commande, {{ order.grouped_order.orga }} :
@@ -32,4 +32,4 @@
Voir la page de commande
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/la_chariotte/order/admin.py b/la_chariotte/order/admin.py
index 8182731..96905e1 100644
--- a/la_chariotte/order/admin.py
+++ b/la_chariotte/order/admin.py
@@ -1,9 +1,10 @@
from django.contrib import admin
-from .models import GroupedOrder, Item, Order, OrderAuthor, OrderedItem
+from .models import GroupedOrder, Item, Order, OrderAuthor, OrderedItem, Place
admin.site.register(GroupedOrder)
admin.site.register(Order)
admin.site.register(Item)
admin.site.register(OrderedItem)
admin.site.register(OrderAuthor)
+admin.site.register(Place)
diff --git a/la_chariotte/order/forms.py b/la_chariotte/order/forms.py
index 2cf2817..ecb6bf2 100644
--- a/la_chariotte/order/forms.py
+++ b/la_chariotte/order/forms.py
@@ -1,11 +1,12 @@
import datetime
from django import forms
+from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth import get_user_model
from django.forms.utils import to_current_timezone
from django.utils import timezone
-from la_chariotte.order.models import GroupedOrder, Item
+from la_chariotte.order.models import GroupedOrder, Item, Place
class GroupedOrderForm(forms.ModelForm):
@@ -22,6 +23,13 @@ class GroupedOrderForm(forms.ModelForm):
label="Numéro de téléphone obligatoire pour les participants",
required=False,
)
+ places = forms.ModelMultipleChoiceField(
+ label="Lieux de distribution",
+ # TODO: filter own places
+ queryset=Place.objects.all(),
+ widget=forms.CheckboxSelectMultiple,
+ required=False,
+ )
class Meta:
model = GroupedOrder
@@ -31,7 +39,7 @@ class GroupedOrderForm(forms.ModelForm):
"deadline_time",
"delivery_date",
"delivery_slot",
- "place",
+ "places",
"description",
"is_phone_mandatory",
]
@@ -46,7 +54,6 @@ class GroupedOrderForm(forms.ModelForm):
"delivery_slot": forms.TextInput(
attrs={"placeholder": "14h - 17h (facultatif)"}
),
- "place": forms.TextInput(attrs={"placeholder": "(facultatif)"}),
"description": forms.Textarea(
attrs={
"placeholder": "Plus d'infos sur la commande groupée ? (facultatif)"
@@ -94,12 +101,57 @@ class ItemCreateForm(forms.ModelForm):
class JoinGroupedOrderForm(forms.Form):
- code = forms.CharField(label="Code pour rejoindre la commande", max_length=6)
+ order_code = forms.CharField(
+ label="Code pour rejoindre la commande", max_length=6, required=False
+ )
+ place_code = forms.CharField(
+ label="Code pour rejoindre le lieu", max_length=20, required=False
+ )
- def clean_code(self):
- form_code = self.cleaned_data["code"]
- if not GroupedOrder.objects.filter(code=form_code).exists():
+ def clean_place_code(self):
+ form_place_code = self.cleaned_data["place_code"]
+ if form_place_code and not Place.objects.filter(code=form_place_code).exists():
+ raise forms.ValidationError(
+ "Désolé, nous ne trouvons aucun lieu avec ce code"
+ )
+ return form_place_code
+
+ def clean_order_code(self):
+ form_order_code = self.cleaned_data["order_code"]
+ if (
+ form_order_code
+ and not GroupedOrder.objects.filter(code=form_order_code).exists()
+ ):
raise forms.ValidationError(
"Désolé, nous ne trouvons aucune commande avec ce code"
)
- return form_code
+ return form_order_code
+
+
+class PlaceForm(forms.ModelForm):
+ class Meta:
+ model = Place
+ fields = [
+ "name",
+ "description",
+ "code",
+ ]
+ widgets = {
+ "name": forms.TextInput(
+ attrs={"placeholder": "ex : Centre social Kropotkine"}
+ ),
+ "description": forms.Textarea(
+ attrs={"placeholder": "Plus d'infos sur le lieu ? (facultatif)"}
+ ),
+ "code": forms.TextInput(
+ attrs={"placeholder": "Identifiant unique du lieu (raccourci)"}
+ ),
+ }
+
+ def __init__(self, *args, **kwargs):
+ self.user = kwargs.pop("user")
+ super().__init__(*args, **kwargs)
+
+ def save(self, commit=True):
+ self.instance.orga = get_user_model().objects.get(id=self.user.pk)
+ return super().save(commit=commit)
diff --git a/la_chariotte/order/migrations/0030_add_place.py b/la_chariotte/order/migrations/0030_add_place.py
new file mode 100644
index 0000000..69005c2
--- /dev/null
+++ b/la_chariotte/order/migrations/0030_add_place.py
@@ -0,0 +1,83 @@
+from django.conf import settings
+from django.db import migrations, models
+
+import base36
+import random
+
+def random_code():
+ return base36.dumps(
+ random.randint(pow(36, code_length - 2), pow(36, code_length - 1) - 1)
+ )
+
+# Migrate existing GroupedOrder `place` to the new `place` table
+def link_existing_place(apps, schema_editor):
+ GroupedOrder = apps.get_model('order', 'GroupedOrder')
+ Place = apps.get_model('order', 'Place')
+ for grouped_order in GroupedOrder.objects.all():
+ if grouped_order.place:
+ # Generate new random code for this existing place
+ code = random_code()
+ while Place.objects.all().filter(code=code):
+ # Random code already exists, try a new random code
+ code = random_code()
+
+ place = Place.objects.create(
+ name=grouped_order.place,
+ code=code,
+ )
+
+ grouped_order.places.add(place)
+ grouped_order.save()
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("order", "0029_set_phone_mandatory_for_existing_orders"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Place",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=100, verbose_name="Nom du lieu de distribution")),
+ ("code", models.CharField(max_length=20, verbose_name="Identifiant unique du lieu (raccourci)", unique=True)),
+ ("orga", models.ForeignKey(on_delete=models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Organisateur·ice')),
+ ("description", models.TextField(blank=True, null=True, verbose_name="Description")),
+ ],
+ ),
+ migrations.AddField(
+ model_name="groupedorder",
+ name="places",
+ field=models.ManyToManyField(
+ to="order.place",
+ verbose_name="Lieux de distribution",
+ related_name="orders",
+ )
+ ),
+ migrations.RunPython(link_existing_place),
+ migrations.RemoveField(
+ model_name='groupedorder',
+ name='place',
+ ),
+ migrations.AddField(
+ model_name="order",
+ name="place",
+ field=models.ForeignKey(blank=True, null=True, on_delete=models.deletion.CASCADE, to='order.place'),
+ ),
+ migrations.AddIndex(
+ model_name="groupedorder",
+ index=models.Index(fields=["code"], name="order_group_code_50902d_idx"),
+ ),
+ migrations.AddIndex(
+ model_name="place",
+ index=models.Index(fields=["code"], name="order_place_code_4e2b27_idx"),
+ ),
+ ]
diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py
index 5450f72..57bc2ef 100644
--- a/la_chariotte/order/models.py
+++ b/la_chariotte/order/models.py
@@ -1,3 +1,4 @@
+import logging
import random
import base36
@@ -8,6 +9,8 @@ from django.utils import timezone
from la_chariotte.settings import AUTH_USER_MODEL
+logger = logging.getLogger(__name__)
+
class GroupedOrder(models.Model):
name = models.CharField(
@@ -21,15 +24,21 @@ class GroupedOrder(models.Model):
max_length=50, null=True, blank=True, verbose_name="Créneau de distribution"
)
deadline = models.DateTimeField("Date limite de commande")
- place = models.CharField(
- max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
+
+ # Associate with zero/more saved distribution places
+ places = models.ManyToManyField(
+ "order.Place", verbose_name="Lieux de distribution", related_name="orders"
)
+
description = models.TextField("Description", null=True, blank=True)
code = models.CharField(auto_created=True)
is_phone_mandatory = models.BooleanField(
default=False, verbose_name="Numéro de téléphone obligatoire"
)
+ class Meta:
+ indexes = [models.Index(fields=["code"])]
+
def create_code_from_pk(self):
"""When a grouped order is created, a unique code is generated, to be used to
build the order URL.
@@ -123,6 +132,9 @@ class Order(models.Model):
author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE)
created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True)
note = models.TextField(max_length=200, null=True, blank=True)
+ place = models.ForeignKey(
+ "order.place", on_delete=models.CASCADE, null=True, blank=True
+ )
@property
def articles_nb(self):
@@ -196,3 +208,47 @@ class OrderedItem(models.Model):
def __str__(self): # pragma: no cover
return f"{self.nb} {self.item}, dans la commande {self.order.pk}"
+
+
+class Place(models.Model):
+ name = models.CharField(max_length=100, verbose_name="Nom du lieu de distribution")
+ orga = models.ForeignKey(
+ AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Organisateur·ice"
+ )
+ code = models.CharField(
+ max_length=20,
+ verbose_name="Identifiant unique du lieu (raccourci)",
+ unique=True,
+ )
+ description = models.TextField("Description", null=True, blank=True)
+
+ class Meta:
+ indexes = [models.Index(fields=["code"])]
+
+ def __str__(self): # pragma: no cover
+ return self.name
+
+ def get_absolute_url(self):
+ return reverse("order:place_overview", kwargs={"code": self.code})
+
+ def active_orders(self):
+ # Only return currently active orders (not yet delivered)
+ active_orders = (
+ self.orders.all()
+ .filter(delivery_date__gt=timezone.now())
+ .order_by("-delivery_date")
+ )
+ return active_orders
+
+ def save(self, *args, **kwargs):
+ # Check that self.code was not changed
+ if self.pk is not None:
+ original = Place.objects.get(pk=self.pk)
+ if original.code != self.code:
+ # Restore the old place code
+ self.code = original.code
+ logger.warn(
+ "Le code du lieu '%s' ne peut pas changer après la création"
+ % self.name
+ )
+ super().save()
diff --git a/la_chariotte/order/templates/order/grouped_order_detail.html b/la_chariotte/order/templates/order/grouped_order_detail.html
index b73d467..0d5d5cc 100644
--- a/la_chariotte/order/templates/order/grouped_order_detail.html
+++ b/la_chariotte/order/templates/order/grouped_order_detail.html
@@ -27,10 +27,6 @@
{% endif %}
{{ grouped_order.place }}
- {% endif %}Commandes avant le {{ grouped_order.deadline|date:'d M Y' }} à {{ grouped_order.deadline|date:'H:i' }} @@ -171,6 +167,16 @@
+ {% if places %} + + + {% endif %} +