mirror of
https://framagit.org/la-chariotte/la-chariotte.git
synced 2025-05-17 11:11:49 +02:00
feature: Add placekey relationship for distribution spots
This commit is contained in:
parent
d354708ffe
commit
f2d8d9b9c2
24 changed files with 499 additions and 58 deletions
0
la_chariotte/lieu/__init__.py
Normal file
0
la_chariotte/lieu/__init__.py
Normal file
6
la_chariotte/lieu/apps.py
Normal file
6
la_chariotte/lieu/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class LieuConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "la_chariotte.lieu"
|
35
la_chariotte/lieu/forms.py
Normal file
35
la_chariotte/lieu/forms.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import datetime
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.forms.utils import to_current_timezone
|
||||
from django.utils import timezone
|
||||
|
||||
from la_chariotte.lieu.models import Lieu
|
||||
|
||||
|
||||
class LieuForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Lieu
|
||||
fields = [
|
||||
"name",
|
||||
"description",
|
||||
"url",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(
|
||||
attrs={"placeholder": "ex : Centre social Kropotkine"}
|
||||
),
|
||||
"description": forms.Textarea(
|
||||
attrs={"placeholder": "Plus d'infos sur le lieu ? (facultatif)"}
|
||||
),
|
||||
"url": forms.TextInput(attrs={"placeholder": "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)
|
28
la_chariotte/lieu/migrations/0001_initial.py
Normal file
28
la_chariotte/lieu/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Lieu",
|
||||
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")),
|
||||
("url", models.CharField(max_length=20, verbose_name='Portion du lien pour le lieu', 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")),
|
||||
],
|
||||
),
|
||||
]
|
0
la_chariotte/lieu/migrations/__init__.py
Normal file
0
la_chariotte/lieu/migrations/__init__.py
Normal file
27
la_chariotte/lieu/models.py
Normal file
27
la_chariotte/lieu/models.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import random
|
||||
|
||||
import base36
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from la_chariotte.order.models import GroupedOrder
|
||||
from la_chariotte.settings import AUTH_USER_MODEL
|
||||
|
||||
|
||||
class Lieu(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"
|
||||
)
|
||||
url = models.CharField(
|
||||
max_length=20, verbose_name="Portion du lien pour le lieu", unique=True
|
||||
)
|
||||
description = models.TextField("Description", null=True, blank=True)
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("lieu:lieu_update", kwargs={"url": self.url})
|
62
la_chariotte/lieu/templates/lieu/index.html
Normal file
62
la_chariotte/lieu/templates/lieu/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 'lieu:create_lieu' %}">
|
||||
<i class="fa fa-plus-circle mr-3" aria-hidden="true"></i>
|
||||
Créer un nouveau lieu de distribution</a>
|
||||
</div>
|
||||
{% if lieu_context.lieu_liste %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Lieu</th>
|
||||
<th>Commandes</th>
|
||||
<th>Description</th>
|
||||
<th>URL</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for lieu in lieu_context.lieu_liste %}
|
||||
<tr>
|
||||
<td>
|
||||
<a title="Détail du lieu de distribution" href="{% url 'lieu:lieu_update' lieu.url %}">{{ lieu }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if lieu.url in lieu_context.commandes.keys %}
|
||||
{% for lieu_url, lieu_commandes in lieu_context.commandes.items %}
|
||||
{% if lieu_url == lieu.url %}
|
||||
{% for commande in lieu_commandes %}
|
||||
{% url 'order:grouped_order_detail' code=commande.code as order_url %}
|
||||
{% if order_url %}
|
||||
<a href="{{ order_url }}">{{commande.name}}</a><br>
|
||||
{% else %}
|
||||
{{ commande.name }}<br>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
Aucune
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ lieu.description }}
|
||||
</td>
|
||||
<td>
|
||||
{{ lieu.url }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>Pas de lieux de distribution pour l'instant</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
26
la_chariotte/lieu/templates/lieu/lieu_create.html
Normal file
26
la_chariotte/lieu/templates/lieu/lieu_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">Nouvelle 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 'lieu:index' %}">Annuler</a>
|
||||
<input class="button is-primary" type="submit" value="Suivant">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
22
la_chariotte/lieu/templates/lieu/lieu_detail.html
Normal file
22
la_chariotte/lieu/templates/lieu/lieu_detail.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ lieu }}{% 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">{{ lieu.name }} - modifier</p>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<div class="buttons">
|
||||
<a class="button is-light" href="{% url 'lieu:index' %}">Annuler</a>
|
||||
<input class="button is-primary" type="submit" value="Suivant">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
22
la_chariotte/lieu/templates/lieu/lieu_update.html
Normal file
22
la_chariotte/lieu/templates/lieu/lieu_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">{{ lieu.name }} - modifier</p>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form | crispy }}
|
||||
<div class="buttons">
|
||||
<a class="button is-light" href="{% url 'lieu:index' %}">Annuler</a>
|
||||
<input class="button is-primary" type="submit" value="Suivant">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
14
la_chariotte/lieu/urls.py
Normal file
14
la_chariotte/lieu/urls.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "lieu"
|
||||
urlpatterns = [
|
||||
path("", views.IndexView.as_view(), name="index"),
|
||||
path(
|
||||
"<str:url>/",
|
||||
views.LieuUpdateView.as_view(),
|
||||
name="lieu_update",
|
||||
),
|
||||
path("creer", views.LieuCreateView.as_view(), name="create_lieu"),
|
||||
]
|
6
la_chariotte/lieu/views/__init__.py
Normal file
6
la_chariotte/lieu/views/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from .lieu import (
|
||||
IndexView,
|
||||
LieuCreateView,
|
||||
LieuDetailView,
|
||||
LieuUpdateView,
|
||||
)
|
87
la_chariotte/lieu/views/lieu.py
Normal file
87
la_chariotte/lieu/views/lieu.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
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 LieuForm
|
||||
from ..models import Lieu
|
||||
|
||||
|
||||
class IndexView(LoginRequiredMixin, generic.ListView):
|
||||
"""View showing all the grouped orders managed by the authenticated user"""
|
||||
|
||||
template_name = "lieu/index.html"
|
||||
context_object_name = "lieu_context"
|
||||
|
||||
def get_queryset(self):
|
||||
lieux = Lieu.objects.filter(orga=self.request.user)
|
||||
commandes_par_lieu = dict()
|
||||
for lieu in lieux:
|
||||
orders = GroupedOrder.objects.all().filter(placekey=lieu)
|
||||
if orders:
|
||||
commandes_par_lieu[lieu.url] = orders
|
||||
|
||||
return {
|
||||
"lieu_liste": lieux,
|
||||
"commandes": commandes_par_lieu,
|
||||
}
|
||||
|
||||
|
||||
class LieuDetailView(generic.DetailView):
|
||||
model = Lieu
|
||||
template_name = "lieu/lieu_detail.html"
|
||||
context_object_name = "lieu_context"
|
||||
form_class = LieuForm
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Lieu, url=self.kwargs.get("url"))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
lieu = self.get_object()
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
return context
|
||||
|
||||
|
||||
class LieuUpdateView(UserIsOrgaMixin, generic.UpdateView):
|
||||
model = Lieu
|
||||
template_name = "lieu/lieu_update.html"
|
||||
context_object_name = "lieu"
|
||||
form_class = LieuForm
|
||||
# Can't change URL after creation
|
||||
form_class.base_fields["url"].disabled = True
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Lieu, url=self.kwargs.get("url"))
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["user"] = self.request.user
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
class LieuCreateView(LoginRequiredMixin, generic.CreateView):
|
||||
model = Lieu
|
||||
form_class = LieuForm
|
||||
template_name = "lieu/lieu_create.html"
|
||||
|
||||
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)
|
|
@ -1,10 +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.lieu.models import Lieu
|
||||
from la_chariotte.order.models import GroupedOrder, Item
|
||||
|
||||
|
||||
|
@ -22,6 +24,12 @@ class GroupedOrderForm(forms.ModelForm):
|
|||
label="Numéro de téléphone obligatoire pour les participants",
|
||||
required=False,
|
||||
)
|
||||
placekey = forms.ModelMultipleChoiceField(
|
||||
label="Lieux de distribution",
|
||||
queryset=Lieu.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = GroupedOrder
|
||||
|
@ -34,6 +42,7 @@ class GroupedOrderForm(forms.ModelForm):
|
|||
"place",
|
||||
"description",
|
||||
"is_phone_mandatory",
|
||||
"placekey",
|
||||
]
|
||||
widgets = {
|
||||
"name": forms.TextInput(
|
||||
|
|
23
la_chariotte/order/migrations/0030_add_lieu.py
Normal file
23
la_chariotte/order/migrations/0030_add_lieu.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("order", "0029_set_phone_mandatory_for_existing_orders"),
|
||||
("lieu", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="groupedorder",
|
||||
name="placekey",
|
||||
field=models.ManyToManyField(
|
||||
verbose_name="Lieu de distribution",
|
||||
to="lieu.lieu",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="order",
|
||||
name="placekey",
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=models.deletion.CASCADE, to='lieu.lieu'),
|
||||
),
|
||||
]
|
|
@ -21,9 +21,17 @@ class GroupedOrder(models.Model):
|
|||
max_length=50, null=True, blank=True, verbose_name="Créneau de distribution"
|
||||
)
|
||||
deadline = models.DateTimeField("Date limite de commande")
|
||||
|
||||
# Try to associate with a saved distribution place in DB
|
||||
placekey = models.ManyToManyField(
|
||||
"lieu.Lieu",
|
||||
verbose_name="Lieu de distribution",
|
||||
)
|
||||
# Alternatively, propose a free-text distribution place field
|
||||
place = models.CharField(
|
||||
max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
|
||||
)
|
||||
|
||||
description = models.TextField("Description", null=True, blank=True)
|
||||
code = models.CharField(auto_created=True)
|
||||
is_phone_mandatory = models.BooleanField(
|
||||
|
@ -123,6 +131,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)
|
||||
placekey = models.ForeignKey(
|
||||
"lieu.lieu", on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
|
||||
@property
|
||||
def articles_nb(self):
|
||||
|
|
|
@ -171,6 +171,17 @@
|
|||
<p><label for="note">Note à l'organisateur·ice<em> (facultatif)</em> :</label>
|
||||
<textarea id="note" rows=3 name="note">{{ note }}</textarea></p>
|
||||
|
||||
{% if placekey %}
|
||||
<label for="placekey">Point de distribution:</label>
|
||||
<select name="placekey" id="placekey-select">
|
||||
{% for place in placekey %}
|
||||
<option value="{{ place.url }}">{{ 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.placekey %}<p class="has-text-grey-dark">Lieu: {{ order.placekey }}</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>
|
||||
|
|
|
@ -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>
|
||||
{% if by_lieu %}
|
||||
{% for lieu_name, ordered_dict in lieux.items %}
|
||||
<h3>{{ lieu_name }}</h3>
|
||||
{% include 'order/grouped_order_sheet_list.html' with orders_dict=lieu_orders items=items grouped_order=grouped_order %}
|
||||
{% 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é
|
||||
{% 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 %}
|
|
@ -11,6 +11,8 @@ from django.views import generic
|
|||
from django_weasyprint import WeasyTemplateResponseMixin
|
||||
from icalendar import Calendar, Event, vCalAddress, vText
|
||||
|
||||
from la_chariotte.lieu.models import Lieu
|
||||
|
||||
from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm
|
||||
from ..models import GroupedOrder, OrderAuthor
|
||||
from .mixins import UserIsOrgaMixin
|
||||
|
@ -136,6 +138,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,
|
||||
"placekey": grouped_order.placekey.all(),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
@ -198,6 +201,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"] = Lieu.objects.filter(orga=self.request.user)
|
||||
return context
|
||||
|
||||
|
||||
class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
|
||||
def get_object(self, queryset=None):
|
||||
|
@ -296,6 +304,8 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
|
|||
context = super(GroupedOrderExportView, self).get_context_data(**kwargs)
|
||||
grouped_order = self.get_object()
|
||||
|
||||
by_lieu = len(grouped_order.placekey.all()) > 0
|
||||
|
||||
items = [
|
||||
item
|
||||
for item in grouped_order.item_set.all().order_by("name")
|
||||
|
@ -316,6 +326,14 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
|
|||
|
||||
context["items"] = items
|
||||
context["orders_dict"] = orders_dict
|
||||
context["by_lieu"] = by_lieu
|
||||
if by_lieu:
|
||||
lieux = dict()
|
||||
for order, order_items in orders_dict.items():
|
||||
if order.placekey.name not in lieux:
|
||||
lieux[order.placekey.name] = dict()
|
||||
lieux[order.placekey.name][order] = order_items
|
||||
context["lieux"] = lieux
|
||||
|
||||
return context
|
||||
|
||||
|
@ -355,6 +373,8 @@ class ExportGroupOrderEmailAdressesToDownloadView(UserPassesTestMixin, generic.V
|
|||
|
||||
class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
grouped_order = self.get_object()
|
||||
enableLieu = len(grouped_order.placekey.all()) > 0
|
||||
super(ExportGroupedOrderToCSVView, self).get(self, request, *args, **kwargs)
|
||||
context = self.get_context_data()
|
||||
|
||||
|
@ -376,6 +396,8 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
|||
row.append("Note")
|
||||
row.append("Date")
|
||||
row.append("Heure")
|
||||
if enableLieu:
|
||||
row.append("Lieu")
|
||||
writer.writerow(row)
|
||||
|
||||
row = ["", "Prix unitaire TTC (€)"]
|
||||
|
@ -398,6 +420,8 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
|
|||
row.append(order.note)
|
||||
row.append(order.created_date.strftime("%d/%m/%Y"))
|
||||
row.append(order.created_date.strftime("%H:%M"))
|
||||
if enableLieu:
|
||||
row.append(order.placekey.name)
|
||||
writer.writerow(row)
|
||||
|
||||
# write total row
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.urls import reverse, reverse_lazy
|
|||
from django.utils import timezone
|
||||
from django.views import generic
|
||||
|
||||
from la_chariotte.lieu.models import Lieu
|
||||
from la_chariotte.mail.utils import send_order_confirmation_mail
|
||||
|
||||
from ..models import GroupedOrder, Order, OrderAuthor, OrderedItem
|
||||
|
@ -13,6 +14,7 @@ from ..models import GroupedOrder, Order, OrderAuthor, OrderedItem
|
|||
def place_order(request, code):
|
||||
# Creates an AnonymousUser and an Order with related OrderedItems
|
||||
grouped_order = get_object_or_404(GroupedOrder, code=code)
|
||||
places = grouped_order.placekey.all()
|
||||
|
||||
# Handle permissions
|
||||
user_is_orga = request.user == grouped_order.orga
|
||||
|
@ -46,6 +48,19 @@ def place_order(request, code):
|
|||
phone = request.POST["phone"]
|
||||
email = request.POST["email"]
|
||||
note = request.POST["note"]
|
||||
|
||||
# Make sure requested location is valid in this group order (and exists at all)
|
||||
# If no placekey is enabled for this group order, placekey is always null
|
||||
if len(places) == 0:
|
||||
placekey = None
|
||||
else:
|
||||
placekey = get_object_or_404(Lieu, url=request.POST["placekey"])
|
||||
if placekey not in places:
|
||||
raise http.Http404(
|
||||
"Le lieu demandé n'est pas valide pour cette commande: %s"
|
||||
% requested_placekey
|
||||
)
|
||||
|
||||
author = OrderAuthor.objects.create(
|
||||
first_name=first_name, last_name=last_name, email=email, phone=phone
|
||||
)
|
||||
|
@ -54,6 +69,7 @@ def place_order(request, code):
|
|||
grouped_order=grouped_order,
|
||||
note=note,
|
||||
created_date=timezone.now(),
|
||||
placekey=placekey,
|
||||
)
|
||||
|
||||
# add items to the order
|
||||
|
@ -80,6 +96,7 @@ def place_order(request, code):
|
|||
"error_message": error_message,
|
||||
"note": order.note,
|
||||
"author": author,
|
||||
"placekey": placekey,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -98,6 +115,7 @@ def place_order(request, code):
|
|||
"error_message": error_message,
|
||||
"note": order.note,
|
||||
"author": author,
|
||||
"placekey": placekey,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ if os.getenv("ALLOWED_HOSTS"):
|
|||
# Applications & middlewares
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"la_chariotte.lieu",
|
||||
"la_chariotte.order",
|
||||
"la_chariotte.accounts",
|
||||
"la_chariotte.mail",
|
||||
|
|
|
@ -30,6 +30,7 @@ from la_chariotte.order.views.stats import stats
|
|||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("commande/", include("la_chariotte.order.urls")),
|
||||
path("lieu/", include("la_chariotte.lieu.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