mirror of
https://framagit.org/la-chariotte/la-chariotte.git
synced 2025-04-28 18:02:43 +02:00
Merge branch 'feat-distribution-place' into 'develop'
Draft: feature: Add placekey relationship for distribution spots See merge request la-chariotte/la-chariotte!129
This commit is contained in:
commit
fb1731fe73
26 changed files with 1781 additions and 138 deletions
|
@ -22,7 +22,7 @@
|
|||
|
||||
<p><strong>Rendez-vous pour la distribution</strong> :
|
||||
le {{ order.grouped_order.delivery_date }}{% if order.grouped_order.delivery_slot %}, {{ order.grouped_order.delivery_slot }}{% endif %}
|
||||
{% if order.grouped_order.place %}<br>Lieu : {{ order.grouped_order.place }}{% endif %}
|
||||
{% if order.place %}<br>Lieu : {{ order.place }}{% endif %}
|
||||
</p>
|
||||
|
||||
<p><strong>Une question sur cette commande groupée ?</strong><br>Vous pouvez contacter l'organisateur·ice de la commande, <strong>{{ order.grouped_order.orga }}</strong> :
|
||||
|
@ -32,4 +32,4 @@
|
|||
<a class="button" href="{{ base_url }}{% url 'order:grouped_order_detail' order.grouped_order.code %}">Voir la page de commande</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
83
la_chariotte/order/migrations/0030_add_place.py
Normal file
83
la_chariotte/order/migrations/0030_add_place.py
Normal file
|
@ -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"),
|
||||
),
|
||||
]
|
|
@ -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()
|
||||
|
|
|
@ -27,10 +27,6 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="column">
|
||||
{% if grouped_order.place %}
|
||||
<p><i class="fa fa-map-pin mr-3" aria-label="Lieu" title="Lieu"
|
||||
aria-hidden="true"></i>{{ grouped_order.place }}</p>
|
||||
{% endif %}
|
||||
<p><i class="fa fa-calendar-check-o mr-3" aria-label="Date limite de commande"
|
||||
title="Date limite de commande" aria-hidden="true"></i>
|
||||
Commandes avant le {{ grouped_order.deadline|date:'d M Y' }} à {{ grouped_order.deadline|date:'H:i' }}
|
||||
|
@ -171,6 +167,16 @@
|
|||
<p><label for="note">Note à l'organisateur·ice<em> (facultatif)</em> :</label>
|
||||
<textarea id="note" rows=3 name="note">{{ note }}</textarea></p>
|
||||
|
||||
{% if places %}
|
||||
<label for="place">Point de distribution:</label>
|
||||
<select name="place" id="place-select">
|
||||
{% for place in places %}
|
||||
<option value="{{ place.code }}">{{ place.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
<hr>
|
||||
|
||||
<div class="buttons">
|
||||
<button id="submit" type="submit" value="Order" class="button is-primary">
|
||||
<i class="fa fa-shopping-basket mr-3" aria-hidden="true"></i>Commander
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
<header class="modal-card-head has-background-info">
|
||||
<div class="modal-card-title-container">
|
||||
<p class="modal-card-title mb-2">Commande de {{ order.author }}</p>
|
||||
{% if order.place %}<p class="has-text-grey-dark">Lieu: {{ order.place }}</p>{% endif %}
|
||||
<p class="has-text-grey-dark">Le {{ order.created_date|date:'d M Y' }} à {{ order.created_date|date:'H:i' }}</p>
|
||||
</div>
|
||||
<button class="delete" aria-label="close"></button>
|
||||
|
@ -370,4 +371,4 @@
|
|||
});
|
||||
|
||||
{% endblock %}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -75,64 +75,13 @@
|
|||
<h2 style="text-align: center">
|
||||
{{ grouped_order.name }} - {{ grouped_order.delivery_date }}
|
||||
</h2>
|
||||
{% if items %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="font-size: 0.5em; width: 2em">OK</th>
|
||||
<th style="text-align: center; width: 20%">Nom</th>
|
||||
{% for item in items %}
|
||||
<th class="item_name" style="font-weight: normal;">
|
||||
<div>{{ item.name }}</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th style="width: 2cm">Prix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="background-color: #bababa">
|
||||
<td></td>
|
||||
<td style="text-align: left">Prix unitaire</td>
|
||||
{% for item in items %}
|
||||
<td>
|
||||
{{ item.price }} €
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr style="background-color: #bababa">
|
||||
<th></th>
|
||||
<th style="text-align: left">TOTAL</th>
|
||||
{% for item in items %}
|
||||
<th>
|
||||
{{ item.ordered_nb }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th>{{ grouped_order.total_price }} €</th>
|
||||
</tr>
|
||||
{% for order, ordered_items in orders_dict.items %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
{{ order.author.last_name|upper }} {{ order.author.first_name }}
|
||||
</td>
|
||||
{% for ordered_item in ordered_items %}
|
||||
<td>
|
||||
{% if ordered_item > 0 %}
|
||||
{{ ordered_item }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td>
|
||||
{{ order.price }} €
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if places %}
|
||||
{% for place_name, place_orders in places.items %}
|
||||
<h3>{{ place_name }}</h3>
|
||||
{% include 'order/grouped_order_sheet_list.html' with orders_dict=place_orders items=items grouped_order=grouped_order %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Aucun produit n'a été commandé
|
||||
{% include 'order/grouped_order_sheet_list.html' with orders_dict=orders_dict items=items grouped_order=grouped_order %}
|
||||
{% endif %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
{% if items %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="font-size: 0.5em; width: 2em">OK</th>
|
||||
<th style="text-align: center; width: 20%">Nom</th>
|
||||
{% for item in items %}
|
||||
<th class="item_name" style="font-weight: normal;">
|
||||
<div>{{ item.name }}</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th style="width: 2cm">Prix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="background-color: #bababa">
|
||||
<td></td>
|
||||
<td style="text-align: left">Prix unitaire</td>
|
||||
{% for item in items %}
|
||||
<td>
|
||||
{{ item.price }} €
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr style="background-color: #bababa">
|
||||
<th></th>
|
||||
<th style="text-align: left">TOTAL</th>
|
||||
{% for item in items %}
|
||||
<th>
|
||||
{{ item.ordered_nb }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th>{{ grouped_order.total_price }} €</th>
|
||||
</tr>
|
||||
{% for order, ordered_items in orders_dict.items %}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
{{ order.author.last_name|upper }} {{ order.author.first_name }}
|
||||
</td>
|
||||
{% for ordered_item in ordered_items %}
|
||||
<td>
|
||||
{% if ordered_item > 0 %}
|
||||
{{ ordered_item }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td>
|
||||
{{ order.price }} €
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
Aucun produit n'a été commandé
|
||||
{% endif %}
|
62
la_chariotte/order/templates/place/index.html
Normal file
62
la_chariotte/order/templates/place/index.html
Normal file
|
@ -0,0 +1,62 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Mes lieux de livraison{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p class="desktop-hidden mobile-content-title">
|
||||
{% block content_title %}Lieux de distribution que vous organisez{% endblock %}
|
||||
</p>
|
||||
<div class="buttons is-pulled-right">
|
||||
<a class="button is-primary" href="{% url 'order:place_create' %}">
|
||||
<i class="fa fa-plus-circle mr-3" aria-hidden="true"></i>
|
||||
Créer un nouveau lieu de distribution</a>
|
||||
</div>
|
||||
{% if context.places %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Lieu</th>
|
||||
<th>Commandes</th>
|
||||
<th>Description</th>
|
||||
<th>URL</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for place in context.places %}
|
||||
<tr>
|
||||
<td>
|
||||
(<a href="{% url 'order:place_update' place.code %}">modifier</a>) <a title="Détail du lieu de distribution" href="{% url 'order:place_overview' place.code %}">{{ place }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if place.code in context.orders.keys %}
|
||||
{% for place_code, place_orders in context.orders.items %}
|
||||
{% if place_code == place.code %}
|
||||
{% for order in place_orders %}
|
||||
{% url 'order:grouped_order_detail' code=order.code as order_url %}
|
||||
{% if order_url %}
|
||||
<a href="{{ order_url }}">{{order.name}}</a><br>
|
||||
{% else %}
|
||||
{{ order.name }}<br>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Aucune
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ place.description }}
|
||||
</td>
|
||||
<td>
|
||||
{{ place.code }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>Pas de lieux de distribution pour l'instant</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
26
la_chariotte/order/templates/place/place_create.html
Normal file
26
la_chariotte/order/templates/place/place_create.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Nouveau lieu de distribution{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p class="desktop-hidden mobile-content-title">
|
||||
{% block content_title %}Créer un lieu de distribution{% endblock %}
|
||||
</p>
|
||||
<div class="box">
|
||||
<p class="title">Nouveau lieu</p>
|
||||
<div class="columns">
|
||||
<div class="column is-8">
|
||||
<form method="post" onsubmit="deadlinePassedCheck(event)">
|
||||
{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<div class="buttons">
|
||||
<a class="button is-light" href="{% url 'order:place_index' %}">Annuler</a>
|
||||
<input class="button is-primary" type="submit" value="Suivant">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
58
la_chariotte/order/templates/place/place_overview.html
Normal file
58
la_chariotte/order/templates/place/place_overview.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Commandes groupées à: {{ place.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if user.is_authenticated %}
|
||||
<div class="buttons">
|
||||
<a class="button is-light" href="{% url 'order:place_index' %}">
|
||||
<i class="fa fa-arrow-left mr-3" aria-hidden="true"></i>Retour aux lieux
|
||||
</a>
|
||||
<a class="button is-primary" href="{% url 'order:index' %}">
|
||||
<i class="fa fa-shopping-basket mr-3" aria-hidden="true"></i>Mes commandes
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user == place.orga %}
|
||||
<div class="buttons is-pulled-right">
|
||||
<a class="button is-primary" href="{% url 'order:place_update' place.code %}">
|
||||
<i class="fa fa-plus-circle mr-3" aria-hidden="true"></i>
|
||||
Modifier</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="desktop-hidden mobile-content-title">
|
||||
{% block content_title %}{{ place.name }}: Commandes groupées{% endblock %}
|
||||
</p>
|
||||
{% if active_orders %}
|
||||
<p class="title">{{ place.name }} ({{ active_orders | length }} commandes groupées en cours)</p>
|
||||
{% for order in active_orders %}
|
||||
<div class="box">
|
||||
<h4><a href="{% url "order:grouped_order_detail" order.code %}">{{ order.name }}</a> ({{ order.item_set.all|length }} produit{% if order.item_set.all|length > 1 %}s{% endif %})</h4>
|
||||
<p>
|
||||
<i class="fa fa-calendar" aria-hidden="true"></i> <i>Fin des commandes</i> le {{ order.deadline }}
|
||||
<br><i class="fa fa-truck" aria-hidden="true"></i> <i>Livraison</i> le {{ order.delivery_date }}
|
||||
{% if order.places.all|length > 1 %}
|
||||
<br><i class="fa fa-building" aria-hidden="true"></i> <i>Autres lieux de livraison:</i>
|
||||
{% for other_place in order.places.all %}
|
||||
{% if other_place.code != place.code %}
|
||||
<a href="{% url 'order:place_overview' other_place.code %}">{{ other_place.name }}</a>
|
||||
{% if forloop.counter0|add:"2" < order.places.all|length %}, {% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="buttons">
|
||||
<a class="button is-primary" href="{% url 'order:grouped_order_detail' order.code %}">
|
||||
<i class="fa fa-shopping-basket mr-3" aria-hidden="true"></i>
|
||||
Commander</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Aucune commande groupée en cours dans ce lieu.</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
22
la_chariotte/order/templates/place/place_update.html
Normal file
22
la_chariotte/order/templates/place/place_update.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Modifier le lieu de livraison {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p class="desktop-hidden mobile-content-title">
|
||||
{% block content_title %}Modifier le lieu de livraison{% endblock %}
|
||||
</p>
|
||||
<div class="box">
|
||||
<p class="title">{{ place.name }} - modifier</p>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<div class="buttons">
|
||||
<a class="button is-light" href="{% url 'order:place_index' %}">Annuler</a>
|
||||
<input class="button is-primary" type="submit" value="Suivant">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,80 +1,103 @@
|
|||
import logging
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app_name = "order"
|
||||
urlpatterns = [
|
||||
path("", views.IndexView.as_view(), name="index"),
|
||||
path("commande/", views.IndexView.as_view(), name="index"),
|
||||
path(
|
||||
"<str:code>/",
|
||||
"commande/<str:code>/",
|
||||
views.GroupedOrderDetailView.as_view(),
|
||||
name="grouped_order_detail",
|
||||
),
|
||||
path(
|
||||
"<str:code>/ics/",
|
||||
"commande/<str:code>/ics/",
|
||||
views.GroupedOrderEventView.as_view(),
|
||||
name="grouped_order_event",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer",
|
||||
"commande/<str:code>/gerer",
|
||||
views.GroupedOrderOverview.as_view(),
|
||||
name="grouped_order_overview",
|
||||
),
|
||||
path("<str:code>/commander/", views.place_order, name="order"),
|
||||
path("commande/<str:code>/commander/", views.place_order, name="order"),
|
||||
path(
|
||||
"<str:code>/<int:pk>/confirmation/",
|
||||
"commande/<str:code>/<int:pk>/confirmation/",
|
||||
views.OrderDetailView.as_view(),
|
||||
name="order_confirm",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer/<int:pk>/supprimer",
|
||||
"commande/<str:code>/gerer/<int:pk>/supprimer",
|
||||
views.OrderDeleteView.as_view(),
|
||||
name="order_delete",
|
||||
),
|
||||
path("creer", views.GroupedOrderCreateView.as_view(), name="create_grouped_order"),
|
||||
path(
|
||||
"<str:code>/gerer-produits",
|
||||
"commande/creer",
|
||||
views.GroupedOrderCreateView.as_view(),
|
||||
name="create_grouped_order",
|
||||
),
|
||||
path(
|
||||
"commande/<str:code>/gerer-produits",
|
||||
views.GroupedOrderAddItemsView.as_view(),
|
||||
name="manage_items",
|
||||
),
|
||||
path(
|
||||
"<str:code>/modifier",
|
||||
"commande/<str:code>/modifier",
|
||||
views.GroupedOrderUpdateView.as_view(),
|
||||
name="update_grouped_order",
|
||||
),
|
||||
path(
|
||||
"<str:code>/supprimer",
|
||||
"commande/<str:code>/supprimer",
|
||||
views.GroupedOrderDeleteView.as_view(),
|
||||
name="delete_grouped_order",
|
||||
),
|
||||
path(
|
||||
"<str:code>/dupliquer",
|
||||
"commande/<str:code>/dupliquer",
|
||||
views.GroupedOrderDuplicateView.as_view(),
|
||||
name="duplicate_grouped_order",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer-produits/nouveau",
|
||||
"commande/<str:code>/gerer-produits/nouveau",
|
||||
views.ItemCreateView.as_view(),
|
||||
name="item_create",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer-produits/<int:pk>/supprimer",
|
||||
"commande/<str:code>/gerer-produits/<int:pk>/supprimer",
|
||||
views.ItemDeleteView.as_view(),
|
||||
name="item_delete",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer/imprimer",
|
||||
"commande/<str:code>/gerer/imprimer",
|
||||
views.DownloadGroupedOrderSheetView.as_view(),
|
||||
name="grouped_order_sheet",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer/liste-mails",
|
||||
"commande/<str:code>/gerer/liste-mails",
|
||||
views.ExportGroupOrderEmailAdressesToDownloadView.as_view(),
|
||||
name="email_list",
|
||||
),
|
||||
path(
|
||||
"<str:code>/gerer/csv",
|
||||
"commande/<str:code>/gerer/csv",
|
||||
views.ExportGroupedOrderToCSVView.as_view(),
|
||||
name="grouped_order_csv_export",
|
||||
),
|
||||
path("lieu/", views.PlaceIndexView.as_view(), name="place_index"),
|
||||
path(
|
||||
"lieu/<str:code>/modifier",
|
||||
views.PlaceUpdateView.as_view(),
|
||||
name="place_update",
|
||||
),
|
||||
# TODO: It's not great that we reserve special keywords as we go
|
||||
# It would be better to use different HTTP verbs, or have a predefined
|
||||
# list of reserved keywords, or use an entirely different route...
|
||||
path("lieu/creer", views.PlaceCreateView.as_view(), name="place_create"),
|
||||
path(
|
||||
"lieu/<str:code>",
|
||||
views.PlaceOverviewView.as_view(),
|
||||
name="place_overview",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -16,3 +16,4 @@ from .grouped_order import (
|
|||
)
|
||||
from .item import ItemCreateView, ItemDeleteView
|
||||
from .order import OrderDeleteView, OrderDetailView, place_order
|
||||
from .place import PlaceCreateView, PlaceIndexView, PlaceOverviewView, PlaceUpdateView
|
||||
|
|
|
@ -12,7 +12,7 @@ from django_weasyprint import WeasyTemplateResponseMixin
|
|||
from icalendar import Calendar, Event, vCalAddress, vText
|
||||
|
||||
from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm
|
||||
from ..models import GroupedOrder, OrderAuthor
|
||||
from ..models import GroupedOrder, OrderAuthor, Place
|
||||
from .mixins import UserIsOrgaMixin
|
||||
|
||||
|
||||
|
@ -55,11 +55,31 @@ class JoinGroupedOrderView(generic.FormView, generic.RedirectView, LoginRequired
|
|||
template_name = "dashboard.html"
|
||||
|
||||
def form_valid(self, form):
|
||||
return redirect(
|
||||
reverse_lazy(
|
||||
"order:grouped_order_detail", kwargs={"code": form.cleaned_data["code"]}
|
||||
# Give priority to order code, for arbitrary reasons
|
||||
if form.cleaned_data["order_code"]:
|
||||
return redirect(
|
||||
reverse_lazy(
|
||||
"order:grouped_order_detail",
|
||||
kwargs={"code": form.cleaned_data["order_code"]},
|
||||
)
|
||||
)
|
||||
)
|
||||
elif form.cleaned_data["place_code"]:
|
||||
return redirect(
|
||||
reverse_lazy(
|
||||
"order:place_overview",
|
||||
kwargs={"code": form.cleaned_data["place_code"]},
|
||||
)
|
||||
)
|
||||
else:
|
||||
# No valid data found. Either no data was supplied, or they failed validation.
|
||||
if (
|
||||
"order_code" not in form.data
|
||||
or not form.data["order_code"]
|
||||
or "place_code" not in form.data
|
||||
or not form.data["place_code"]
|
||||
):
|
||||
# No data supplied, redirect to dashboard
|
||||
return redirect(reverse_lazy("dashboard"))
|
||||
|
||||
|
||||
class GroupedOrderEventView(generic.DetailView):
|
||||
|
@ -75,7 +95,8 @@ class GroupedOrderEventView(generic.DetailView):
|
|||
event.add("dtstart", self.object.delivery_date)
|
||||
event.add("dtend", self.object.delivery_date)
|
||||
event.add("date", self.object.delivery_date)
|
||||
event.add("location", vText(self.object.place))
|
||||
# TODO
|
||||
# event.add("location", vText(self.object.place))
|
||||
|
||||
description = ""
|
||||
if self.object.delivery_slot:
|
||||
|
@ -136,6 +157,7 @@ class GroupedOrderDetailView(generic.DetailView):
|
|||
"order_author": order_author,
|
||||
# Used to set if the phone is required in the form
|
||||
"is_phone_mandatory": grouped_order.is_phone_mandatory,
|
||||
"places": grouped_order.places.all(),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
@ -198,6 +220,11 @@ class GroupedOrderUpdateView(UserIsOrgaMixin, generic.UpdateView):
|
|||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GroupedOrderUpdateView, self).get_context_data(**kwargs)
|
||||
context["places"] = Place.objects.filter(orga=self.request.user)
|
||||
return context
|
||||
|
||||
|
||||
class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
|
||||
def get_object(self, queryset=None):
|
||||
|
@ -214,10 +241,11 @@ class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
|
|||
orga=self.request.user,
|
||||
delivery_date=initial_grouped_order.delivery_date,
|
||||
deadline=initial_grouped_order.deadline,
|
||||
place=initial_grouped_order.place,
|
||||
description=initial_grouped_order.description,
|
||||
)
|
||||
|
||||
# duplicate the places set
|
||||
new_grouped_order.places.set(initial_grouped_order.places.all())
|
||||
# create a unique code for the new grouped order
|
||||
new_grouped_order.create_code_from_pk()
|
||||
new_grouped_order.save()
|
||||
|
@ -317,6 +345,20 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
|
|||
context["items"] = items
|
||||
context["orders_dict"] = orders_dict
|
||||
|
||||
# Query the DB only once for this grouped order's places
|
||||
places = [x for x in grouped_order.places.all()]
|
||||
if len(places) > 0:
|
||||
# Initialize empty list of orders for every place enabled for this GroupedOrder
|
||||
places_orders = {x.code: {} for x in places}
|
||||
|
||||
for order, order_items in orders_dict.items():
|
||||
places_orders[order.place.code][order] = order_items
|
||||
|
||||
# places contains the full places objects
|
||||
# places_orders contains the order filtered by place.code
|
||||
context["places"] = {x.code: x for x in places}
|
||||
context["places_orders"] = places_orders
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
@ -355,6 +397,7 @@ class ExportGroupOrderEmailAdressesToDownloadView(UserPassesTestMixin, generic.V
|
|||
|
||||
class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
grouped_order = self.get_object()
|
||||
super(ExportGroupedOrderToCSVView, self).get(self, request, *args, **kwargs)
|
||||
context = self.get_context_data()
|
||||
|
||||
|
@ -376,6 +419,8 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
|||
row.append("Note")
|
||||
row.append("Date")
|
||||
row.append("Heure")
|
||||
if "places" in context:
|
||||
row.append("Lieu")
|
||||
writer.writerow(row)
|
||||
|
||||
row = ["", "Prix unitaire TTC (€)"]
|
||||
|
@ -386,8 +431,7 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
|||
row = ["Nom", "Prénom"]
|
||||
writer.writerow(row)
|
||||
|
||||
# write ordered values rows
|
||||
for order, ordered_items in context["orders_dict"].items():
|
||||
def format_csv_order_row(order, ordered_items, place=None):
|
||||
row = [order.author.last_name]
|
||||
row.append(order.author.first_name)
|
||||
for ordered_nb in ordered_items:
|
||||
|
@ -398,7 +442,23 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
|||
row.append(order.note)
|
||||
row.append(order.created_date.strftime("%d/%m/%Y"))
|
||||
row.append(order.created_date.strftime("%H:%M"))
|
||||
writer.writerow(row)
|
||||
if place:
|
||||
row.append(place.name)
|
||||
return row
|
||||
|
||||
# Write ordered values rows.
|
||||
# Avoid extra queries by reusing GroupedOrderExportView context
|
||||
if "places" in context:
|
||||
for place_code, place_orders in context["places_orders"].items():
|
||||
for order, ordered_items in place_orders.items():
|
||||
row = format_csv_order_row(
|
||||
order, ordered_items, place=context["places"][place_code]
|
||||
)
|
||||
writer.writerow(row)
|
||||
else:
|
||||
for order, ordered_items in context["orders_dict"].items():
|
||||
row = format_csv_order_row(order, ordered_items)
|
||||
writer.writerow(row)
|
||||
|
||||
# write total row
|
||||
row = ["", "TOTAL"]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django import http
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
|
@ -7,7 +8,7 @@ from django.views import generic
|
|||
|
||||
from la_chariotte.mail.utils import send_order_confirmation_mail
|
||||
|
||||
from ..models import GroupedOrder, Order, OrderAuthor, OrderedItem
|
||||
from ..models import GroupedOrder, Order, OrderAuthor, OrderedItem, Place
|
||||
|
||||
|
||||
def place_order(request, code):
|
||||
|
@ -46,14 +47,55 @@ def place_order(request, code):
|
|||
phone = request.POST["phone"]
|
||||
email = request.POST["email"]
|
||||
note = request.POST["note"]
|
||||
|
||||
error_message = None
|
||||
author = OrderAuthor.objects.create(
|
||||
first_name=first_name, last_name=last_name, email=email, phone=phone
|
||||
)
|
||||
|
||||
# Make sure requested place is valid in this group order
|
||||
# Query grouped order places only once to avoid many DB roundtrips
|
||||
grouped_order_places = grouped_order.places.all()
|
||||
place = None
|
||||
if len(grouped_order_places) > 0:
|
||||
# Places are enabled for this grouped order, make sure one was supplied
|
||||
if "place" not in request.POST or request.POST["place"] == "":
|
||||
error_message = (
|
||||
"Aucun lieu n'est spécifié alors que la commande groupée en exige un"
|
||||
)
|
||||
else:
|
||||
place = request.POST["place"]
|
||||
try:
|
||||
# QuerySet is already queried from DB so this is a cheap operation.
|
||||
# Could fail if no entry is found, or multiple entries are found
|
||||
place = grouped_order_places.get(code=place)
|
||||
except Exception as e:
|
||||
# The requested place, whether it exists or not,
|
||||
# is not enabled for this GroupedOrder
|
||||
error_message = (
|
||||
"Le lieu demandé n'est pas valide pour cette commande: %s" % place
|
||||
)
|
||||
|
||||
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,
|
||||
"place": place,
|
||||
},
|
||||
)
|
||||
|
||||
order = Order.objects.create(
|
||||
author=author,
|
||||
grouped_order=grouped_order,
|
||||
note=note,
|
||||
created_date=timezone.now(),
|
||||
place=place,
|
||||
)
|
||||
|
||||
# add items to the order
|
||||
|
@ -80,6 +122,7 @@ def place_order(request, code):
|
|||
"error_message": error_message,
|
||||
"note": order.note,
|
||||
"author": author,
|
||||
"place": place,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -98,6 +141,7 @@ def place_order(request, code):
|
|||
"error_message": error_message,
|
||||
"note": order.note,
|
||||
"author": author,
|
||||
"place": place,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
110
la_chariotte/order/views/place.py
Normal file
110
la_chariotte/order/views/place.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
import csv
|
||||
import json
|
||||
|
||||
from django import http
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.views import generic
|
||||
from django_weasyprint import WeasyTemplateResponseMixin
|
||||
from icalendar import Calendar, Event, vCalAddress, vText
|
||||
|
||||
from la_chariotte.order.models import GroupedOrder
|
||||
from la_chariotte.order.views.mixins import UserIsOrgaMixin
|
||||
|
||||
from ..forms import PlaceForm
|
||||
from ..models import Place
|
||||
|
||||
|
||||
class PlaceIndexView(LoginRequiredMixin, generic.ListView):
|
||||
"""View showing all the distribution places managed by the authenticated user"""
|
||||
|
||||
template_name = "place/index.html"
|
||||
context_object_name = "context"
|
||||
|
||||
def get_queryset(self):
|
||||
places = Place.objects.filter(orga=self.request.user)
|
||||
|
||||
# Let's filter orders by distribution place (for UI grouping)
|
||||
orders = dict()
|
||||
for place in places:
|
||||
# Only get currently-active grouped orders
|
||||
active_orders = place.active_orders()
|
||||
if active_orders:
|
||||
orders[place.code] = active_orders
|
||||
|
||||
return {
|
||||
"places": places,
|
||||
"orders": orders,
|
||||
}
|
||||
|
||||
|
||||
class PlaceUpdateView(UserIsOrgaMixin, generic.UpdateView):
|
||||
"""View showing details and allowing updates to a distribution place"""
|
||||
|
||||
model = Place
|
||||
template_name = "place/place_update.html"
|
||||
context_object_name = "place"
|
||||
form_class = PlaceForm
|
||||
|
||||
# Prevent URL change after creation
|
||||
# TODO: is this working?
|
||||
form_class.base_fields["code"].disabled = True
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Place, code=self.kwargs.get("code"))
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["user"] = self.request.user
|
||||
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class PlaceOverviewView(generic.DetailView):
|
||||
"""View details about orders available on a specific place"""
|
||||
|
||||
model = Place
|
||||
template_name = "place/place_overview.html"
|
||||
context_object_name = "place"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Place, code=self.kwargs.get("code"))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
place = self.get_object()
|
||||
active_orders = place.active_orders()
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
"active_orders": active_orders,
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class PlaceCreateView(LoginRequiredMixin, generic.CreateView):
|
||||
"""View for creating a new distribution place"""
|
||||
|
||||
model = Place
|
||||
form_class = PlaceForm
|
||||
template_name = "place/place_create.html"
|
||||
|
||||
# Allow setting URL for creation
|
||||
form_class.base_fields["code"].disabled = False
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
return redirect(reverse_lazy("order:place_index"))
|
|
@ -86,6 +86,9 @@
|
|||
<a class="navbar-item" href="{% url 'order:index' %}">
|
||||
<i class="fa fa-shopping-basket mr-3" aria-hidden="true"></i>Mes commandes groupées
|
||||
</a>
|
||||
<a class="navbar-item" href="{% url 'order:place_index' %}">
|
||||
<i class="fa fa-home mr-3" aria-hidden="true"></i>Mes lieux de distribution
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="navbar-item" href="{% url 'about_chariotte' %}">
|
||||
<i class="fa fa-info-circle mr-3" aria-hidden="true"></i>La Chariotte, c'est quoi ?
|
||||
|
|
|
@ -5,6 +5,17 @@
|
|||
{% block content %}
|
||||
{% load static %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="buttons is-centered mt-3">
|
||||
<a class="button is-primary" href="{% url 'order:index' %}">
|
||||
<i class="fa fa-shopping-basket mr-3" aria-hidden="true"></i>Mes commandes groupées
|
||||
</a>
|
||||
<a class="button is-primary" href="{% url 'order:place_index' %}">
|
||||
<i class="fa fa-home mr-3" aria-hidden="true"></i>Mes lieux de distribution
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p class="desktop-hidden mobile-content-title">
|
||||
{% block content_title %}Bienvenue{% if user.is_authenticated %}, {{ user.first_name }}{% else %} sur la Chariotte !{% endif %}{% endblock %}
|
||||
</p>
|
||||
|
@ -15,15 +26,15 @@
|
|||
<p>Si vous avez un code à 6 caractères, entrez-le ici :</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% for error in form.code.errors %}
|
||||
{% for error in form.order_code.errors %}
|
||||
<p class="help is-danger">{{ error }}</p>
|
||||
{% endfor %}
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<input name="code" class="input {% if form.code.errors %}is-danger{% endif %}" type="text">
|
||||
<input name="order_code" class="input {% if form.order_code.errors %}is-danger{% endif %}" type="text">
|
||||
</div>
|
||||
<div class="control">
|
||||
<input class="button is-primary" type="submit" value="Rejoindre">
|
||||
<input class="button is-primary" type="submit" value="Rejoindre une commande">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -31,23 +42,42 @@
|
|||
</div>
|
||||
<div class="column is-half">
|
||||
<div class="box full-height">
|
||||
{% if user.is_authenticated %}
|
||||
<p class="subtitle">... Ou accédez à vos commandes groupées</p>
|
||||
<a class="button is-primary" href="{% url 'order:index' %}">
|
||||
<i class="fa fa-shopping-basket mr-3" aria-hidden="true"></i>Mes commandes groupées
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="subtitle">... Ou connectez-vous pour organiser une commande groupée</p>
|
||||
<a class="button is-primary" href="{% url 'accounts:login' %}">
|
||||
<i class="fa fa-sign-in mr-3" aria-hidden="true"></i>Se connecter
|
||||
</a>
|
||||
<a class="button is-light" href="{% url 'accounts:signup' %}">
|
||||
<strong>Créer un compte</strong>
|
||||
</a>
|
||||
{% endif %}
|
||||
<p class="subtitle">Rejoindre un lieu de distribution</p>
|
||||
<p>Si vous avez un code de lieu, entrez-le ici :</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% for error in form.place_code.errors %}
|
||||
<p class="help is-danger">{{ error }}</p>
|
||||
{% endfor %}
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
<input name="place_code" class="input {% if form.place_code.errors %}is-danger{% endif %}" type="text">
|
||||
</div>
|
||||
<div class="control">
|
||||
<input class="button is-primary" type="submit" value="Rejoindre un lieu">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not user.is_authenticated %}
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-centered is-half">
|
||||
<div class="box">
|
||||
<p class="subtitle">... Ou connectez-vous pour organiser une commande groupée</p>
|
||||
<div class="buttons is-centered mt-3">
|
||||
<a class="button is-primary" href="{% url 'accounts:login' %}">
|
||||
<i class="fa fa-sign-in mr-3" aria-hidden="true"></i>Se connecter
|
||||
</a>
|
||||
<a class="button is-light" href="{% url 'accounts:signup' %}">
|
||||
<strong>Créer un compte</strong>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-10">
|
||||
<div class="box">
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.contrib import auth
|
|||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from la_chariotte.order.models import GroupedOrder, Item
|
||||
from la_chariotte.order.models import GroupedOrder, Item, Place
|
||||
|
||||
from .utils import create_grouped_order
|
||||
|
||||
|
@ -140,3 +140,51 @@ class TestItemModel:
|
|||
assert grouped_order.item_set.first() == item
|
||||
assert grouped_order.item_set.all()[1] == item3
|
||||
assert grouped_order.item_set.all()[2] == item2
|
||||
|
||||
|
||||
class TestPlaceModel:
|
||||
def test_create_place(self, client_log):
|
||||
assert Place.objects.count() == 0
|
||||
create_place_url = reverse("order:place_create")
|
||||
response = client_log.get(create_place_url)
|
||||
assert response.status_code == 200
|
||||
response = client_log.post(
|
||||
create_place_url,
|
||||
{
|
||||
"name": "Centre social Kropotkine",
|
||||
"code": "kropotkine",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert Place.objects.count() == 1
|
||||
|
||||
def test_place_active_orders(self, client_log):
|
||||
now = timezone.now()
|
||||
|
||||
place = Place.objects.create(
|
||||
name="Centre Social Kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
code="kropotkine",
|
||||
)
|
||||
|
||||
grouped_order_active = GroupedOrder.objects.create(
|
||||
name="test",
|
||||
orga=auth.get_user(client_log),
|
||||
delivery_date=now.date() + datetime.timedelta(days=30),
|
||||
deadline=now + datetime.timedelta(days=30),
|
||||
)
|
||||
grouped_order_active.places.add(place.id)
|
||||
|
||||
grouped_order_inactive = GroupedOrder.objects.create(
|
||||
name="test",
|
||||
orga=auth.get_user(client_log),
|
||||
delivery_date=now.date() - datetime.timedelta(days=30),
|
||||
deadline=now + datetime.timedelta(days=30),
|
||||
)
|
||||
grouped_order_inactive.places.add(place.id)
|
||||
|
||||
assert place.orders.all().count() == 2
|
||||
|
||||
active_orders = place.active_orders()
|
||||
assert active_orders.count() == 1
|
||||
assert active_orders.first().id == grouped_order_active.id
|
||||
|
|
|
@ -191,7 +191,7 @@ class TestGroupedOrderIndexView:
|
|||
|
||||
|
||||
class TestJoinGroupedOrderView:
|
||||
def test_correct_code_redirects_properly(self, client, client_log):
|
||||
def test_correct_order_code_redirects_properly(self, client, client_log):
|
||||
logged_user = auth.get_user(client_log)
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
|
@ -200,7 +200,7 @@ class TestJoinGroupedOrderView:
|
|||
orga_user=logged_user,
|
||||
)
|
||||
join_url = reverse("dashboard")
|
||||
response = client.post(join_url, {"code": grouped_order.code})
|
||||
response = client.post(join_url, {"order_code": grouped_order.code})
|
||||
|
||||
expected_url = reverse(
|
||||
"order:grouped_order_detail", kwargs={"code": grouped_order.code}
|
||||
|
@ -208,17 +208,75 @@ class TestJoinGroupedOrderView:
|
|||
assert response.status_code == 302
|
||||
assert response.url == expected_url
|
||||
|
||||
def test_incorrect_code_errors_out(self, client):
|
||||
def test_incorrect_order_code_errors_out(self, client):
|
||||
assert len(models.GroupedOrder.objects.all()) == 0
|
||||
|
||||
join_url = reverse("dashboard")
|
||||
response = client.post(join_url, {"code": "123456"})
|
||||
response = client.post(join_url, {"order_code": "123456"})
|
||||
|
||||
assert (
|
||||
"Désolé, nous ne trouvons aucune commande avec ce code"
|
||||
in response.content.decode()
|
||||
)
|
||||
|
||||
def test_correct_place_code_redirects_properly(self, client, client_log):
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
code="foo",
|
||||
)
|
||||
|
||||
join_url = reverse("dashboard")
|
||||
response = client.post(join_url, {"place_code": place.code})
|
||||
|
||||
expected_url = reverse("order:place_overview", kwargs={"code": place.code})
|
||||
assert response.status_code == 302
|
||||
assert response.url == expected_url
|
||||
|
||||
def test_incorrect_place_code_errors_out(self, client):
|
||||
assert len(models.Place.objects.all()) == 0
|
||||
|
||||
join_url = reverse("dashboard")
|
||||
response = client.post(join_url, {"place_code": "123456"})
|
||||
|
||||
assert (
|
||||
"Désolé, nous ne trouvons aucun lieu avec ce code"
|
||||
in response.content.decode()
|
||||
)
|
||||
|
||||
def test_correct_order_code_has_precedence(self, client, client_log):
|
||||
logged_user = auth.get_user(client_log)
|
||||
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=logged_user,
|
||||
code="foo",
|
||||
)
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="test",
|
||||
orga_user=logged_user,
|
||||
)
|
||||
|
||||
join_url = reverse("dashboard")
|
||||
response = client.post(
|
||||
join_url, {"place_code": place.code, "order_code": grouped_order.code}
|
||||
)
|
||||
|
||||
expected_url = reverse(
|
||||
"order:grouped_order_detail", kwargs={"code": grouped_order.code}
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert response.url == expected_url
|
||||
|
||||
def test_no_code_redirects_to_dashboard(self, client_log):
|
||||
join_url = reverse("dashboard")
|
||||
response = client_log.post(join_url, {})
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.url == join_url
|
||||
|
||||
|
||||
class TestGroupedOrderDetailView:
|
||||
def test_order_item_with_authenticated_user(self, client, connected_grouped_order):
|
||||
|
@ -920,7 +978,7 @@ class TestGroupedOrderUpdateView:
|
|||
grouped_order.save()
|
||||
assert models.GroupedOrder.objects.count() == 1
|
||||
assert models.GroupedOrder.objects.first().name == "gr order test"
|
||||
assert models.GroupedOrder.objects.first().place == None
|
||||
assert models.GroupedOrder.objects.first().places.count() == 0
|
||||
|
||||
# get the update form
|
||||
update_grouped_order_url = reverse(
|
||||
|
@ -942,7 +1000,7 @@ class TestGroupedOrderUpdateView:
|
|||
)
|
||||
assert models.GroupedOrder.objects.count() == 1
|
||||
assert models.GroupedOrder.objects.first().name == "gr order test"
|
||||
assert models.GroupedOrder.objects.first().place == None
|
||||
assert models.GroupedOrder.objects.first().places.count() == 0
|
||||
|
||||
# get the update form
|
||||
update_grouped_order_url = reverse(
|
||||
|
@ -954,6 +1012,9 @@ class TestGroupedOrderUpdateView:
|
|||
# post the update form
|
||||
date = timezone.now().date() + datetime.timedelta(days=42)
|
||||
deadline = timezone.now() + datetime.timedelta(days=32)
|
||||
place = models.Place.objects.create(
|
||||
code="foobar", name="quelque part", orga=auth.get_user(client_log)
|
||||
)
|
||||
response = client_log.post(
|
||||
update_grouped_order_url,
|
||||
{
|
||||
|
@ -961,14 +1022,15 @@ class TestGroupedOrderUpdateView:
|
|||
"deadline_date": deadline.date(),
|
||||
"deadline_time": deadline.time().strftime("%H:%M"),
|
||||
"delivery_date": date,
|
||||
"place": "quelque part",
|
||||
"places": place.id,
|
||||
},
|
||||
)
|
||||
# assert response.content.decode() == ""
|
||||
assert response.status_code == 302
|
||||
assert response.url.endswith("gerer-produits")
|
||||
assert models.GroupedOrder.objects.count() == 1
|
||||
assert models.GroupedOrder.objects.first().name == "titre test modifié"
|
||||
assert models.GroupedOrder.objects.first().place == "quelque part"
|
||||
assert models.GroupedOrder.objects.first().places.first().name == "quelque part"
|
||||
|
||||
def test_update_grouped_order__delivery_date_passed(self, client_log):
|
||||
"""
|
||||
|
@ -983,7 +1045,7 @@ class TestGroupedOrderUpdateView:
|
|||
)
|
||||
assert models.GroupedOrder.objects.count() == 1
|
||||
assert models.GroupedOrder.objects.first().name == "gr order test"
|
||||
assert models.GroupedOrder.objects.first().place == None
|
||||
assert models.GroupedOrder.objects.first().places.count() == 0
|
||||
|
||||
# get the update form
|
||||
update_grouped_order_url = reverse(
|
||||
|
@ -995,6 +1057,9 @@ class TestGroupedOrderUpdateView:
|
|||
# post the update form
|
||||
date = timezone.now().date() + datetime.timedelta(days=-1)
|
||||
deadline = timezone.now() + datetime.timedelta(days=-3)
|
||||
place = models.Place.objects.create(
|
||||
code="foobar", name="quelque part", orga=auth.get_user(client_log)
|
||||
)
|
||||
response = client_log.post(
|
||||
update_grouped_order_url,
|
||||
{
|
||||
|
@ -1002,14 +1067,14 @@ class TestGroupedOrderUpdateView:
|
|||
"deadline_date": deadline.date(),
|
||||
"deadline_time": deadline.time().strftime("%H:%M"),
|
||||
"delivery_date": date,
|
||||
"place": "quelque part",
|
||||
"places": place.id,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert response.url.endswith("gerer-produits")
|
||||
assert models.GroupedOrder.objects.count() == 1
|
||||
assert models.GroupedOrder.objects.first().name == "titre test modifié"
|
||||
assert models.GroupedOrder.objects.first().place == "quelque part"
|
||||
assert models.GroupedOrder.objects.first().places.first().name == "quelque part"
|
||||
|
||||
def test_update_grouped_order__not_orga(self, client_log, other_user):
|
||||
"""A user that is not organiszer of the GO accesses update page.
|
||||
|
@ -1315,7 +1380,9 @@ class TestGroupedOrderDuplicateView:
|
|||
assert new_grouped_order.name == "gr order test - copie"
|
||||
assert new_grouped_order.delivery_date == grouped_order.delivery_date
|
||||
assert new_grouped_order.deadline == grouped_order.deadline
|
||||
assert new_grouped_order.place == grouped_order.place
|
||||
assert [x.code for x in new_grouped_order.places.all()] == [
|
||||
x.code for x in grouped_order.places.all()
|
||||
]
|
||||
assert new_grouped_order.orga == auth.get_user(client_log)
|
||||
assert new_grouped_order.description == grouped_order.description
|
||||
assert new_grouped_order.item_set.count() == grouped_order.item_set.count()
|
||||
|
@ -1428,6 +1495,7 @@ class TestGroupedOrderSheetView:
|
|||
assert response.context["grouped_order"] == grouped_order
|
||||
assert len(response.context["items"]) == 0
|
||||
assert len(response.context["orders_dict"]) == 0
|
||||
assert "places" not in response.context.keys()
|
||||
|
||||
# we order some items in the grouped order
|
||||
order = order_items_in_grouped_order(grouped_order)
|
||||
|
@ -1437,6 +1505,7 @@ class TestGroupedOrderSheetView:
|
|||
assert len(response.context["items"]) == 2
|
||||
assert response.context["orders_dict"][order] == [3, 2]
|
||||
assert response.context["grouped_order"].total_price == 35
|
||||
assert "places" not in response.context.keys()
|
||||
|
||||
# test if the orders are sorted by last names
|
||||
orders = list(response.context["orders_dict"].keys())
|
||||
|
@ -1446,6 +1515,76 @@ class TestGroupedOrderSheetView:
|
|||
assert orders[1].author.first_name == "bobby"
|
||||
assert orders[2].author.last_name == "lescargot"
|
||||
|
||||
def test_get_pdf_sheet_with_places(self, client_log, other_user):
|
||||
"""The orga of the grouped models.Order accesses the pdf sheet"""
|
||||
|
||||
place1 = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
code="kropotkine",
|
||||
)
|
||||
place2 = models.Place.objects.create(
|
||||
name="Centre social Luxemburg",
|
||||
orga=auth.get_user(client_log),
|
||||
code="luxemburg",
|
||||
)
|
||||
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place1.id)
|
||||
grouped_order.places.add(place2.id)
|
||||
generate_sheet_url = reverse(
|
||||
"order:grouped_order_sheet",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.get(generate_sheet_url)
|
||||
assert response.status_code == 200
|
||||
assert response.context["grouped_order"] == grouped_order
|
||||
assert len(response.context["items"]) == 0
|
||||
assert len(response.context["orders_dict"]) == 0
|
||||
assert "places" in response.context.keys()
|
||||
assert "places_orders" in response.context.keys()
|
||||
assert len(response.context["places"]) == 2
|
||||
assert len(response.context["places_orders"]) == 2
|
||||
assert len(response.context["places_orders"]["kropotkine"]) == 0
|
||||
assert len(response.context["places_orders"]["luxemburg"]) == 0
|
||||
|
||||
# we order some items in the grouped order
|
||||
order = order_items_in_grouped_order(grouped_order, place=place2)
|
||||
response = client_log.get(generate_sheet_url)
|
||||
assert response.status_code == 200
|
||||
assert response.context["grouped_order"] == grouped_order
|
||||
assert len(response.context["items"]) == 2
|
||||
assert response.context["orders_dict"][order] == [3, 2]
|
||||
assert response.context["grouped_order"].total_price == 35
|
||||
assert "places" in response.context.keys()
|
||||
|
||||
# test that orders are split by distribution place
|
||||
places_orders = response.context["places_orders"]
|
||||
assert len(places_orders["kropotkine"]) == 0
|
||||
assert len(places_orders["luxemburg"]) == 3
|
||||
|
||||
orders = list(places_orders["luxemburg"])
|
||||
assert orders[0].author.last_name == "alescargot"
|
||||
assert orders[0].author.first_name == "bob"
|
||||
assert orders[1].author.last_name == "alescargot"
|
||||
assert orders[1].author.first_name == "bobby"
|
||||
assert orders[2].author.last_name == "lescargot"
|
||||
|
||||
# test that splitting by place does not affect orders_dict overall
|
||||
orders = list(response.context["orders_dict"].keys())
|
||||
assert orders[0].author.last_name == "alescargot"
|
||||
assert orders[0].author.first_name == "bob"
|
||||
assert orders[1].author.last_name == "alescargot"
|
||||
assert orders[1].author.first_name == "bobby"
|
||||
assert orders[2].author.last_name == "lescargot"
|
||||
|
||||
|
||||
class TestExportGroupOrderEmailAdressesToDownloadView:
|
||||
def test_user_not_logged_gets_redirected(self, client, other_user):
|
||||
|
@ -1554,6 +1693,67 @@ class TestExportGroupOrderEmailAdressesToDownloadView:
|
|||
content = response.content.decode()
|
||||
assert "test@mail.fr\r\n" in content
|
||||
|
||||
def test_export_format_csv_with_places(self, client_log):
|
||||
place = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
code="kropotkine",
|
||||
)
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place.id)
|
||||
grouped_order.save()
|
||||
item = models.Item.objects.create(
|
||||
name="test item 1", grouped_order=grouped_order, price=2
|
||||
)
|
||||
|
||||
overview_url = reverse(
|
||||
"order:grouped_order_overview",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
order_url = reverse(
|
||||
"order:order",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.post(
|
||||
order_url,
|
||||
{
|
||||
f"quantity_{item.pk}": [4, 0],
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"phone": "0645632569",
|
||||
"email": "test@mail.fr",
|
||||
"note": "",
|
||||
"place": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.content.decode() == ""
|
||||
assert response.status_code == 302
|
||||
|
||||
email_list_view_url = (
|
||||
reverse(
|
||||
"order:email_list",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
+ "?format=csv"
|
||||
)
|
||||
response = client_log.get(email_list_view_url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "text/csv"
|
||||
content = response.content.decode()
|
||||
assert "test@mail.fr\r\n" in content
|
||||
|
||||
def test_export_format_default(self, client_log):
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
|
@ -1716,3 +1916,93 @@ class TestExportGroupedOrderToCSVView:
|
|||
],
|
||||
["", "TOTAL", "4", "3", "35,00"],
|
||||
]
|
||||
|
||||
def test_csv_export_with_places(self, client_log):
|
||||
"""
|
||||
The grouped order orga accesses the csv view
|
||||
"""
|
||||
place = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
code="kropotkine",
|
||||
)
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place.id)
|
||||
|
||||
order = order_items_in_grouped_order(grouped_order, place=place)
|
||||
csv_view_url = reverse(
|
||||
"order:grouped_order_csv_export",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.get(csv_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
content = response.content.decode("utf-8")
|
||||
csv_reader = csv.reader(StringIO(content), delimiter=";")
|
||||
body = list(csv_reader)
|
||||
created_date = f"{timezone.now().strftime('%d/%m/%Y')}"
|
||||
created_time = f"{timezone.now().strftime('%H:%M')}"
|
||||
assert body == [
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"test item",
|
||||
"test item 2",
|
||||
"Prix de la commande",
|
||||
"Mail",
|
||||
"Téléphone",
|
||||
"Note",
|
||||
"Date",
|
||||
"Heure",
|
||||
"Lieu",
|
||||
],
|
||||
["", "Prix unitaire TTC (€)", "2,00", "9,00"],
|
||||
["Nom", "Prénom"],
|
||||
[
|
||||
"alescargot",
|
||||
"bob",
|
||||
"1",
|
||||
"0",
|
||||
"2,00",
|
||||
"bob2@escargot.fr",
|
||||
"'000",
|
||||
"",
|
||||
created_date,
|
||||
created_time,
|
||||
"Centre social Kropotkine",
|
||||
],
|
||||
[
|
||||
"alescargot",
|
||||
"bobby",
|
||||
"0",
|
||||
"1",
|
||||
"9,00",
|
||||
"bob3@escargot.fr",
|
||||
"'000",
|
||||
"",
|
||||
created_date,
|
||||
created_time,
|
||||
"Centre social Kropotkine",
|
||||
],
|
||||
[
|
||||
"lescargot",
|
||||
"bob",
|
||||
"3",
|
||||
"2",
|
||||
"24,00",
|
||||
"bob@escargot.fr",
|
||||
"'000",
|
||||
"",
|
||||
created_date,
|
||||
created_time,
|
||||
"Centre social Kropotkine",
|
||||
],
|
||||
["", "TOTAL", "4", "3", "35,00"],
|
||||
]
|
||||
|
|
|
@ -268,3 +268,173 @@ class TestOrder:
|
|||
assert grouped_order.order_set.all().count() == 2
|
||||
assert grouped_order.total_price == 35 - order_price
|
||||
assert item.ordered_nb == 1
|
||||
|
||||
def test_order_valid_place(self, client_log):
|
||||
"""The orga user requests a non-existing place for distribution. It fails"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
place = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
code="kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place.id)
|
||||
|
||||
item = models.Item.objects.create(
|
||||
name="test item 1", grouped_order=grouped_order, price=1
|
||||
)
|
||||
|
||||
order_url = reverse(
|
||||
"order:order",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.post(
|
||||
order_url,
|
||||
{
|
||||
f"quantity_{item.pk}": [4, 0],
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"phone": "0645632569",
|
||||
"email": "test@mail.fr",
|
||||
"note": "",
|
||||
"place": place.code,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
|
||||
# TODO: How to test trying to use a place from a different user/orga?
|
||||
def test_order_invalid_place(self, client_log):
|
||||
"""The orga user requests a non-existing place for distribution. It fails"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
place = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
code="kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place.id)
|
||||
|
||||
item = models.Item.objects.create(
|
||||
name="test item 1", grouped_order=grouped_order, price=1
|
||||
)
|
||||
|
||||
order_url = reverse(
|
||||
"order:order",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.post(
|
||||
order_url,
|
||||
{
|
||||
f"quantity_{item.pk}": [4, 0],
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"phone": "0645632569",
|
||||
"email": "test@mail.fr",
|
||||
"note": "",
|
||||
"place": "foobar",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
response.context["error_message"]
|
||||
== "Le lieu demandé n'est pas valide pour cette commande: foobar"
|
||||
)
|
||||
|
||||
def test_order_unaffiliated_place(self, client_log):
|
||||
"""The orga user requests a place for distribution unrelated to this grouped order. It fails"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
place = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
code="kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
place2 = models.Place.objects.create(
|
||||
name="Centre social Luxemburg",
|
||||
code="luxemburg",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place.id)
|
||||
|
||||
item = models.Item.objects.create(
|
||||
name="test item 1", grouped_order=grouped_order, price=1
|
||||
)
|
||||
|
||||
order_url = reverse(
|
||||
"order:order",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.post(
|
||||
order_url,
|
||||
{
|
||||
f"quantity_{item.pk}": [4, 0],
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"phone": "0645632569",
|
||||
"email": "test@mail.fr",
|
||||
"note": "",
|
||||
"place": place2.code,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert (
|
||||
response.context["error_message"]
|
||||
== "Le lieu demandé n'est pas valide pour cette commande: %s" % place2.code
|
||||
)
|
||||
|
||||
def test_order_no_place(self, client_log):
|
||||
"""The orga user requests a non-existing place for distribution. It fails"""
|
||||
grouped_order = create_grouped_order(
|
||||
days_before_delivery_date=5,
|
||||
days_before_deadline=2,
|
||||
name="gr order test",
|
||||
orga_user=auth.get_user(client_log),
|
||||
)
|
||||
place = models.Place.objects.create(
|
||||
name="Centre social Kropotkine",
|
||||
code="kropotkine",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order.places.add(place.id)
|
||||
|
||||
item = models.Item.objects.create(
|
||||
name="test item 1", grouped_order=grouped_order, price=1
|
||||
)
|
||||
|
||||
order_url = reverse(
|
||||
"order:order",
|
||||
kwargs={
|
||||
"code": grouped_order.code,
|
||||
},
|
||||
)
|
||||
response = client_log.post(
|
||||
order_url,
|
||||
{
|
||||
f"quantity_{item.pk}": [4, 0],
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"phone": "0645632569",
|
||||
"email": "test@mail.fr",
|
||||
"note": "",
|
||||
},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert "Aucun lieu n'est spécifié" in response.context["error_message"]
|
||||
|
|
489
la_chariotte/tests/test_order_views_place.py
Normal file
489
la_chariotte/tests/test_order_views_place.py
Normal file
|
@ -0,0 +1,489 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
from django.contrib import auth
|
||||
from django.urls import reverse
|
||||
|
||||
from la_chariotte.order import models
|
||||
|
||||
from .utils import create_grouped_order, order_items_in_grouped_order
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class TestPlaceOverviewView:
|
||||
def test_overview_place(self, client_log):
|
||||
"""A user checks out a place successfully"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
code="foo",
|
||||
)
|
||||
|
||||
overview_place_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
response = client_log.get(overview_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
assert "foo: Commandes groupées"
|
||||
assert "Aucune commande groupée en cours"
|
||||
|
||||
def test_overview_with_orders(self, client_log):
|
||||
"""A user checks out a place with several orders"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
code="foo",
|
||||
)
|
||||
|
||||
overview_place_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
grouped_order1 = create_grouped_order(
|
||||
31, 30, "firstorder", auth.get_user(client_log)
|
||||
)
|
||||
grouped_order1.places.add(place)
|
||||
grouped_order2 = create_grouped_order(
|
||||
31, 30, "secondorder", auth.get_user(client_log)
|
||||
)
|
||||
grouped_order2.places.add(place)
|
||||
|
||||
# Generate some items
|
||||
order = order_items_in_grouped_order(grouped_order1, place=place)
|
||||
|
||||
response = client_log.get(overview_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
assert "foo: Commandes groupées"
|
||||
assert "foo (2 commandes groupées en cours)" in body
|
||||
assert '">firstorder</a> (2 produits)' in body
|
||||
assert '">secondorder</a> (0 produit)' in body
|
||||
|
||||
def test_overview_place__anonymous_user(self, client, other_user):
|
||||
"""
|
||||
A user that is not logged can check out a place
|
||||
"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=other_user,
|
||||
code="foo",
|
||||
)
|
||||
|
||||
overview_place_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
response = client.get(overview_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
assert "foo: Commandes groupées"
|
||||
assert "Aucune commande groupée en cours"
|
||||
|
||||
|
||||
class TestPlaceUpdateView:
|
||||
def test_update_place(self, client_log):
|
||||
"""A user updates their place successfully"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
code="foo",
|
||||
)
|
||||
|
||||
update_place_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
overview_place_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
response = client_log.post(
|
||||
update_place_view_url,
|
||||
{
|
||||
"name": "bar",
|
||||
"description": "longer bar",
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.url == overview_place_view_url
|
||||
|
||||
place = models.Place.objects.all().filter(code=place.code).first()
|
||||
|
||||
assert place.name == "bar"
|
||||
assert place.description == "longer bar"
|
||||
assert place.code == "foo"
|
||||
|
||||
def test_update_place_code_silently_fails(self, client_log):
|
||||
"""A user tries to change a place's identifier, which fails because it's immutable"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
code="foo",
|
||||
)
|
||||
|
||||
update_place_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
response = client_log.post(
|
||||
update_place_view_url,
|
||||
{
|
||||
"name": "bar",
|
||||
"description": "longer bar",
|
||||
"code": "newcode",
|
||||
},
|
||||
)
|
||||
|
||||
# Ideally, response should look successful, but not alter the place's code
|
||||
# But for now this just returns 404
|
||||
assert response.content.decode() == ""
|
||||
assert response.status_code == 302
|
||||
|
||||
place = models.Place.objects.all().filter(code=place.code).first()
|
||||
|
||||
assert place.name == "bar"
|
||||
assert place.description == "longer bar"
|
||||
assert place.code == "foo"
|
||||
|
||||
def test_update_place_does_not_exist_fails(self, client_log):
|
||||
"""A user tries to update a place which does not exist, which fails"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
code="foo",
|
||||
)
|
||||
|
||||
update_place_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": "notfoo",
|
||||
},
|
||||
)
|
||||
|
||||
response = client_log.post(
|
||||
update_place_view_url,
|
||||
{
|
||||
"name": "bar",
|
||||
"description": "longer bar",
|
||||
"code": "newcode",
|
||||
},
|
||||
)
|
||||
|
||||
# Response should look successful, but not alter the place's code
|
||||
assert response.status_code == 404
|
||||
|
||||
place = models.Place.objects.all().filter(code=place.code).first()
|
||||
|
||||
assert place.name == "foo"
|
||||
assert place.description == None
|
||||
assert place.code == "foo"
|
||||
|
||||
def test_update_place__not_orga(self, client_log, other_user):
|
||||
"""
|
||||
A user that is not orga cannot edit a place. They get a 403 error.
|
||||
"""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
code="foo",
|
||||
orga=other_user,
|
||||
)
|
||||
|
||||
update_place_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": "foo",
|
||||
},
|
||||
)
|
||||
response = client_log.post(
|
||||
update_place_view_url, {"name": "notfoo", "code": "foo"}
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
class TestPlaceCreateView:
|
||||
def test_create_place(self, client_log):
|
||||
"""A user creates a new place."""
|
||||
create_place_view_url = reverse(
|
||||
"order:place_create",
|
||||
)
|
||||
response = client_log.post(
|
||||
create_place_view_url, {"name": "foo", "code": "foo"}
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert response.url == reverse(
|
||||
"order:place_index",
|
||||
)
|
||||
assert models.Place.objects.first().name == "foo"
|
||||
response = client_log.get(response.url)
|
||||
assert "foo" in response.content.decode()
|
||||
|
||||
def test_create_place__anonymous_user(self, client, other_user):
|
||||
"""
|
||||
A user that is not logged cannot create a place. They get a redirected to loginview.
|
||||
"""
|
||||
create_place_view_url = reverse(
|
||||
"order:place_create",
|
||||
)
|
||||
response = client.post(create_place_view_url, {"name": "foo", "code": "foo"})
|
||||
assert response.status_code == 302
|
||||
assert (
|
||||
response.url == f"{reverse('accounts:login')}?next={create_place_view_url}"
|
||||
)
|
||||
|
||||
def test_create_place__already_exists(self, client_log):
|
||||
"""A user creates a new place whose code already exists. It fails"""
|
||||
create_place_view_url = reverse(
|
||||
"order:place_create",
|
||||
)
|
||||
response = client_log.post(
|
||||
create_place_view_url, {"name": "foo", "code": "foo"}
|
||||
)
|
||||
# TODO: is this correct????
|
||||
assert response.status_code == 302
|
||||
assert response.url == reverse(
|
||||
"order:place_index",
|
||||
)
|
||||
assert models.Place.objects.first().name == "foo"
|
||||
response = client_log.get(response.url)
|
||||
assert "foo" in response.content.decode()
|
||||
|
||||
|
||||
class TestPlaceIndexView:
|
||||
def test_index_place(self, client_log):
|
||||
"""A user lists their places."""
|
||||
place1 = models.Place.objects.create(
|
||||
name="foo",
|
||||
code="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
place2 = models.Place.objects.create(
|
||||
name="bar",
|
||||
code="bar",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
index_place_view_url = reverse(
|
||||
"order:place_index",
|
||||
)
|
||||
response = client_log.get(index_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
|
||||
place1_overview_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place1.code,
|
||||
},
|
||||
)
|
||||
place2_overview_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place2.code,
|
||||
},
|
||||
)
|
||||
|
||||
place1_update_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place1.code,
|
||||
},
|
||||
)
|
||||
place2_update_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place2.code,
|
||||
},
|
||||
)
|
||||
|
||||
assert place1_overview_view_url in body
|
||||
assert place1_update_view_url in body
|
||||
assert place2_overview_view_url in body
|
||||
assert place2_update_view_url in body
|
||||
|
||||
def test_index_place_active_order(self, client_log):
|
||||
"""A user lists their places, only one has an active order. Inactive orders are not shown."""
|
||||
place1 = models.Place.objects.create(
|
||||
name="foo",
|
||||
code="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
place2 = models.Place.objects.create(
|
||||
name="bar",
|
||||
code="bar",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order1 = create_grouped_order(
|
||||
31, 30, "order", auth.get_user(client_log)
|
||||
)
|
||||
grouped_order1.places.add(place1)
|
||||
grouped_order2 = create_grouped_order(
|
||||
-2, -1, "inactive_order", auth.get_user(client_log)
|
||||
)
|
||||
grouped_order2.places.add(place1)
|
||||
|
||||
index_place_view_url = reverse(
|
||||
"order:place_index",
|
||||
)
|
||||
response = client_log.get(index_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
|
||||
place1_overview_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place1.code,
|
||||
},
|
||||
)
|
||||
place2_overview_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place2.code,
|
||||
},
|
||||
)
|
||||
|
||||
place1_update_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place1.code,
|
||||
},
|
||||
)
|
||||
place2_update_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place2.code,
|
||||
},
|
||||
)
|
||||
|
||||
grouped_order1_detail_view_url = reverse(
|
||||
"order:grouped_order_detail",
|
||||
kwargs={
|
||||
"code": grouped_order1.code,
|
||||
},
|
||||
)
|
||||
grouped_order2_detail_view_url = reverse(
|
||||
"order:grouped_order_detail",
|
||||
kwargs={
|
||||
"code": grouped_order2.code,
|
||||
},
|
||||
)
|
||||
|
||||
assert place1_overview_view_url in body
|
||||
assert place1_update_view_url in body
|
||||
assert grouped_order1_detail_view_url in body
|
||||
assert place2_overview_view_url in body
|
||||
assert place2_update_view_url in body
|
||||
|
||||
# Inactive order is not displayed
|
||||
assert "inactive_order" not in body
|
||||
assert str(grouped_order2_detail_view_url) not in body
|
||||
|
||||
def test_index_place_active_order_in_correct_order(self, client_log):
|
||||
"""A user lists their places. One has two active orders. The latest delivery date is displayed first."""
|
||||
place = models.Place.objects.create(
|
||||
name="foo",
|
||||
code="foo",
|
||||
orga=auth.get_user(client_log),
|
||||
)
|
||||
grouped_order1 = create_grouped_order(
|
||||
31, 30, "order", auth.get_user(client_log)
|
||||
)
|
||||
grouped_order1.places.add(place)
|
||||
grouped_order2 = create_grouped_order(
|
||||
61, 60, "more_recent_order", auth.get_user(client_log)
|
||||
)
|
||||
grouped_order2.places.add(place)
|
||||
|
||||
index_place_view_url = reverse(
|
||||
"order:place_index",
|
||||
)
|
||||
response = client_log.get(index_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
|
||||
place_update_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
place_overview_view_url = reverse(
|
||||
"order:place_overview",
|
||||
kwargs={
|
||||
"code": place.code,
|
||||
},
|
||||
)
|
||||
|
||||
grouped_order1_detail_view_url = reverse(
|
||||
"order:grouped_order_detail",
|
||||
kwargs={
|
||||
"code": grouped_order1.code,
|
||||
},
|
||||
)
|
||||
grouped_order2_detail_view_url = reverse(
|
||||
"order:grouped_order_detail",
|
||||
kwargs={
|
||||
"code": grouped_order2.code,
|
||||
},
|
||||
)
|
||||
|
||||
assert place_overview_view_url in body
|
||||
assert place_update_view_url in body
|
||||
assert grouped_order1_detail_view_url in body
|
||||
assert grouped_order2_detail_view_url in body
|
||||
|
||||
def test_index_place__not_orga(self, client_log, other_user):
|
||||
"""
|
||||
A user that is not orga cannot view places they don't own.
|
||||
"""
|
||||
place = models.Place.objects.create(
|
||||
name="NOTMYPLACE",
|
||||
code="NOTMYPLACE",
|
||||
orga=other_user,
|
||||
)
|
||||
|
||||
update_place_view_url = reverse(
|
||||
"order:place_update",
|
||||
kwargs={
|
||||
"code": "NOTMYPLACE",
|
||||
},
|
||||
)
|
||||
|
||||
index_place_view_url = reverse(
|
||||
"order:place_index",
|
||||
)
|
||||
|
||||
response = client_log.get(index_place_view_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
body = response.content.decode()
|
||||
assert place.name not in body
|
||||
assert update_place_view_url not in body
|
|
@ -29,7 +29,7 @@ def create_grouped_order(
|
|||
return grouped_order
|
||||
|
||||
|
||||
def order_items_in_grouped_order(grouped_order):
|
||||
def order_items_in_grouped_order(grouped_order, place=None):
|
||||
"""Creates 2 OrderedItems and orders in the given grouped order. Returns the order"""
|
||||
item_1 = grouped_order.item_set.create(name="test item", price="2")
|
||||
item_2 = grouped_order.item_set.create(name="test item 2", price="9")
|
||||
|
@ -45,9 +45,9 @@ def order_items_in_grouped_order(grouped_order):
|
|||
phone="000",
|
||||
email="bob3@escargot.fr",
|
||||
)
|
||||
order = grouped_order.order_set.create(author=author_1)
|
||||
order_2 = grouped_order.order_set.create(author=author_2)
|
||||
order_3 = grouped_order.order_set.create(author=author_3)
|
||||
order = grouped_order.order_set.create(author=author_1, place=place)
|
||||
order_2 = grouped_order.order_set.create(author=author_2, place=place)
|
||||
order_3 = grouped_order.order_set.create(author=author_3, place=place)
|
||||
models.OrderedItem.objects.create(order=order, item=item_1, nb=3)
|
||||
models.OrderedItem.objects.create(order=order, item=item_2, nb=2)
|
||||
models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1)
|
||||
|
|
|
@ -29,7 +29,8 @@ from la_chariotte.order.views.stats import stats
|
|||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("commande/", include("la_chariotte.order.urls")),
|
||||
# No route specified because there are multiple subroutes defined in order.urls
|
||||
path("", include("la_chariotte.order.urls")),
|
||||
path("comptes/", include("la_chariotte.accounts.urls")),
|
||||
# Some paths for accounts are easier to leave here
|
||||
# - PasswordResetView sends the mail
|
||||
|
|
Loading…
Reference in a new issue