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) - [Public server](https://chariotte.fr)
- [Online documentation](https://docs.chariotte.fr) - [Online documentation](https://docs.chariotte.fr)
This project has been initially developped and published by [Hashbang](https:// This project has been initially developed and published by [Hashbang](https://hashbang.fr/)
hashbang.fr/) under an [Affero GPLv3](LICENSE) license, and is now maintained under an [Affero GPLv3](LICENSE) license, and is now maintained and developed by a team of volunteers.
and developed by a team of volunteers.

View file

@ -60,3 +60,7 @@ build it locally, just run:
```bash ```bash
mkdocs serve 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 production settings are stored in `~/ la_chariotte/prod_settings.py`, and
the secrets are defined in the admin console. 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 ### The different sites
In the AD console, here are the defined sites: In the AD console, here are the defined sites:

View file

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

View file

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

View file

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

View file

@ -1,16 +1,10 @@
import datetime import datetime
from typing import Any, Optional, Sequence, Type, Union
import pytz
from django import forms from django import forms
from django.contrib.auth import get_user_model 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.utils import to_current_timezone
from django.forms.widgets import Widget
from django.utils import timezone from django.utils import timezone
from la_chariotte import settings
from la_chariotte.order.models import GroupedOrder, Item from la_chariotte.order.models import GroupedOrder, Item

View file

@ -5,27 +5,52 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Grouped_order', name="Grouped_order",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('orga', models.CharField(max_length=100)), "id",
('date', models.DateField(verbose_name='Date de livraison')), 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( migrations.CreateModel(
name='Order', name="Order",
fields=[ 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')), "id",
('grouped_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.grouped_order')), 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('order', '0001_initial'), ("order", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='grouped_order', model_name="grouped_order",
name='name', name="name",
field=models.CharField(max_length=100, null=True), field=models.CharField(max_length=100, null=True),
), ),
] ]

View file

@ -5,18 +5,31 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('order', '0002_grouped_order_name'), ("order", "0002_grouped_order_name"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Item', name="Item",
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=100)), "id",
('grouped_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='order.grouped_order')), 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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('order', '0003_item'), ("order", "0003_item"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='item', model_name="item",
name='ordered_nb', name="ordered_nb",
field=models.IntegerField(default=0), field=models.IntegerField(default=0),
), ),
] ]

View file

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

View file

@ -5,20 +5,27 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('order', '0006_item_ordered_nb'), ("order", "0006_item_ordered_nb"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='ordereditem', model_name="ordereditem",
name='item', name="item",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='order.item'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="orders",
to="order.item",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='ordereditem', model_name="ordereditem",
name='order', name="order",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ordered_items', to='order.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): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('order', '0007_alter_ordereditem_item_alter_ordereditem_order'), ("order", "0007_alter_ordereditem_item_alter_ordereditem_order"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='grouped_order', model_name="grouped_order",
name='deadline', 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'), 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, preserve_default=False,
), ),
] ]

View file

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

View file

@ -14,9 +14,12 @@ 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. 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) 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}" return f"{base_36_pk}{random_string}"
def set_code_default(apps, schema_editor): def set_code_default(apps, schema_editor):
"""Provides a default code to existing grouped orders during migration""" """Provides a default code to existing grouped orders during migration"""
GroupedOrder = apps.get_model("order", "GroupedOrder") GroupedOrder = apps.get_model("order", "GroupedOrder")
@ -24,11 +27,12 @@ def set_code_default(apps, schema_editor):
grouped_order.code = create_code_from_pk(grouped_order.pk) grouped_order.code = create_code_from_pk(grouped_order.pk)
grouped_order.save() grouped_order.save()
def reverse_set_code_default(apps, schema_editor): def reverse_set_code_default(apps, schema_editor):
"""Reverse the set_code default function""" """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(): for grouped_order in GroupedOrder.objects.all().iterator():
grouped_order.code = '' grouped_order.code = ""
grouped_order.save() grouped_order.save()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,20 +1,15 @@
import csv import csv
import json import json
from datetime import timedelta
from io import BytesIO
from django import http from django import http
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.shortcuts import get_object_or_404, redirect 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.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 django_weasyprint import WeasyTemplateResponseMixin from django_weasyprint import WeasyTemplateResponseMixin
from django_weasyprint.views import WeasyTemplateResponse, WeasyTemplateView
from icalendar import Calendar, Event, vCalAddress, vText from icalendar import Calendar, Event, vCalAddress, vText
from xhtml2pdf import pisa
from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm from ..forms import GroupedOrderForm, Item, JoinGroupedOrderForm
from ..models import GroupedOrder, OrderAuthor from ..models import GroupedOrder, OrderAuthor

View file

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

Binary file not shown.