1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-21 18:08:21 +02:00

Compare commits

..

4 Commits

Author SHA1 Message Date
4a4e8769bc Merge branch 'summary_notes' into 'main'
treasury summary

See merge request bde/nk20!223
2025-03-10 20:03:49 +01:00
5a0fe7a6f0 Fr translation (treasury summary) 2024-08-07 20:01:22 +02:00
eda8460014 Inclusif, admin and migrations (treasury summary) 2024-08-07 19:24:23 +02:00
15c71ad31a treasury summary 2024-08-02 01:35:13 +02:00
36 changed files with 588 additions and 716 deletions

View File

@ -58,13 +58,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
(env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial
```
6. (Optionnel) **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son
emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`).
7. Enjoy :
6. Enjoy :
```bash
(env)$ ./manage.py runserver 0.0.0.0:8000
@ -234,13 +228,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
(env)$ ./manage.py check # pas de bêtise qui traine
(env)$ ./manage.py migrate
7. **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son
emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`).
8. *Enjoy \o/*
7. *Enjoy \o/*
### Installation avec Docker

View File

@ -35,7 +35,7 @@ class GuestAdmin(admin.ModelAdmin):
"""
Admin customisation for Guest
"""
list_display = ('last_name', 'first_name', 'school', 'activity', 'inviter')
list_display = ('last_name', 'first_name', 'activity', 'inviter')
form = GuestForm

View File

@ -51,9 +51,9 @@ class GuestViewSet(ReadProtectedModelViewSet):
queryset = Guest.objects.order_by('id')
serializer_class = GuestSerializer
filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'school', 'inviter', 'inviter__alias__name',
filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'inviter', 'inviter__alias__name',
'inviter__alias__normalized_name', ]
search_fields = ['$activity__name', '$last_name', '$first_name', '$school', '$inviter__user__email', '$inviter__alias__name',
search_fields = ['$activity__name', '$last_name', '$first_name', '$inviter__user__email', '$inviter__alias__name',
'$inviter__alias__normalized_name', ]

View File

@ -107,7 +107,7 @@ class GuestForm(forms.ModelForm):
class Meta:
model = Guest
fields = ('last_name', 'first_name', 'school', 'inviter', )
fields = ('last_name', 'first_name', 'inviter', )
widgets = {
"inviter": Autocomplete(
NoteUser,

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.20 on 2025-03-25 09:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("activity", "0005_alter_opener_options_alter_opener_opener"),
]
operations = [
migrations.AddField(
model_name="guest",
name="school",
field=models.CharField(default="", max_length=255, verbose_name="school"),
preserve_default=False,
),
]

View File

@ -247,11 +247,6 @@ class Guest(models.Model):
verbose_name=_("first name"),
)
school = models.CharField(
max_length=255,
verbose_name=_("school"),
)
inviter = models.ForeignKey(
NoteUser,
on_delete=models.PROTECT,

View File

@ -51,7 +51,7 @@ class GuestTable(tables.Table):
}
model = Guest
template_name = 'django_tables2/bootstrap4.html'
fields = ("last_name", "first_name", "inviter", "school")
fields = ("last_name", "first_name", "inviter", )
def render_entry(self, record):
if record.has_entry:

View File

@ -50,7 +50,6 @@ class TestActivities(TestCase):
inviter=self.user.note,
last_name="GUEST",
first_name="Guest",
school="School",
)
def test_activity_list(self):
@ -157,7 +156,6 @@ class TestActivities(TestCase):
inviter=self.user.note.id,
last_name="GUEST2",
first_name="Guest",
school="School",
))
self.assertEqual(response.status_code, 200)
@ -169,7 +167,6 @@ class TestActivities(TestCase):
inviter=self.user.note.id,
last_name="GUEST2",
first_name="Guest",
school="School",
))
self.assertRedirects(response, reverse("activity:activity_detail", args=(self.activity.pk,)), 302, 200)
@ -203,7 +200,6 @@ class TestActivityAPI(TestAPI):
inviter=self.user.note,
last_name="GUEST",
first_name="Guest",
school="School",
)
self.entry = Entry.objects.create(

View File

@ -168,7 +168,6 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
activity=activity,
first_name="",
last_name="",
school="",
inviter=self.request.user.note,
)

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2024-08-07 12:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0012_club_add_registration_form'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2024, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View File

@ -294,10 +294,3 @@ searchbar.addEventListener("keyup", function (e) {
if (firstMatch && e.key === "Enter")
firstMatch.click()
});
function createshiny() {
const list_btn = document.querySelectorAll('.btn-outline-dark')
const shiny_class = list_btn[Math.floor(Math.random() * list_btn.length)].classList
shiny_class.replace('btn-outline-dark', 'btn-outline-dark-shiny')
}
createshiny()

View File

@ -0,0 +1,65 @@
{% load pretty_money %}
{% load i18n %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>[Note Kfet] Récapitulatif de trésorerie</title>
</head>
<body>
<h1>
Récapitulatif de trésorerie au {{ summary.date|date:"d/m/Y" }} à {{ summary.date|date:"H:i:s" }} :
</h1>
<h2>
Tous les utilisateur⋅rices :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_user }}</li>
<li>Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user / 100 }} €</li>
</ul>
<h2>
Les {{ summary.total_positive_user_bde + summary.total_zero_user_bde + summary.total_negative_user_bde }} adhérent⋅es BDE :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_user_bde }}</li>
<li>Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde / 100 }} €</li>
</ul>
<h2>
Clubs :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_club }}</li>
<li>Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club / 100 }} €</li>
</ul>
<h2>
Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" :
</h2>
<ul>
<li>Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde / 100 }} €</li>
<li>Neutres : {{ summary.total_zero_club_nbde }}</li>
<li>Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde / 100 }} €</li>
</ul>
<h2>
Progression :
</h2>
<ul>
<li>Ceci correspond à une différence de {{ balance_difference_user / 100 }} € pour les utilisateur⋅rices</li>
<li>Ceci correspond à une différence de {{ balance_difference_club / 100 }} € pour les clubs</li>
</ul>
--
<p>
Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p>
</body>
</html>

View File

@ -0,0 +1,33 @@
{% load pretty_money %}
{% load i18n %}
Récapitulatif de trésorerie au {{ summary.date|date:"d/m/Y" }} à {{ summary.date|date:"H:i:s" }} :
Tous les utilisateur⋅rices :
- Positifs : {{ summary.total_positive_user }} soit {{ summary.balance_positive_user / 100 }} €
- Neutres : {{ summary.total_zero_user }}
- Négatifs : {{ summary.total_negative_user }} soit {{ summary.balance_negative_user / 100 }} €
Les {{ summary.total_positive_user_bde + summary.total_zero_user_bde + summary.total_negative_user_bde }} adhérent⋅es BDE :
- Positifs : {{ summary.total_positive_user_bde }} soit {{ summary.balance_positive_user_bde / 100 }} €
- Neutres : {{ summary.total_zero_user_bde }}
- Négatifs : {{ summary.total_negative_user_bde }} soit {{ summary.balance_negative_user_bde /100 }} €
Clubs :
- Positifs : {{ summary.total_positive_club }} soit {{ summary.balance_positive_club / 100 }} €
- Neutres : {{ summary.total_zero_club }}
- Négatifs : {{ summary.total_negative_club }} soit {{ summary.balance_negative_club / 100 }} €
Clubs hors BDE / Kfet et club dont le nom fini par "- BDE" :
- Positifs : {{ summary.total_positive_club_nbde }} soit {{ summary.balance_positive_club_nbde / 100 }} €
- Neutres : {{ summary.total_zero_club_nbde }}
- Négatifs : {{ summary.total_negative_club_nbde }} soit {{ summary.balance_negative_club_nbde / 100 }} €
Progression :
- Ceci correspond à une différence de {{ balance_difference_user / 100 }} € pour les utilisateur⋅rices
- Ceci correspond à une différence de {{ balance_difference_club / 100 }} € pour les clubs
--
Le BDE
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

View File

@ -324,7 +324,7 @@
"mask": 2,
"field": "",
"permanent": false,
"description": "Créer une transaction de ou vers la note d'un club tant que la source reste au dessus de -20 €"
"description": "Créer une transaction de ou vers la note d'un club"
}
},
{
@ -3815,7 +3815,7 @@
"mask": 2,
"field": "",
"permanent": false,
"description": "Créer une transaction vers la note d'un club tant que la source reste au dessus de -20 €"
"description": "Créer une transaction vers la note d'un club"
}
},
{
@ -4186,86 +4186,6 @@
"description": "Voir la note d'un club enfant"
}
},
{
"model": "permission.permission",
"pk": 266,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"OR\", {\"source_alias\": \"Carte bancaire\"}, {\"source_alias\": \"Espèces\"}, {\"source_alias\": \"Chèque\"}, {\"source_alias\": \"Virement bancaire\"}]",
"type": "view",
"mask": 2,
"field": "",
"permanent": false,
"description": "Voir les transactions de rechargement"
}
},
{
"model": "permission.permission",
"pk": 267,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"OR\", {\"source_alias\": \"Carte bancaire\"}, {\"source_alias\": \"Espèces\"}, {\"source_alias\": \"Chèque\"}, {\"source_alias\": \"Virement bancaire\"}]",
"type": "change",
"mask": 2,
"field": "valid",
"permanent": false,
"description": "Mettre à jour le statut de validation d'une transaction de rechargement"
}
},
{
"model": "permission.permission",
"pk": 268,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"OR\", {\"source_alias\": \"Carte bancaire\"}, {\"source_alias\": \"Espèces\"}, {\"source_alias\": \"Chèque\"}, {\"source_alias\": \"Virement bancaire\"}]",
"type": "change",
"mask": 2,
"field": "invalidity_reason",
"permanent": false,
"description": "Modifier la raison d'invalidité d'une transaction de rechargement"
}
},
{
"model": "permission.permission",
"pk": 269,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"OR\", {\"source_alias\": \"Carte bancaire\"}, {\"source_alias\": \"Espèces\"}, {\"source_alias\": \"Chèque\"}, {\"source_alias\": \"Virement bancaire\"}]",
"type": "add",
"mask": 2,
"field": "",
"permanent": false,
"description": "Créer une transaction de rechargement"
}
},
{
"model": "permission.permission",
"pk": 270,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}}, {\"valid\": false}]]",
"type": "add",
"mask": 2,
"field": "",
"permanent": false,
"description": "Créer une transaction de ou vers la note d'un club tant que la source reste au dessus de -50 €"
}
},
{
"model": "permission.role",
"pk": 1,

View File

@ -5,13 +5,13 @@ from django.contrib import admin
from note_kfet.admin import admin_site
from .forms import ProductForm
from .models import RemittanceType, Remittance, SogeCredit, Invoice, Product
from .models import Invoice, NoteSummary, Product, RemittanceType, Remittance, SogeCredit
@admin.register(RemittanceType, site=admin_site)
class RemittanceTypeAdmin(admin.ModelAdmin):
"""
Admin customisation for RemiitanceType
Admin customisation for RemittanceType
"""
list_display = ('note', )
@ -55,3 +55,19 @@ class InvoiceAdmin(admin.ModelAdmin):
"""
list_display = ('object', 'id', 'bde', 'name', 'date', 'acquitted',)
inlines = (ProductInline,)
@admin.register(NoteSummary, site=admin_site)
class NoteSummaryAdmin(admin.ModelAdmin):
"""
Admin customisation for NoteSummary
"""
list_display = (
'date', 'total_positive_user', 'balance_positive_user', 'total_positive_user_bde',
'balance_positive_user_bde', 'total_zero_user', 'total_zero_user_bde', 'total_negative_user',
'balance_negative_user', 'total_negative_user_bde', 'balance_negative_user_bde',
'total_vnegative_user', 'balance_vnegative_user', 'total_vnegative_user_bde',
'balance_vnegative_user_bde', 'total_positive_club', 'balance_positive_club',
'total_positive_club_nbde', 'balance_positive_club_nbde', 'total_zero_club', 'total_zero_club_nbde',
'total_negative_club', 'balance_negative_club', 'total_negative_club_nbde', 'balance_negative_club_nbde',
)

View File

@ -0,0 +1,49 @@
# Generated by Django 2.2.28 on 2024-08-07 12:09
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0008_auto_20240322_0045'),
]
operations = [
migrations.CreateModel(
name='NoteSummary',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(default=datetime.date.today, verbose_name='Date')),
('total_positive_user', models.PositiveIntegerField(verbose_name='Total positive user')),
('balance_positive_user', models.PositiveIntegerField(verbose_name='Balance positive user')),
('total_positive_user_bde', models.PositiveIntegerField(verbose_name='Total positive user BDE')),
('balance_positive_user_bde', models.PositiveIntegerField(verbose_name='Balance positive user BDE')),
('total_zero_user', models.PositiveIntegerField(verbose_name='Total zero user')),
('total_zero_user_bde', models.PositiveIntegerField(verbose_name='Total zero user BDE')),
('total_negative_user', models.PositiveIntegerField(verbose_name='Total negative user')),
('balance_negative_user', models.PositiveIntegerField(verbose_name='Balance negative user')),
('total_negative_user_bde', models.PositiveIntegerField(verbose_name='Total negative user BDE')),
('balance_negative_user_bde', models.PositiveIntegerField(verbose_name='Balance negative user BDE')),
('total_vnegative_user', models.PositiveIntegerField(verbose_name='Total very negative user')),
('balance_vnegative_user', models.PositiveIntegerField(verbose_name='Balance very negative user')),
('total_vnegative_user_bde', models.PositiveIntegerField(verbose_name='Total very negative user BDE')),
('balance_vnegative_user_bde', models.PositiveIntegerField(verbose_name='Balance very negative user BDE')),
('total_positive_club', models.PositiveIntegerField(verbose_name='Total positive club')),
('balance_positive_club', models.PositiveIntegerField(verbose_name='Balance positive club')),
('total_positive_club_nbde', models.PositiveIntegerField(verbose_name='Total positive club nbde')),
('balance_positive_club_nbde', models.PositiveIntegerField(verbose_name='Balance positive club nbde')),
('total_zero_club', models.PositiveIntegerField(verbose_name='Total zero club')),
('total_zero_club_nbde', models.PositiveIntegerField(verbose_name='Total zero club nbde')),
('total_negative_club', models.PositiveIntegerField(verbose_name='Total negative club')),
('balance_negative_club', models.PositiveIntegerField(verbose_name='Balance negative club')),
('total_negative_club_nbde', models.PositiveIntegerField(verbose_name='Total negative club nbde')),
('balance_negative_club_nbde', models.PositiveIntegerField(verbose_name='Balance negative club nbde')),
],
options={
'verbose_name': 'Summary',
'verbose_name_plural': 'Summaries',
},
),
]

View File

@ -460,3 +460,117 @@ class SogeCredit(models.Model):
self.credit_transaction._force_save = True
self.credit_transaction.save()
super().delete(**kwargs)
class NoteSummary(models.Model):
"""
Summary of every notes
"""
date = models.DateField(
default=date.today,
verbose_name=_("Date"),
)
total_positive_user = models.PositiveIntegerField(
verbose_name=_("Total positive user"),
)
balance_positive_user = models.PositiveIntegerField(
verbose_name=_("Balance positive user"),
)
total_positive_user_bde = models.PositiveIntegerField(
verbose_name=_("Total positive user BDE"),
)
balance_positive_user_bde = models.PositiveIntegerField(
verbose_name=_("Balance positive user BDE"),
)
total_zero_user = models.PositiveIntegerField(
verbose_name=_("Total zero user"),
)
total_zero_user_bde = models.PositiveIntegerField(
verbose_name=_("Total zero user BDE"),
)
total_negative_user = models.PositiveIntegerField(
verbose_name=_("Total negative user"),
)
balance_negative_user = models.PositiveIntegerField(
verbose_name=_("Balance negative user"),
)
total_negative_user_bde = models.PositiveIntegerField(
verbose_name=_("Total negative user BDE"),
)
balance_negative_user_bde = models.PositiveIntegerField(
verbose_name=_("Balance negative user BDE"),
)
total_vnegative_user = models.PositiveIntegerField(
verbose_name=_("Total very negative user"),
)
balance_vnegative_user = models.PositiveIntegerField(
verbose_name=_("Balance very negative user"),
)
total_vnegative_user_bde = models.PositiveIntegerField(
verbose_name=_("Total very negative user BDE"),
)
balance_vnegative_user_bde = models.PositiveIntegerField(
verbose_name=_("Balance very negative user BDE"),
)
total_positive_club = models.PositiveIntegerField(
verbose_name=_("Total positive club"),
)
balance_positive_club = models.PositiveIntegerField(
verbose_name=_("Balance positive club"),
)
total_positive_club_nbde = models.PositiveIntegerField(
verbose_name=_("Total positive club nbde"),
)
balance_positive_club_nbde = models.PositiveIntegerField(
verbose_name=_("Balance positive club nbde"),
)
total_zero_club = models.PositiveIntegerField(
verbose_name=_("Total zero club"),
)
total_zero_club_nbde = models.PositiveIntegerField(
verbose_name=_("Total zero club nbde"),
)
total_negative_club = models.PositiveIntegerField(
verbose_name=_("Total negative club"),
)
balance_negative_club = models.PositiveIntegerField(
verbose_name=_("Balance negative club"),
)
total_negative_club_nbde = models.PositiveIntegerField(
verbose_name=_("Total negative club nbde"),
)
balance_negative_club_nbde = models.PositiveIntegerField(
verbose_name=_("Balance negative club nbde"),
)
class Meta:
verbose_name = _("Summary")
verbose_name_plural = _("Summaries")
def __str__(self):
return "Note summary of {date}".format(date=self.date)

View File

@ -38,7 +38,7 @@ class Command(BaseCommand):
required=False,
help="""User will have their(s) wrapped generated,
all = all users
adh = all users who have a valid cd memberships to BDE during the BDE considered
adh = all users who have a valid memberships to BDE during the BDE considered
supersuser = all superusers
custom user1,user2,... = a list of username,
custom_id id1,id2,... = a list of user id""",
@ -70,7 +70,15 @@ class Command(BaseCommand):
dest='create',
)
def handle(self, *args, **options): # NOQA
def handle(self, *args, **options):
# useful string for output
red = '\033[31;1m'
yellow = '\033[33;1m'
green = '\033[32;1m'
abort = red + 'ABORT'
warning = yellow + 'WARNING'
success = green + 'SUCCESS'
# Traitement des paramètres
verb = options['verbosity']
bde = []
@ -81,11 +89,11 @@ class Command(BaseCommand):
if options['bde_id']:
if bde:
if verb >= 1:
self.stdout.write(self.style.WARNING(
"WARNING\nYou already defined bde with their name !"))
print(warning)
print(yellow + 'You already defined bde with their name !')
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
bde_id = options['bde_id'].split(',')
bde = [Bde.objects.get(pk=i) for i in bde_id]
@ -105,11 +113,11 @@ class Command(BaseCommand):
user = ['custom_id', [User.objects.get(pk=u) for u in user_id]]
else:
if verb >= 1:
self.sdtout.write(self.style.WARNING(
"WARNING\nYou user option is not recognized"))
print(warning)
print(yellow + 'You user option is not recognized')
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
club = []
if options['club']:
@ -125,11 +133,11 @@ class Command(BaseCommand):
club = ['custom_id', [Club.objects.get(pk=c) for c in club_id]]
else:
if verb >= 1:
self.stdout.write(self.style.WARNING(
"WARNING\nYou club option is not recognized"))
print(warning)
print(yellow + 'You club option is not recognized')
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
change = options['change']
create = options['create']
@ -137,75 +145,72 @@ class Command(BaseCommand):
# check if parameters are sufficient for generate wrapped with the desired option
if not bde:
if verb >= 1:
self.stdout.write(self.style.WARNING(
"WARNING\nYou have not selectionned a BDE !"))
print(warning)
print(yellow + 'You have not selectionned a BDE !')
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
if not (user or club):
if verb >= 1:
self.stdout.write(self.style.WARNING(
"WARNING\nNo club or user selected !"))
print(warning)
print(yellow + 'No club or user selected !')
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
if verb >= 3:
self.stdout.write("Options:")
print('\033[1mOptions:\033[m')
bde_str = ''
for b in bde:
bde_str += str(b) + '\n'
self.stdout.write("BDE: " + bde_str)
bde_str += str(b)
print('BDE: ' + bde_str)
if user:
self.stdout.write('User: ' + user[0])
print('User: ' + user[0])
if club:
self.stdout.write('Club: ' + club[0])
self.stdout.write('change: ' + str(change))
self.stdout.write('create: ' + str(create) + '\n')
print('Club: ' + club[0])
print('change: ' + str(change))
print('create: ' + str(create))
print('')
if not (change or create):
if verb >= 1:
self.stdout.write(self.style.WARNING(
"WARNING\nchange and create is set to false, none wrapped will be created"))
print(warning)
print(yellow + 'change and create is set to false, none wrapped will be created')
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
if verb >= 1 and change:
self.stdout.write(self.style.WARNING(
"WARNING\nchange is set to true, some wrapped may be replaced !"))
print(warning)
print(yellow + 'change is set to true, some wrapped may be replaced !')
if verb >= 1 and not create:
self.stdout.write(self.style.WARNING(
"WARNING\ncreate is set to false, wrapped will not be created !"))
print(warning)
print(yellow + 'create is set to false, wrapped will not be created !')
if verb >= 3 or change or not create:
a = str(input('\033[mContinue ? (y/n) ')).lower()
if a in ['n', 'no', 'non', '0']:
if verb >= 0:
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
print(abort)
return
note = self.convert_to_note(change, create, bde=bde, user=user, club=club, verb=verb)
if verb >= 1:
self.stdout.write(self.style.SUCCESS(
"User and/or Club given has successfully convert in their note"))
print("\033[32mUser and/or Club given has successfully convert in their note\033[m")
global_data = self.global_data(bde, verb=verb)
if verb >= 1:
self.stdout.write(self.style.SUCCESS(
"Global data has been successfully generated"))
print("\033[32mGlobal data has been successfully generated\033[m")
unique_data = self.unique_data(bde, note, global_data=global_data, verb=verb)
if verb >= 1:
self.stdout.write(self.style.SUCCESS(
"Unique data has been successfully generated"))
print("\033[32mUnique data has been successfully generated\033[m")
self.make_wrapped(unique_data, note, bde, change, create, verb=verb)
if verb >= 1:
self.stdout.write(self.style.SUCCESS(
"The wrapped has been generated !"))
print(green + "The wrapped has been generated !")
if verb >= 0:
self.stdout.write(self.style.SUCCESS("SUCCESS"))
exit(0)
print(success)
def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1): # NOQA
return
def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1):
notes = []
for b in bde:
note_for_bde = Note.objects.filter(pk__lte=-1)
@ -248,17 +253,17 @@ class Command(BaseCommand):
note_for_bde = self.filter_note(b, note_for_bde, change, create, verb=verb)
notes.append(note_for_bde)
if verb >= 2:
self.stdout.write(f"{len(note_for_bde)} note selectionned for bde {b.name}")
print("\033[m{nb} note selectionned for bde {bde}".format(nb=len(note_for_bde), bde=b.name))
return notes
def global_data(self, bde, verb=1): # NOQA
def global_data(self, bde, verb=1):
data = {}
for b in bde:
if b.name == 'Rave Part[list]':
if verb >= 2:
self.stdout.write("Begin to make global data")
print("Begin to make global data")
if verb >= 3:
self.stdout.write("nb_transaction")
print('nb_transaction')
# nb total de transactions
data['nb_transaction'] = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -266,7 +271,7 @@ class Command(BaseCommand):
valid=True).count()
if verb >= 3:
self.stdout.write("nb_vieux_con")
print('nb_vieux_con')
# nb total de vielleux con·ne·s derrière le bar
button_id = [2884, 2585]
transactions = Transaction.objects.filter(
@ -281,7 +286,7 @@ class Command(BaseCommand):
data['nb_vieux_con'] = q
if verb >= 3:
self.stdout.write("nb_soiree")
print('nb_soiree')
# nb total de soirée
a_type_id = [1, 2, 4, 5, 7, 10]
data['nb_soiree'] = Activity.objects.filter(
@ -291,7 +296,7 @@ class Command(BaseCommand):
activity_type__pk__in=a_type_id).count()
if verb >= 3:
self.stdout.write('pots, nb_entree_pot')
print('pots, nb_entree_pot')
# nb d'entrée totale aux pots
pot_id = [1, 4, 10]
pots = Activity.objects.filter(
@ -305,7 +310,7 @@ class Command(BaseCommand):
data['nb_entree_pot'] += Entry.objects.filter(activity=pot).count()
if verb >= 3:
self.stdout.write('top3_buttons')
print('top3_buttons')
# top 3 des boutons les plus cliqués
transactions = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -324,7 +329,7 @@ class Command(BaseCommand):
data['top3_buttons'] = list(sorted(d.items(), key=lambda item: item[1], reverse=True))[:3]
if verb >= 3:
self.stdout.write('class_conso_all')
print('class_conso_all')
# le classement des plus gros consommateurs (BDE + club)
transactions = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -343,7 +348,7 @@ class Command(BaseCommand):
data['class_conso_all'] = dict(sorted(d.items(), key=lambda item: item[1], reverse=True))
if verb >= 3:
self.stdout.write('class_conso_bde')
print('class_conso_bde')
# le classement des plus gros consommateurs BDE
transactions = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -363,10 +368,11 @@ class Command(BaseCommand):
else:
# make your wrapped or reuse previous wrapped
raise NotImplementedError(f"The BDE: {b.name} has not personalized wrapped, make it !")
raise NotImplementedError("The BDE: {bde_name} has not personalized wrapped, make it !"
.format(bde_name=b.name))
return data
def unique_data(self, bde, note, global_data=None, verb=1): # NOQA
def unique_data(self, bde, note, global_data=None, verb=1):
data = []
for i in range(len(bde)):
data_bde = []
@ -374,7 +380,8 @@ class Command(BaseCommand):
if verb >= 3:
total = len(note[i])
current = 0
self.stdout.write(f"Make {total} data for wrapped sponsored by {bde[i].name}")
print('Make {nb} data for wrapped sponsored by {bde}'
.format(nb=total, bde=bde[i].name))
for n in note[i]:
d = {}
if 'user' in n.__dir__():
@ -535,11 +542,12 @@ class Command(BaseCommand):
data_bde.append(json.dumps(d))
if verb >= 3:
current += 1
self.stdout.write("\033[2K" + f"({current}/{total})" + "\033[1A")
print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
else:
# make your wrapped or reuse previous wrapped
raise NotImplementedError(f"The BDE: {bde[i].name} has not personalized wrapped, make it !")
raise NotImplementedError("The BDE: {bde_name} has not personalized wrapped, make it !"
.format(bde_name=bde[i].name))
data.append(data_bde)
return data
@ -549,7 +557,7 @@ class Command(BaseCommand):
total = 0
for n in note:
total += len(n)
self.stdout.write(f"Make {total} wrapped")
print('\033[mMake {nb} wrapped'.format(nb=total))
for i in range(len(bde)):
for j in range(len(note[i])):
if create and not Wrapped.objects.filter(bde=bde[i], note=note[i][j]):
@ -564,7 +572,7 @@ class Command(BaseCommand):
w.save()
if verb >= 3:
current += 1
self.stdout.write("\033[2K" + f"({current}/{total})" + "\033[1A")
print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
return
def filter_note(self, bde, note, change, create, verb=1):

View File

@ -23,9 +23,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
let d1 = document.getElementById("consumer");
let d2 = document.getElementById("creditor");
if (con) { d1.textContent = {{ big_consumer | safe }}[0] + " " + gettext("with") + " " + {{ big_consumer | safe}}[1] + "€";}
else { d1.textContent = gettext("{% trans "Infortunately, you doesn't have consumer this year" %}");};
else { d1.textContent = gettext("Infortunately, you doesn't have consumer this year");};
if (cre) { d2.textContent = {{ big_creancier | safe}}[0] + " " + gettext("with") + " " + {{ big_creancier | safe}}[1] + "€";}
else { d2.textContent = gettext("{% trans "Congratulations you are a real rat !" %}"); };
else { d2.textContent = gettext("Congratulations you are a real rat !"); };
</script>
{% endblock %}

View File

@ -6,24 +6,17 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% load i18n %}
{% block content %}
<div id="wrapped_tables">
{% if tables|length > 0 %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "My wrapped" %}
</h3>
{% render_table tables.1 %}
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card card-border shadow">
<div class="card-header text-center">
<h5> {{ title }}</h5>
</div>
<div class="card-body px-0 py-0" id="wrapped_table">
{% render_table table %}
</div>
</div>
{% endif %}
{% if tables|length > 0 %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "Public wrapped" %}
</h3>
{% render_table tables.0 %}
</div>
{% endif %}
</div>
{% endblock %}
@ -32,7 +25,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
let club_not_public = {{ club_not_public }};
if (club_not_public) { (addMsg("{% trans "Do not forget to ask permission to people who are in your wrapped before to make them public" %}", 'warning'));}
function refreshTable() {
$("#wrapped_tables").load(location.pathname + " #wrapped_tables");
$("#wrapped_table").load(location.pathname + " #wrapped_table");
}
function copylink(id) {

View File

@ -1,91 +0,0 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta
from api.tests import TestAPI
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from ..api.views import WrappedViewSet, BdeViewSet
from ..models import Bde, Wrapped
class TestWrapped(TestCase):
"""
Test activities
"""
fixtures = ('initial',)
def setUp(self):
self.user = User.objects.create_superuser(
username="admintoto",
password="tototototo",
email="toto@example.com"
)
self.client.force_login(self.user)
sess = self.client.session
sess["permission_mask"] = 42
sess.save()
self.bde = Bde.objects.create(
name="The best BDE",
date_start=timezone.now() - timedelta(days=365),
date_end=timezone.now(),
)
self.wrapped = Wrapped.objects.create(
generated=True,
public=False,
bde=self.bde,
note=self.user.note,
data_json="{}",
)
def test_wrapped_list(self):
"""
Display the list of all wrapped
"""
response = self.client.get(reverse("wrapped:wrapped_list"))
self.assertEqual(response.status_code, 200)
def test_wrapped_detail(self):
"""
Display the detail of an wrapped
"""
response = self.client.get(reverse("wrapped:wrapped_detail", args=(self.wrapped.pk,)))
self.assertEqual(response.status_code, 200)
class TestWrappedAPI(TestAPI):
def setUp(self) -> None:
super().setUp()
self.bde = Bde.objects.create(
name="The best BDE",
date_start=timezone.now() - timedelta(days=365),
date_end=timezone.now(),
)
self.wrapped = Wrapped.objects.create(
generated=True,
public=False,
bde=self.bde,
note=self.user.note,
data_json="{}",
)
def test_bde_api(self):
"""
Load Bde API page and test all filters and permissions
"""
self.check_viewset(BdeViewSet, "/api/wrapped/bde/")
def test_wrapped_api(self):
"""
Load Wrapped API page and test all filters and permissions
"""
self.check_viewset(WrappedViewSet, "/api/wrapped/wrapped/")

View File

@ -6,8 +6,7 @@ import json
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView
from django.views.generic.list import ListView
from django_tables2.views import MultiTableMixin
from django_tables2.views import SingleTableView
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
@ -15,29 +14,21 @@ from .models import Wrapped
from .tables import WrappedTable
class WrappedListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
class WrappedListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
Display all Wrapped, and classify by year
"""
model = Wrapped
tables = [
lambda data: WrappedTable(data, prefix="public-"),
lambda data: WrappedTable(data, prefix="personnal-"),
]
table_class = WrappedTable
template_name = 'wrapped/wrapped_list.html'
extra_context = {'title': _("List of wrapped")}
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).distinct()
def get_tables_data(self):
return [
Wrapped.objects.filter(public=True),
Wrapped.objects
.filter(PermissionBackend.filter_queryset(self.request, Wrapped, "change", field='public'))
.distinct()
.order_by("-bde__date_start")
]
def get_table_data(self):
return Wrapped.objects.filter(PermissionBackend.filter_queryset(
self.request, Wrapped, "change", field='public')).distinct().order_by("-bde__date_start")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

View File

@ -1,118 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: model_graph Pages: 1 -->
<svg width="319pt" height="245pt"
viewBox="0.00 0.00 319.00 245.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 241)">
<title>model_graph</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-241 315,-241 315,4 -4,4"/>
<!-- wrapped_models_Bde -->
<g id="node1" class="node">
<title>wrapped_models_Bde</title>
<polygon fill="white" stroke="transparent" points="8,-4 8,-79 158,-79 158,-4 8,-4"/>
<polygon fill="#1b563f" stroke="transparent" points="9,-56.5 9,-77.5 157,-77.5 157,-56.5 9,-56.5"/>
<text text-anchor="start" x="52" y="-65.5" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="62" y="-65.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white"> &#160;&#160;&#160;Bde &#160;&#160;&#160;</text>
<text text-anchor="start" x="11" y="-49.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="21" y="-49.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
<text text-anchor="start" x="31" y="-49.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="77" y="-49.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="87" y="-49.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
<text text-anchor="start" x="131" y="-49.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="11" y="-36.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="21" y="-36.1" font-family="Roboto" font-size="8.00">date_end</text>
<text text-anchor="start" x="60" y="-36.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="77" y="-36.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="87" y="-36.1" font-family="Roboto" font-size="8.00">DateTimeField</text>
<text text-anchor="start" x="145" y="-36.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="11" y="-23.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="21" y="-23.1" font-family="Roboto" font-size="8.00">date_start</text>
<text text-anchor="start" x="63" y="-23.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="77" y="-23.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="87" y="-23.1" font-family="Roboto" font-size="8.00">DateTimeField</text>
<text text-anchor="start" x="145" y="-23.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="11" y="-10.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="21" y="-10.1" font-family="Roboto" font-size="8.00">name</text>
<text text-anchor="start" x="45" y="-10.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="77" y="-10.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="87" y="-10.1" font-family="Roboto" font-size="8.00">CharField</text>
<text text-anchor="start" x="125" y="-10.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<polygon fill="none" stroke="black" points="8,-4 8,-79 158,-79 158,-4 8,-4"/>
</g>
<!-- wrapped_models_Wrapped -->
<g id="node2" class="node">
<title>wrapped_models_Wrapped</title>
<polygon fill="white" stroke="transparent" points="67,-132 67,-233 231,-233 231,-132 67,-132"/>
<polygon fill="#1b563f" stroke="transparent" points="68,-210.5 68,-231.5 230,-231.5 230,-210.5 68,-210.5"/>
<text text-anchor="start" x="103" y="-219.5" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="113" y="-219.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white"> &#160;&#160;&#160;Wrapped &#160;&#160;&#160;</text>
<text text-anchor="start" x="70" y="-203.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="80" y="-203.1" font-family="Roboto" font-weight="bold" font-size="8.00">id</text>
<text text-anchor="start" x="90" y="-203.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="137" y="-203.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="147" y="-203.1" font-family="Roboto" font-weight="bold" font-size="8.00">AutoField</text>
<text text-anchor="start" x="191" y="-203.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="70" y="-190.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="80" y="-190.1" font-family="Roboto" font-weight="bold" font-size="8.00">bde</text>
<text text-anchor="start" x="98" y="-190.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="137" y="-190.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="147" y="-190.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
<text text-anchor="start" x="218" y="-190.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="70" y="-177.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="80" y="-177.1" font-family="Roboto" font-weight="bold" font-size="8.00">note</text>
<text text-anchor="start" x="101" y="-177.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="137" y="-177.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="147" y="-177.1" font-family="Roboto" font-weight="bold" font-size="8.00">ForeignKey (id)</text>
<text text-anchor="start" x="218" y="-177.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="70" y="-164.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="80" y="-164.1" font-family="Roboto" font-size="8.00">data_json</text>
<text text-anchor="start" x="120" y="-164.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="137" y="-164.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="147" y="-164.1" font-family="Roboto" font-size="8.00">TextField</text>
<text text-anchor="start" x="182" y="-164.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="70" y="-151.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="80" y="-151.1" font-family="Roboto" font-size="8.00">generated</text>
<text text-anchor="start" x="123" y="-151.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="137" y="-151.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="147" y="-151.1" font-family="Roboto" font-size="8.00">BooleanField</text>
<text text-anchor="start" x="200" y="-151.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="70" y="-138.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="80" y="-138.1" font-family="Roboto" font-size="8.00">public</text>
<text text-anchor="start" x="105" y="-138.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="137" y="-138.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<text text-anchor="start" x="147" y="-138.1" font-family="Roboto" font-size="8.00">BooleanField</text>
<text text-anchor="start" x="200" y="-138.1" font-family="Roboto" font-size="8.00"> &#160;&#160;&#160;</text>
<polygon fill="none" stroke="black" points="67,-132 67,-233 231,-233 231,-132 67,-132"/>
</g>
<!-- wrapped_models_Wrapped&#45;&gt;wrapped_models_Bde -->
<g id="edge1" class="edge">
<title>wrapped_models_Wrapped&#45;&gt;wrapped_models_Bde</title>
<path fill="none" stroke="black" d="M119.99,-120.4C114,-107.79 107.84,-94.82 102.31,-83.16"/>
<ellipse fill="black" stroke="black" cx="121.77" cy="-124.15" rx="4" ry="4"/>
<text text-anchor="middle" x="132" y="-103.6" font-family="Roboto" font-size="8.00"> bde (+)</text>
</g>
<!-- note_models_notes_Note -->
<g id="node3" class="node">
<title>note_models_notes_Note</title>
<polygon fill="white" stroke="transparent" points="192,-31 192,-52 240,-52 240,-31 192,-31"/>
<polygon fill="#1b563f" stroke="transparent" points="192,-30.5 192,-51.5 240,-51.5 240,-30.5 192,-30.5"/>
<text text-anchor="start" x="196.5" y="-38.9" font-family="Roboto" font-size="8.00"> &#160;</text>
<text text-anchor="start" x="201.5" y="-38.9" font-family="Roboto" font-size="12.00" fill="white">Note</text>
<text text-anchor="start" x="230.5" y="-38.9" font-family="Roboto" font-size="8.00"> &#160;</text>
</g>
<!-- wrapped_models_Wrapped&#45;&gt;note_models_notes_Note -->
<g id="edge2" class="edge">
<title>wrapped_models_Wrapped&#45;&gt;note_models_notes_Note</title>
<path fill="none" stroke="black" d="M178.48,-120.33C189.12,-98.27 200.3,-75.07 207.66,-59.8"/>
<ellipse fill="black" stroke="black" cx="176.64" cy="-124.16" rx="4" ry="4"/>
<text text-anchor="middle" x="204.5" y="-103.6" font-family="Roboto" font-size="8.00"> note (+)</text>
</g>
<!-- \n\n\n -->
<g id="node4" class="node">
<title>\n\n\n</title>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -55,7 +55,6 @@ Les adhérent⋅es ont la possibilité d'inviter des ami⋅es. Pour cela, les di
* Activité concernée (clé étrangère)
* Nom de famille
* Prénom
* École
* Note de la personne ayant invité
Certaines contraintes s'appliquent :

View File

@ -14,7 +14,6 @@ Applications de la Note Kfet 2020
logs
treasury
wei
wrapped
La Note Kfet 2020 est un projet Django, décomposé en applications.
Certaines applications sont développées uniquement pour ce projet, et sont indispensables,
@ -70,6 +69,4 @@ Applications facultatives
Interface de gestion pour les trésorièr⋅es, émission de factures, remises de chèque, statistiques...
* `WEI <wei>`_ :
Interface de gestion du WEI.
* `Wrapped <wrapped>`_ :
Récapitulatif personnalisé annuel de statitiques globales et personnelles.

View File

@ -1,108 +0,0 @@
Wrapped
=======
Cette application montre les statistiques annuelles des utilisateur·ice·s et/ou des clubs.
Modèles
-------
Bde
~~~
Le modèle ``Bde`` contient des informations relatifs à un BDE :
* ``name`` : ``CharField``, nom du BDE.
* ``date_start`` : ``DateField``, date de prise de fonction du bureau BDE considéré.
* ``date_end`` : ``DateField``, date de démission du bureau BDE considéré.
Wrapped
~~~~~~~
Contient les informations sur un wrapped :
* ``generated`` : ``BooleanField``, indique si le wrapped a été généré ou non.
* ``public`` : ``BooleanField``, indique si le wrapped est visible de tous les utilisateur·ice·s ou non.
* ``bde`` : ``ForeignKey(Bde)``, BDE auquel le wrapped correspond.
* ``note`` : ``ForeignKey(Note)``, note à laquelle le wrapped correspond.
* ``data_json`` : ``TextField``, diverses statistique concernant les notes durant le mandat BDE
considéré ou sur la NoteKfet dans sa globalité.
Graphe des modèles
~~~~~~~~~~~~~~~~~~
.. image:: ../_static/img/graphs/wrapped.svg
:width: 960
:alt: Graphe des modèles de l'application Wrapped
Fonctionnement
--------------
Création d'un BDE
~~~~~~~~~~~~~~~~~
Seul un⋅e respo info peut créer un BDE. Pour cela, se rendre dans l'onglet « Admin »., puis « BDE » et
enfin « + Ajouter BDE ». Iel doit renseigner, les dates de début et de fin du bureau BDE ainsi que le
nom de la liste.
Génération des wrappeds
~~~~~~~~~~~~~~~~~~~~~~~
Seul un·e respo info peut générer des wrappeds. Pour une utilisation annuelle classique, iel exécute la
commande :
``./manage.py generate_wrapped -b "bde_name" -u adh -c active``
Pour une utilisation plus technique de cette commande se référer à sa documentation
``./manage.py help generate_wrapped``
Le script prend une dizaine de minutes pour générer tous les wrappeds.
Créer ses propres wrappeds
--------------------------
Cette section est plus technique et s'addresse plutôt à des respos infos en cours de mandat qui voudrai
faire les wrappeds de leur propre BDE.
Contenu
~~~~~~~
Il est fortement conseillé de bien réfléchir à ce que l'on souhaite mettre sur un wrapped, plusieurs
critères sont à prendre compte :
* compréhension, est-ce que la donnée fait sens auprès des utilisateur·ice·s.
* pertinence, est-ce que la donnée fonctionne pour un grand nombre d'utilisateur.
* faisabilité, est-ce que le temps de calcul est suffisament rapide.
* complexité, est-ce que c'est trop compliqué à coder.
Script
~~~~~~
Le script *generate_wrapped* fonctionne de la manière suivante :
* ``convert_to_note`` : en fonction des arguments d'entrée, il récupére toutes les notes dont le·s
wrapped·s va/vont être généré·s
ou regénéré·s.
* ``global_data`` : le script génére ensuite des statistiques globales qui concernent pas qu'une seule
note (nombre de soirée, classement, etc).
* ``unique_data`` : le script génére les statitiques uniques à chaque note, et rajoute des données
globales si nécessaire, pour chaque note on souhaite avoir un json avec toutes les données qui
seront dans le wrapped.
* ``make_wrapped`` : enfin, le cas échéant, pour chaque bde, et pour chaque note, le wrapped est crée
ou modifié, et enregistré, s'il est crée il est par défault non public.
Seules les fonctions ``global_data`` et ``unique_data`` sont à modifier, pour implementer un nouveau
BDE.
Template
~~~~~~~~
Il y a au moins deux templates a écrire pour chaque bde :
* ``templates/wrapped/{bde_id}/wrapped_view_club.html``: le template pour les wrappeds des clubs
* ``templates/wrapped/{bde_id}/wrapped_view_user.html``: le template pour les wrappeds des
utilisateur·ice·s
Il est conseillé de suivre la même arborescence pour les fichiers statics (fonts personnalisées,
images, css, etc). De même, il est conseillé de créé un fichier
``templates/wrapped/{bde_id}/wrapped_base.html`` et d'étendre cette template.

View File

@ -43,11 +43,6 @@ On a ensuite besoin de définir nos propres scopes afin d'avoir des permissions
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
'PKCE_REQUIRED': False,
'OIDC_ENABLED': True,
'OIDC_RSA_PRIVATE_KEY':
os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'),
'SCOPES': { 'openid': "OpenID Connect scope" },
}
Cela a pour effet d'avoir des scopes sous la forme ``PERMISSION_CLUB``,
@ -62,14 +57,6 @@ On ajoute enfin les routes dans ``urls.py`` :
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
)
Enfin pour utiliser OIDC, il faut générer une clé privé que l'on va, par défaut,
mettre dans `/var/secrets/oidc.key` :
.. code:: bash
cd /var/secrets/
openssl genrsa -out oidc.key 4096
L'OAuth2 est désormais prêt à être utilisé.

View File

@ -227,22 +227,6 @@ En production, ce fichier contient :
)
Génération d'une clé privé pour OIDC
------------------------------------
Pour pouvoir proposer le service de connexion Openid Connect (OIDC) par OAuth2, il y a
besoin d'une clé privé. Par défaut, elle est cherché dans le fichier `/var/secrets/oidc.key`
(sinon, il faut modifier l'emplacement dans les fichiers de configurations).
Pour générer la clé, il faut aller dans le dossier `/var/secrets` (à créer, si nécessaire) puis
utiliser la commande de génération :
.. code:: bash
cd /var/secrets
openssl genrsa -out oidc.key 4096
Configuration des tâches récurrentes
------------------------------------

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-25 11:16+0100\n"
"POT-Creation-Date: 2025-02-25 13:47+0100\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: bleizi <bleizi@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -25,7 +25,7 @@ msgid "This opener already exists"
msgstr "Cette amitié existe déjà"
#: apps/activity/apps.py:10 apps/activity/models.py:129
#: apps/activity/models.py:169 apps/activity/models.py:328
#: apps/activity/models.py:169 apps/activity/models.py:323
msgid "activity"
msgstr "activité"
@ -37,24 +37,24 @@ msgstr "La note du club est inactive."
msgid "The end date must be after the start date."
msgstr "La date de fin doit être après celle de début."
#: apps/activity/forms.py:83 apps/activity/models.py:276
#: apps/activity/forms.py:83 apps/activity/models.py:271
msgid "You can't invite someone once the activity is started."
msgstr ""
"Vous ne pouvez pas inviter quelqu'un une fois que l'activité a démarré."
#: apps/activity/forms.py:86 apps/activity/models.py:279
#: apps/activity/forms.py:86 apps/activity/models.py:274
msgid "This activity is not validated yet."
msgstr "Cette activité n'est pas encore validée."
#: apps/activity/forms.py:96 apps/activity/models.py:287
#: apps/activity/forms.py:96 apps/activity/models.py:282
msgid "This person has been already invited 5 times this year."
msgstr "Cette personne a déjà été invitée 5 fois cette année."
#: apps/activity/forms.py:100 apps/activity/models.py:291
#: apps/activity/forms.py:100 apps/activity/models.py:286
msgid "This person is already invited."
msgstr "Cette personne est déjà invitée."
#: apps/activity/forms.py:104 apps/activity/models.py:295
#: apps/activity/forms.py:104 apps/activity/models.py:290
msgid "You can't invite more than 3 people to this activity."
msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
@ -228,36 +228,32 @@ msgstr "nom de famille"
msgid "first name"
msgstr "prénom"
#: apps/activity/models.py:252
msgid "school"
msgstr "école"
#: apps/activity/models.py:259
#: apps/activity/models.py:254
msgid "inviter"
msgstr "hôte"
#: apps/activity/models.py:263
#: apps/activity/models.py:258
msgid "guest"
msgstr "invité·e"
#: apps/activity/models.py:264
#: apps/activity/models.py:259
msgid "guests"
msgstr "invité·e·s"
#: apps/activity/models.py:317
#: apps/activity/models.py:312
msgid "Invitation"
msgstr "Invitation"
#: apps/activity/models.py:335 apps/activity/models.py:339
#: apps/activity/models.py:330 apps/activity/models.py:334
msgid "Opener"
msgstr "Ouvreur⋅se"
#: apps/activity/models.py:340
#: apps/activity/models.py:335
#: apps/activity/templates/activity/activity_detail.html:16
msgid "Openers"
msgstr "Ouvreur⋅ses"
#: apps/activity/models.py:344
#: apps/activity/models.py:339
#, fuzzy, python-brace-format
#| msgid "Entry for {note} to the activity {activity}"
msgid "{opener} is opener of activity {acivity}"
@ -467,25 +463,25 @@ msgstr "Détails de l'activité"
msgid "Update activity"
msgstr "Modifier l'activité"
#: apps/activity/views.py:178
#: apps/activity/views.py:177
msgid "Invite guest to the activity \"{}\""
msgstr "Invitation pour l'activité « {} »"
#: apps/activity/views.py:218
#: apps/activity/views.py:217
msgid "You are not allowed to display the entry interface for this activity."
msgstr ""
"Vous n'êtes pas autorisé·e à afficher l'interface des entrées pour cette "
"activité."
#: apps/activity/views.py:221
#: apps/activity/views.py:220
msgid "This activity does not support activity entries."
msgstr "Cette activité ne requiert pas d'entrées."
#: apps/activity/views.py:224
#: apps/activity/views.py:223
msgid "This activity is closed."
msgstr "Cette activité est fermée."
#: apps/activity/views.py:329
#: apps/activity/views.py:328
msgid "Entry for activity \"{}\""
msgstr "Entrées pour l'activité « {} »"
@ -869,7 +865,7 @@ msgstr "Taille maximale : 2 Mo"
msgid "This image cannot be loaded."
msgstr "Cette image ne peut pas être chargée."
#: apps/member/forms.py:154 apps/member/views.py:117
#: apps/member/forms.py:154 apps/member/views.py:103
#: apps/registration/forms.py:33 apps/registration/views.py:282
msgid "An alias with a similar name already exists."
msgstr "Un alias avec un nom similaire existe déjà."
@ -1198,11 +1194,11 @@ msgstr "Adhésion de {user} pour le club {club}"
msgid "The role {role} does not apply to the club {club}."
msgstr "Le rôle {role} ne s'applique pas au club {club}."
#: apps/member/models.py:388 apps/member/views.py:759
#: apps/member/models.py:388 apps/member/views.py:745
msgid "User is already a member of the club"
msgstr "L'utilisateur·rice est déjà membre du club"
#: apps/member/models.py:400 apps/member/views.py:768
#: apps/member/models.py:400 apps/member/views.py:754
msgid "User is not a member of the parent club"
msgstr "L'utilisateur·rice n'est pas membre du club parent"
@ -1255,7 +1251,7 @@ msgid "Account #"
msgstr "Compte n°"
#: apps/member/templates/member/base.html:48
#: apps/member/templates/member/base.html:62 apps/member/views.py:61
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
#: apps/registration/templates/registration/future_profile_detail.html:48
#: apps/wei/templates/wei/weimembership_form.html:117
msgid "Update Profile"
@ -1316,8 +1312,8 @@ msgstr ""
"seront à nouveau possible."
#: apps/member/templates/member/club_alias.html:10
#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:318
#: apps/member/views.py:559
#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:304
#: apps/member/views.py:545
msgid "Note aliases"
msgstr "Alias de la note"
@ -1509,51 +1505,51 @@ msgstr "Sauvegarder les changements"
msgid "Registrations"
msgstr "Inscriptions"
#: apps/member/views.py:74 apps/registration/forms.py:23
#: apps/member/views.py:73 apps/registration/forms.py:23
msgid "This address must be valid."
msgstr "Cette adresse doit être valide."
#: apps/member/views.py:154
#: apps/member/views.py:140
msgid "Profile detail"
msgstr "Détails de l'utilisateur⋅rice"
#: apps/member/views.py:220
#: apps/member/views.py:206
msgid "Search user"
msgstr "Chercher un·e utilisateur·rice"
#: apps/member/views.py:272
#: apps/member/views.py:258
msgid "Note friendships"
msgstr "Amitiés note"
#: apps/member/views.py:342
#: apps/member/views.py:328
msgid "Update note picture"
msgstr "Modifier la photo de la note"
#: apps/member/views.py:391
#: apps/member/views.py:377
msgid "Manage auth token"
msgstr "Gérer les jetons d'authentification"
#: apps/member/views.py:418
#: apps/member/views.py:404
msgid "Create new club"
msgstr "Créer un nouveau club"
#: apps/member/views.py:437
#: apps/member/views.py:423
msgid "Search club"
msgstr "Chercher un club"
#: apps/member/views.py:475
#: apps/member/views.py:461
msgid "Club detail"
msgstr "Détails du club"
#: apps/member/views.py:587
#: apps/member/views.py:573
msgid "Update club"
msgstr "Modifier le club"
#: apps/member/views.py:621
#: apps/member/views.py:607
msgid "Add new member to the club"
msgstr "Ajouter un·e nouvelleau membre au club"
#: apps/member/views.py:750 apps/wei/views.py:991
#: apps/member/views.py:736 apps/wei/views.py:991
msgid ""
"This user don't have enough money to join this club, and can't have a "
"negative balance."
@ -1561,19 +1557,19 @@ msgstr ""
"Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et ne "
"peut pas avoir un solde négatif."
#: apps/member/views.py:772
#: apps/member/views.py:758
msgid "The membership must start after {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}."
#: apps/member/views.py:777
#: apps/member/views.py:763
msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
#: apps/member/views.py:927
#: apps/member/views.py:913
msgid "Manage roles of an user in the club"
msgstr "Gérer les rôles d'un⋅e utilisateur⋅rice dans le club"
#: apps/member/views.py:952
#: apps/member/views.py:938
msgid "Members of the club"
msgstr "Membres du club"
@ -1989,10 +1985,16 @@ msgstr "Historique des transactions récentes"
#: apps/note/templates/note/mails/negative_balance.txt:25
#: apps/note/templates/note/mails/negative_notes_report.html:46
#: apps/note/templates/note/mails/negative_notes_report.txt:13
#: apps/note/templates/note/mails/summary_notes_report.html:62
#: apps/note/templates/note/mails/summary_ntoes_report.txt:33
#: apps/note/templates/note/mails/weekly_report.html:51
#: apps/note/templates/note/mails/weekly_report.txt:32
#: apps/registration/templates/registration/mails/email_validation_email.html:40
#: apps/registration/templates/registration/mails/email_validation_email.txt:16
#: apps/scripts/templates/scripts/horaires.html:35
#: apps/scripts/templates/scripts/horaires.txt:17
#: apps/scripts/templates/scripts/intro_mail.html:49
#: apps/scripts/templates/scripts/intro_mail.txt:25
msgid "Mail generated by the Note Kfet on the"
msgstr "Mail généré par la Note Kfet le"
@ -2084,7 +2086,7 @@ msgid "Button displayed"
msgstr "Bouton affiché"
#: apps/note/templates/note/transactiontemplate_list.html:100
#: apps/wrapped/templates/wrapped/wrapped_list.html:70
#: apps/wrapped/templates/wrapped/wrapped_list.html:63
msgid "An error occured"
msgstr "Une erreur s'est produite"
@ -2563,6 +2565,7 @@ msgid "Address"
msgstr "Adresse"
#: apps/treasury/models.py:69 apps/treasury/models.py:202
#: apps/treasury/models.py:472
msgid "Date"
msgstr "Date"
@ -2682,6 +2685,102 @@ msgstr ""
"sa note. Merci de lui demander de recharger sa note avant d'invalider ce "
"crédit."
#: apps/treasury/models.py:476
msgid "Total positive user"
msgstr "Nombre d'utilisateur⋅rices en positif"
#: apps/treasury/models.py:480
msgid "Balance positive user"
msgstr "Solde des utilisateur⋅rices en positif"
#: apps/treasury/models.py:484
msgid "Total positive user BDE"
msgstr "Nombre d'adhérent⋅es au BDE en positif"
#: apps/treasury/models.py:488
msgid "Balance positive user BDE"
msgstr "Solde des adhérent⋅es au BDE en positif"
#: apps/treasury/models.py:492
msgid "Total zero user"
msgstr "Nombre d'utilisateur⋅rices à zéro"
#: apps/treasury/models.py:496
msgid "Total zero user BDE"
msgstr "Nombre d'adhérent⋅es au BDE à zéro"
#: apps/treasury/models.py:500
msgid "Total negative user"
msgstr "Nombre d'utilisateur⋅rices en négatif"
#: apps/treasury/models.py:504
msgid "Balance negative user"
msgstr "Solde des utilisateur⋅rices en négatif"
#: apps/treasury/models.py:508
msgid "Total negative user BDE"
msgstr "Nombre d'adhérent⋅es au BDE en négatif"
#: apps/treasury/models.py:512
msgid "Balance negative user BDE"
msgstr "Solde des adhérent⋅es au BDE en négatif"
#: apps/treasury/models.py:516
msgid "Total very negative user"
msgstr "Nombre d'utilisateur⋅rices en négatif sévère"
#: apps/treasury/models.py:520
msgid "Balance very negative user"
msgstr "Solde des utilisateur⋅rices en négatif sévère"
#: apps/treasury/models.py:524
msgid "Total very negative user BDE"
msgstr "Nombre d'adhérent⋅es au BDE en négatif sévère"
#: apps/treasury/models.py:528
msgid "Balance very negative user BDE"
msgstr "Solde des adhérent⋅es au BDE en négatif sévère"
#: apps/treasury/models.py:532
msgid "Total positive club"
msgstr "Nombre de clubs en positif"
#: apps/treasury/models.py:536
msgid "Balance positive club"
msgstr "Solde des clubs en positif"
#: apps/treasury/models.py:540
msgid "Total positive club nbde"
msgstr "Nombre de clubs non-BDE en positif"
#: apps/treasury/models.py:544
msgid "Balance positive club nbde"
msgstr "Solde des clubs non-BDE en positif"
#: apps/treasury/models.py:548
msgid "Total zero club"
msgstr "Nombre de clubs à zéro"
#: apps/treasury/models.py:552
msgid "Total zero club nbde"
msgstr "Nombre de clubs non-BDE à zéro"
#: apps/treasury/models.py:556
msgid "Total negative club"
msgstr "Nombre de clubs en négatif"
#: apps/treasury/models.py:560
msgid "Balance negative club"
msgstr "Solde des clubs en négatif"
#: apps/treasury/models.py:564
msgid "Total negative club nbde"
msgstr "Nombre de clubs non-BDE en négatif"
#: apps/treasury/models.py:568
msgid "Balance negative club nbde"
msgstr "Solde des clubs non-BDE en négatif"
#: apps/treasury/tables.py:20
msgid "Invoice #{:d}"
msgstr "Facture n°{:d}"
@ -3662,14 +3761,6 @@ msgstr "soirée·s organisée·s"
msgid "distinct members"
msgstr "Membres distinct·e·s"
#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:26
msgid "Infortunately, you doesn't have consumer this year"
msgstr "Malheureusement, tu n'as pas de consommateur cette année"
#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:28
msgid "Congratulations you are a real rat !"
msgstr "Félicitations, tu es un vrai rat !"
#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13
msgid "You participate to the wei: "
msgstr "Tu as participé au wei : "
@ -3707,15 +3798,7 @@ msgstr "avec"
msgid "Your expenses to BDE: "
msgstr "Tes dépenses au BDE : "
#: apps/wrapped/templates/wrapped/wrapped_list.html:13
msgid "My wrapped"
msgstr "Mes wrapped"
#: apps/wrapped/templates/wrapped/wrapped_list.html:22
msgid "Public wrapped"
msgstr "Wrapped public"
#: apps/wrapped/templates/wrapped/wrapped_list.html:33
#: apps/wrapped/templates/wrapped/wrapped_list.html:26
msgid ""
"Do not forget to ask permission to people who are in your wrapped before to "
"make them public"
@ -3723,19 +3806,19 @@ msgstr ""
"N'oublies pas de demander la permission des personnes apparaissant dans un "
"wrapped avant de le rendre public"
#: apps/wrapped/templates/wrapped/wrapped_list.html:40
#: apps/wrapped/templates/wrapped/wrapped_list.html:33
msgid "Link copied"
msgstr "Lien copié"
#: apps/wrapped/templates/wrapped/wrapped_list.html:65
#: apps/wrapped/templates/wrapped/wrapped_list.html:58
msgid "Wrapped is private"
msgstr "Le wrapped est privé"
#: apps/wrapped/templates/wrapped/wrapped_list.html:66
#: apps/wrapped/templates/wrapped/wrapped_list.html:59
msgid "Wrapped is public"
msgstr "Le wrapped est public"
#: apps/wrapped/views.py:28
#: apps/wrapped/views.py:24
msgid "List of wrapped"
msgstr "Liste des wrapped"

View File

@ -20,6 +20,8 @@ MAILTO=notekfet2020@lists.crans.org
00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0
# Envoyer le rapport mensuel aux trésoriers et respos info
00 8 * * 5 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0
# Envoyer le recap de tresorerie
00 8 * * 5 root cd /var/www/note_kfet && env/bin/python manage.py send_summary_notes_report --negative-amount 2000
# Envoyer les rapports aux gens
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0
# Mettre à jour les boutons mis en avant

View File

@ -268,10 +268,6 @@ OAUTH2_PROVIDER = {
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
'OIDC_ENABLED': True,
'OIDC_RSA_PRIVATE_KEY':
os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'),
'SCOPES': { 'openid': "OpenID Connect scope" },
}
# Take control on how widget templates are sourced

View File

@ -61,11 +61,6 @@ mark {
/* Make navbar more readable */
.navbar-dark .navbar-nav .nav-link {
color: rgba(255, 255, 255, .75);
text-shadow: 2px 2px 15px #ffeb40;
}
.navbar-brand {
text-shadow: 2px 2px 15px #ffeb40;
}
/* Last BDE colors */
@ -73,8 +68,7 @@ mark {
/* background-color: rgb(18, 67, 4) !important; */
/* MODE VIEUXCON=ON */
/* background-color: rgb(166, 0, 2) !important; */
background-color: rgb(0, 0, 0);
background-image: url('/static/img/rp_bg.png');
background-color: rgb(100, 30, 100) !important;
}
html {
@ -89,94 +83,81 @@ body {
.btn-outline-primary:hover,
.btn-outline-primary:not(:disabled):not(.disabled).active,
.btn-outline-primary:not(:disabled):not(.disabled):active {
color: rgb(0, 0, 0);
background-color: rgb(255, 0, 101);
border-color: rgb(255, 203, 32);
color: rgb(240, 200, 240);
background-color: rgb(30, 120, 150);
border-color: rgb(190, 150, 190);
}
.btn-outline-primary {
color: #000;
background-color: #ffcb20;
border-color: #000;
color: #a2a;
background-color: #6bc;
border-color: #719;
}
.turbolinks-progress-bar {
background-color: #ffffff;
background-color: #12342E;
}
.btn-primary:hover,
.btn-primary:not(:disabled):not(.disabled).active,
.btn-primary:not(:disabled):not(.disabled):active {
color: rgb(0, 0, 0);
background-color: rgb(255, 0, 101);
border-color: rgb(255, 203, 32);
color: rgb(150, 200, 240);
background-color: rgb(50, 100, 140);
border-color: rgb(0, 0, 0);
}
.btn-primary {
color: #ffcb20;
background-color: #000000;
border-color: #ffcd20;
color: #eae;
background-color: #616;
border-color: #000000;
}
.border-primary {
border-color: rgb(255, 255, 255) !important;
border-color: rgb(222, 180, 222) !important;
}
.btn-secondary {
color: #ff0065;
background-color: #000000;
border-color: #ff0065;
color: #eae;
background-color: #616;
border-color: #000000;
}
.btn-secondary:hover,
.btn-secondary:not(:disabled):not(.disabled).active,
.btn-secondary:not(:disabled):not(.disabled):active {
color: rgb(0, 0, 0);
background-color: rgb(255, 203, 32);
border-color: rgb(255, 0, 101);
color: rgb(150, 200, 240);
background-color: rgb(50, 100, 140);
border-color: rgb(0, 0, 0);
}
.btn-outline-dark-shiny {
background-color: #222;
border-color: #61605b;
color: rgba(255, 0, 101, 75%);
}
.btn-outline-dark-shiny:hover,
.btn-outline-dark-shiny:not(:disabled):not(.disabled).active,
.btn-outline-dark-shiny:not(:disabled):not(.disabled):active {
color: rgb(0, 0, 0);
background-color: rgb(255, 203, 32);
border-color: rgb(255, 0, 101);
}
.btn-outline-dark {
background-color: #222;
border-color: #61605b;
color: rgba(255, 203, 32, 75%);
color: #000000;
border-color: #000000;
}
.btn-outline-dark:hover,
.btn-outline-dark:not(:disabled):not(.disabled).active,
.btn-outline-dark:not(:disabled):not(.disabled):active {
color: rgb(0, 0, 0);
background-color: rgb(255, 0, 101);
border-color: rgb(255, 203, 32);
color: rgb(50, 100, 160);
background-color: rgb(240, 150, 240);
border-color: rgb(50, 100, 160);
}
a {
color: rgb(255, 0, 101);
color: rgb(0, 150, 150);
}
a:hover {
color: rgb(255, 203, 32);
color: rgb(200, 0, 200);
}
.form-control:focus {
box-shadow: 0 0 0 0.25rem rgb(255 0 101 / 50%);
border-color: rgb(255, 0, 101);
box-shadow: 0 0 0 0.25rem rgb(0 150 150 / 50%);
border-color: rgb(0, 200, 200);
}
.btn-outline-primary.focus {
box-shadow: 0 0 0 0.25rem rgb(255 203 32 / 22%);
box-shadow: 0 0 0 0.25rem rgb(0 150 150 / 22%);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

View File

@ -26,7 +26,7 @@ function afterKonami () {
})
rythm.addRythm('d-flex', 'color', 50, 50, {
from: [64, 64, 64],
to: [255, 0, 101]
to: [128, 64, 128]
})
rythm.addRythm('nav-link', 'jump', 150, 50, {
min: 0,