Compare commits

..

4 commits

Author SHA1 Message Date
Laetitia Getti
d879e8bb95 Merge branch 'feat/202/improve-notice' into 'develop'
[202] Ajout FAQ et amélioration du mode d'emploi

See merge request la-chariotte/la_chariotte!110
2024-09-14 13:43:54 +00:00
Laetitia
58f73ac038 reformulation reponse faq et création liens feedback et HelloAsso dans les settings 2024-08-04 22:39:26 +02:00
Laetitia
88295ad1f0 ajout de la FAQ
contenu à relire, et front à amélirer
2024-08-04 18:26:06 +02:00
Laetitia
cce7446776 mise en page du mode d'emploi
Il faut encore écrire la fin du tuto, en copiant depuis jean cloud
2024-08-04 18:24:52 +02:00
51 changed files with 2331 additions and 8781 deletions

3
.gitignore vendored
View file

@ -3,8 +3,11 @@ coverage.xml
.coverage .coverage
la_chariotte.egg-info/ la_chariotte.egg-info/
node_modules node_modules
/static/*
/media/*
local_settings.py local_settings.py
.venv .venv
*venv* *venv*
mails.sqlite mails.sqlite
package-lock.json package-lock.json
static

View file

@ -5,23 +5,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.3.0 (2024-11-02)
This is a small release, with a few features and bugfixes. The code is now
hosted on https://framagit.org rather than https://gitlab.com.
### Added
- Changed hour and date format in the generated CSV files
- Added the date in the grouped order dashboard
- Changed the display of the last possible date and hour to place an order
- Added the total number of ordered items in the grouped orders overview
- Removed header line and column for grouped order name in the CSV export for emails
### Fixed
- The "reset password" form is now fixed, it was broken since the last release.
## 1.2.0 (2024-07-12) ## 1.2.0 (2024-07-12)
A small release, with a few features, a new footer and a `/stats` endpoint. A small release, with a few features, a new footer and a `/stats` endpoint.

1326
LICENSE

File diff suppressed because it is too large Load diff

1213
chariotte-v0-data.sql Normal file

File diff suppressed because it is too large Load diff

View file

@ -22,21 +22,22 @@ classDiagram
name name
deadline : DateTime deadline : DateTime
delivery_date : Date delivery_date : Date
delivery_slot
place place
description description
orga : CustomUser orga : CustomUser
total_price
} }
class Item{ class Item{
name name
grouped_order : GroupedOrder grouped_order : GroupedOrder
ordered_nb ordered_nb
price total_price
max_limit max_limit
} }
class Order{ class Order{
grouped_order : GroupedOrder grouped_order : GroupedOrder
author : OrderAuthor author : OrderAuthor
price
created_date created_date
note note
} }

View file

@ -1 +1 @@
__version__ = "1.3.0" __version__ = "1.2.0"

View file

@ -8,6 +8,6 @@
</p> </p>
<div class="box"> <div class="box">
<p>Votre mot de passe a été correctement réinitialisé. Vous pouvez maintenant vous connecter.</p> <p>Votre mot de passe a été correctement réinitialisé. Vous pouvez maintenant vous connecter.</p>
<a class="button is-primary" href="{% url 'accounts:login' %}">Se connecter</a> <button class="button is-primary" href="{% url 'accounts:login'%}">Se connecter</button>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -9,8 +9,8 @@
{% if validlink %} {% if validlink %}
<form method="POST"> <form method="POST">
<p>Veuillez entrer un nouveau mot de passe tout neuf.</p> <p>Veuillez entrer un nouveau mot de passe tout neuf.</p>
{{ form | crispy }}
{% csrf_token %} {% csrf_token %}
{% crispy form %}
<input class="button is-primary" <input class="button is-primary"
type="submit" type="submit"
value="Changer le mot de passe"> value="Changer le mot de passe">

View file

@ -8,8 +8,7 @@
<div class="box"> <div class="box">
<form method="POST"> <form method="POST">
<p>Entrez votre mail pour recevoir les instructions pour le réinitialiser.</p> <p>Entrez votre mail pour recevoir les instructions pour le réinitialiser.</p>
{{ form | crispy }} {% crispy form %}
{% csrf_token %}
<div class="buttons"> <div class="buttons">
<input class="button is-primary" type="submit" value="Envoyer"> <input class="button is-primary" type="submit" value="Envoyer">
</div> </div>

View file

@ -18,10 +18,6 @@ class GroupedOrderForm(forms.ModelForm):
widget=forms.TimeInput(attrs={"type": "time"}), widget=forms.TimeInput(attrs={"type": "time"}),
initial=datetime.time(hour=23, minute=59, second=59), initial=datetime.time(hour=23, minute=59, second=59),
) )
is_phone_mandatory = forms.BooleanField(
label="Numéro de téléphone obligatoire pour les participants",
required=False,
)
class Meta: class Meta:
model = GroupedOrder model = GroupedOrder
@ -33,7 +29,6 @@ class GroupedOrderForm(forms.ModelForm):
"delivery_slot", "delivery_slot",
"place", "place",
"description", "description",
"is_phone_mandatory",
] ]
widgets = { widgets = {
"name": forms.TextInput( "name": forms.TextInput(

View file

@ -1,36 +0,0 @@
# Generated by Django 4.2.16 on 2024-10-31 21:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("order", "0026_groupedorder_delivery_slot"),
]
operations = [
migrations.RemoveField(
model_name="groupedorder",
name="total_price",
),
migrations.RemoveField(
model_name="item",
name="ordered_nb",
),
migrations.RemoveField(
model_name="order",
name="articles_nb",
),
migrations.RemoveField(
model_name="order",
name="price",
),
migrations.AlterField(
model_name="order",
name="created_date",
field=models.DateTimeField(
auto_now_add=True, verbose_name="Date et heure de commande"
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 4.2.16 on 2024-12-08 15:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("order", "0027_remove_groupedorder_total_price_and_more"),
]
operations = [
migrations.AddField(
model_name="groupedorder",
name="is_phone_mandatory",
field=models.BooleanField(
default=False, verbose_name="Numéro de téléphone obligatoire"
),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 4.2.16 on 2024-12-08 15:41
from django.db import migrations
def set_phone_mandatory(apps, schema_editor):
"""For continuity, force mandatory phone for the orders that were created so far."""
GroupedOrder = apps.get_model("order", "GroupedOrder")
for grouped_order in GroupedOrder.objects.all():
grouped_order.is_phone_mandatory = True
grouped_order.save()
class Migration(migrations.Migration):
dependencies = [
("order", "0028_groupedorder_is_phone_mandatory"),
]
operations = [
migrations.RunPython(set_phone_mandatory),
]

View file

@ -25,10 +25,8 @@ class GroupedOrder(models.Model):
max_length=100, null=True, blank=True, verbose_name="Lieu de livraison" max_length=100, null=True, blank=True, verbose_name="Lieu de livraison"
) )
description = models.TextField("Description", null=True, blank=True) description = models.TextField("Description", null=True, blank=True)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
code = models.CharField(auto_created=True) code = models.CharField(auto_created=True)
is_phone_mandatory = models.BooleanField(
default=False, verbose_name="Numéro de téléphone obligatoire"
)
def create_code_from_pk(self): def create_code_from_pk(self):
"""When a grouped order is created, a unique code is generated, to be used to """When a grouped order is created, a unique code is generated, to be used to
@ -54,18 +52,16 @@ class GroupedOrder(models.Model):
) )
self.code = f"{base_36_pk}{random_string}"[:code_length] self.code = f"{base_36_pk}{random_string}"[:code_length]
@property def compute_total_price(self):
def total_price(self):
price = 0 price = 0
for order in self.order_set.all(): for order in self.order_set.all():
price += order.price price += order.price
return price self.total_price = price
self.save()
def get_total_ordered_items(self): def compute_items_ordered_nb(self):
total_nb = 0
for item in self.item_set.all(): for item in self.item_set.all():
total_nb += item.ordered_nb item.compute_ordered_nb()
return total_nb
def is_open(self): def is_open(self):
return self.deadline >= timezone.now() return self.deadline >= timezone.now()
@ -121,24 +117,26 @@ class Order(models.Model):
GroupedOrder, on_delete=models.CASCADE, related_name="order_set" GroupedOrder, on_delete=models.CASCADE, related_name="order_set"
) )
author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE) author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE)
articles_nb = models.PositiveIntegerField(default=0)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True) created_date = models.DateTimeField("Date et heure de commande", auto_now_add=True)
note = models.TextField(max_length=200, null=True, blank=True) note = models.TextField(max_length=200, null=True, blank=True)
@property def compute_order_articles_nb(self):
def articles_nb(self):
"""Computes the number of articles in this order""" """Computes the number of articles in this order"""
articles_nb = 0 articles_nb = 0
for ord_item in self.ordered_items.all(): for ord_item in self.ordered_items.all():
articles_nb += ord_item.nb articles_nb += ord_item.nb
return articles_nb self.articles_nb = articles_nb
self.save()
@property def compute_order_price(self):
def price(self):
"""Computes the total price of the order""" """Computes the total price of the order"""
price = 0 price = 0
for ord_item in self.ordered_items.all(): for ord_item in self.ordered_items.all():
price += ord_item.get_price() price += ord_item.get_price()
return price self.price = price
self.save()
def __str__(self): # pragma: no cover def __str__(self): # pragma: no cover
return ( return (
@ -153,13 +151,15 @@ class Item(models.Model):
price = models.DecimalField(max_digits=10, decimal_places=2) price = models.DecimalField(max_digits=10, decimal_places=2)
max_limit = models.PositiveSmallIntegerField(null=True, blank=True) max_limit = models.PositiveSmallIntegerField(null=True, blank=True)
@property ordered_nb = models.IntegerField(default=0)
def ordered_nb(self):
def compute_ordered_nb(self):
"""Computes the number of times this item has been ordered""" """Computes the number of times this item has been ordered"""
ordered_nb = 0 ordered_nb = 0
for order in self.orders.all(): for order in self.orders.all():
ordered_nb += order.nb ordered_nb += order.nb
return ordered_nb self.ordered_nb = ordered_nb
self.save()
def get_total_price(self): def get_total_price(self):
"""Returns the total price of all orders on this item""" """Returns the total price of all orders on this item"""

View file

@ -1,7 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Nouvelle commande groupée{% endblock %} {% block title %}Nouvelle commande groupée{% endblock %}
{% block content %} {% block content %}
@ -12,9 +10,8 @@
<p class="title">Nouvelle commande groupée</p> <p class="title">Nouvelle commande groupée</p>
<div class="columns"> <div class="columns">
<div class="column is-8"> <div class="column is-8">
<form method="post" onsubmit="deadlinePassedCheck(event)"> <form method="post" onsubmit="deadlinePassedCheck(event)">{% csrf_token %}
{% csrf_token %} {{ form.as_p }}
{{ form | crispy }}
<div class="buttons"> <div class="buttons">
<a class="button is-light" href="{% url 'order:index' %}">Annuler</a> <a class="button is-light" href="{% url 'order:index' %}">Annuler</a>
<input class="button is-primary" type="submit" value="Suivant"> <input class="button is-primary" type="submit" value="Suivant">

View file

@ -158,18 +158,16 @@
value="{{ order_author.last_name }}" required></p> value="{{ order_author.last_name }}" required></p>
</div> </div>
<div class="column"> <div class="column">
<p><label for="phone">Numéro de téléphone {% if not is_phone_mandatory %}<em>(facultatif)</em> {% endif %}:</label> <p><label for="phone">Numéro de téléphone :</label>
<input id="phone" type="tel" pattern="[0-9]{10}" <input id="phone" type="tel" pattern="[0-9]{10}" placeholder="0601020304" name="phone"
placeholder="0601020304" name="phone" value="{{ order_author.phone }}" required></p>
value="{{ order_author.phone }}"
{% if is_phone_mandatory %}required{% endif %}></p>
<p><label for="email">Adresse mail : </label> <p><label for="email">Adresse mail : </label>
<input id="email" type="email" placeholder="exemple@mail.fr" name="email" <input id="email" type="email" placeholder="exemple@mail.fr" name="email"
value="{{ order_author.email }}" required></p> value="{{ order_author.email }}" required></p>
</div> </div>
</div> </div>
<p><label for="note">Note à l'organisateur·ice<em> (facultatif)</em> :</label> <p><label for="note">Note à l'organisateur·ice</label>
<textarea id="note" rows=3 name="note">{{ note }}</textarea></p> <textarea id="note" rows=3 placeholder="(facultatif)" name="note">{{ note }}</textarea></p>
<div class="buttons"> <div class="buttons">
<button id="submit" type="submit" value="Order" class="button is-primary"> <button id="submit" type="submit" value="Order" class="button is-primary">
@ -188,6 +186,7 @@
// Compute total price whenever a value in input is modified // Compute total price whenever a value in input is modified
document.getElementById("inputs-parent").addEventListener("change", function () { document.getElementById("inputs-parent").addEventListener("change", function () {
inputs = [...document.getElementsByTagName("input")].filter(input => input.getAttribute("name").indexOf("quantity_") === 0); //filter the inputs to get the quantity inputs only inputs = [...document.getElementsByTagName("input")].filter(input => input.getAttribute("name").indexOf("quantity_") === 0); //filter the inputs to get the quantity inputs only
prices = {{ prices_dict | safe }}; prices = {{ prices_dict | safe }};
let total_price = 0; let total_price = 0;

View file

@ -90,10 +90,10 @@
<div class="column"> <div class="column">
<p>Pour vous aider à distribuer les produits le jour J, vous pouvez télécharger la liste des commandes <p>Pour vous aider à distribuer les produits le jour J, vous pouvez télécharger la liste des commandes
au format PDF pour l'<strong>imprimer</strong>, ou au format CSV pour l'<strong>afficher dans un tableur</strong> :</p> au format PDF pour l'<strong>imprimer</strong>, ou au format CSV pour l'<strong>afficher dans un tableur</strong> :</p>
<a class="button is-info" href="{% url 'order:grouped_order_sheet' grouped_order.code %}"> <a class="button is-info" href="{% url 'order:grouped_order_sheet' grouped_order.code %}" target="_blank">
<i class="fa fa-file-pdf-o mr-3" aria-hidden="true"></i>Commandes en PDF <i class="fa fa-file-pdf-o mr-3" aria-hidden="true"></i>Commandes en PDF
</a> </a>
<a class="button is-info" href="{% url 'order:grouped_order_csv_export' grouped_order.code %}"> <a class="button is-info" href="{% url 'order:grouped_order_csv_export' grouped_order.code %}" target="_blank">
<i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Commandes en CSV <i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Commandes en CSV
</a> </a>
</div> </div>
@ -126,7 +126,7 @@
<tfoot> <tfoot>
<th>Total</th> <th>Total</th>
<th></th> <th></th>
<th>{{ total_ordered_items }}</th> <th></th>
<th>{{ grouped_order.total_price }} €</th> <th>{{ grouped_order.total_price }} €</th>
</tfoot> </tfoot>
</table> </table>
@ -137,11 +137,11 @@
<div id="commandes" class="box tabcontent"> <div id="commandes" class="box tabcontent">
<div class="buttons is-pulled-right"> <div class="buttons is-pulled-right">
<a class="button is-info" href="{% url 'order:email_list' grouped_order.code %}?format=csv"> <a class="button is-info" href="{% url 'order:email_list' grouped_order.code %}?format=csv" target="_blank">
<i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Emails en CSV <i class="fa fa-file-excel-o mr-3" aria-hidden="true"></i>Emails en CSV
</a> </a>
<input id="email_list" name="email_list" hidden="true" value="{{ emails_list|join:';' }}" /> <input id="email_list" name="email_list" hidden="true" value="{{ emails_list|join:';' }}" />
<a class="button is-info" onclick="copyText('email_list')"> <a class="button is-info" onclick="copyText('email_list')" target="_blank">
<i class="fa fa-files-o mr-3" aria-hidden="true"></i>Copier les emails <i class="fa fa-files-o mr-3" aria-hidden="true"></i>Copier les emails
</a> </a>
</div> </div>
@ -161,7 +161,7 @@
<tr> <tr>
<td>{{ order.author }}</td> <td>{{ order.author }}</td>
<td>{{ order.price }} €</td> <td>{{ order.price }} €</td>
<td><a href="mailto:{{ order.author.email }}">{{ order.author.email }}</a>{% if order.author.phone %} / {{ order.author.phone }}{% endif %}</td> <td><a href="mailto:{{ order.author.email }}">{{ order.author.email }}</a></td>
<td> <td>
<button class="button is-info is-small js-modal-trigger" data-target="order-detail-{{ order.id }}"> <button class="button is-info is-small js-modal-trigger" data-target="order-detail-{{ order.id }}">
Voir Voir

View file

@ -21,22 +21,26 @@
} }
{% if items|length > 10 %} {% if items|length > 10 %}
.item_name { .item_name {
text-align:center;
white-space:nowrap;
-webkit-transform: rotate(-90deg); -webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg); -moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg); -ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg); -o-transform: rotate(-90deg);
transform: rotate(-90deg); transform: rotate(-90deg);
font-size: 8pt; font-size: 8pt;
height:220px;
} }
.item_name div { .item_name div {
width:200px; margin:-10px -120% ;
-webkit-transform: translateX(-40%); display:inline-block;
-moz-transform: translateX(-40%); }
-ms-transform: translateX(-40%); .item_name div:before{
-o-transform: translateX(-40%); content:'';
transform: translateX(-40%); width:0;
padding-top:110%;
display:inline-block;
vertical-align:middle;
} }
{% endif %} {% endif %}
@ -49,19 +53,20 @@
border: 1px black solid; border: 1px black solid;
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
table-layout: fixed
} }
th, td { th, td {
vertical-align: center;
padding: 3px 2px 2px 2px; padding: 3px 2px 2px 2px;
border: 1px black solid; border: 1px black solid;
font-weight: normal; font-weight: normal;
text-align: center; text-align: center;
word-wrap: break-word;
} }
th { th {
page-break-inside: avoid; page-break-inside: avoid;
word-break: break-all;
word-wrap: break-word;
font-weight: bold; font-weight: bold;
} }
@ -80,7 +85,7 @@
<thead> <thead>
<tr> <tr>
<th style="font-size: 0.5em; width: 2em">OK</th> <th style="font-size: 0.5em; width: 2em">OK</th>
<th style="text-align: center; width: 20%">Nom</th> <th style="text-align: center">Nom</th>
{% for item in items %} {% for item in items %}
<th class="item_name" style="font-weight: normal;"> <th class="item_name" style="font-weight: normal;">
<div>{{ item.name }}</div> <div>{{ item.name }}</div>

View file

@ -1,7 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Modifier la commande groupée{% endblock %} {% block title %}Modifier la commande groupée{% endblock %}
{% block content %} {% block content %}
@ -11,14 +9,13 @@
<div class="box"> <div class="box">
<p class="title">{{ grouped_order.name }} - modifier</p> <p class="title">{{ grouped_order.name }} - modifier</p>
<form method="post" onsubmit="deadlinePassedCheck(event)">{% csrf_token %} <form method="post" onsubmit="deadlinePassedCheck(event)">{% csrf_token %}
{{ form | crispy }} {{ form.as_p }}
<div class="buttons"> <div class="buttons">
<a class="button is-light" href="{% url 'order:index' %}">Annuler</a> <a class="button is-light" href="{% url 'order:index' %}">Annuler</a>
<input class="button is-primary" type="submit" value="Suivant"> <input class="button is-primary" type="submit" value="Suivant">
</div> </div>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}

View file

@ -112,18 +112,18 @@ class GroupedOrderDetailView(generic.DetailView):
return get_object_or_404(GroupedOrder, code=self.kwargs.get("code")) return get_object_or_404(GroupedOrder, code=self.kwargs.get("code"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
grouped_order = self.get_object() items = self.get_object().item_set.all()
items = grouped_order.item_set.all()
remaining_qty = {item.id: item.get_remaining_nb() for item in items} remaining_qty = {item.id: item.get_remaining_nb() for item in items}
prices_dict = {item.id: item.price for item in items} prices_dict = {item.id: item.price for item in items}
if self.request.user.is_authenticated: if self.request.user.is_authenticated:
order_author = OrderAuthor( order_author = OrderAuthor.objects.create(
first_name=self.request.user.first_name, first_name=self.request.user.first_name,
last_name=self.request.user.last_name, last_name=self.request.user.last_name,
email=self.request.user.username, email=self.request.user.username,
) )
order_author.save()
else: else:
order_author = None order_author = None
@ -134,8 +134,6 @@ class GroupedOrderDetailView(generic.DetailView):
"prices_dict": json.dumps(prices_dict, cls=DjangoJSONEncoder), "prices_dict": json.dumps(prices_dict, cls=DjangoJSONEncoder),
"remaining_qty": remaining_qty, "remaining_qty": remaining_qty,
"order_author": order_author, "order_author": order_author,
# Used to set if the phone is required in the form
"is_phone_mandatory": grouped_order.is_phone_mandatory,
} }
) )
return context return context
@ -153,6 +151,12 @@ class GroupedOrderOverview(UserIsOrgaMixin, generic.DetailView):
# Staff can see but not edit grouped orders # Staff can see but not edit grouped orders
return super().test_func() or self.request.user.is_staff return super().test_func() or self.request.user.is_staff
def get(self, request, *args, **kwargs):
# Compute grouped order total price before display
self.get_object().compute_total_price()
self.get_object().compute_items_ordered_nb()
return super().get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GroupedOrderOverview, self).get_context_data(**kwargs) context = super(GroupedOrderOverview, self).get_context_data(**kwargs)
# Add share link to context # Add share link to context
@ -163,7 +167,6 @@ class GroupedOrderOverview(UserIsOrgaMixin, generic.DetailView):
order__in=self.get_object().order_set.all() order__in=self.get_object().order_set.all()
) )
context["emails_list"] = set(participant.email for participant in participants) context["emails_list"] = set(participant.email for participant in participants)
context["total_ordered_items"] = self.get_object().get_total_ordered_items()
return context return context
@ -225,6 +228,7 @@ class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
# duplicate each item and add it to new_grouped_order # duplicate each item and add it to new_grouped_order
for item in initial_grouped_order.item_set.all(): for item in initial_grouped_order.item_set.all():
item.pk = None item.pk = None
item.ordered_nb = 0
item.save() item.save()
new_grouped_order.item_set.add(item) new_grouped_order.item_set.add(item)
@ -296,11 +300,7 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
context = super(GroupedOrderExportView, self).get_context_data(**kwargs) context = super(GroupedOrderExportView, self).get_context_data(**kwargs)
grouped_order = self.get_object() grouped_order = self.get_object()
items = [ items = grouped_order.item_set.filter(ordered_nb__gt=0).order_by("name")
item
for item in grouped_order.item_set.all().order_by("name")
if item.ordered_nb > 0
]
orders = grouped_order.order_set.all().order_by( orders = grouped_order.order_set.all().order_by(
"author__last_name", "author__first_name" "author__last_name", "author__first_name"
) )
@ -344,7 +344,7 @@ class ExportGroupOrderEmailAdressesToDownloadView(UserPassesTestMixin, generic.V
response = http.HttpResponse(content_type="text/csv") response = http.HttpResponse(content_type="text/csv")
filename = f"commande _{grouped_order.name.replace(' ', '_')}" filename = f"commande _{grouped_order.name.replace(' ', '_')}"
response["Content-Disposition"] = f'attachment; filename="{filename}.csv"' response["Content-Disposition"] = f'attachment; filename="{filename}.csv"'
writer = csv.writer(response, delimiter=";") writer = csv.writer(response)
for email in email_list: for email in email_list:
writer.writerow([email]) writer.writerow([email])
return response return response
@ -361,10 +361,10 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
response = http.HttpResponse( response = http.HttpResponse(
content_type="text/csv", content_type="text/csv",
headers={ headers={
"Content-Disposition": f'attachment; filename="{context["object"].name}-commandes"' "Content-Disposition": f'attachment; filename="{ context["object"].name }-commandes"'
}, },
) )
writer = csv.writer(response, delimiter=";") writer = csv.writer(response)
# write headers rows # write headers rows
row = ["", ""] row = ["", ""]

View file

@ -72,6 +72,7 @@ def place_order(request, code):
if error_message: if error_message:
order.delete() order.delete()
author.delete() author.delete()
grouped_order.compute_items_ordered_nb()
return render( return render(
request, request,
"order/grouped_order_detail.html", "order/grouped_order_detail.html",
@ -102,6 +103,8 @@ def place_order(request, code):
) )
# Send confirmation mail and redirect to confirmation page # Send confirmation mail and redirect to confirmation page
order.compute_order_price()
grouped_order.compute_items_ordered_nb()
send_order_confirmation_mail(order) send_order_confirmation_mail(order)
# Redirect to prevent data from being posted twice when the user hits the Back # Redirect to prevent data from being posted twice when the user hits the Back
@ -120,6 +123,7 @@ def validate_item_ordered_nb(item, ordered_nb):
def validate_articles_ordered_nb(order): def validate_articles_ordered_nb(order):
"""Return an error if no items are ordered""" """Return an error if no items are ordered"""
order.compute_order_articles_nb()
if order.articles_nb == 0: if order.articles_nb == 0:
return "Veuillez commander au moins un produit" return "Veuillez commander au moins un produit"
return None return None

View file

@ -7,7 +7,7 @@ from sentry_sdk.integrations.django import DjangoIntegration
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
BASE_URL = os.getenv("BASE_URL", "http://127.0.0.1:8000") BASE_URL = os.getenv("BASE_URL", "http://127.0.0.1:8000")
PROJECT_NAME = os.getenv("PROJECT_NAME", "La Chariotte") PROJECT_NAME = os.getenv("PROJECT_NAME", "La Chariotte")
GITLAB_LINK = "https://gitlab.com/la-chariotte/la_chariotte" GITLAB_LINK = "https://gitlab.com/hashbangfr/la_chariotte"
CONTACT_MAIL = "contact@chariotte.fr" CONTACT_MAIL = "contact@chariotte.fr"
HELLOASSO_LINK = "https://www.helloasso.com/associations/la-chariotte/" HELLOASSO_LINK = "https://www.helloasso.com/associations/la-chariotte/"
FEEDBACK_LINK = "https://framaforms.org/votre-avis-sur-la-chariotte-1709742328" FEEDBACK_LINK = "https://framaforms.org/votre-avis-sur-la-chariotte-1709742328"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View file

@ -1,14 +0,0 @@
.accordion
transition: max-height 0.2s ease-out
border: none
outline: none
transition: 0.4s
.message-header
cursor: pointer
background: $white-ter
border: 1px solid $primary
color: $primary
p
margin: 0
.message-body
display: none

View file

@ -10,7 +10,3 @@
.formatted-text .formatted-text
white-space: pre-wrap white-space: pre-wrap
img.notice-img
border: $info 3px solid
margin-bottom: 1em

View file

@ -131,7 +131,7 @@
<div class="logo mb-4"> <div class="logo mb-4">
<p> <p>
{% settings_value "PROJECT_NAME" %} {% settings_value "PROJECT_NAME" %}
| <a href='{% settings_value "GITLAB_LINK" %}'>version {{ version }}</a> | <a href="https://gitlab.com/la-chariotte/">version {{ version }}</a>
</p> </p>
</div> </div>
<ul class="list-unstyled footer-menu lh-35"> <ul class="list-unstyled footer-menu lh-35">
@ -174,9 +174,11 @@
<li><a href="{% settings_value "HELLOASSO_LINK" %}"><i class="fa fa-heart mr-2 text-muted"></i>Faire un (petit) don</a></li> <li><a href="{% settings_value "HELLOASSO_LINK" %}"><i class="fa fa-heart mr-2 text-muted"></i>Faire un (petit) don</a></li>
</ul> </ul>
</div> </div>
</div> </div>
</footer> </div>
</body> </div>
</footer>
</body>
</html> </html>
<script> <script>
// For responsive menu // For responsive menu

View file

@ -31,7 +31,7 @@
<p> <p>
La Chariotte est développée sous licence libre Affero GPL. La Chariotte est développée sous licence libre Affero GPL.
Cela signifie que vous pouvez Cela signifie que vous pouvez
<a href="{{ gitlab }}" rel="noopener">aller voir le code</a> si vous êtes curieux, et y contribuer si vous êtes motivé·e ! <a href="{{ gitlab }}" rel="noopener" target="_blank">aller voir le code</a> si vous êtes curieux, et y contribuer si vous êtes motivé·e !
</p> </p>
</div> </div>
@ -45,7 +45,7 @@
Nous sommes très, très preneurs de vos suggestions, critiques constructives et demandes, et ouvert·e·s à toute question ! Nous sommes très, très preneurs de vos suggestions, critiques constructives et demandes, et ouvert·e·s à toute question !
</p> </p>
<p> <p>
Faites des retours sur la version actuelle <a href="https://framaforms.org/vos-retours-sur-la-chariotte-1692345453" rel="noopener">ici</a>, ou par mail à <a href="mailto:{{ mail }}">{{ mail }}</a>. Faites des retours sur la version actuelle <a href="https://framaforms.org/vos-retours-sur-la-chariotte-1692345453" rel="noopener" target="_blank">ici</a>, ou par mail à <a href="mailto:{{ mail }}">{{ mail }}</a>.
</p> </p>
</div> </div>

View file

@ -40,21 +40,6 @@
les membres de la Chariotte font vivre et améliorent continuellement l'outil pour répondre à vos besoins. les membres de la Chariotte font vivre et améliorent continuellement l'outil pour répondre à vos besoins.
</div> </div>
</article> </article>
<article class="message">
<div class="message-header">
<p>Est-ce que je peux rejoindre une commande commande créée par une personne que je ne connais pas ?</p>
<a>
<span class="icon">
<i class="fa fa-angle-down" aria-hidden="true"></i>
</span>
</a>
</div>
<div class="message-body">
C'est prévu a long terme mais pour l'instant non ! Aujourd'hui la Chariotte n'a pas pour visée de
mettre en relation des organisateurs/producteurs et des participants. Il n'est donc pas
possible de trouver une liste de commandes groupées existantes que vous pourriez rejoindre à volonté.
</div>
</article>
<article class="message"> <article class="message">
<div class="message-header"> <div class="message-header">
<p>Faut-il faire partie de l'asso pour utiliser la chariotte ?</p> <p>Faut-il faire partie de l'asso pour utiliser la chariotte ?</p>
@ -129,10 +114,7 @@
</div> </div>
<div class="message-body"> <div class="message-body">
Dans les faits, la plupart des AMAPs fonctionnent sur la base de commandes Dans les faits, la plupart des AMAPs fonctionnent sur la base de commandes
groupées régulières et à destination d'un public d'adhérents fixe.<br> groupées régulières et à destination d'un public d'adhérents fixe.
La Chariotte n'a pas vocation à remplacer le système des AMAPs, qui apporte un revenu régulier aux producteur.ice.s !
L'idée est d'apporter une solution complémentaire pour les endroits où les AMAPs ne sont pas établies, ou pour un produit en particulier qui n'est pas distribué régulièrement dans votre AMAP.
Il arrive aussi parfois que la Chariotte soit choisie comme outil de gestion pour l'AMAP si elle n'en avait pas encore.
</div> </div>
</article> </article>
<article class="message"> <article class="message">
@ -146,9 +128,9 @@
</div> </div>
<div class="message-body"> <div class="message-body">
Nous avons conçu la Chariotte spécifiquement pour répondre aux besoins des Nous avons conçu la Chariotte spécifiquement pour répondre aux besoins des
organisateur.ice.s et des participant.e.s à une commande groupée, en particulier des commandes groupées ponctuelles. organisateurs et des participants à une commande groupée, en particulier des commandes groupées ponctuelles.
Il s'agit d'un outil spécialisé dont la seule ambition est de simplifier la vie de ses utilisateurs. Il s'agit d'un outil spécialisé dont la seule ambition est de simplifier la vie de ses utilisateurs.
Libre à vous d'évaluer vos options et de choisir l'outil qui convient le mieux à votre organisation ! Libre à vous d'évaluer vos options et de choisir l'outil qui convient le mieux à votre organisation.
</div> </div>
</article> </article>
<article class="message"> <article class="message">
@ -198,8 +180,8 @@
N'hésitez pas à nous faire un rapport de votre expérience de la Chariotte en pointant N'hésitez pas à nous faire un rapport de votre expérience de la Chariotte en pointant
les bons et les mauvais côtés de l'application. Par exemple en remplissant les bons et les mauvais côtés de l'application. Par exemple en remplissant
<a href="{% settings_value "FEEDBACK_LINK" %}">ce formulaire</a> !</li> <a href="{% settings_value "FEEDBACK_LINK" %}">ce formulaire</a> !</li>
<li>Si le projet vous parle et que vous avez envie de nous soutenir de manière plus significative, <li>Si le projet vous parle et que vous avez envie de nous soutenir de manière plus significative
nous avons besoin d'un peu d'argent de poche pour soutenir les frais de l'association ; encore, nous avons besoin d'un peu d'argent de poche pour soutenir les frais de l'association ;
<a href ="{% settings_value "HELLOASSO_LINK" %}">par ici</a> pour en savoir plus !</li> <a href ="{% settings_value "HELLOASSO_LINK" %}">par ici</a> pour en savoir plus !</li>
</ul> </ul>
<p>Et enfin, si vous souhaitez en savoir plus sur l'association en elle-même et que vous <p>Et enfin, si vous souhaitez en savoir plus sur l'association en elle-même et que vous

View file

@ -34,7 +34,7 @@
<p class="title">Licence</p> <p class="title">Licence</p>
<p> <p>
La Chariotte est développée sous licence libre <strong>Affero GPL</strong>. Le code source est La Chariotte est développée sous licence libre <strong>Affero GPL</strong>. Le code source est
consultable <a href="{{ gitlab }}" rel="noopener">sur Gitlab</a> et ouvert à toute contribution ou suggestion. consultable <a href="{{ gitlab }}" rel="noopener" target="_blank">sur Gitlab</a> et ouvert à toute contribution ou suggestion.
</p> </p>
<p class="title">Conditions d'utilisation</p> <p class="title">Conditions d'utilisation</p>
<p> <p>

View file

@ -9,126 +9,32 @@
{% block content_title %}La Chariotte - mode d'emploi{% endblock %} {% block content_title %}La Chariotte - mode d'emploi{% endblock %}
</p> </p>
<div class="box"> <div class="box">
<p>Comment marche la Chariotte et qu'est-ce qu'on peut y faire ? Voilà comment vous pouvez :</p> <p>Comment marche la Chariotte et qu'est-ce qu'on peut y faire ? Voilà toutes les réponses à vos questions :</p>
<ul> <ul>
<li><a href="#join_grouped_order">Rejoindre une commande et commander des produits</a></li> <li><a href="#creer_compte">Créer un compte</a></li>
<li><a href="#pay">Payer (pas encore !)</a></li> <li><a href="#creer_commande_groupee">Créer une commande groupée</a></li>
<li><a href="#create_account">Créer un compte (nécessaire seulement pour organiser une commande groupée)</a></li> <li><a href="#ajouter_prodtuits">Ajouter des produits à une commande groupée</a></li>
<li><a href="#create_grouped_order">Créer une commande groupée</a></li>
<li><a href="#add_item">Ajouter des produits à une commande groupée</a></li>
<li><a href="#go_to_overview">Accéder à la page de gestion de ma commande groupée</a></li>
<li><a href="#delete_item">Modifier des produits d'une commande après sa création</a></li>
<li><a href="#edit_grouped_order">Modifier des informations de la commande</a></li>
<li><a href="#share_grouped_order">Partager une commande à des participants</a></li>
<li><a href="#print_orders_list">Imprimer ma liste de commandes</a></li>
<li><a href="#get_spreadsheet">Voir les commandes sous forme de tableur</a></li>
</ul> </ul>
</div> </div>
<div class="box" id="join_grouped_order"> <div class="box" id="creer_compte">
<p class="title">Rejoindre une commande et commander des produits</p> <p class="title">Créer un compte</p>
<p>Pour rejoindre une commande groupée, il faut y avoir été invité.e par son organisateur.ice. <p>Cliquez sur “Créer un compte”.</p>
Si vous avez un code à 6 caractères, vous pouvez l'entrer dans l'encadré "Rejoindre une commande groupée" <img src="{% static 'img/notice/create_account.jpg' %}"/>
sur la page d'accueil de la Chariotte.</p> <p>Remplissez toutes les informations du formulaire, puis cliquez sur valider.</p>
<img class="notice-img" src="{% static 'img/notice/join_grouped_order.png' %}"/> <img src="{% static 'img/notice/create_account_form.jpg' %}"/>
<p>Sinon, cliquez sur le lien que l'organisateur.ice vous a envoyé. Vous arriverez sur la page de commande.</p> <p>Vous pouvez maintenant vous connecter.</p>
<img class="notice-img" src="{% static 'img/notice/order_form.png' %}"/>
<p>Une fois la commande validée, vous recevrez un mail de confirmation.</p>
</div> </div>
<div class="box" id="pay"> <div class="box" id="creer_commande_groupee">
<p class="title">Payer (pas encore !)</p>
<p>Pour linstant, le paiement nest pas pris en compte par le site de La Chariotte,
cest lorganisateur.ice qui doit vous indiquer les modalités de paiement.</p>
</div>
<div class="box" id="create_account">
<p class="title">Créer un compte (nécessaire seulement pour organiser une commande groupée)</p>
<p>1. Cliquez sur “Créer un compte”.</p>
<img class="notice-img" src="{% static 'img/notice/create_account.jpg' %}"/>
<p>2. Remplissez toutes les informations du formulaire, puis cliquez sur valider.</p>
<img class="notice-img" src="{% static 'img/notice/create_account_form.jpg' %}"/>
<p>3. Vous pouvez maintenant vous connecter.</p>
</div>
<div class="box" id="create_grouped_order">
<p class="title">Créer une commande groupée</p> <p class="title">Créer une commande groupée</p>
<p>1. En ayant un compte (nécessaire seulement pour les organisateurs!), vous aurez accès à une page de gestion des commandes groupées en cliquant sur le menu “Mes commandes groupées”.</p> <p>En ayant un compte (nécessaire seulement pour les organisateurs!), vous aurez accès à une page de gestion des commandes groupées en cliquant sur le menu “Mes commandes groupées”.</p>
<img class="notice-img" src="{% static 'img/notice/my_grouped_orders.jpg' %}"/> <img src="{% static 'img/notice/my_grouped_orders.jpg' %}"/>
<p>2. Cest depuis cette page que vous pourrez créer une nouvelle commande en cliquant sur le bouton “Créer une nouvelle commande groupée” : </p> <p>Cest depuis cette page que vous pourrez créer une nouvelle commande en cliquant sur le bouton “Créer une nouvelle commande groupée” : </p>
<img class="notice-img" src="{% static 'img/notice/create_grouped_order.jpg' %}"/> <img src="{% static 'img/notice/create_grouped_order.jpg' %}"/>
<p>3. Quelques informations vous seront alors demandées :</p> <p>Quelques informations vous seront alors demandées :</p>
<img class="notice-img" src="{% static 'img/notice/create_grouped_order_form.jpg' %}"/> <img src="{% static 'img/notice/create_grouped_order_form.jpg' %}"/>
<p>4. En cliquant sur "Suivant", votre commande groupée sera validée.</p> <p>En cliquand sur "Suivant", votre commande groupée sera validée.</p>
</div>
<div class="box" id="add_item">
<p class="title">Ajouter des produits à une commande groupée</p>
<p>Directement après avoir validé la commande groupée, vous accédez à la page de gestion des produits.</p>
<img class="notice-img" src="{% static 'img/notice/add_item.png' %}"/>
<p>Ici, vous pouvez renseigner les produits qui constitueront votre commande groupée.
Indiquez le nom du produit, son prix et la quantité disponible.
Cliquez sur “Ajouter” pour valider lajout.</p>
<img class="notice-img" src="{% static 'img/notice/add_item_2.png' %}"/>
</div>
<div class="box" id="go_to_overview">
<p class="title">Accéder à la page de gestion de ma commande groupée</p>
<p>Depuis le menu “Mes commandes groupées”, puis en cliquant sur le nom de la commande groupée
que vous voulez gérer, vous pourrez cliquer ensuite sur “Gestion de la commande groupee”.
</p>
<img class="notice-img" src="{% static 'img/notice/go_to_overview.png' %}"/>
<p>Sinon depuis la page “Mes commandes groupées” il est aussi possible de cliquer directement sur
licone de la ligne "liste" de la commande groupée que vous voulez gérer.</p>
</div>
<div class="box" id="delete_item">
<p class="title">Modifier des produits d'une commande après sa création</p>
<p>1. Si vous souhaitez modifier une information concernant la commande (titre, date, description, lieu de livraison, …),
il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Ensuite, dans "Résumé de la commande”, cliquez sur “Gérer les produits”.</p>
<img class="notice-img" src="{% static 'img/notice/grouped_order_sum_up.png' %}"/>
<img class="notice-img" src="{% static 'img/notice/items_overview.png' %}"/>
<p>3. Vous êtes maintenant sur la page de gestion des produits depuis laquelle vous pouvez ajouter ou supprimer des produits.
Cliquez sur “Valider” pour sauvegarder les modifications.</p>
<p>Attention : actuellement, il n'est pas possible de supprimer un produit une fois qu'il a été commandé par un.e participant.e.
Si vous voulez vraiment le supprimer, il faudra d'abord supprimer les commandes des participant.e.s concerné.e.s.</p>
</div>
<div class="box" id="edit_grouped_order">
<p class="title">Modifier des informations de la commande</p>
<p>1. Si vous souhaitez modifier une information concernant la commande (titre, date, description, lieu de livraison, …),
il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Vous pouvez ensuite cliquer sur le bouton “Modifier la commande”
qui vous amenera vers le formulaire pre-rempli des informations de la commande. </p>
<img class="notice-img" src="{% static 'img/notice/edit_grouped_order.png' %}"/>
<p>3. Modifiez les informations que vous voulez, puis cliquez sur “Suivant” pour valider.</p>
</div>
<div class="box" id="share_grouped_order">
<p class="title">Partager une commande à des participants</p>
<p>Une fois votre commande bien préparée (quantité et prix des produits corrects, informations de la commande justes, …),
il faut maintenant la partager à des participants pour quils puissent commander.</p>
<p>1. Depuis la page de <a href="#go_to_overview">gestion de la commande</a>,
allez dans longlet “partager et exporter”.</p>
<img class="notice-img" src="{% static 'img/notice/share_grouped_order.png' %}"/>
<p>2. Vous pouvez copier le lien, et lenvoyer à vos connaissances.</p>
<img class="notice-img" src="{% static 'img/notice/copy_link.png' %}"/>
</div>
<div class="box" id="print_orders_list">
<p class="title">Imprimer ma liste de commandes</p>
<p>1. Pour cela, il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Ensuite, dans "Partager et exporter”, cliquez sur “Commandes en PDF”.</p>
<img class="notice-img" src="{% static 'img/notice/print_orders_list.png' %}"/>
<p>3. Cela lance le téléchargement de votre liste de commandes en PDF.</p>
</div>
<div class="box" id="get_spreadsheet">
<p class="title">Voir les commandes sous forme de tableur</p>
<p>1. Pour cela, il faut dabord <a href="#go_to_overview">accéder à la page de gestion de la commande groupée</a>.</p>
<p>2. Ensuite, dans "Partager et exporter”, cliquez sur “Commandes en CSV”.</p>
<img class="notice-img" src="{% static 'img/notice/get_spreadsheet.png' %}"/>
<p>3. Le format CSV vous permet d'ouvrir le fichier avec le tableur de votre choix (libreoffice, excel, ...).</p>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -662,36 +662,6 @@ class TestGroupedOrderDetailView:
assert order.price == 4 assert order.price == 4
assert item.get_remaining_nb() == 16 assert item.get_remaining_nb() == 16
def test_phone_not_required_display(self, client, other_user):
"""a user orders something without entering phone when it is required"""
grouped_order = create_grouped_order(
days_before_delivery_date=5,
days_before_deadline=2,
name="gr order test",
orga_user=other_user,
)
assert grouped_order.is_phone_mandatory == True
item = models.Item.objects.create(
name="test item 1", grouped_order=grouped_order, price=1, max_limit=2
)
detail_url = reverse(
"order:grouped_order_detail",
kwargs={
"code": grouped_order.code,
},
)
response = client.get(detail_url)
assert response.status_code == 200
assert "gr order test" in response.content.decode()
assert (
"Numéro de téléphone <em>(facultatif)</em>" not in response.content.decode()
)
grouped_order.is_phone_mandatory = False
grouped_order.save()
response = client.get(detail_url)
assert "gr order test" in response.content.decode()
assert "Numéro de téléphone <em>(facultatif)</em>" in response.content.decode()
class TestGroupedOrderOverview: class TestGroupedOrderOverview:
def test_get_overview(self, client_log): def test_get_overview(self, client_log):
@ -736,7 +706,6 @@ class TestGroupedOrderOverview:
assert response.status_code == 200 assert response.status_code == 200
assert "test item" in response.content.decode() assert "test item" in response.content.decode()
assert {"test@mail.fr"} == response.context["emails_list"] assert {"test@mail.fr"} == response.context["emails_list"]
assert 4 == response.context["total_ordered_items"]
assert "gr order test" in response.content.decode() assert "gr order test" in response.content.decode()
item.refresh_from_db() item.refresh_from_db()
assert item.get_total_price() == 8 assert item.get_total_price() == 8
@ -1426,7 +1395,7 @@ class TestGroupedOrderSheetView:
response = client_log.get(generate_sheet_url) response = client_log.get(generate_sheet_url)
assert response.status_code == 200 assert response.status_code == 200
assert response.context["grouped_order"] == grouped_order assert response.context["grouped_order"] == grouped_order
assert len(response.context["items"]) == 0 assert response.context["items"].count() == 0
assert len(response.context["orders_dict"]) == 0 assert len(response.context["orders_dict"]) == 0
# we order some items in the grouped order # we order some items in the grouped order
@ -1434,7 +1403,7 @@ class TestGroupedOrderSheetView:
response = client_log.get(generate_sheet_url) response = client_log.get(generate_sheet_url)
assert response.status_code == 200 assert response.status_code == 200
assert response.context["grouped_order"] == grouped_order assert response.context["grouped_order"] == grouped_order
assert len(response.context["items"]) == 2 assert response.context["items"].count() == 2
assert response.context["orders_dict"][order] == [3, 2] assert response.context["orders_dict"][order] == [3, 2]
assert response.context["grouped_order"].total_price == 35 assert response.context["grouped_order"].total_price == 35
@ -1659,7 +1628,7 @@ class TestExportGroupedOrderToCSVView:
assert response.status_code == 200 assert response.status_code == 200
content = response.content.decode("utf-8") content = response.content.decode("utf-8")
csv_reader = csv.reader(StringIO(content), delimiter=";") csv_reader = csv.reader(StringIO(content))
body = list(csv_reader) body = list(csv_reader)
created_date = f"{timezone.now().strftime('%d/%m/%Y')}" created_date = f"{timezone.now().strftime('%d/%m/%Y')}"
created_time = f"{timezone.now().strftime('%H:%M')}" created_time = f"{timezone.now().strftime('%H:%M')}"

View file

@ -9,20 +9,12 @@ pytestmark = pytest.mark.django_db
def create_grouped_order( def create_grouped_order(
days_before_delivery_date, days_before_delivery_date, days_before_deadline, name, orga_user
days_before_deadline,
name,
orga_user,
is_phone_mandatory=True,
): ):
date = timezone.now().date() + datetime.timedelta(days=days_before_delivery_date) date = timezone.now().date() + datetime.timedelta(days=days_before_delivery_date)
deadline = timezone.now() + datetime.timedelta(days=days_before_deadline) deadline = timezone.now() + datetime.timedelta(days=days_before_deadline)
grouped_order = models.GroupedOrder.objects.create( grouped_order = models.GroupedOrder.objects.create(
name=name, name=name, orga=orga_user, delivery_date=date, deadline=deadline
orga=orga_user,
delivery_date=date,
deadline=deadline,
is_phone_mandatory=is_phone_mandatory,
) )
grouped_order.create_code_from_pk() grouped_order.create_code_from_pk()
grouped_order.save() grouped_order.save()
@ -52,4 +44,11 @@ def order_items_in_grouped_order(grouped_order):
models.OrderedItem.objects.create(order=order, item=item_2, nb=2) models.OrderedItem.objects.create(order=order, item=item_2, nb=2)
models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1) models.OrderedItem.objects.create(order=order_2, item=item_1, nb=1)
models.OrderedItem.objects.create(order=order_3, item=item_2, nb=1) models.OrderedItem.objects.create(order=order_3, item=item_2, nb=1)
item_1.compute_ordered_nb()
item_2.compute_ordered_nb()
order.compute_order_price()
order_2.compute_order_price()
order_3.compute_order_price()
grouped_order.compute_total_price()
grouped_order.save()
return order return order

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long