Merge branch '186/mettre-le-lien-du-gitlab-dans-le-footer' of gitlab.com:la-chariotte/la_chariotte into 186/mettre-le-lien-du-gitlab-dans-le-footer

This commit is contained in:
pauline 2024-01-02 21:18:32 +01:00
commit b03e67e737
25 changed files with 208 additions and 97 deletions

View file

@ -7,6 +7,5 @@ La Chariotte is a web application to handle grouped orders.
- [Public server](https://chariotte.fr)
- [Online documentation](https://docs.chariotte.fr)
This project has been initially developped and published by [Hashbang](https://
hashbang.fr/) under an [Affero GPLv3](LICENSE) license, and is now maintained
and developed by a team of volunteers.
This project has been initially developed and published by [Hashbang](https://hashbang.fr/)
under an [Affero GPLv3](LICENSE) license, and is now maintained and developed by a team of volunteers.

View file

@ -60,3 +60,7 @@ build it locally, just run:
```bash
mkdocs serve
```
## Merging rules
In order to be merged, your code needs to be reviewed by two maintainers. Don't hesitate to ping us if needed.

View file

@ -50,6 +50,61 @@ en/latest/), managed by AlwaysData.
The production settings are stored in `~/ la_chariotte/prod_settings.py`, and
the secrets are defined in the admin console.
Here are the settings, with some comments that might be useful.
```python title="prod_settings.py"
SECRET_KEY = "YOUR SECRET KEY HERE, used to hash the passwords. CHANGE IT."
# We're connecting to a psql server, AD manages the access and the backups.
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "chariotte_prod",
"USER": "chariotte_prod",
"PASSWORD": "",
"HOST": "host",
}
}
ALLOWED_HOSTS = ["chariotte.fr",]
DEBUG = False
# We're sending mails using AD infrastructure
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_FROM = 'notification@chariotte.fr'
EMAIL_HOST = 'smtp-chariotte.alwaysdata.net'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'notification@chariotte.fr'
EMAIL_HOST_PASSWORD = "XXX"
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = os.getenv(
"DJANGO_DEFAULT_FROM_EMAIL", "La Chariotte <notification@chariotte.fr>"
)
CONTACT_MAIL = "contact@chariotte.fr"
# We're collecting the static files on this specific folder.
STATIC_ROOT = "/home/chariotte/static/"
```
We're using sentry (sentry.io) to be alerted when an error happens on the server
```python title="prod_settings.py"
import sentry_sdk
sentry_sdk.init(
dsn="PUT YOUR DSN HERE",
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for performance monitoring.
traces_sample_rate=1.0,
# Set profiles_sample_rate to 1.0 to profile 100%
# of sampled transactions.
# We recommend adjusting this value in production.
profiles_sample_rate=1.0,
)
```
### The different sites
In the AD console, here are the defined sites:

View file

@ -3,6 +3,8 @@ from django.db import models
class CustomUser(AbstractUser):
EMAIL_FIELD = "username"
username = models.EmailField(
"Email",
unique=True,

View file

@ -2,7 +2,6 @@ import pytest
from django.urls import reverse
from la_chariotte import settings
from la_chariotte.order.models import GroupedOrder
pytestmark = pytest.mark.django_db

View file

@ -2,7 +2,6 @@ import html2text
from django.conf import settings
from django.core import mail
from django.template.loader import render_to_string
from django.utils.html import strip_tags
def send_order_confirmation_mail(order):

View file

@ -1,16 +1,10 @@
import datetime
from typing import Any, Optional, Sequence, Type, Union
import pytz
from django import forms
from django.contrib.auth import get_user_model
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.db import IntegrityError
from django.forms.utils import to_current_timezone
from django.forms.widgets import Widget
from django.utils import timezone
from la_chariotte import settings
from la_chariotte.order.models import GroupedOrder, Item

View file

@ -5,27 +5,52 @@ import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Grouped_order',
name="Grouped_order",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('orga', models.CharField(max_length=100)),
('date', models.DateField(verbose_name='Date de livraison')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("orga", models.CharField(max_length=100)),
("date", models.DateField(verbose_name="Date de livraison")),
],
),
migrations.CreateModel(
name='Order',
name="Order",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('author', models.CharField(max_length=100, verbose_name='Personne qui passe la commande')),
('grouped_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.grouped_order')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"author",
models.CharField(
max_length=100, verbose_name="Personne qui passe la commande"
),
),
(
"grouped_order",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="order.grouped_order",
),
),
],
),
]

View file

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0001_initial'),
("order", "0001_initial"),
]
operations = [
migrations.AddField(
model_name='grouped_order',
name='name',
model_name="grouped_order",
name="name",
field=models.CharField(max_length=100, null=True),
),
]

View file

@ -5,18 +5,31 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('order', '0002_grouped_order_name'),
("order", "0002_grouped_order_name"),
]
operations = [
migrations.CreateModel(
name='Item',
name="Item",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('grouped_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.grouped_order')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100)),
(
"grouped_order",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="order.grouped_order",
),
),
],
),
]

View file

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0003_item'),
("order", "0003_item"),
]
operations = [
migrations.AddField(
model_name='item',
name='ordered_nb',
model_name="item",
name="ordered_nb",
field=models.IntegerField(default=0),
),
]

View file

@ -5,28 +5,53 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('order', '0004_item_ordered_nb'),
("order", "0004_item_ordered_nb"),
]
operations = [
migrations.RemoveField(
model_name='item',
name='ordered_nb',
model_name="item",
name="ordered_nb",
),
migrations.AlterField(
model_name='order',
name='grouped_order',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_set', to='order.grouped_order'),
model_name="order",
name="grouped_order",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="order_set",
to="order.grouped_order",
),
),
migrations.CreateModel(
name='OrderedItem',
name="OrderedItem",
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nb', models.PositiveSmallIntegerField(default=0)),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ordered_items', to='order.item')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='order.order')),
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("nb", models.PositiveSmallIntegerField(default=0)),
(
"item",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="ordered_items",
to="order.item",
),
),
(
"order",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="orders",
to="order.order",
),
),
],
),
]

View file

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0005_remove_item_ordered_nb_alter_order_grouped_order_and_more'),
("order", "0005_remove_item_ordered_nb_alter_order_grouped_order_and_more"),
]
operations = [
migrations.AddField(
model_name='item',
name='ordered_nb',
model_name="item",
name="ordered_nb",
field=models.IntegerField(default=0),
),
]

View file

@ -5,20 +5,27 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('order', '0006_item_ordered_nb'),
("order", "0006_item_ordered_nb"),
]
operations = [
migrations.AlterField(
model_name='ordereditem',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='order.item'),
model_name="ordereditem",
name="item",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="orders",
to="order.item",
),
),
migrations.AlterField(
model_name='ordereditem',
name='order',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ordered_items', to='order.order'),
model_name="ordereditem",
name="order",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="ordered_items",
to="order.order",
),
),
]

View file

@ -5,16 +5,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('order', '0007_alter_ordereditem_item_alter_ordereditem_order'),
("order", "0007_alter_ordereditem_item_alter_ordereditem_order"),
]
operations = [
migrations.AddField(
model_name='grouped_order',
name='deadline',
field=models.DateTimeField(default=datetime.datetime(2023, 3, 23, 14, 38, 17, 365192, tzinfo=datetime.timezone.utc), verbose_name='Date limite de commande'),
model_name="grouped_order",
name="deadline",
field=models.DateTimeField(
default=datetime.datetime(
2023, 3, 23, 14, 38, 17, 365192, tzinfo=datetime.timezone.utc
),
verbose_name="Date limite de commande",
),
preserve_default=False,
),
]

View file

@ -4,15 +4,14 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('order', '0008_grouped_order_deadline'),
("order", "0008_grouped_order_deadline"),
]
operations = [
migrations.RenameField(
model_name='grouped_order',
old_name='date',
new_name='delivery_date',
model_name="grouped_order",
old_name="date",
new_name="delivery_date",
),
]

View file

@ -7,30 +7,34 @@ import random
def create_code_from_pk(pk):
"""When a grouped order is created, we compute a unique code that will be used in url path
How we generate this code :
How we generate this code :
1. The instance pk, written in base36 (max 5 digits for now - we assume that there will not be more than 60466175 grouped orders)
2. A random int written in base36 (5 digits long)
3. Only the 6 first digits of this string
The use of pk in the beginning of the string guarantees the uniqueness, and the random part makes that we cannot guess the url path.
The use of pk in the beginning of the string guarantees the uniqueness, and the random part makes that we cannot guess the url path.
"""
base_36_pk = base36.dumps(pk)
random_string = base36.dumps(random.randint(1727605,60466175)) # generates a 5 digits long string
random_string = base36.dumps(
random.randint(1727605, 60466175)
) # generates a 5 digits long string
return f"{base_36_pk}{random_string}"
def set_code_default(apps, schema_editor):
"""Provides a default code to existing grouped orders during migration"""
GroupedOrder = apps.get_model("order","GroupedOrder")
GroupedOrder = apps.get_model("order", "GroupedOrder")
for grouped_order in GroupedOrder.objects.all().iterator():
grouped_order.code = create_code_from_pk(grouped_order.pk)
grouped_order.save()
def reverse_set_code_default(apps, schema_editor):
"""Reverse the set_code default function"""
GroupedOrder = apps.get_model("order","GroupedOrder")
GroupedOrder = apps.get_model("order", "GroupedOrder")
for grouped_order in GroupedOrder.objects.all().iterator():
grouped_order.code = ''
grouped_order.code = ""
grouped_order.save()
class Migration(migrations.Migration):
dependencies = [

View file

@ -83,7 +83,8 @@
{% if items %}
<table>
<thead>
<tr>
<tr>
<th style="font-size: 0.5em; width: 2em">OK</th>
<th style="text-align: center">Nom</th>
{% for item in items %}
<th class="item_name" style="font-weight: normal;">
@ -91,11 +92,11 @@
</th>
{% endfor %}
<th style="width: 2cm">Prix</th>
<th style="font-size: 0.5em; width: 2em">OK</th>
</tr>
</thead>
<tbody>
<tr style="background-color: #bababa">
<td></td>
<td style="text-align: left">Prix unitaire</td>
{% for item in items %}
<td>
@ -103,9 +104,9 @@
</td>
{% endfor %}
<td></td>
<td></td>
</tr>
<tr style="background-color: #bababa">
<th></th>
<th style="text-align: left">TOTAL</th>
{% for item in items %}
<th>
@ -113,10 +114,10 @@
</th>
{% endfor %}
<th>{{ grouped_order.total_price }} €</th>
<th></th>
</tr>
{% for order, ordered_items in orders_dict.items %}
<tr>
<td></td>
<td>
{{ order.author.last_name|upper }} {{ order.author.first_name }}
</td>
@ -130,7 +131,6 @@
<td>
{{ order.price }} €
</td>
<td></td>
</tr>
{% endfor %}
</tbody>

View file

@ -1,9 +1,6 @@
import datetime
import pytest
from django.contrib import auth
from django.urls import reverse
from django.utils import timezone
from la_chariotte.order import models
from la_chariotte.order.tests.utils import create_grouped_order

View file

@ -1,9 +1,6 @@
import datetime
import pytest
from django.contrib import auth
from django.urls import reverse
from django.utils import timezone
from la_chariotte.order import models
from la_chariotte.order.tests.utils import (

View file

@ -1,8 +1,6 @@
import datetime
import pytest
from django.contrib import auth
from django.urls import reverse
from django.utils import timezone
from la_chariotte.order import models

View file

@ -1,5 +1,4 @@
from django.urls import path
from django.views.generic.base import TemplateView
from . import views

View file

@ -1,20 +1,15 @@
import csv
import json
from datetime import timedelta
from io import BytesIO
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.template.loader import get_template
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views import generic
from django_weasyprint import WeasyTemplateResponseMixin
from django_weasyprint.views import WeasyTemplateResponse, WeasyTemplateView
from icalendar import Calendar, Event, vCalAddress, vText
from xhtml2pdf import pisa
from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm
from ..models import GroupedOrder, OrderAuthor

View file

@ -19,7 +19,6 @@ from django.contrib.auth.views import (
PasswordResetCompleteView,
PasswordResetConfirmView,
PasswordResetDoneView,
PasswordResetView,
)
from django.urls import include, path
from django.views.generic.base import TemplateView

Binary file not shown.