20 Commits

Author SHA1 Message Date
f477346f1e allow redis and postgres to run in docker compose 2025-01-11 01:12:17 +01:00
8cc23f01fd use .env for project configuration 2025-01-11 01:12:12 +01:00
d456a1d9d8 use .env for project configuration 2025-01-10 22:47:35 +01:00
e200f28267 Merge pull request #1001 from ae-utbm/counter
Fix selling ordering bug that created "not enough money" errors
2025-01-10 16:39:44 +01:00
Sli
a4c6439981 Fix selling ordering bug that created "not enough money" errors
* Add tests
* Add tests for cons/dcons
2025-01-10 16:35:42 +01:00
6ee2e8c5da Merge pull request #996 from ae-utbm/elections
Remove shorten dependency and use clip instead
2025-01-10 15:41:31 +01:00
Sli
f4af29acb4 Fix missing translation 2025-01-10 15:34:46 +01:00
b26e85ebb2 Merge pull request #999 from ae-utbm/fix-perms
fix ban page access
2025-01-10 01:41:19 +01:00
8b8a295e16 fix ban page access 2025-01-10 01:29:24 +01:00
894690a97f Merge pull request #997 from ae-utbm/counter
Fix inconsistent search behavior on counter click codes
2025-01-09 22:32:59 +01:00
843ce2e3a7 Merge pull request #990 from ae-utbm/jquery
Remove some jquery
2025-01-09 22:32:39 +01:00
Sli
9f33ddd883 Fix inconsistent search behavior on counter click codes 2025-01-09 01:04:11 +01:00
Sli
a2dc4f1964 Create a new better script for showing more/less 2025-01-08 14:51:14 +01:00
cca486f2b9 Merge pull request #995 from ae-utbm/elections
Fix election display on mobile and add missing signal for news deletion
2025-01-08 09:42:18 +01:00
b9e27ef191 Merge pull request #976 from ae-utbm/tom-select-style
make ajax select appearance consistant with other inputs
2025-01-08 09:40:31 +01:00
Sli
29e875bcde Fix election display on mobile and add missing signal for news deletion 2025-01-08 09:32:24 +01:00
4226ba88ae Merge pull request #993 from ae-utbm/elections
Quick fix for election display
2025-01-08 08:55:22 +01:00
Sli
672bc91e36 Quick fix for election display 2025-01-08 03:17:18 +01:00
Sli
2db3290bed Remove some jquery 2025-01-05 20:17:30 +01:00
0d3fd954a3 make ajax select appearance consistant with other inputs 2024-12-29 18:16:52 +01:00
36 changed files with 798 additions and 339 deletions

83
.env.example Normal file
View File

@ -0,0 +1,83 @@
HTTPS=off
DEBUG=true
# This is not the real key used in prod
SECRET_KEY=(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2
DATABASE_URL=sqlite:///db.sqlite3
# uncomment the next line if you want to use a postgres database
#DATABASE_URL=postgres://user:password@127.0.0.1:5432/sith
CACHE_URL=redis://127.0.0.1:6379/0
MEDIA_ROOT=data
STATIC_ROOT=static
DEFAULT_FROM_EMAIL=bibou@git.an
SITH_COM_EMAIL=bibou_com@git.an
HONEYPOT_VALUE=content
HONEYPOT_FIELD_NAME=body2
HONEYPOT_FIELD_NAME_FORUM=message2
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
EMAIL_HOST=localhost
EMAIL_PORT=25
SITH_URL=127.0.0.1:8000
SITH_NAME="AE UTBM"
SITH_MAIN_CLUB_ID=1
SITH_GROUP_ROOT_ID=1
SITH_GROUP_PUBLIC_ID=2
SITH_GROUP_SUBSCRIBERS_ID=3
SITH_GROUP_OLD_SUBSCRIBERS_ID=4
SITH_GROUP_ACCOUNTING_ADMIN_ID=5
SITH_GROUP_COM_ADMIN_ID=6
SITH_GROUP_COUNTER_ADMIN_ID=7
SITH_GROUP_SAS_ADMIN_ID=8
SITH_GROUP_FORUM_ADMIN_ID=9
SITH_GROUP_PEDAGOGY_ADMIN_ID=10
SITH_GROUP_BANNED_ALCOHOL_ID=11
SITH_GROUP_BANNED_COUNTER_ID=12
SITH_GROUP_BANNED_SUBSCRIPTION_ID=13
SITH_CLUB_REFOUND_ID=89
SITH_COUNTER_REFOUND_ID=38
SITH_PRODUCT_REFOUND_ID=5
# Counter
SITH_COUNTER_ACCOUNT_DUMP_ID=39
# Defines which product type is the refilling type, and thus increases the account amount
SITH_COUNTER_PRODUCTTYPE_REFILLING=3
SITH_ECOCUP_CONS=1152
SITH_ECOCUP_DECO=1151
# Defines which product is the one year subscription and which one is the six month subscription
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER=1
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS=2
SITH_PRODUCTTYPE_SUBSCRIPTION=2
# Defines which clubs let its members the ability to see users subscription history
SITH_CAN_CREATE_SUBSCRIPTION_HISTORY=1
SITH_CAN_READ_SUBSCRIPTION_HISTORY=1
# SAS variables
SITH_SAS_ROOT_DIR_ID=4
# ET variables
SITH_EBOUTIC_CB_ENABLED=true
SITH_EBOUTIC_ET_URL="https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi"
SITH_EBOUTIC_PBX_SITE=1999888
SITH_EBOUTIC_PBX_RANG=32
SITH_EBOUTIC_PBX_IDENTIFIANT=2
SITH_EBOUTIC_HMAC_KEY=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
SITH_EBOUTIC_PUB_KEY_PATH=sith/et_keys/pubkey.pem
SITH_MAILING_FETCH_KEY=IloveMails
SENTRY_DSN=
SENTRY_ENV=production

View File

@ -7,6 +7,10 @@ on:
branches: [master, taiste] branches: [master, taiste]
workflow_dispatch: workflow_dispatch:
env:
SECRET_KEY: notTheRealOne
DATABASE_URL: sqlite:///db.sqlite3
jobs: jobs:
pre-commit: pre-commit:
name: Launch pre-commits checks (ruff) name: Launch pre-commits checks (ruff)

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ node_modules/
# compiled documentation # compiled documentation
site/ site/
.env

1
.npmrc Normal file
View File

@ -0,0 +1 @@
@jsr:registry=https://npm.jsr.io

View File

@ -1,10 +1,10 @@
from django.db.models.base import post_save from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver from django.dispatch import receiver
from com.calendar import IcsCalendar from com.calendar import IcsCalendar
from com.models import News from com.models import News
@receiver(post_save, sender=News, dispatch_uid="update_internal_ics") @receiver([post_save, post_delete], sender=News, dispatch_uid="update_internal_ics")
def update_internal_ics(*args, **kwargs): def update_internal_ics(*args, **kwargs):
_ = IcsCalendar.make_internal() _ = IcsCalendar.make_internal()

View File

@ -125,7 +125,7 @@
<i class="fa-brands fa-discord fa-xl"></i> <i class="fa-brands fa-discord fa-xl"></i>
<a rel="nofollow" target="#" href="https://discord.gg/QvTm3XJrHR">{% trans %}Discord AE{% endtrans %}</a> <a rel="nofollow" target="#" href="https://discord.gg/QvTm3XJrHR">{% trans %}Discord AE{% endtrans %}</a>
{% if user.was_subscribed %} {% if user.was_subscribed %}
- <a rel="nofollow" target="#" href="https://discord.gg/XK9WfPsUFm">{% trans %}Dev Team{% endtrans %}</a> - <a rel="nofollow" target="#" href="https://discord.gg/u6EuMfyGaJ">{% trans %}Dev Team{% endtrans %}</a>
{% endif %} {% endif %}
</li> </li>
<li> <li>

View File

@ -460,6 +460,7 @@ Welcome to the wiki page!
limit_age=18, limit_age=18,
) )
cons = Product.objects.create( cons = Product.objects.create(
id=settings.SITH_ECOCUP_CONS,
name="Consigne Eco-cup", name="Consigne Eco-cup",
code="CONS", code="CONS",
product_type=verre, product_type=verre,
@ -469,6 +470,7 @@ Welcome to the wiki page!
club=main_club, club=main_club,
) )
dcons = Product.objects.create( dcons = Product.objects.create(
id=settings.SITH_ECOCUP_DECO,
name="Déconsigne Eco-cup", name="Déconsigne Eco-cup",
code="DECO", code="DECO",
product_type=verre, product_type=verre,

View File

@ -16,6 +16,7 @@
from django.conf import settings from django.conf import settings
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.db import connection
class Command(BaseCommand): class Command(BaseCommand):
@ -29,7 +30,7 @@ class Command(BaseCommand):
if not data_dir.is_dir(): if not data_dir.is_dir():
data_dir.mkdir() data_dir.mkdir()
db_path = settings.BASE_DIR / "db.sqlite3" db_path = settings.BASE_DIR / "db.sqlite3"
if db_path.exists(): if db_path.exists() or connection.vendor != "sqlite":
call_command("flush", "--noinput") call_command("flush", "--noinput")
self.stdout.write("Existing database reset") self.stdout.write("Existing database reset")
call_command("migrate") call_command("migrate")

View File

@ -0,0 +1,73 @@
import clip from "@arendjr/text-clipper";
/*
This script adds a way to have a 'show more / show less' button
on some text content.
The usage is very simple, you just have to add the attribute `show-more`
with the desired max size to the element you want to add the button to.
This script does html matching and is able to properly cut rendered markdown.
Example usage:
<p show-more="20">
My very long text will be cut by this script
</p>
*/
function showMore(element: HTMLElement) {
if (!element.hasAttribute("show-more")) {
return;
}
// Mark element as loaded so we can hide unloaded
// tags with css and avoid blinking text
element.setAttribute("show-more-loaded", "");
const fullContent = element.innerHTML;
const clippedContent = clip(
element.innerHTML,
Number.parseInt(element.getAttribute("show-more") as string),
{
html: true,
},
);
// If already at the desired size, we don't do anything
if (clippedContent === fullContent) {
return;
}
const actionLink = document.createElement("a");
actionLink.setAttribute("class", "show-more-link");
let opened = false;
const setText = () => {
if (opened) {
element.innerHTML = fullContent;
actionLink.innerText = gettext("Show less");
} else {
element.innerHTML = clippedContent;
actionLink.innerText = gettext("Show more");
}
element.appendChild(document.createElement("br"));
element.appendChild(actionLink);
};
const toggle = () => {
opened = !opened;
setText();
};
setText();
actionLink.addEventListener("click", (event) => {
event.preventDefault();
toggle();
});
}
document.addEventListener("DOMContentLoaded", () => {
for (const elem of document.querySelectorAll("[show-more]")) {
showMore(elem as HTMLElement);
}
});

View File

@ -1,11 +1,27 @@
.ts-wrapper.multi .ts-control {
min-width: calc(100% - 0.2rem);
}
/* This also requires ajax-select-index.css */ /* This also requires ajax-select-index.css */
.ts-dropdown { .ts-dropdown {
width: calc(100% - 0.2rem);
left: 0.1rem;
top: calc(100% - 0.2rem - var(--nf-input-border-bottom-width));
border: var(--nf-input-border-color) var(--nf-input-border-width) solid;
border-top: none;
border-bottom-width: var(--nf-input-border-bottom-width);
.option.active {
background-color: #e5eafa;
color: inherit;
}
.select-item { .select-item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 10px; gap: 10px;
align-items: center; align-items: center;
overflow: hidden;
img { img {
height: 40px; height: 40px;
@ -16,19 +32,44 @@
} }
} }
.ts-wrapper { .ts-wrapper.single {
margin: 5px; > .ts-control {
box-shadow: none;
max-width: 300px;
background-color: var(--nf-input-background-color);
&::after {
content: none;
}
}
> .ts-dropdown {
max-width: 300px;
}
} }
.ts-wrapper.single { .ts-wrapper input[type="text"] {
width: 263px; // same length as regular text inputs border: none;
border-radius: 0;
}
.ts-wrapper.multi, .ts-wrapper.single {
.ts-control:has(input:focus) {
outline: none;
border-color: var(--nf-input-focus-border-color);
box-shadow: none;
}
} }
.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove { .ts-wrapper.plugin-remove_button:not(.rtl) .item .remove {
border-left: 1px solid #aaa; border-left: 1px solid #aaa;
} }
.ts-wrapper.multi .ts-control { .ts-wrapper.multi.has-items .ts-control {
padding: calc(var(--nf-input-size) * 0.65);
display: flex;
gap: calc(var(--nf-input-size) / 3);
[data-value], [data-value],
[data-value].active { [data-value].active {
background-image: none; background-image: none;
@ -37,19 +78,17 @@
border: 1px solid #aaa; border: 1px solid #aaa;
border-radius: 4px; border-radius: 4px;
display: inline-block; display: inline-block;
margin-left: 5px;
margin-top: 5px;
margin-bottom: 5px;
padding-right: 10px; padding-right: 10px;
padding-left: 10px; padding-left: 10px;
text-shadow: none; text-shadow: none;
box-shadow: none; box-shadow: none;
.remove {
vertical-align: baseline;
}
} }
} }
.ts-dropdown { .ts-wrapper.focus .ts-control {
.option.active { box-shadow: none;
background-color: #e5eafa; }
color: inherit;
}
}

View File

@ -48,7 +48,8 @@
input, input,
textarea[type="text"], textarea[type="text"],
[type="number"] { [type="number"],
.ts-control {
border: none; border: none;
text-decoration: none; text-decoration: none;
background-color: $background-button-color; background-color: $background-button-color;
@ -69,7 +70,7 @@
font-family: sans-serif; font-family: sans-serif;
} }
select { select, .ts-control {
border: none; border: none;
text-decoration: none; text-decoration: none;
font-size: 1.2em; font-size: 1.2em;
@ -177,7 +178,7 @@ form {
} }
// wrap texts // wrap texts
label, legend, ul.errorlist>li, .helptext { label, legend, ul.errorlist > li, .helptext {
text-wrap: wrap; text-wrap: wrap;
} }
@ -218,23 +219,25 @@ form {
} }
} }
input[type="text"], :not(.ts-control) > {
input[type="email"], input[type="text"],
input[type="tel"], input[type="email"],
input[type="url"], input[type="tel"],
input[type="password"], input[type="url"],
input[type="number"], input[type="password"],
input[type="date"], input[type="number"],
input[type="week"], input[type="date"],
input[type="time"], input[type="week"],
input[type="month"], input[type="time"],
input[type="search"], input[type="search"],
textarea, textarea,
select { input[type="month"],
min-width: 300px; select {
min-width: 300px;
&.grow { &.grow {
width: 95%; width: 95%;
}
} }
} }
@ -253,7 +256,8 @@ form {
input[type="month"], input[type="month"],
input[type="search"], input[type="search"],
textarea, textarea,
select { select,
.ts-control {
background: var(--nf-input-background-color); background: var(--nf-input-background-color);
font-size: var(--nf-input-font-size); font-size: var(--nf-input-font-size);
border-color: var(--nf-input-border-color); border-color: var(--nf-input-border-color);
@ -713,7 +717,11 @@ form {
// ---------------- SELECT // ---------------- SELECT
select { select,
.ts-wrapper.multi .ts-control,
.ts-wrapper.single .ts-control,
.ts-wrapper.single.input-active .ts-control {
background-color: var(--nf-input-background-color);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-position: right calc(var(--nf-input-size) * 0.75) bottom 50%; background-position: right calc(var(--nf-input-size) * 0.75) bottom 50%;
background-repeat: no-repeat; background-repeat: no-repeat;

View File

@ -131,6 +131,10 @@ body {
display: none !important; display: none !important;
} }
[show-more]:not([show-more-loaded]) {
display: none !important;
}
/*--------------------------------HEADER-------------------------------*/ /*--------------------------------HEADER-------------------------------*/
#popupheader { #popupheader {

View File

@ -125,15 +125,14 @@
navbar.style.setProperty("display", current === "none" ? "block" : "none"); navbar.style.setProperty("display", current === "none" ? "block" : "none");
} }
$(document).keydown(function (e) { document.addEventListener("keydown", (e) => {
if ($(e.target).is('input')) { return } // Looking at the `s` key when not typing in a form
if ($(e.target).is('textarea')) { return } if (e.keyCode !== 83 || ["INPUT", "TEXTAREA", "SELECT"].includes(e.target.nodeName)) {
if ($(e.target).is('select')) { return } return;
if (e.keyCode === 83) {
$("#search").focus();
return false;
} }
}); document.getElementById("search").focus();
e.preventDefault(); // Don't type the character in the focused search input
})
</script> </script>
{% endblock %} {% endblock %}
</body> </body>

View File

@ -57,13 +57,4 @@
{% endblock %} {% endblock %}
{% endif %} {% endif %}
{% block script %}
{{ super() }}
{% if popup %}
<script>
parent.$(".choose_file_widget").css("height", "75%");
</script>
{% endif %}
{% endblock %}
{% endblock %} {% endblock %}

View File

@ -244,27 +244,30 @@
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script> <script>
$(function () { // Image selection
var keys = []; for (const img of document.querySelectorAll("#small_pictures img")){
var pattern = "71,85,89,71,85,89"; img.addEventListener("click", (e) => {
$(document).keydown(function (e) { const displayed = document.querySelector("#big_picture img");
keys.push(e.keyCode); displayed.src = e.target.src;
if (keys.toString() == pattern) { displayed.alt = e.target.alt;
keys = []; displayed.title = e.target.title;
$("#big_picture img").attr("src", "{{ static('core/img/yug.jpg') }}");
}
if (keys.length == 6) {
keys.shift();
}
});
});
$(function () {
$("#small_pictures img").click(function () {
$("#big_picture img").attr("src", $(this)[0].src);
$("#big_picture img").attr("alt", $(this)[0].alt);
$("#big_picture img").attr("title", $(this)[0].title);
}) })
}
let keys = [];
const pattern = "71,85,89,71,85,89";
document.addEventListener("keydown", (e) => {
keys.push(e.keyCode);
if (keys.toString() === pattern) {
keys = [];
document.querySelector("#big_picture img").src = "{{ static('core/img/yug.jpg') }}";
}
if (keys.length === 6) {
keys.shift();
}
}); });
$(function () { $(function () {
$("#drop_gifts").accordion({ $("#drop_gifts").accordion({
heightStyle: "content", heightStyle: "content",

View File

@ -23,7 +23,7 @@
<li><a href="{{ url('rootplace:operation_logs') }}">{% trans %}Operation logs{% endtrans %}</a></li> <li><a href="{{ url('rootplace:operation_logs') }}">{% trans %}Operation logs{% endtrans %}</a></li>
<li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li> <li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li>
{% endif %} {% endif %}
{% if user.has_perm("core:view_userban") %} {% if user.has_perm("core.view_userban") %}
<li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li> <li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li>
{% endif %} {% endif %}
{% if user.can_create_subscription or user.is_root %} {% if user.can_create_subscription or user.is_root %}

View File

@ -76,7 +76,15 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
return { return {
...super.tomSelectSettings(), ...super.tomSelectSettings(),
openOnFocus: false, openOnFocus: false,
searchField: ["code", "text"], // We make searching on exact code matching a higher priority
// We need to manually set weights or it results on an inconsistent
// behavior between production and development environment
searchField: [
// @ts-ignore documentation says it's fine, specified type is wrong
{ field: "code", weight: 2 },
// @ts-ignore documentation says it's fine, specified type is wrong
{ field: "text", weight: 0.5 },
],
}; };
} }
} }

View File

@ -236,6 +236,10 @@ class TestCounterClick(TestFullClickBase):
BanGroup.objects.get(pk=settings.SITH_GROUP_BANNED_COUNTER_ID) BanGroup.objects.get(pk=settings.SITH_GROUP_BANNED_COUNTER_ID)
) )
cls.gift = product_recipe.make(
selling_price="-1.5",
special_selling_price="-1.5",
)
cls.beer = product_recipe.make( cls.beer = product_recipe.make(
limit_age=18, selling_price="1.5", special_selling_price="1" limit_age=18, selling_price="1.5", special_selling_price="1"
) )
@ -253,7 +257,12 @@ class TestCounterClick(TestFullClickBase):
limit_age=0, selling_price="1.5", special_selling_price="1" limit_age=0, selling_price="1.5", special_selling_price="1"
) )
cls.counter.products.add(cls.beer, cls.beer_tap, cls.snack) cls.cons = Product.objects.get(id=settings.SITH_ECOCUP_CONS)
cls.dcons = Product.objects.get(id=settings.SITH_ECOCUP_DECO)
cls.counter.products.add(
cls.gift, cls.beer, cls.beer_tap, cls.snack, cls.cons, cls.dcons
)
cls.other_counter.products.add(cls.snack) cls.other_counter.products.add(cls.snack)
@ -594,6 +603,84 @@ class TestCounterClick(TestFullClickBase):
else: else:
assert not counter.has_annotated_barman assert not counter.has_annotated_barman
def test_selling_ordering(self):
# Cheaper items should be processed with a higher priority
self.login_in_bar(self.barmen)
assert (
self.submit_basket(
self.customer,
[
BasketItem(self.beer.id, 1),
BasketItem(self.gift.id, 1),
],
).status_code
== 302
)
assert self.updated_amount(self.customer) == 0
def test_recordings(self):
self.refill_user(self.customer, self.cons.selling_price * 3)
self.login_in_bar(self.barmen)
assert (
self.submit_basket(
self.customer,
[BasketItem(self.cons.id, 3)],
).status_code
== 302
)
assert self.updated_amount(self.customer) == 0
assert (
self.submit_basket(
self.customer,
[BasketItem(self.dcons.id, 3)],
).status_code
== 302
)
assert self.updated_amount(self.customer) == self.dcons.selling_price * -3
assert (
self.submit_basket(
self.customer,
[BasketItem(self.dcons.id, settings.SITH_ECOCUP_LIMIT)],
).status_code
== 302
)
assert self.updated_amount(self.customer) == self.dcons.selling_price * (
-3 - settings.SITH_ECOCUP_LIMIT
)
assert (
self.submit_basket(
self.customer,
[BasketItem(self.dcons.id, 1)],
).status_code
== 200
)
assert self.updated_amount(self.customer) == self.dcons.selling_price * (
-3 - settings.SITH_ECOCUP_LIMIT
)
assert (
self.submit_basket(
self.customer,
[
BasketItem(self.cons.id, 1),
BasketItem(self.dcons.id, 1),
],
).status_code
== 302
)
assert self.updated_amount(self.customer) == self.dcons.selling_price * (
-3 - settings.SITH_ECOCUP_LIMIT
)
class TestCounterStats(TestCase): class TestCounterStats(TestCase):
@classmethod @classmethod

View File

@ -194,7 +194,11 @@ class CounterClick(CounterTabsMixin, CanViewMixin, SingleObjectMixin, FormView):
with transaction.atomic(): with transaction.atomic():
self.request.session["last_basket"] = [] self.request.session["last_basket"] = []
for form in formset: # We sort items from cheap to expensive
# This is important because some items have a negative price
# Negative priced items gives money to the customer and should
# be processed first so that we don't throw a not enough money error
for form in sorted(formset, key=lambda form: form.product.price):
self.request.session["last_basket"].append( self.request.session["last_basket"].append(
f"{form.cleaned_data['quantity']} x {form.product.name}" f"{form.cleaned_data['quantity']} x {form.product.name}"
) )

36
docker-compose.yml Normal file
View File

@ -0,0 +1,36 @@
services:
db:
image: postgres:16.6
restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
ports:
- "5431:5432"
environment:
POSTGRES_USER: sith
POSTGRES_PASSWORD: sith
POSTGRES_DB: sith
redis:
image: redis:latest
restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
ports:
- "6378:6379"
command: redis-server
volumes:
- redis_data:/var/lib/redis/data/
volumes:
postgres_data:
driver: local
redis_data:
driver: local

View File

@ -2,13 +2,13 @@
Pour connecter l'application à une instance de sentry (ex: https://sentry.io), Pour connecter l'application à une instance de sentry (ex: https://sentry.io),
il est nécessaire de configurer la variable `SENTRY_DSN` il est nécessaire de configurer la variable `SENTRY_DSN`
dans le fichier `settings_custom.py`. dans le fichier `.env`.
Cette variable est composée d'un lien complet vers votre projet sentry. Cette variable est composée d'un lien complet vers votre projet sentry.
## Récupérer les statiques ## Récupérer les statiques
Nous utilisons du SCSS dans le projet. Nous utilisons du SCSS dans le projet.
En environnement de développement (`DEBUG=True`), En environnement de développement (`DEBUG=true`),
le SCSS est compilé à chaque fois que le fichier est demandé. le SCSS est compilé à chaque fois que le fichier est demandé.
Pour la production, le projet considère Pour la production, le projet considère
que chacun des fichiers est déjà compilé. que chacun des fichiers est déjà compilé.

View File

@ -47,19 +47,19 @@ Commencez par installer les dépendances système :
=== "Debian/Ubuntu" === "Debian/Ubuntu"
```bash ```bash
sudo apt install postgresql redis libq-dev nginx sudo apt install postgresql libq-dev nginx
``` ```
=== "Arch Linux" === "Arch Linux"
```bash ```bash
sudo pacman -S postgresql redis nginx sudo pacman -S postgresql nginx
``` ```
=== "macOS" === "macOS"
```bash ```bash
brew install postgresql redis lipbq nginx brew install postgresql lipbq nginx
export PATH="/usr/local/opt/libpq/bin:$PATH" export PATH="/usr/local/opt/libpq/bin:$PATH"
source ~/.zshrc source ~/.zshrc
``` ```
@ -77,34 +77,6 @@ uv sync --group prod
C'est parce que ces dépendances compilent certains modules C'est parce que ces dépendances compilent certains modules
à l'installation. à l'installation.
## Configurer Redis
Redis est utilisé comme cache.
Assurez-vous qu'il tourne :
```bash
sudo systemctl redis status
```
Et s'il ne tourne pas, démarrez-le :
```bash
sudo systemctl start redis
sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot
```
Puis ajoutez le code suivant à la fin de votre fichier
`settings_custom.py` :
```python
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": "redis://127.0.0.1:6379",
}
}
```
## Configurer PostgreSQL ## Configurer PostgreSQL
PostgreSQL est utilisé comme base de données. PostgreSQL est utilisé comme base de données.
@ -139,26 +111,19 @@ en étant connecté en tant que postgres :
psql -d sith -c "GRANT ALL PRIVILEGES ON SCHEMA public to sith"; psql -d sith -c "GRANT ALL PRIVILEGES ON SCHEMA public to sith";
``` ```
Puis ajoutez le code suivant à la fin de votre Puis modifiez votre `.env`.
`settings_custom.py` : Dedans, décommentez l'url de la base de données
de postgres et commentez l'url de sqlite :
```python ```dotenv
DATABASES = { #DATABASE_URL=sqlite:///db.sqlite3
"default": { DATABASE_URL=postgres://sith:password@localhost:5432/sith
"ENGINE": "django.db.backends.postgresql",
"NAME": "sith",
"USER": "sith",
"PASSWORD": "password",
"HOST": "localhost",
"PORT": "", # laissez ce champ vide pour que le choix du port soit automatique
}
}
``` ```
Enfin, créez vos données : Enfin, créez vos données :
```bash ```bash
uv run ./manage.py populate uv run ./manage.py setup
``` ```
!!! note !!! note
@ -247,7 +212,7 @@ Puis lancez ou relancez nginx :
sudo systemctl restart nginx sudo systemctl restart nginx
``` ```
Dans votre `settings_custom.py`, remplacez `DEBUG=True` par `DEBUG=False`. Dans votre `.env`, remplacez `DEBUG=true` par `DEBUG=false`.
Enfin, démarrez le serveur Django : Enfin, démarrez le serveur Django :

View File

@ -7,6 +7,7 @@ Certaines dépendances sont nécessaires niveau système :
- libjpeg - libjpeg
- zlib1g-dev - zlib1g-dev
- gettext - gettext
- redis
### Installer WSL ### Installer WSL
@ -65,8 +66,8 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
```bash ```bash
sudo apt install curl build-essential libssl-dev \ sudo apt install curl build-essential libssl-dev \
libjpeg-dev zlib1g-dev npm libffi-dev pkg-config \ libjpeg-dev zlib1g-dev npm libffi-dev pkg-config \
gettext git gettext git redis
curl -LsSf https://astral.sh/uv/install.sh | sh curl -LsSf https://astral.sh/uv/install.sh | sh
``` ```
@ -75,7 +76,7 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
```bash ```bash
sudo pacman -Syu # on s'assure que les dépôts et le système sont à jour sudo pacman -Syu # on s'assure que les dépôts et le système sont à jour
sudo pacman -S uv gcc git gettext pkgconf npm sudo pacman -S uv gcc git gettext pkgconf npm redis
``` ```
=== "macOS" === "macOS"
@ -84,7 +85,7 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
Il est également nécessaire d'avoir installé xcode Il est également nécessaire d'avoir installé xcode
```bash ```bash
brew install git uv npm brew install git uv npm redis
# Pour bien configurer gettext # Pour bien configurer gettext
brew link gettext # (suivez bien les instructions supplémentaires affichées) brew link gettext # (suivez bien les instructions supplémentaires affichées)
@ -99,6 +100,24 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
Python ne fait pas parti des dépendances puisqu'il est automatiquement Python ne fait pas parti des dépendances puisqu'il est automatiquement
installé par uv. installé par uv.
Parmi les dépendances installées se trouve redis (que nous utilisons comme cache).
Redis est un service qui doit être activé pour être utilisé.
Pour cela, effectuez les commandes :
```bash
sudo systemctl start redis
sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot
```
Parmi les dépendances installées se trouve redis (que nous utilisons comme cache).
Redis est un service qui doit être activé pour être utilisé.
Pour cela, effectuez les commandes :
```bash
sudo systemctl start redis
sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot
```
## Finaliser l'installation ## Finaliser l'installation
Clonez le projet (depuis votre console WSL, si vous utilisez WSL) Clonez le projet (depuis votre console WSL, si vous utilisez WSL)
@ -120,20 +139,24 @@ uv run ./manage.py install_xapian
de texte à l'écran. de texte à l'écran.
C'est normal, il ne faut pas avoir peur. C'est normal, il ne faut pas avoir peur.
Maintenant que les dépendances sont installées, nous Une fois les dépendances installées, il faut encore
allons créer la base de données, la remplir avec des données de test, mettre en place quelques éléments de configuration,
et compiler les traductions. qui peuvent varier d'un environnement à l'autre.
Cependant, avant de faire cela, il est nécessaire de modifier Ces variables sont stockées dans un fichier `.env`.
la configuration pour signifier que nous sommes en mode développement. Pour le créer, vous pouvez copier le fichier `.env.example` :
Pour cela, nous allons créer un fichier `sith/settings_custom.py`
et l'utiliser pour surcharger les settings de base.
```bash ```bash
echo "DEBUG=True" > sith/settings_custom.py cp .env.example .env
echo 'SITH_URL = "localhost:8000"' >> sith/settings_custom.py
``` ```
Enfin, nous pouvons lancer les commandes suivantes : Les variables par défaut contenues dans le fichier `.env`
devraient convenir pour le développement, sans modification.
Maintenant que les dépendances sont installées
et la configuration remplie, nous allons pouvoir générer
des données utiles pendant le développement.
Pour cela, lancez les commandes suivantes :
```bash ```bash
# Prépare la base de données # Prépare la base de données
@ -171,6 +194,30 @@ uv run ./manage.py runserver
[http://localhost:8000/api/docs](http://localhost:8000/api/docs), [http://localhost:8000/api/docs](http://localhost:8000/api/docs),
une interface swagger, avec toutes les routes de l'API. une interface swagger, avec toutes les routes de l'API.
!!! question "Pourquoi l'installation est aussi complexe ?"
Cette question nous a été posée de nombreuses fois par des personnes
essayant d'installer le projet.
Il y a en effet un certain nombre d'étapes à suivre,
de paquets à installer et de commandes à exécuter.
Le processus d'installation peut donc sembler complexe.
En réalité, il est difficile de faire plus simple.
En effet, un site web a besoin de beaucoup de composants
pour être développé : il lui faut au minimum
une base de données, un cache, un bundler Javascript
et un interpréteur pour le code du serveur.
Pour nos besoin particuliers, nous utilisons également
un moteur de recherche full-text.
Nous avons tenté au maximum de limiter le nombre de dépendances
et de sélecionner les plus simples à installer.
Cependant, il est impossible de retirer l'intégralité
de la complexité du processus.
Si vous rencontrez des difficulté lors de l'installation,
n'hésitez pas à demander de l'aide.
## Générer la documentation ## Générer la documentation
La documentation est automatiquement mise en ligne à chaque envoi de code sur GitHub. La documentation est automatiquement mise en ligne à chaque envoi de code sur GitHub.

View File

@ -72,12 +72,14 @@ sith/
├── .gitattributes ├── .gitattributes
├── .gitignore ├── .gitignore
├── .mailmap ├── .mailmap
├── manage.py (26) ├── .env (26)
├── mkdocs.yml (27) ├── .env.example (27)
├── manage.py (28)
├── mkdocs.yml (29)
├── uv.lock ├── uv.lock
├── pyproject.toml (28) ├── pyproject.toml (30)
├── .venv/ (29) ├── .venv/ (31)
├── .python-version (30) ├── .python-version (32)
└── README.md └── README.md
``` ```
</div> </div>
@ -121,15 +123,19 @@ sith/
de manière transparente pour l'utilisateur. de manière transparente pour l'utilisateur.
24. Fichier de configuration de coverage. 24. Fichier de configuration de coverage.
25. Fichier de configuration de direnv. 25. Fichier de configuration de direnv.
26. Fichier généré automatiquement par Django. C'est lui 26. Contient les variables d'environnement, qui sont susceptibles
de varier d'une machine à l'autre.
27. Contient des valeurs par défaut pour le `.env`
pouvant convenir à un environnment de développement local
28. Fichier généré automatiquement par Django. C'est lui
qui permet d'appeler des commandes de gestion du projet qui permet d'appeler des commandes de gestion du projet
avec la syntaxe `python ./manage.py <nom de la commande>` avec la syntaxe `python ./manage.py <nom de la commande>`
27. Le fichier de configuration de la documentation, 29. Le fichier de configuration de la documentation,
avec ses plugins et sa table des matières. avec ses plugins et sa table des matières.
28. Le fichier où sont déclarés les dépendances et la configuration 30. Le fichier où sont déclarés les dépendances et la configuration
de certaines d'entre elles. de certaines d'entre elles.
29. Dossier d'environnement virtuel généré par uv 31. Dossier d'environnement virtuel généré par uv
30. Fichier qui contrôle quel version de python utiliser pour le projet 32. Fichier qui contrôle quelle version de python utiliser pour le projet
## L'application principale ## L'application principale
@ -144,10 +150,9 @@ Il est organisé comme suit :
``` ```
sith/ sith/
├── settings.py (1) ├── settings.py (1)
├── settings_custom.py (2) ├── toolbar_debug.py (2)
├── toolbar_debug.py (3) ├── urls.py (3)
── urls.py (4) ── wsgi.py (4)
└── wsgi.py (5)
``` ```
</div> </div>
@ -155,13 +160,10 @@ sith/
Ce fichier contient les paramètres de configuration du projet. Ce fichier contient les paramètres de configuration du projet.
Par exemple, il contient la liste des applications Par exemple, il contient la liste des applications
installées dans le projet. installées dans le projet.
2. Configuration maison pour votre environnement. 2. Configuration de la barre de debug.
Toute variable que vous définissez dans ce fichier sera prioritaire
sur la configuration donnée dans `settings.py`.
3. Configuration de la barre de debug.
C'est inutilisé en prod, mais c'est très pratique en développement. C'est inutilisé en prod, mais c'est très pratique en développement.
4. Fichier de configuration des urls du projet. 3. Fichier de configuration des urls du projet.
5. Fichier de configuration pour le serveur WSGI. 4. Fichier de configuration pour le serveur WSGI.
WSGI est un protocole de communication entre le serveur WSGI est un protocole de communication entre le serveur
et les applications. et les applications.
Ce fichier ne vous servira sans doute pas sur un environnement Ce fichier ne vous servira sans doute pas sur un environnement

View File

@ -15,8 +15,8 @@ $min_col_width: 100px;
flex-direction: row; flex-direction: row;
gap: $gap; gap: $gap;
> input, >input,
> label { >label {
margin: 0; margin: 0;
} }
@ -25,12 +25,12 @@ $min_col_width: 100px;
} }
} }
.election_vote { #page #content {
overflow-x: scroll !important; overflow-x: scroll;
} }
.election_table { .election_table {
width: 100%; width: inherit;
>.lists { >.lists {
display: flex; display: flex;
@ -93,16 +93,30 @@ $min_col_width: 100px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin: 0; margin: 0;
row-gap: 10px;
padding: $padding; padding: $padding;
width: 100%; width: 100%;
>.role_text { >.role_text {
display: flex;
flex-direction: column;
>h4 { >h4 {
margin: 0; margin: 0;
} }
>p {
.role_description {
flex-grow: 1;
margin-top: .5em; margin-top: .5em;
text-wrap: auto;
text-align: left;
// Show more/less element
a {
text-align: center;
display: block;
}
} }
} }
@ -112,9 +126,9 @@ $min_col_width: 100px;
align-items: center; align-items: center;
gap: $gap; gap: $gap;
> button, >button,
> button > i, >button>i,
> a { >a {
width: 20px; width: 20px;
height: 20px; height: 20px;
background-color: #e9e9e9; background-color: #e9e9e9;
@ -127,23 +141,23 @@ $min_col_width: 100px;
justify-content: center; justify-content: center;
&:hover, &:hover,
&:hover > i { &:hover>i {
background-color: #fff; background-color: #fff;
} }
} }
> button { >button {
width: 30px; width: 30px;
height: 30px; height: 30px;
} }
> button[disabled] { >button[disabled] {
background-color: #eee; background-color: #eee;
cursor: not-allowed; cursor: not-allowed;
>i, >i,
&:hover, &:hover,
&:hover > i { &:hover>i {
background-color: #eee; background-color: #eee;
} }
} }
@ -178,12 +192,12 @@ $min_col_width: 100px;
width: 100%; width: 100%;
gap: $gap; gap: $gap;
>input[type="radio"]:checked + label, >input[type="radio"]:checked+label,
>input[type="checkbox"]:checked + label { >input[type="checkbox"]:checked+label {
background-color: lightgray; background-color: lightgray;
border-radius: 10px; border-radius: 10px;
>figure>.edit_btns>a:hover{ >figure>.edit_btns>a:hover {
background-color: #fff; background-color: #fff;
} }
} }
@ -215,7 +229,9 @@ $min_col_width: 100px;
margin: 0; margin: 0;
text-align: center; text-align: center;
} }
.candidate_program { .candidate_program {
text-wrap: auto;
margin: 5px 0; margin: 5px 0;
} }
} }
@ -228,7 +244,7 @@ $min_col_width: 100px;
right: $gap; right: $gap;
gap: $gap; gap: $gap;
> a { >a {
width: 20px; width: 20px;
height: 20px; height: 20px;
background-color: #e9e9e9; background-color: #e9e9e9;
@ -253,40 +269,44 @@ $min_col_width: 100px;
} }
} }
.election_details { #content {
margin: .5em 0;
}
.buttons { .election_details {
display: flex; margin: .5em 0;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: $gap;
}
.button {
border: none;
color: black;
text-decoration: none;
background-color: $primary-neutral-light-color;
padding: 0.4em;
margin: 0.1em;
font-size: 1.18em;
border-radius: 5px;
box-shadow: #dfdfdf 0 0 1px;
cursor: pointer;
&:hover {
color: black;
background: #d4d4d4;
} }
&_send { .buttons {
background-color: #59aee2; display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: $gap;
}
.button {
border: none;
color: black;
text-decoration: none;
background-color: $primary-neutral-light-color;
padding: 0.4em;
margin: 0.1em;
font-size: 1.18em;
border-radius: 5px;
box-shadow: #dfdfdf 0 0 1px;
cursor: pointer;
&:hover { &:hover {
background-color: rgb(130, 186, 235); color: black;
background: #d4d4d4;
}
&_send {
background-color: #59aee2;
&:hover {
background-color: rgb(130, 186, 235);
}
} }
} }
} }

View File

@ -4,13 +4,12 @@
{{ object.title }} {{ object.title }}
{% endblock %} {% endblock %}
{% block head %} {% block additional_js %}
{{ super() -}} <script type="module" src="{{ static('bundled/core/read-more-index.ts') }}"></script>
<link rel="stylesheet" href="{{ static('election/css/election.scss') }}"> {% endblock %}
{%- endblock %}
{% block additional_css %} {% block additional_css %}
<script src="{{ static('bundled/vendored/jquery.shorten.min.js') }}"></script> <link rel="stylesheet" href="{{ static('election/css/election.scss') }}">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -47,7 +46,6 @@
{% csrf_token %} {% csrf_token %}
<table class="election_table"> <table class="election_table">
{%- set election_lists = election.election_lists.all() -%} {%- set election_lists = election.election_lists.all() -%}
<caption></caption>
<thead class="lists"> <thead class="lists">
<tr> <tr>
<th class="column" style="width: {{ 100 / (election_lists.count() + 1) }}%">{% trans %}Blank vote{% endtrans %}</th> <th class="column" style="width: {{ 100 / (election_lists.count() + 1) }}%">{% trans %}Blank vote{% endtrans %}</th>
@ -70,7 +68,7 @@
<td class="role_title"> <td class="role_title">
<div class="role_text"> <div class="role_text">
<h4>{{ role.title }}</h4> <h4>{{ role.title }}</h4>
<p class="role_description">{{ role.description }}</p> <p class="role_description" show-more="300">{{ role.description }}</p>
{%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %} {%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %}
<strong>{% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %}</strong> <strong>{% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %}</strong>
{%- endif %} {%- endif %}
@ -141,7 +139,9 @@
<figcaption class="candidate__details"> <figcaption class="candidate__details">
<h5>{{ candidature.user.first_name }} <em>{{candidature.user.nick_name or ''}} </em>{{ candidature.user.last_name }}</h5> <h5>{{ candidature.user.first_name }} <em>{{candidature.user.nick_name or ''}} </em>{{ candidature.user.last_name }}</h5>
{%- if not election.is_vote_finished %} {%- if not election.is_vote_finished %}
<q class="candidate_program">{{ candidature.program | markdown or '' }}</q> <q class="candidate_program" show-more="200">
{{ candidature.program|markdown or '' }}
</q>
{%- endif %} {%- endif %}
</figcaption> </figcaption>
{%- if user.can_edit(candidature) -%} {%- if user.can_edit(candidature) -%}
@ -200,18 +200,6 @@
{% block script %} {% block script %}
{{ super() }} {{ super() }}
<script type="text/javascript">
$('.role_description').shorten({
moreText: "{% trans %}Show more{% endtrans %}",
lessText: "{% trans %}Show less{% endtrans %}",
showChars: 50
});
$('.candidate_program').shorten({
moreText: "{% trans %}Show more{% endtrans %}",
lessText: "{% trans %}Show less{% endtrans %}",
showChars: 200
});
</script>
<script type="text/javascript"> <script type="text/javascript">
document.querySelectorAll('.role__multiple-choices').forEach(setupRestrictions); document.querySelectorAll('.role__multiple-choices').forEach(setupRestrictions);

View File

@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-05 16:39+0100\n" "POT-Creation-Date: 2025-01-08 12:23+0100\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -935,6 +935,10 @@ msgstr "rôle"
msgid "description" msgid "description"
msgstr "description" msgstr "description"
#: club/models.py
msgid "past member"
msgstr "ancien membre"
#: club/models.py #: club/models.py
msgid "Email address" msgid "Email address"
msgstr "Adresse email" msgstr "Adresse email"
@ -2062,16 +2066,12 @@ msgid "reason"
msgstr "raison" msgstr "raison"
#: core/models.py #: core/models.py
#, fuzzy
#| msgid "user"
msgid "user ban" msgid "user ban"
msgstr "utilisateur" msgstr "utilisateur banni"
#: core/models.py #: core/models.py
#, fuzzy
#| msgid "user"
msgid "user bans" msgid "user bans"
msgstr "utilisateur" msgstr "utilisateurs bannis"
#: core/models.py #: core/models.py
msgid "receive the Weekmail" msgid "receive the Weekmail"
@ -3328,8 +3328,8 @@ msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
#: core/views/forms.py #: core/views/forms.py
msgid "" msgid ""
"Profile: you need to be visible on the picture, in order to be recognized (e." "Profile: you need to be visible on the picture, in order to be recognized "
"g. by the barmen)" "(e.g. by the barmen)"
msgstr "" msgstr ""
"Photo de profil: vous devez être visible sur la photo afin d'être reconnu " "Photo de profil: vous devez être visible sur la photo afin d'être reconnu "
"(par exemple par les barmen)" "(par exemple par les barmen)"
@ -3935,8 +3935,8 @@ msgstr ""
#: counter/templates/counter/mails/account_dump.jinja #: counter/templates/counter/mails/account_dump.jinja
msgid "If you think this was a mistake, please mail us at ae@utbm.fr." msgid "If you think this was a mistake, please mail us at ae@utbm.fr."
msgstr "" msgstr ""
"Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un mail à ae@utbm." "Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un mail à "
"fr." "ae@utbm.fr."
#: counter/templates/counter/mails/account_dump.jinja #: counter/templates/counter/mails/account_dump.jinja
msgid "" msgid ""
@ -4456,14 +4456,6 @@ msgstr "Ajouter un nouveau rôle"
msgid "Submit the vote !" msgid "Submit the vote !"
msgstr "Envoyer le vote !" msgstr "Envoyer le vote !"
#: election/templates/election/election_detail.jinja
msgid "Show more"
msgstr "Montrer plus"
#: election/templates/election/election_detail.jinja
msgid "Show less"
msgstr "Montrer moins"
#: election/templates/election/election_list.jinja #: election/templates/election/election_list.jinja
msgid "Election list" msgid "Election list"
msgstr "Liste des élections" msgstr "Liste des élections"

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-04 23:07+0100\n" "POT-Creation-Date: 2025-01-08 12:23+0100\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n" "PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n" "Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@ -113,6 +113,14 @@ msgstr "Guide markdown"
msgid "Unsupported NFC card" msgid "Unsupported NFC card"
msgstr "Carte NFC non supportée" msgstr "Carte NFC non supportée"
#: core/static/bundled/core/read-more-index.ts
msgid "Show less"
msgstr "Montrer moins"
#: core/static/bundled/core/read-more-index.ts
msgid "Show more"
msgstr "Montrer plus"
#: core/static/bundled/user/family-graph-index.js #: core/static/bundled/user/family-graph-index.js
msgid "family_tree.%(extension)s" msgid "family_tree.%(extension)s"
msgstr "arbre_genealogique.%(extension)s" msgstr "arbre_genealogique.%(extension)s"

13
package-lock.json generated
View File

@ -10,6 +10,7 @@
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"dependencies": { "dependencies": {
"@alpinejs/sort": "^3.14.7", "@alpinejs/sort": "^3.14.7",
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@fullcalendar/core": "^6.1.15", "@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/daygrid": "^6.1.15",
@ -30,7 +31,6 @@
"htmx.org": "^2.0.3", "htmx.org": "^2.0.3",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"jquery-ui": "^1.14.0", "jquery-ui": "^1.14.0",
"jquery.shorten": "^1.0.0",
"native-file-system-adapter": "^3.0.1", "native-file-system-adapter": "^3.0.1",
"three": "^0.169.0", "three": "^0.169.0",
"three-spritetext": "^1.9.0", "three-spritetext": "^1.9.0",
@ -85,6 +85,12 @@
"url": "https://github.com/sponsors/philsturgeon" "url": "https://github.com/sponsors/philsturgeon"
} }
}, },
"node_modules/@arendjr/text-clipper": {
"name": "@jsr/arendjr__text-clipper",
"version": "3.0.0",
"resolved": "https://npm.jsr.io/~/11/@jsr/arendjr__text-clipper/3.0.0.tgz",
"integrity": "sha512-Uu3CYSvFrNdDkYKEaEKHAk0decaxVFlSSqf50Okte/9vJjO2rESzPF1ngQjS9H1aX45RIXRGMYOXJ/LPDFwUdQ=="
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
@ -4392,11 +4398,6 @@
"jquery": ">=1.12.0 <5.0.0" "jquery": ">=1.12.0 <5.0.0"
} }
}, },
"node_modules/jquery.shorten": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jquery.shorten/-/jquery.shorten-1.0.0.tgz",
"integrity": "sha512-49rJlpcyVI/Y2eQRwSexyz6l+fwTKfurO0XttXK4XnG9eQxIuE2Fb4rwNqnsnzStJ8M7ynlhH31fWE9P70B9rg=="
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View File

@ -36,6 +36,7 @@
}, },
"dependencies": { "dependencies": {
"@alpinejs/sort": "^3.14.7", "@alpinejs/sort": "^3.14.7",
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@fullcalendar/core": "^6.1.15", "@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15", "@fullcalendar/daygrid": "^6.1.15",
@ -56,7 +57,6 @@
"htmx.org": "^2.0.3", "htmx.org": "^2.0.3",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"jquery-ui": "^1.14.0", "jquery-ui": "^1.14.0",
"jquery.shorten": "^1.0.0",
"native-file-system-adapter": "^3.0.1", "native-file-system-adapter": "^3.0.1",
"three": "^0.169.0", "three": "^0.169.0",
"three-spritetext": "^1.9.0", "three-spritetext": "^1.9.0",

View File

@ -44,6 +44,8 @@ dependencies = [
"django-honeypot<2.0.0,>=1.2.1", "django-honeypot<2.0.0,>=1.2.1",
"pydantic-extra-types<3.0.0,>=2.10.1", "pydantic-extra-types<3.0.0,>=2.10.1",
"ical<9.0.0,>=8.3.0", "ical<9.0.0,>=8.3.0",
"redis[hiredis]<6.0.0,>=5.2.0",
"environs[django]<15.0.0,>=14.1.0",
] ]
[project.urls] [project.urls]
@ -53,7 +55,6 @@ documentation = "https://sith-ae.readthedocs.io/"
[dependency-groups] [dependency-groups]
prod = [ prod = [
"psycopg[c]<4.0.0,>=3.2.3", "psycopg[c]<4.0.0,>=3.2.3",
"redis[hiredis]<6.0.0,>=5.2.0",
] ]
dev = [ dev = [
"django-debug-toolbar<5.0.0,>=4.4.6", "django-debug-toolbar<5.0.0,>=4.4.6",

View File

@ -7,7 +7,7 @@
{% block content %} {% block content %}
{% if user.has_perm("core:add_userban") %} {% if user.has_perm("core.add_userban") %}
<a href="{{ url("rootplace:ban_create") }}" class="btn btn-red margin-bottom"> <a href="{{ url("rootplace:ban_create") }}" class="btn btn-red margin-bottom">
<i class="fa fa-person-circle-xmark"></i> <i class="fa fa-person-circle-xmark"></i>
{% trans %}Ban a user{% endtrans %} {% trans %}Ban a user{% endtrans %}
@ -44,7 +44,7 @@
<summary class="clickable">{% trans %}Reason{% endtrans %}</summary> <summary class="clickable">{% trans %}Reason{% endtrans %}</summary>
<p>{{ user_ban.reason }}</p> <p>{{ user_ban.reason }}</p>
</details> </details>
{% if user.has_perm("core:delete_userban") %} {% if user.has_perm("core.delete_userban") %}
<span> <span>
<a <a
href="{{ url("rootplace:ban_remove", ban_id=user_ban.id) }}" href="{{ url("rootplace:ban_remove", ban_id=user_ban.id) }}"

View File

@ -34,7 +34,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
""" """
import binascii import binascii
import logging
import os import os
import sys import sys
from datetime import timedelta from datetime import timedelta
@ -43,25 +42,32 @@ from pathlib import Path
import sentry_sdk import sentry_sdk
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from environs import Env
from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django import DjangoIntegration
from .honeypot import custom_honeypot_error from .honeypot import custom_honeypot_error
BASE_DIR = Path(__file__).parent.parent.resolve() env = Env()
env.read_env()
os.environ["HTTPS"] = "off" BASE_DIR = Path(__file__).parent.parent.resolve()
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" SECRET_KEY = env.str("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = env.bool("DEBUG", default=False)
TESTING = "pytest" in sys.modules TESTING = "pytest" in sys.modules
INTERNAL_IPS = ["127.0.0.1"] INTERNAL_IPS = ["127.0.0.1"]
# force csrf tokens and cookies to be secure when in https
CSRF_COOKIE_SECURE = env.bool("HTTPS", default=True)
SESSION_COOKIE_SECURE = env.bool("HTTPS", default=True)
X_FRAME_OPTIONS = "SAMEORIGIN"
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
# Application definition # Application definition
@ -208,12 +214,12 @@ WSGI_APPLICATION = "sith.wsgi.application"
# Database # Database
DATABASES = { DATABASES = {
"default": { "default": env.dj_db_url("DATABASE_URL", conn_max_age=None, conn_health_checks=True)
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
},
} }
if "CACHE_URL" in os.environ:
CACHES = {"default": env.dj_cache_url("CACHE_URL")}
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
# Logging # Logging
@ -265,13 +271,13 @@ PHONENUMBER_DEFAULT_REGION = "FR"
# Medias # Medias
MEDIA_URL = "/data/" MEDIA_URL = "/data/"
MEDIA_ROOT = BASE_DIR / "data" MEDIA_ROOT = env.path("MEDIA_ROOT", default=BASE_DIR / "data")
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/ # https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = "/static/" STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "static" STATIC_ROOT = env.path("STATIC_ROOT", default=BASE_DIR / "static")
# Static files finders which allow to see static folder in all apps # Static files finders which allow to see static folder in all apps
STATICFILES_FINDERS = [ STATICFILES_FINDERS = [
@ -295,24 +301,28 @@ AUTHENTICATION_BACKENDS = ["core.auth_backends.SithModelBackend"]
LOGIN_URL = "/login" LOGIN_URL = "/login"
LOGOUT_URL = "/logout" LOGOUT_URL = "/logout"
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
DEFAULT_FROM_EMAIL = "bibou@git.an" DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", default="bibou@git.an")
SITH_COM_EMAIL = "bibou_com@git.an" SITH_COM_EMAIL = env.str("SITH_COM_EMAIL", default="bibou_com@git.an")
# Those values are to be changed in production to be more effective # Those values are to be changed in production to be more effective
HONEYPOT_FIELD_NAME = "body2" HONEYPOT_FIELD_NAME = env.str("HONEYPOT_FIELD_NAME", default="body2")
HONEYPOT_VALUE = "content" HONEYPOT_VALUE = env.str("HONEYPOT_VALUE", default="content")
HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious
HONEYPOT_FIELD_NAME_FORUM = "message2" # Only used on forum HONEYPOT_FIELD_NAME_FORUM = env.str(
"HONEYPOT_FIELD_NAME_FORUM", default="message2"
) # Only used on forum
# Email # Email
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = env.str(
EMAIL_HOST = "localhost" "EMAIL_BACKEND", default="django.core.mail.backends.dummy.EmailBackend"
EMAIL_PORT = 25 )
EMAIL_HOST = env.str("EMAIL_HOST", default="localhost")
EMAIL_PORT = env.int("EMAIL_PORT", default=25)
# Below this line, only Sith-specific variables are defined # Below this line, only Sith-specific variables are defined
SITH_URL = "my.url.git.an" SITH_URL = env.str("SITH_URL", default="127.0.0.1:8000")
SITH_NAME = "Sith website" SITH_NAME = env.str("SITH_NAME", default="AE UTBM")
SITH_TWITTER = "@ae_utbm" SITH_TWITTER = "@ae_utbm"
# Enable experimental features # Enable experimental features
@ -321,7 +331,7 @@ SITH_ENABLE_GALAXY = False
# AE configuration # AE configuration
# TODO: keep only that first setting, with the ID, and do the same for the other clubs # TODO: keep only that first setting, with the ID, and do the same for the other clubs
SITH_MAIN_CLUB_ID = 1 SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1)
SITH_MAIN_CLUB = { SITH_MAIN_CLUB = {
"name": "AE", "name": "AE",
"unix_name": "ae", "unix_name": "ae",
@ -356,26 +366,28 @@ SITH_SCHOOL_START_YEAR = 1999
# id of the Root account # id of the Root account
SITH_ROOT_USER_ID = 0 SITH_ROOT_USER_ID = 0
SITH_GROUP_ROOT_ID = 1 SITH_GROUP_ROOT_ID = env.int("SITH_GROUP_ROOT_ID", default=1)
SITH_GROUP_PUBLIC_ID = 2 SITH_GROUP_PUBLIC_ID = env.int("SITH_GROUP_PUBLIC_ID", default=2)
SITH_GROUP_SUBSCRIBERS_ID = 3 SITH_GROUP_SUBSCRIBERS_ID = env.int("SITH_GROUP_SUBSCRIBERS_ID", default=3)
SITH_GROUP_OLD_SUBSCRIBERS_ID = 4 SITH_GROUP_OLD_SUBSCRIBERS_ID = env.int("SITH_GROUP_OLD_SUBSCRIBERS_ID", default=4)
SITH_GROUP_ACCOUNTING_ADMIN_ID = 5 SITH_GROUP_ACCOUNTING_ADMIN_ID = env.int("SITH_GROUP_ACCOUNTING_ADMIN_ID", default=5)
SITH_GROUP_COM_ADMIN_ID = 6 SITH_GROUP_COM_ADMIN_ID = env.int("SITH_GROUP_COM_ADMIN_ID", default=6)
SITH_GROUP_COUNTER_ADMIN_ID = 7 SITH_GROUP_COUNTER_ADMIN_ID = env.int("SITH_GROUP_COUNTER_ADMIN_ID", default=7)
SITH_GROUP_SAS_ADMIN_ID = 8 SITH_GROUP_SAS_ADMIN_ID = env.int("SITH_GROUP_SAS_ADMIN_ID", default=8)
SITH_GROUP_FORUM_ADMIN_ID = 9 SITH_GROUP_FORUM_ADMIN_ID = env.int("SITH_GROUP_FORUM_ADMIN_ID", default=9)
SITH_GROUP_PEDAGOGY_ADMIN_ID = 10 SITH_GROUP_PEDAGOGY_ADMIN_ID = env.int("SITH_GROUP_PEDAGOGY_ADMIN_ID", default=10)
SITH_GROUP_BANNED_ALCOHOL_ID = 11 SITH_GROUP_BANNED_ALCOHOL_ID = env.int("SITH_GROUP_BANNED_ALCOHOL_ID", default=11)
SITH_GROUP_BANNED_COUNTER_ID = 12 SITH_GROUP_BANNED_COUNTER_ID = env.int("SITH_GROUP_BANNED_COUNTER_ID", default=12)
SITH_GROUP_BANNED_SUBSCRIPTION_ID = 13 SITH_GROUP_BANNED_SUBSCRIPTION_ID = env.int(
"SITH_GROUP_BANNED_SUBSCRIPTION_ID", default=13
)
SITH_CLUB_REFOUND_ID = 89 SITH_CLUB_REFOUND_ID = env.int("SITH_CLUB_REFOUND_ID", default=89)
SITH_COUNTER_REFOUND_ID = 38 SITH_COUNTER_REFOUND_ID = env.int("SITH_COUNTER_REFOUND_ID", default=38)
SITH_PRODUCT_REFOUND_ID = 5 SITH_PRODUCT_REFOUND_ID = env.int("SITH_PRODUCT_REFOUND_ID", default=5)
SITH_COUNTER_ACCOUNT_DUMP_ID = 39 SITH_COUNTER_ACCOUNT_DUMP_ID = env.int("SITH_COUNTER_ACCOUNT_DUMP_ID", default=39)
# Pages # Pages
SITH_CORE_PAGE_SYNTAX = "Aide_sur_la_syntaxe" SITH_CORE_PAGE_SYNTAX = "Aide_sur_la_syntaxe"
@ -385,7 +397,7 @@ SITH_CORE_PAGE_SYNTAX = "Aide_sur_la_syntaxe"
SITH_FORUM_PAGE_LENGTH = 30 SITH_FORUM_PAGE_LENGTH = 30
# SAS variables # SAS variables
SITH_SAS_ROOT_DIR_ID = 4 SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4)
SITH_SAS_IMAGES_PER_PAGE = 60 SITH_SAS_IMAGES_PER_PAGE = 60
SITH_BOARD_SUFFIX = "-bureau" SITH_BOARD_SUFFIX = "-bureau"
@ -492,9 +504,9 @@ SITH_LOG_OPERATION_TYPE = [
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide" SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
SITH_ECOCUP_CONS = 1152 SITH_ECOCUP_CONS = env.int("SITH_ECOCUP_CONS", default=1151)
SITH_ECOCUP_DECO = 1151 SITH_ECOCUP_DECO = env.int("SITH_ECOCUP_DECO", default=1152)
# The limit is the maximum difference between cons and deco possible for a customer # The limit is the maximum difference between cons and deco possible for a customer
SITH_ECOCUP_LIMIT = 3 SITH_ECOCUP_LIMIT = 3
@ -509,21 +521,31 @@ SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
# Defines which product type is the refilling type, # Defines which product type is the refilling type,
# and thus increases the account amount # and thus increases the account amount
SITH_COUNTER_PRODUCTTYPE_REFILLING = 3 SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int(
"SITH_COUNTER_PRODUCTTYPE_REFILLING", default=3
)
# Defines which product is the one year subscription # Defines which product is the one year subscription
# and which one is the six month subscription # and which one is the six month subscription
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1 SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = env.int(
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2 "SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER", default=1
SITH_PRODUCTTYPE_SUBSCRIPTION = 2 )
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = env.int(
"SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS", default=2
)
SITH_PRODUCTTYPE_SUBSCRIPTION = env.int("SITH_PRODUCTTYPE_SUBSCRIPTION", default=2)
# Defines which club lets its member the ability to make subscriptions # Defines which club lets its member the ability to make subscriptions
# Elements of this list are club's id # Elements of this list are club's id
SITH_CAN_CREATE_SUBSCRIPTIONS = [1] SITH_CAN_CREATE_SUBSCRIPTIONS = env.list(
"SITH_CAN_CREATE_SUBSCRIPTION_HISTORY", default=[1]
)
# Defines which clubs lets its members the ability to see users subscription history # Defines which clubs lets its members the ability to see users subscription history
# Elements of this list are club's id # Elements of this list are club's id
SITH_CAN_READ_SUBSCRIPTION_HISTORY = [] SITH_CAN_READ_SUBSCRIPTION_HISTORY = env.list(
"SITH_CAN_READ_SUBSCRIPTION_HISTORY", default=[1]
)
# Number of weeks before the end of a subscription when the subscriber can resubscribe # Number of weeks before the end of a subscription when the subscriber can resubscribe
SITH_SUBSCRIPTION_END = 10 SITH_SUBSCRIPTION_END = 10
@ -632,21 +654,29 @@ SITH_BARMAN_TIMEOUT = 30
SITH_LAST_OPERATIONS_LIMIT = 10 SITH_LAST_OPERATIONS_LIMIT = 10
# ET variables # ET variables
SITH_EBOUTIC_CB_ENABLED = True SITH_EBOUTIC_CB_ENABLED = env.bool("SITH_EBOUTIC_CB_ENABLED", default=True)
SITH_EBOUTIC_ET_URL = ( SITH_EBOUTIC_ET_URL = env.str(
"https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" "SITH_EBOUTIC_ET_URL",
default="https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi",
) )
SITH_EBOUTIC_PBX_SITE = "1999888" SITH_EBOUTIC_PBX_SITE = env.str("SITH_EBOUTIC_PBX_SITE", default="1999888")
SITH_EBOUTIC_PBX_RANG = "32" SITH_EBOUTIC_PBX_RANG = env.str("SITH_EBOUTIC_PBX_RANG", default="32")
SITH_EBOUTIC_PBX_IDENTIFIANT = "2" SITH_EBOUTIC_PBX_IDENTIFIANT = env.str("SITH_EBOUTIC_PBX_IDENTIFIANT", default="2")
SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify( SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify(
"0123456789ABCDEF0123456789ABCDEF" env.str(
"0123456789ABCDEF0123456789ABCDEF" "SITH_EBOUTIC_HMAC_KEY",
"0123456789ABCDEF0123456789ABCDEF" default=(
"0123456789ABCDEF0123456789ABCDEF" "0123456789ABCDEF0123456789ABCDEF"
"0123456789ABCDEF0123456789ABCDEF"
"0123456789ABCDEF0123456789ABCDEF"
"0123456789ABCDEF0123456789ABCDEF"
),
)
) )
SITH_EBOUTIC_PUB_KEY = "" SITH_EBOUTIC_PUB_KEY = ""
with open(os.path.join(os.path.dirname(__file__), "et_keys/pubkey.pem")) as f: with open(
env.path("SITH_EBOUTIC_PUB_KEY_PATH", default=BASE_DIR / "sith/et_keys/pubkey.pem")
) as f:
SITH_EBOUTIC_PUB_KEY = f.read() SITH_EBOUTIC_PUB_KEY = f.read()
# Launderette variables # Launderette variables
@ -688,24 +718,17 @@ SITH_QUICK_NOTIF = {
# Mailing related settings # Mailing related settings
SITH_MAILING_DOMAIN = "utbm.fr" SITH_MAILING_DOMAIN = "utbm.fr"
SITH_MAILING_FETCH_KEY = "IloveMails" SITH_MAILING_FETCH_KEY = env.str("SITH_MAILING_FETCH_KEY", default="ILoveMails")
SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))] SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))]
SENTRY_DSN = "" SENTRY_DSN = env.str("SENRY_DSN", default=None)
SENTRY_ENV = "production" SENTRY_ENV = env.str("SENTRY_ENV", default="production")
TOXIC_DOMAINS_PROVIDERS = [ TOXIC_DOMAINS_PROVIDERS = [
"https://www.stopforumspam.com/downloads/toxic_domains_whole.txt", "https://www.stopforumspam.com/downloads/toxic_domains_whole.txt",
] ]
try:
from .settings_custom import * # noqa F403 (this star-import is actually useful)
logging.getLogger("django").info("Custom settings imported")
except ImportError:
logging.getLogger("django").warning("Custom settings failed")
if DEBUG: if DEBUG:
INSTALLED_APPS += ("debug_toolbar",) INSTALLED_APPS += ("debug_toolbar",)
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE) MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)

View File

@ -81,7 +81,7 @@ def sentry_debug(request):
The error will be displayed on Sentry The error will be displayed on Sentry
inside the "development" environment inside the "development" environment
NOTE : you need to specify the SENTRY_DSN setting in settings_custom.py NOTE : you need to specify the SENTRY_DSN setting in .env
""" """
if settings.SENTRY_ENV != "development" or not settings.SENTRY_DSN: if settings.SENTRY_ENV != "development" or not settings.SENTRY_DSN:
raise Http404 raise Http404

86
uv.lock generated
View File

@ -155,7 +155,7 @@ name = "click"
version = "8.1.8" version = "8.1.8"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" }, { name = "colorama", marker = "platform_system == 'Windows'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
wheels = [ wheels = [
@ -276,6 +276,28 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 },
] ]
[[package]]
name = "dj-database-url"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "django" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/98/9f/fc9905758256af4f68a55da94ab78a13e7775074edfdcaddd757d4921686/dj_database_url-2.3.0.tar.gz", hash = "sha256:ae52e8e634186b57e5a45e445da5dc407a819c2ceed8a53d1fac004cc5288787", size = 10980 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/91/641a4e5c8903ed59f6cbcce571003bba9c5d2f731759c31db0ba83bb0bdb/dj_database_url-2.3.0-py3-none-any.whl", hash = "sha256:bb0d414ba0ac5cd62773ec7f86f8cc378a9dbb00a80884c2fc08cc570452521e", size = 7793 },
]
[[package]]
name = "dj-email-url"
version = "1.0.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/ef/8eb478accd9b0369d46a98d1b43027ee0c254096149265c78e6b2e2fa3b0/dj-email-url-1.0.6.tar.gz", hash = "sha256:55ffe3329e48f54f8a75aa36ece08f365e09d61f8a209773ef09a1d4760e699a", size = 15590 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/f9/fcb9745099d821f9a26092d3d6f4df8f10049885045c3a93ff726d2e40a6/dj_email_url-1.0.6-py2.py3-none-any.whl", hash = "sha256:cbd08327fbb08b104eac160fb4703f375532e4c0243eb230f5b960daee7a96db", size = 6296 },
]
[[package]] [[package]]
name = "django" name = "django"
version = "4.2.17" version = "4.2.17"
@ -290,6 +312,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/85/457360cb3de496382e35db4c2af054066df5c40e26df31400d0109a0500c/Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0", size = 7993390 }, { url = "https://files.pythonhosted.org/packages/5e/85/457360cb3de496382e35db4c2af054066df5c40e26df31400d0109a0500c/Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0", size = 7993390 },
] ]
[[package]]
name = "django-cache-url"
version = "3.4.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/28/d420aaa89028d2ec0cf17c1510d06ff6a8ed0bf1abfb7f33c999e1c5befa/django-cache-url-3.4.5.tar.gz", hash = "sha256:eb9fb194717524348c95cad9905b70b647452741c1d9e481fac6d2125f0ad917", size = 7230 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/48/90/01755e4a42558b763f7021e9369aa6aa94c2ede7313deed56cb7483834ab/django_cache_url-3.4.5-py2.py3-none-any.whl", hash = "sha256:5f350759978483ab85dc0e3e17b3d53eed3394a28148f6bf0f53d11d0feb5b3c", size = 4760 },
]
[[package]] [[package]]
name = "django-countries" name = "django-countries"
version = "7.6.1" version = "7.6.1"
@ -439,6 +470,26 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472 }, { url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472 },
] ]
[[package]]
name = "environs"
version = "14.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "marshmallow" },
{ name = "python-dotenv" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3c/8f/952bd034eac79c8b68b6c770cb78c2bdcb3140d31ff224847f3520077d75/environs-14.1.0.tar.gz", hash = "sha256:a5f2afe9d5a21b468e74a3cceacf5d2371fd67dbb9a7e54fe62290c75a09cdfa", size = 30985 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/ad/57cfa3e8a006df88e723524127dbab2971a4877c97e7bad070257e15cb6c/environs-14.1.0-py3-none-any.whl", hash = "sha256:a7edda1668ddf1fbfcb7662bdc242dac25648eff2c7fdbaa5d959693afed7a3e", size = 15332 },
]
[package.optional-dependencies]
django = [
{ name = "dj-database-url" },
{ name = "dj-email-url" },
{ name = "django-cache-url" },
]
[[package]] [[package]]
name = "executing" name = "executing"
version = "2.1.0" version = "2.1.0"
@ -708,6 +759,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
] ]
[[package]]
name = "marshmallow"
version = "3.25.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bd/5c/cbfa41491d6c83b36471f2a2f75602349d20a8f88afd94f83c1e68bbc298/marshmallow-3.25.0.tar.gz", hash = "sha256:5ba94a4eb68894ad6761a505eb225daf7e5cb7b4c32af62d4a45e9d42665bc31", size = 176751 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/26/b347619b719d4c048e038929769f8f6b28c6d930149b40d950bbdde31d48/marshmallow-3.25.0-py3-none-any.whl", hash = "sha256:50894cd57c6b097a6c6ed2bf216af47d10146990a54db52d03e32edb0448c905", size = 49480 },
]
[[package]] [[package]]
name = "matplotlib-inline" name = "matplotlib-inline"
version = "0.1.7" version = "0.1.7"
@ -744,7 +807,7 @@ version = "1.6.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
{ name = "colorama", marker = "sys_platform == 'win32'" }, { name = "colorama", marker = "platform_system == 'Windows'" },
{ name = "ghp-import" }, { name = "ghp-import" },
{ name = "jinja2" }, { name = "jinja2" },
{ name = "markdown" }, { name = "markdown" },
@ -1229,6 +1292,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
] ]
[[package]]
name = "python-dotenv"
version = "1.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
]
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0.2" version = "6.0.2"
@ -1428,6 +1500,7 @@ dependencies = [
{ name = "django-ordered-model" }, { name = "django-ordered-model" },
{ name = "django-phonenumber-field" }, { name = "django-phonenumber-field" },
{ name = "django-simple-captcha" }, { name = "django-simple-captcha" },
{ name = "environs", extra = ["django"] },
{ name = "ical" }, { name = "ical" },
{ name = "jinja2" }, { name = "jinja2" },
{ name = "libsass" }, { name = "libsass" },
@ -1436,6 +1509,7 @@ dependencies = [
{ name = "pillow" }, { name = "pillow" },
{ name = "pydantic-extra-types" }, { name = "pydantic-extra-types" },
{ name = "python-dateutil" }, { name = "python-dateutil" },
{ name = "redis", extra = ["hiredis"] },
{ name = "reportlab" }, { name = "reportlab" },
{ name = "sentry-sdk" }, { name = "sentry-sdk" },
{ name = "sphinx" }, { name = "sphinx" },
@ -1462,7 +1536,6 @@ docs = [
] ]
prod = [ prod = [
{ name = "psycopg", extra = ["c"] }, { name = "psycopg", extra = ["c"] },
{ name = "redis", extra = ["hiredis"] },
] ]
tests = [ tests = [
{ name = "freezegun" }, { name = "freezegun" },
@ -1486,6 +1559,7 @@ requires-dist = [
{ name = "django-ordered-model", specifier = ">=3.7.4,<4.0.0" }, { name = "django-ordered-model", specifier = ">=3.7.4,<4.0.0" },
{ name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" }, { name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" },
{ name = "django-simple-captcha", specifier = ">=0.6.0,<1.0.0" }, { name = "django-simple-captcha", specifier = ">=0.6.0,<1.0.0" },
{ name = "environs", extras = ["django"], specifier = ">=14.1.0,<15.0.0" },
{ name = "ical", specifier = ">=8.3.0,<9.0.0" }, { name = "ical", specifier = ">=8.3.0,<9.0.0" },
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" }, { name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
{ name = "libsass", specifier = ">=0.23.0,<1.0.0" }, { name = "libsass", specifier = ">=0.23.0,<1.0.0" },
@ -1494,6 +1568,7 @@ requires-dist = [
{ name = "pillow", specifier = ">=11.0.0,<12.0.0" }, { name = "pillow", specifier = ">=11.0.0,<12.0.0" },
{ name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0.0" }, { name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0.0" },
{ name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" }, { name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" },
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" },
{ name = "reportlab", specifier = ">=4.2.5,<5.0.0" }, { name = "reportlab", specifier = ">=4.2.5,<5.0.0" },
{ name = "sentry-sdk", specifier = ">=2.19.2,<3.0.0" }, { name = "sentry-sdk", specifier = ">=2.19.2,<3.0.0" },
{ name = "sphinx", specifier = ">=5,<6" }, { name = "sphinx", specifier = ">=5,<6" },
@ -1518,10 +1593,7 @@ docs = [
{ name = "mkdocstrings", specifier = ">=0.27.0,<1.0.0" }, { name = "mkdocstrings", specifier = ">=0.27.0,<1.0.0" },
{ name = "mkdocstrings-python", specifier = ">=1.12.2,<2.0.0" }, { name = "mkdocstrings-python", specifier = ">=1.12.2,<2.0.0" },
] ]
prod = [ prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" }]
{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" },
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" },
]
tests = [ tests = [
{ name = "freezegun", specifier = ">=1.5.1,<2.0.0" }, { name = "freezegun", specifier = ">=1.5.1,<2.0.0" },
{ name = "model-bakery", specifier = ">=1.20.0,<2.0.0" }, { name = "model-bakery", specifier = ">=1.20.0,<2.0.0" },

View File

@ -97,10 +97,6 @@ export default defineConfig((config: UserConfig) => {
src: resolve(nodeModules, "jquery-ui/dist/jquery-ui.min.js"), src: resolve(nodeModules, "jquery-ui/dist/jquery-ui.min.js"),
dest: vendored, dest: vendored,
}, },
{
src: resolve(nodeModules, "jquery.shorten/src/jquery.shorten.min.js"),
dest: vendored,
},
], ],
}), }),
], ],