From cacf55561bb246b4d637e03b676a7844ee929756 Mon Sep 17 00:00:00 2001
From: selfhoster1312
Date: Thu, 6 Mar 2025 19:25:09 +0100
Subject: [PATCH] feat: Allow email/phone to be optional, required, or disabled
---
la_chariotte/order/forms.py | 24 +-
...roupedorder_is_phone_mandatory_and_more.py | 55 ++++
la_chariotte/order/models.py | 25 +-
.../templates/order/grouped_order_detail.html | 14 +-
la_chariotte/order/views/grouped_order.py | 5 +-
la_chariotte/order/views/order.py | 59 +++-
.../tests/test_order_views_grouped_order.py | 47 ++-
la_chariotte/tests/test_order_views_order.py | 284 ++++++++++++++++++
la_chariotte/tests/utils.py | 6 +-
9 files changed, 499 insertions(+), 20 deletions(-)
create mode 100644 la_chariotte/order/migrations/0030_remove_groupedorder_is_phone_mandatory_and_more.py
diff --git a/la_chariotte/order/forms.py b/la_chariotte/order/forms.py
index 2cf2817..0d53139 100644
--- a/la_chariotte/order/forms.py
+++ b/la_chariotte/order/forms.py
@@ -18,9 +18,26 @@ class GroupedOrderForm(forms.ModelForm):
widget=forms.TimeInput(attrs={"type": "time"}),
initial=datetime.time(hour=23, minute=59, second=59),
)
- is_phone_mandatory = forms.BooleanField(
- label="Numéro de téléphone obligatoire pour les participants",
+
+ required_phone = forms.TypedChoiceField(
+ label="Numéro de téléphone",
required=False,
+ choices=[
+ (0, "Désactivé"),
+ (1, "Facultatif"),
+ (2, "Obligatoire"),
+ ],
+ coerce=int,
+ )
+ required_email = forms.TypedChoiceField(
+ label="Adresse email",
+ required=False,
+ choices=[
+ (0, "Désactivé"),
+ (1, "Facultatif"),
+ (2, "Obligatoire"),
+ ],
+ coerce=int,
)
class Meta:
@@ -33,7 +50,8 @@ class GroupedOrderForm(forms.ModelForm):
"delivery_slot",
"place",
"description",
- "is_phone_mandatory",
+ "required_phone",
+ "required_email",
]
widgets = {
"name": forms.TextInput(
diff --git a/la_chariotte/order/migrations/0030_remove_groupedorder_is_phone_mandatory_and_more.py b/la_chariotte/order/migrations/0030_remove_groupedorder_is_phone_mandatory_and_more.py
new file mode 100644
index 0000000..d082ac6
--- /dev/null
+++ b/la_chariotte/order/migrations/0030_remove_groupedorder_is_phone_mandatory_and_more.py
@@ -0,0 +1,55 @@
+# Generated by Django 4.2.19 on 2025-03-06 16:37
+
+import django.core.validators
+from django.db import migrations, models
+
+
+def migrate_from_is_phone_mandatory(apps, schema_editor):
+ """For continuity, move is_phone_mandatory to the new required_phone field, and set
+ required_email=2 for the orders that were created so far."""
+
+ GroupedOrder = apps.get_model("order", "GroupedOrder")
+ for grouped_order in GroupedOrder.objects.all():
+ grouped_order.required_email = 2
+ grouped_order.required_phone = 2 if grouped_order.is_phone_mandatory else 1
+ grouped_order.save()
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("order", "0029_set_phone_mandatory_for_existing_orders"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="groupedorder",
+ name="required_email",
+ field=models.IntegerField(
+ verbose_name="Adresse email",
+ blank=True,
+ default=2,
+ validators=[
+ django.core.validators.MaxValueValidator(2),
+ django.core.validators.MinValueValidator(0),
+ ],
+ ),
+ ),
+ migrations.AddField(
+ model_name="groupedorder",
+ name="required_phone",
+ field=models.IntegerField(
+ verbose_name="Numéro de téléphone",
+ blank=True,
+ default=2,
+ validators=[
+ django.core.validators.MaxValueValidator(2),
+ django.core.validators.MinValueValidator(0),
+ ],
+ ),
+ ),
+ migrations.RunPython(migrate_from_is_phone_mandatory),
+ migrations.RemoveField(
+ model_name="groupedorder",
+ name="is_phone_mandatory",
+ ),
+ ]
diff --git a/la_chariotte/order/models.py b/la_chariotte/order/models.py
index 5450f72..e21c24e 100644
--- a/la_chariotte/order/models.py
+++ b/la_chariotte/order/models.py
@@ -2,6 +2,7 @@ import random
import base36
from django.core.exceptions import ValidationError
+from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
from django.utils import timezone
@@ -26,8 +27,28 @@ class GroupedOrder(models.Model):
)
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"
+
+ # Whether phone/email registration is possible/necessary for this grouped order
+ # 0 = disabled
+ # 1 = optional
+ # 2 = required
+ required_phone = models.IntegerField(
+ verbose_name="Numéro de téléphone",
+ default=2,
+ blank=True,
+ validators=[
+ MaxValueValidator(2),
+ MinValueValidator(0),
+ ],
+ )
+ required_email = models.IntegerField(
+ verbose_name="Adresse email",
+ default=2,
+ blank=True,
+ validators=[
+ MaxValueValidator(2),
+ MinValueValidator(0),
+ ],
)
def create_code_from_pk(self):
diff --git a/la_chariotte/order/templates/order/grouped_order_detail.html b/la_chariotte/order/templates/order/grouped_order_detail.html
index b73d467..ef3ba38 100644
--- a/la_chariotte/order/templates/order/grouped_order_detail.html
+++ b/la_chariotte/order/templates/order/grouped_order_detail.html
@@ -157,16 +157,22 @@
+ {% if required_phone or required_email %}
-
+ {% if required_phone != 0 %}
+
-
+ {% if required_phone == 2 %}required{% endif %}>
+ {% endif %}
+ {% if required_email != 0 %}
+
+ value="{{ order_author.email }}" {% if required_email == 2 %}required{% endif %}>
+ {% endif %}
+ {% endif %}
diff --git a/la_chariotte/order/views/grouped_order.py b/la_chariotte/order/views/grouped_order.py
index 55d9cca..ea31135 100644
--- a/la_chariotte/order/views/grouped_order.py
+++ b/la_chariotte/order/views/grouped_order.py
@@ -134,8 +134,9 @@ class GroupedOrderDetailView(generic.DetailView):
"prices_dict": json.dumps(prices_dict, cls=DjangoJSONEncoder),
"remaining_qty": remaining_qty,
"order_author": order_author,
- # Used to set if the phone is required in the form
- "is_phone_mandatory": grouped_order.is_phone_mandatory,
+ # Used to set if the phone/email is required/optional/disabled in the form
+ "required_phone": grouped_order.required_phone,
+ "required_email": grouped_order.required_email,
}
)
return context
diff --git a/la_chariotte/order/views/order.py b/la_chariotte/order/views/order.py
index 032b219..3a5f019 100644
--- a/la_chariotte/order/views/order.py
+++ b/la_chariotte/order/views/order.py
@@ -40,15 +40,67 @@ def place_order(request, code):
)
orders_dict[i] = value
- # create an order
+ error_message = None
+
first_name = request.POST["first_name"]
last_name = request.POST["last_name"]
- phone = request.POST["phone"]
- email = request.POST["email"]
note = request.POST["note"]
+
+ # check if required/optional phone is supplied
+ # When not supplied, use "" because the order_author table phone has a NOT NULL statement
+ error_message = None
+ if grouped_order.required_phone == 0:
+ phone = ""
+ else:
+ if "phone" in request.POST and request.POST["phone"] != "":
+ # Phone is supplied, whether required or not
+ phone = request.POST["phone"]
+ elif grouped_order.required_phone == 2:
+ # Phone is not supplied, but was required
+ error_message = (
+ "Le numéro de téléphone est obligatoire pour cette commande groupée"
+ )
+ phone = ""
+ else:
+ # Phone is not supplied, but was not mandatory
+ phone = ""
+
+ # check if required/optional email is supplied
+ # When not supplied, use "" because the order_author table email has a NOT NULL statement
+ if grouped_order.required_email == 0:
+ email = ""
+ else:
+ if "email" in request.POST and request.POST["email"] != "":
+ # email is supplied, whether required or not
+ email = request.POST["email"]
+ elif grouped_order.required_email == 2:
+ # email is not supplied, but was required
+ error_message = (
+ "L'adresse email est obligatoire pour cette commande groupée"
+ )
+ email = ""
+ else:
+ # email is not supplied, but was not mandatory
+ email = ""
+
+ # create an author
author = OrderAuthor.objects.create(
first_name=first_name, last_name=last_name, email=email, phone=phone
)
+ if error_message:
+ author.delete()
+ return render(
+ request,
+ "order/grouped_order_detail.html",
+ {
+ "grouped_order": grouped_order,
+ "error_message": error_message,
+ "note": note,
+ "author": author,
+ },
+ )
+
+ # create an order
order = Order.objects.create(
author=author,
grouped_order=grouped_order,
@@ -57,7 +109,6 @@ def place_order(request, code):
)
# add items to the order
- error_message = None
for key, quantity in orders_dict.items():
quantity = int(quantity)
item = grouped_order.item_set.get(pk=key.split("_")[1])
diff --git a/la_chariotte/tests/test_order_views_grouped_order.py b/la_chariotte/tests/test_order_views_grouped_order.py
index ce6fa36..418f944 100644
--- a/la_chariotte/tests/test_order_views_grouped_order.py
+++ b/la_chariotte/tests/test_order_views_grouped_order.py
@@ -362,7 +362,7 @@ class TestGroupedOrderDetailView:
"first_name": {authenticated_user_with_name.first_name},
"last_name": {authenticated_user_with_name.last_name},
"phone": "0645632569",
- "email": {authenticated_user_with_name.email},
+ "email": {authenticated_user_with_name.username},
"note": "test note",
},
)
@@ -670,7 +670,7 @@ class TestGroupedOrderDetailView:
name="gr order test",
orga_user=other_user,
)
- assert grouped_order.is_phone_mandatory == True
+ assert grouped_order.required_phone == 2
item = models.Item.objects.create(
name="test item 1", grouped_order=grouped_order, price=1, max_limit=2
)
@@ -686,12 +686,53 @@ class TestGroupedOrderDetailView:
assert (
"Numéro de téléphone (facultatif)" not in response.content.decode()
)
- grouped_order.is_phone_mandatory = False
+
+ grouped_order.required_phone = 1
grouped_order.save()
response = client.get(detail_url)
assert "gr order test" in response.content.decode()
assert "Numéro de téléphone (facultatif)" in response.content.decode()
+ grouped_order.required_phone = 0
+ grouped_order.save()
+ response = client.get(detail_url)
+ assert "gr order test" in response.content.decode()
+ assert '