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

@ -14,21 +14,25 @@ def create_code_from_pk(pk):
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()

View file

@ -84,6 +84,7 @@
<table>
<thead>
<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.