Compare commits

..

3 commits

Author SHA1 Message Date
selfhoster1312
4eac4f43b8 feature: Add place relationship for distribution spots 2025-03-03 21:24:43 +01:00
Laetitia
19c4a0b6ed feat: update gitlab links everywhere else 2025-02-16 10:35:00 +00:00
Laetitia
9589bcf48d feat: update gitlab link to framagit 2025-02-16 10:35:00 +00:00
33 changed files with 342 additions and 370 deletions

View file

@ -6,7 +6,7 @@ started.
The first step is to clone the project, you can find more information about that in The first step is to clone the project, you can find more information about that in
the [getting started guide](../install.md). Once that's done, you can: the [getting started guide](../install.md). Once that's done, you can:
- choose a task [on the board](https://gitlab.com/la-chariotte/la_chariotte/-/boards) and assign it to - choose a task [on the board](https://framagit.org/la-chariotte/la-chariotte/-/boards) and assign it to
yourself - if you don't know which task to do, feel free to reach to yourself - if you don't know which task to do, feel free to reach to
us. us.
- create a new branch **from develop** naming it to reflect what you want to do - create a new branch **from develop** naming it to reflect what you want to do

View file

@ -11,7 +11,7 @@
- **docs.chariotte.fr**, the docs you are reading now. It's handled by [readthedocs.org](https://readthedocs.org). - **docs.chariotte.fr**, the docs you are reading now. It's handled by [readthedocs.org](https://readthedocs.org).
- **chariotte.fr**, the main instance. It's deployed on Alwaysdata - **chariotte.fr**, the main instance. It's deployed on Alwaysdata
- **blog.chariotte.fr**, our blog. It's [a static website](https://gitlab.com/la-chariotte/la-chariotte.gitlab.io) deployed on Gitlab pages. - **blog.chariotte.fr**, our blog. It's [a static website](https://framagit.org/la-chariotte/la-chariotte.frama.io) deployed on Gitlab pages.
## The main instance ## The main instance

View file

@ -1,7 +1,7 @@
First, clone the project First, clone the project
```bash ```bash
git clone git@gitlab.com:la-chariotte/la_chariotte.git git clone https://framagit.org/la-chariotte/la-chariotte.git
``` ```
## Virtual environment ## Virtual environment

View file

@ -1,6 +0,0 @@
from django.apps import AppConfig
class LieuConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "la_chariotte.lieu"

View file

@ -1,35 +0,0 @@
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)

View file

@ -1,28 +0,0 @@
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")),
],
),
]

View file

@ -1,27 +0,0 @@
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})

View file

@ -1,22 +0,0 @@
{% 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 %}

View file

@ -1,14 +0,0 @@
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"),
]

View file

@ -1,6 +0,0 @@
from .lieu import (
IndexView,
LieuCreateView,
LieuDetailView,
LieuUpdateView,
)

View file

@ -1,87 +0,0 @@
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)

View file

@ -22,7 +22,7 @@
<p><strong>Rendez-vous pour la distribution</strong> : <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 %} 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>
<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> : <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> <a class="button" href="{{ base_url }}{% url 'order:grouped_order_detail' order.grouped_order.code %}">Voir la page de commande</a>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -6,8 +6,7 @@ from django.contrib.auth import get_user_model
from django.forms.utils import to_current_timezone from django.forms.utils import to_current_timezone
from django.utils import timezone from django.utils import timezone
from la_chariotte.lieu.models import Lieu from la_chariotte.order.models import GroupedOrder, Item, Place
from la_chariotte.order.models import GroupedOrder, Item
class GroupedOrderForm(forms.ModelForm): class GroupedOrderForm(forms.ModelForm):
@ -24,9 +23,10 @@ class GroupedOrderForm(forms.ModelForm):
label="Numéro de téléphone obligatoire pour les participants", label="Numéro de téléphone obligatoire pour les participants",
required=False, required=False,
) )
placekey = forms.ModelMultipleChoiceField( places = forms.ModelMultipleChoiceField(
label="Lieux de distribution", label="Lieux de distribution",
queryset=Lieu.objects.all(), # TODO: filter own places
queryset=Place.objects.all(),
widget=forms.CheckboxSelectMultiple, widget=forms.CheckboxSelectMultiple,
required=False, required=False,
) )
@ -39,10 +39,9 @@ class GroupedOrderForm(forms.ModelForm):
"deadline_time", "deadline_time",
"delivery_date", "delivery_date",
"delivery_slot", "delivery_slot",
"place", "places",
"description", "description",
"is_phone_mandatory", "is_phone_mandatory",
"placekey",
] ]
widgets = { widgets = {
"name": forms.TextInput( "name": forms.TextInput(
@ -55,7 +54,6 @@ class GroupedOrderForm(forms.ModelForm):
"delivery_slot": forms.TextInput( "delivery_slot": forms.TextInput(
attrs={"placeholder": "14h - 17h (facultatif)"} attrs={"placeholder": "14h - 17h (facultatif)"}
), ),
"place": forms.TextInput(attrs={"placeholder": "(facultatif)"}),
"description": forms.Textarea( "description": forms.Textarea(
attrs={ attrs={
"placeholder": "Plus d'infos sur la commande groupée ? (facultatif)" "placeholder": "Plus d'infos sur la commande groupée ? (facultatif)"
@ -112,3 +110,32 @@ class JoinGroupedOrderForm(forms.Form):
"Désolé, nous ne trouvons aucune commande avec ce code" "Désolé, nous ne trouvons aucune commande avec ce code"
) )
return form_code return form_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)

View file

@ -1,23 +0,0 @@
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'),
),
]

View file

@ -0,0 +1,75 @@
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'),
),
]

View file

@ -22,14 +22,9 @@ class GroupedOrder(models.Model):
) )
deadline = models.DateTimeField("Date limite de commande") deadline = models.DateTimeField("Date limite de commande")
# Try to associate with a saved distribution place in DB # Associate with zero/more saved distribution places
placekey = models.ManyToManyField( places = models.ManyToManyField(
"lieu.Lieu", "order.Place", verbose_name="Lieux de distribution", related_name="orders"
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) description = models.TextField("Description", null=True, blank=True)
@ -62,6 +57,13 @@ 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 has_places(self):
# Check whether this GroupedOrder has any distribution Place enabled
if len(self.places.all()) > 0:
return True
return False
@property @property
def total_price(self): def total_price(self):
price = 0 price = 0
@ -131,8 +133,8 @@ class Order(models.Model):
author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE) author = models.ForeignKey(OrderAuthor, on_delete=models.CASCADE)
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)
placekey = models.ForeignKey( place = models.ForeignKey(
"lieu.lieu", on_delete=models.CASCADE, null=True, blank=True "order.place", on_delete=models.CASCADE, null=True, blank=True
) )
@property @property
@ -207,3 +209,22 @@ class OrderedItem(models.Model):
def __str__(self): # pragma: no cover def __str__(self): # pragma: no cover
return f"{self.nb} {self.item}, dans la commande {self.order.pk}" 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)
def __str__(self): # pragma: no cover
return self.name
def get_absolute_url(self):
return reverse("order:place_update", kwargs={"code": self.code})

View file

@ -27,10 +27,6 @@
{% endif %} {% endif %}
</div> </div>
<div class="column"> <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" <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> 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' }} Commandes avant le {{ grouped_order.deadline|date:'d M Y' }} à {{ grouped_order.deadline|date:'H:i' }}
@ -171,16 +167,15 @@
<p><label for="note">Note à l'organisateur·ice<em> (facultatif)</em> :</label> <p><label for="note">Note à l'organisateur·ice<em> (facultatif)</em> :</label>
<textarea id="note" rows=3 name="note">{{ note }}</textarea></p> <textarea id="note" rows=3 name="note">{{ note }}</textarea></p>
{% if placekey %} {% if places %}
<label for="placekey">Point de distribution:</label> <label for="place">Point de distribution:</label>
<select name="placekey" id="placekey-select"> <select name="place" id="place-select">
{% for place in placekey %} {% for place in places %}
<option value="{{ place.url }}">{{ place.name }}</option> <option value="{{ place.code }}">{{ place.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
{% endif %} {% endif %}
<hr>
<hr>
<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">

View file

@ -184,7 +184,7 @@
<header class="modal-card-head has-background-info"> <header class="modal-card-head has-background-info">
<div class="modal-card-title-container"> <div class="modal-card-title-container">
<p class="modal-card-title mb-2">Commande de {{ order.author }}</p> <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 %} {% 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> <p class="has-text-grey-dark">Le {{ order.created_date|date:'d M Y' }} à {{ order.created_date|date:'H:i' }}</p>
</div> </div>
<button class="delete" aria-label="close"></button> <button class="delete" aria-label="close"></button>

View file

@ -75,10 +75,10 @@
<h2 style="text-align: center"> <h2 style="text-align: center">
{{ grouped_order.name }} - {{ grouped_order.delivery_date }} {{ grouped_order.name }} - {{ grouped_order.delivery_date }}
</h2> </h2>
{% if by_lieu %} {% if places %}
{% for lieu_name, ordered_dict in lieux.items %} {% for place_name, place_orders in places.items %}
<h3>{{ lieu_name }}</h3> <h3>{{ place_name }}</h3>
{% include 'order/grouped_order_sheet_list.html' with orders_dict=lieu_orders items=items grouped_order=grouped_order %} {% include 'order/grouped_order_sheet_list.html' with orders_dict=place_orders items=items grouped_order=grouped_order %}
{% endfor %} {% endfor %}
{% else %} {% else %}
{% include 'order/grouped_order_sheet_list.html' with orders_dict=orders_dict items=items grouped_order=grouped_order %} {% include 'order/grouped_order_sheet_list.html' with orders_dict=orders_dict items=items grouped_order=grouped_order %}

View file

@ -7,11 +7,11 @@
{% block content_title %}Lieux de distribution que vous organisez{% endblock %} {% block content_title %}Lieux de distribution que vous organisez{% endblock %}
</p> </p>
<div class="buttons is-pulled-right"> <div class="buttons is-pulled-right">
<a class="button is-primary" href="{% url 'lieu:create_lieu' %}"> <a class="button is-primary" href="{% url 'order:place_create' %}">
<i class="fa fa-plus-circle mr-3" aria-hidden="true"></i> <i class="fa fa-plus-circle mr-3" aria-hidden="true"></i>
Créer un nouveau lieu de distribution</a> Créer un nouveau lieu de distribution</a>
</div> </div>
{% if lieu_context.lieu_liste %} {% if context.places %}
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -23,21 +23,21 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for lieu in lieu_context.lieu_liste %} {% for place in context.places %}
<tr> <tr>
<td> <td>
<a title="Détail du lieu de distribution" href="{% url 'lieu:lieu_update' lieu.url %}">{{ lieu }}</a> <a title="Détail du lieu de distribution" href="{% url 'order:place_update' place.code %}">{{ place }}</a>
</td> </td>
<td> <td>
{% if lieu.url in lieu_context.commandes.keys %} {% if place.code in context.orders.keys %}
{% for lieu_url, lieu_commandes in lieu_context.commandes.items %} {% for place_url, place_orders in context.orders.items %}
{% if lieu_url == lieu.url %} {% if place_url == place.code %}
{% for commande in lieu_commandes %} {% for order in place_orders %}
{% url 'order:grouped_order_detail' code=commande.code as order_url %} {% url 'order:grouped_order_detail' code=order.code as order_url %}
{% if order_url %} {% if order_url %}
<a href="{{ order_url }}">{{commande.name}}</a><br> <a href="{{ order_url }}">{{order.name}}</a><br>
{% else %} {% else %}
{{ commande.name }}<br> {{ order.name }}<br>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -47,10 +47,10 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{{ lieu.description }} {{ place.description }}
</td> </td>
<td> <td>
{{ lieu.url }} {{ place.code }}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -9,14 +9,14 @@
{% block content_title %}Créer un lieu de distribution{% endblock %} {% block content_title %}Créer un lieu de distribution{% endblock %}
</p> </p>
<div class="box"> <div class="box">
<p class="title">Nouvelle lieu</p> <p class="title">Nouveau lieu</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 | crispy }} {{ form | crispy }}
<div class="buttons"> <div class="buttons">
<a class="button is-light" href="{% url 'lieu:index' %}">Annuler</a> <a class="button is-light" href="{% url 'order:place_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>

View file

@ -9,11 +9,11 @@
{% block content_title %}Modifier le lieu de livraison{% endblock %} {% block content_title %}Modifier le lieu de livraison{% endblock %}
</p> </p>
<div class="box"> <div class="box">
<p class="title">{{ lieu.name }} - modifier</p> <p class="title">{{ place.name }} - modifier</p>
<form method="post">{% csrf_token %} <form method="post">{% csrf_token %}
{{ form | crispy }} {{ form | crispy }}
<div class="buttons"> <div class="buttons">
<a class="button is-light" href="{% url 'lieu:index' %}">Annuler</a> <a class="button is-light" href="{% url 'order:place_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>

View file

@ -4,77 +4,88 @@ from . import views
app_name = "order" app_name = "order"
urlpatterns = [ urlpatterns = [
path("", views.IndexView.as_view(), name="index"), path("commande/", views.IndexView.as_view(), name="index"),
path( path(
"<str:code>/", "commande/<str:code>/",
views.GroupedOrderDetailView.as_view(), views.GroupedOrderDetailView.as_view(),
name="grouped_order_detail", name="grouped_order_detail",
), ),
path( path(
"<str:code>/ics/", "commande/<str:code>/ics/",
views.GroupedOrderEventView.as_view(), views.GroupedOrderEventView.as_view(),
name="grouped_order_event", name="grouped_order_event",
), ),
path( path(
"<str:code>/gerer", "commande/<str:code>/gerer",
views.GroupedOrderOverview.as_view(), views.GroupedOrderOverview.as_view(),
name="grouped_order_overview", name="grouped_order_overview",
), ),
path("<str:code>/commander/", views.place_order, name="order"), path("commande/<str:code>/commander/", views.place_order, name="order"),
path( path(
"<str:code>/<int:pk>/confirmation/", "commande/<str:code>/<int:pk>/confirmation/",
views.OrderDetailView.as_view(), views.OrderDetailView.as_view(),
name="order_confirm", name="order_confirm",
), ),
path( path(
"<str:code>/gerer/<int:pk>/supprimer", "commande/<str:code>/gerer/<int:pk>/supprimer",
views.OrderDeleteView.as_view(), views.OrderDeleteView.as_view(),
name="order_delete", name="order_delete",
), ),
path("creer", views.GroupedOrderCreateView.as_view(), name="create_grouped_order"),
path( 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(), views.GroupedOrderAddItemsView.as_view(),
name="manage_items", name="manage_items",
), ),
path( path(
"<str:code>/modifier", "commande/<str:code>/modifier",
views.GroupedOrderUpdateView.as_view(), views.GroupedOrderUpdateView.as_view(),
name="update_grouped_order", name="update_grouped_order",
), ),
path( path(
"<str:code>/supprimer", "commande/<str:code>/supprimer",
views.GroupedOrderDeleteView.as_view(), views.GroupedOrderDeleteView.as_view(),
name="delete_grouped_order", name="delete_grouped_order",
), ),
path( path(
"<str:code>/dupliquer", "commande/<str:code>/dupliquer",
views.GroupedOrderDuplicateView.as_view(), views.GroupedOrderDuplicateView.as_view(),
name="duplicate_grouped_order", name="duplicate_grouped_order",
), ),
path( path(
"<str:code>/gerer-produits/nouveau", "commande/<str:code>/gerer-produits/nouveau",
views.ItemCreateView.as_view(), views.ItemCreateView.as_view(),
name="item_create", name="item_create",
), ),
path( path(
"<str:code>/gerer-produits/<int:pk>/supprimer", "commande/<str:code>/gerer-produits/<int:pk>/supprimer",
views.ItemDeleteView.as_view(), views.ItemDeleteView.as_view(),
name="item_delete", name="item_delete",
), ),
path( path(
"<str:code>/gerer/imprimer", "commande/<str:code>/gerer/imprimer",
views.DownloadGroupedOrderSheetView.as_view(), views.DownloadGroupedOrderSheetView.as_view(),
name="grouped_order_sheet", name="grouped_order_sheet",
), ),
path( path(
"<str:code>/gerer/liste-mails", "commande/<str:code>/gerer/liste-mails",
views.ExportGroupOrderEmailAdressesToDownloadView.as_view(), views.ExportGroupOrderEmailAdressesToDownloadView.as_view(),
name="email_list", name="email_list",
), ),
path( path(
"<str:code>/gerer/csv", "commande/<str:code>/gerer/csv",
views.ExportGroupedOrderToCSVView.as_view(), views.ExportGroupedOrderToCSVView.as_view(),
name="grouped_order_csv_export", name="grouped_order_csv_export",
), ),
path("lieu/", views.PlaceIndexView.as_view(), name="place_index"),
path(
"lieu/<str:code>/",
views.PlaceUpdateView.as_view(),
name="place_update",
),
path("lieu/creer", views.PlaceCreateView.as_view(), name="place_create"),
] ]

View file

@ -16,3 +16,4 @@ from .grouped_order import (
) )
from .item import ItemCreateView, ItemDeleteView from .item import ItemCreateView, ItemDeleteView
from .order import OrderDeleteView, OrderDetailView, place_order from .order import OrderDeleteView, OrderDetailView, place_order
from .place import PlaceCreateView, PlaceIndexView, PlaceUpdateView

View file

@ -11,10 +11,8 @@ from django.views import generic
from django_weasyprint import WeasyTemplateResponseMixin from django_weasyprint import WeasyTemplateResponseMixin
from icalendar import Calendar, Event, vCalAddress, vText from icalendar import Calendar, Event, vCalAddress, vText
from la_chariotte.lieu.models import Lieu
from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm
from ..models import GroupedOrder, OrderAuthor from ..models import GroupedOrder, OrderAuthor, Place
from .mixins import UserIsOrgaMixin from .mixins import UserIsOrgaMixin
@ -77,7 +75,8 @@ class GroupedOrderEventView(generic.DetailView):
event.add("dtstart", self.object.delivery_date) event.add("dtstart", self.object.delivery_date)
event.add("dtend", self.object.delivery_date) event.add("dtend", self.object.delivery_date)
event.add("date", 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 = "" description = ""
if self.object.delivery_slot: if self.object.delivery_slot:
@ -138,7 +137,7 @@ class GroupedOrderDetailView(generic.DetailView):
"order_author": order_author, "order_author": order_author,
# Used to set if the phone is required in the form # Used to set if the phone is required in the form
"is_phone_mandatory": grouped_order.is_phone_mandatory, "is_phone_mandatory": grouped_order.is_phone_mandatory,
"placekey": grouped_order.placekey.all(), "places": grouped_order.places.all(),
} }
) )
return context return context
@ -203,7 +202,7 @@ class GroupedOrderUpdateView(UserIsOrgaMixin, generic.UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(GroupedOrderUpdateView, self).get_context_data(**kwargs) context = super(GroupedOrderUpdateView, self).get_context_data(**kwargs)
context["places"] = Lieu.objects.filter(orga=self.request.user) context["places"] = Place.objects.filter(orga=self.request.user)
return context return context
@ -222,10 +221,11 @@ class GroupedOrderDuplicateView(UserIsOrgaMixin, generic.RedirectView):
orga=self.request.user, orga=self.request.user,
delivery_date=initial_grouped_order.delivery_date, delivery_date=initial_grouped_order.delivery_date,
deadline=initial_grouped_order.deadline, deadline=initial_grouped_order.deadline,
place=initial_grouped_order.place,
description=initial_grouped_order.description, 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 # create a unique code for the new grouped order
new_grouped_order.create_code_from_pk() new_grouped_order.create_code_from_pk()
new_grouped_order.save() new_grouped_order.save()
@ -304,8 +304,6 @@ 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()
by_lieu = len(grouped_order.placekey.all()) > 0
items = [ items = [
item item
for item in grouped_order.item_set.all().order_by("name") for item in grouped_order.item_set.all().order_by("name")
@ -326,14 +324,14 @@ class GroupedOrderExportView(UserIsOrgaMixin, generic.DetailView):
context["items"] = items context["items"] = items
context["orders_dict"] = orders_dict context["orders_dict"] = orders_dict
context["by_lieu"] = by_lieu
if by_lieu: if grouped_order.has_places:
lieux = dict() places = dict()
for order, order_items in orders_dict.items(): for order, order_items in orders_dict.items():
if order.placekey.name not in lieux: if order.place.name not in places:
lieux[order.placekey.name] = dict() lieux[order.place.name] = dict()
lieux[order.placekey.name][order] = order_items lieux[order.place.name][order] = order_items
context["lieux"] = lieux context["places"] = places
return context return context
@ -374,7 +372,6 @@ class ExportGroupOrderEmailAdressesToDownloadView(UserPassesTestMixin, generic.V
class ExportGroupedOrderToCSVView(GroupedOrderExportView): class ExportGroupedOrderToCSVView(GroupedOrderExportView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
grouped_order = self.get_object() grouped_order = self.get_object()
enableLieu = len(grouped_order.placekey.all()) > 0
super(ExportGroupedOrderToCSVView, self).get(self, request, *args, **kwargs) super(ExportGroupedOrderToCSVView, self).get(self, request, *args, **kwargs)
context = self.get_context_data() context = self.get_context_data()
@ -396,7 +393,7 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
row.append("Note") row.append("Note")
row.append("Date") row.append("Date")
row.append("Heure") row.append("Heure")
if enableLieu: if grouped_order.has_places:
row.append("Lieu") row.append("Lieu")
writer.writerow(row) writer.writerow(row)
@ -420,8 +417,8 @@ class ExportGroupedOrderToCSVView(GroupedOrderExportView):
row.append(order.note) row.append(order.note)
row.append(order.created_date.strftime("%d/%m/%Y")) row.append(order.created_date.strftime("%d/%m/%Y"))
row.append(order.created_date.strftime("%H:%M")) row.append(order.created_date.strftime("%H:%M"))
if enableLieu: if grouped_order.has_places:
row.append(order.placekey.name) row.append(order.place.name)
writer.writerow(row) writer.writerow(row)
# write total row # write total row

View file

@ -5,16 +5,14 @@ from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.views import generic from django.views import generic
from la_chariotte.lieu.models import Lieu
from la_chariotte.mail.utils import send_order_confirmation_mail 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): def place_order(request, code):
# Creates an AnonymousUser and an Order with related OrderedItems # Creates an AnonymousUser and an Order with related OrderedItems
grouped_order = get_object_or_404(GroupedOrder, code=code) grouped_order = get_object_or_404(GroupedOrder, code=code)
places = grouped_order.placekey.all()
# Handle permissions # Handle permissions
user_is_orga = request.user == grouped_order.orga user_is_orga = request.user == grouped_order.orga
@ -49,17 +47,21 @@ def place_order(request, code):
email = request.POST["email"] email = request.POST["email"]
note = request.POST["note"] note = request.POST["note"]
# Make sure requested location is valid in this group order (and exists at all) # Make sure requested place is valid in this group order (and exists at all)
# If no placekey is enabled for this group order, placekey is always null # If no places are enabled for this group order, chosen place is always None
if len(places) == 0: if grouped_order.has_places:
placekey = None places = grouped_order.places.all()
else: # Return 404 if the requested place does not exist at all
placekey = get_object_or_404(Lieu, url=request.POST["placekey"]) place = get_object_or_404(Place, code=request.POST["place"])
if placekey not in places: if place not in places:
# Return 404 is the requested place exists but is not enabled for this
# GroupedOrder instance
raise http.Http404( raise http.Http404(
"Le lieu demandé n'est pas valide pour cette commande: %s" "Le lieu demandé n'est pas valide pour cette commande: %s"
% requested_placekey % request.POST["place"]
) )
else:
place = None
author = OrderAuthor.objects.create( author = OrderAuthor.objects.create(
first_name=first_name, last_name=last_name, email=email, phone=phone first_name=first_name, last_name=last_name, email=email, phone=phone
@ -69,7 +71,7 @@ def place_order(request, code):
grouped_order=grouped_order, grouped_order=grouped_order,
note=note, note=note,
created_date=timezone.now(), created_date=timezone.now(),
placekey=placekey, place=place,
) )
# add items to the order # add items to the order
@ -96,7 +98,7 @@ def place_order(request, code):
"error_message": error_message, "error_message": error_message,
"note": order.note, "note": order.note,
"author": author, "author": author,
"placekey": placekey, "place": place,
}, },
) )
@ -115,7 +117,7 @@ def place_order(request, code):
"error_message": error_message, "error_message": error_message,
"note": order.note, "note": order.note,
"author": author, "author": author,
"placekey": placekey, "place": place,
}, },
) )

View file

@ -0,0 +1,83 @@
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)
print(places)
# Let's filter orders by distribution place (for UI grouping)
orders = dict()
for place in places:
# TODO: maybe filter out finished GroupedOrder?
if place.orders.all():
orders[place.code] = place.orders.all()
# orders[place.code] = 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
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
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 super().form_valid(form)

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://framagit.org/la-chariotte/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"
@ -27,7 +27,6 @@ if os.getenv("ALLOWED_HOSTS"):
# Applications & middlewares # Applications & middlewares
INSTALLED_APPS = [ INSTALLED_APPS = [
"la_chariotte.lieu",
"la_chariotte.order", "la_chariotte.order",
"la_chariotte.accounts", "la_chariotte.accounts",
"la_chariotte.mail", "la_chariotte.mail",

View file

@ -920,7 +920,7 @@ class TestGroupedOrderUpdateView:
grouped_order.save() grouped_order.save()
assert models.GroupedOrder.objects.count() == 1 assert models.GroupedOrder.objects.count() == 1
assert models.GroupedOrder.objects.first().name == "gr order test" 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 # get the update form
update_grouped_order_url = reverse( update_grouped_order_url = reverse(
@ -942,7 +942,7 @@ class TestGroupedOrderUpdateView:
) )
assert models.GroupedOrder.objects.count() == 1 assert models.GroupedOrder.objects.count() == 1
assert models.GroupedOrder.objects.first().name == "gr order test" 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 # get the update form
update_grouped_order_url = reverse( update_grouped_order_url = reverse(
@ -954,6 +954,9 @@ class TestGroupedOrderUpdateView:
# post the update form # post the update form
date = timezone.now().date() + datetime.timedelta(days=42) date = timezone.now().date() + datetime.timedelta(days=42)
deadline = timezone.now() + datetime.timedelta(days=32) 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( response = client_log.post(
update_grouped_order_url, update_grouped_order_url,
{ {
@ -961,14 +964,15 @@ class TestGroupedOrderUpdateView:
"deadline_date": deadline.date(), "deadline_date": deadline.date(),
"deadline_time": deadline.time().strftime("%H:%M"), "deadline_time": deadline.time().strftime("%H:%M"),
"delivery_date": date, "delivery_date": date,
"place": "quelque part", "places": place.id,
}, },
) )
# assert response.content.decode() == ""
assert response.status_code == 302 assert response.status_code == 302
assert response.url.endswith("gerer-produits") assert response.url.endswith("gerer-produits")
assert models.GroupedOrder.objects.count() == 1 assert models.GroupedOrder.objects.count() == 1
assert models.GroupedOrder.objects.first().name == "titre test modifié" 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): def test_update_grouped_order__delivery_date_passed(self, client_log):
""" """
@ -983,7 +987,7 @@ class TestGroupedOrderUpdateView:
) )
assert models.GroupedOrder.objects.count() == 1 assert models.GroupedOrder.objects.count() == 1
assert models.GroupedOrder.objects.first().name == "gr order test" 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 # get the update form
update_grouped_order_url = reverse( update_grouped_order_url = reverse(
@ -995,6 +999,9 @@ class TestGroupedOrderUpdateView:
# post the update form # post the update form
date = timezone.now().date() + datetime.timedelta(days=-1) date = timezone.now().date() + datetime.timedelta(days=-1)
deadline = timezone.now() + datetime.timedelta(days=-3) 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( response = client_log.post(
update_grouped_order_url, update_grouped_order_url,
{ {
@ -1002,14 +1009,14 @@ class TestGroupedOrderUpdateView:
"deadline_date": deadline.date(), "deadline_date": deadline.date(),
"deadline_time": deadline.time().strftime("%H:%M"), "deadline_time": deadline.time().strftime("%H:%M"),
"delivery_date": date, "delivery_date": date,
"place": "quelque part", "places": place.id,
}, },
) )
assert response.status_code == 302 assert response.status_code == 302
assert response.url.endswith("gerer-produits") assert response.url.endswith("gerer-produits")
assert models.GroupedOrder.objects.count() == 1 assert models.GroupedOrder.objects.count() == 1
assert models.GroupedOrder.objects.first().name == "titre test modifié" 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): def test_update_grouped_order__not_orga(self, client_log, other_user):
"""A user that is not organiszer of the GO accesses update page. """A user that is not organiszer of the GO accesses update page.
@ -1315,7 +1322,9 @@ class TestGroupedOrderDuplicateView:
assert new_grouped_order.name == "gr order test - copie" assert new_grouped_order.name == "gr order test - copie"
assert new_grouped_order.delivery_date == grouped_order.delivery_date assert new_grouped_order.delivery_date == grouped_order.delivery_date
assert new_grouped_order.deadline == grouped_order.deadline 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.orga == auth.get_user(client_log)
assert new_grouped_order.description == grouped_order.description assert new_grouped_order.description == grouped_order.description
assert new_grouped_order.item_set.count() == grouped_order.item_set.count() assert new_grouped_order.item_set.count() == grouped_order.item_set.count()

View file

@ -29,8 +29,8 @@ from la_chariotte.order.views.stats import stats
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), 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("lieu/", include("la_chariotte.lieu.urls")), path("", include("la_chariotte.order.urls")),
path("comptes/", include("la_chariotte.accounts.urls")), path("comptes/", include("la_chariotte.accounts.urls")),
# Some paths for accounts are easier to leave here # Some paths for accounts are easier to leave here
# - PasswordResetView sends the mail # - PasswordResetView sends the mail

View file

@ -1,7 +1,7 @@
site_name: La chariotte site_name: La chariotte
site_description: An application for grouped-orders site_description: An application for grouped-orders
repo_name: la-chariotte/la_chariotte repo_name: la-chariotte/la_chariotte
repo_url: https://gitlab.com/la-chariotte/la_chariotte repo_url: https://framagit.org/la-chariotte/la-chariotte
nav: nav:
- How-tos: - How-tos:
- Getting started: install.md - Getting started: install.md