diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json
index 00f952cc..4a34f99a 100644
--- a/apps/permission/fixtures/initial.json
+++ b/apps/permission/fixtures/initial.json
@@ -3829,9 +3829,89 @@
"mask": 3,
"field": "",
"permanent": false,
- "description": "Voir les profils des membres du club"
+ "description": "Voir les profils des membres du club"
}
},
+ {
+ "model": "permission.permission",
+ "pk": 244,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"public\": true}",
+ "type": "view",
+ "mask": 1,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les wrapped public"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 245,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteuser__user\": [\"user\"]}",
+ "type": "view",
+ "mask": 1,
+ "field": "",
+ "permanent": true,
+ "description": "Voir ses propres wrapped, pour toujours"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 246,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteuser__user\": [\"user\"]}",
+ "type": "change",
+ "mask": 1,
+ "field": "public",
+ "permanent": true,
+ "description": "Modifier la visibilité de ses wrapped, pour toujours"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 247,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteclub__club\": [\"club\"]}",
+ "type": "view",
+ "mask": 1,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les wrapped de son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 248,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteclub__club\": [\"club\"]}",
+ "type": "change",
+ "mask": 1,
+ "field": "public",
+ "permanent": false,
+ "description": "Modifier la visibilité des wrapped de son club"
+ }
+ },
{
"model": "permission.role",
"pk": 1,
@@ -3881,7 +3961,10 @@
203,
204,
205,
- 206
+ 206,
+ 244,
+ 245,
+ 246
]
}
},
@@ -3970,7 +4053,9 @@
227,
233,
234,
- 237
+ 237,
+ 247,
+ 248
]
}
},
diff --git a/apps/wrapped/management/commands/generate_wrapped.py b/apps/wrapped/management/commands/generate_wrapped.py
index 0ba6d396..f156c989 100644
--- a/apps/wrapped/management/commands/generate_wrapped.py
+++ b/apps/wrapped/management/commands/generate_wrapped.py
@@ -1,14 +1,15 @@
# Copyright (C) 2028-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
-### Import ###
-
+import json
from argparse import ArgumentParser
from django.core.management import BaseCommand
from django.db.models import Q
-from note.models import Note
-from member.models import User, Club
+from note.models import Note, Transaction
+from member.models import User, Club, Membership
+from activity.models import Activity, Entry
+from wei.models import WEIClub
from ...models import Bde, Wrapped
@@ -78,9 +79,7 @@ class Command(BaseCommand):
warning = yellow + 'WARNING'
success = green + 'SUCCESS'
- ###################################
- #### Traitement des paramètres ####
- ###################################
+ # Traitement des paramètres
verb = options['verbosity']
bde = []
if options['bde']:
@@ -177,19 +176,18 @@ class Command(BaseCommand):
if verb >=1 and not create:
print(warning)
print(yellow + 'create is set to false, wrapped will not be created !')
- if verb >= 2 or change or not create:
+ 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: print(abort)
return
- note = self.convert_to_note(user=user, club=club, verb=verb)
+ note = self.convert_to_note(change, create, bde=bde, user=user, club=club, verb=verb)
if verb >= 1: 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: print("\033[32mGlobal data has been successfully generated\033[m")
- unique_data = self.unique_data(bde, note, change, create, global_data=global_data, verb=verb)
+ unique_data = self.unique_data(bde, note, global_data=global_data, verb=verb)
if verb >= 1: print("\033[32mUnique data has been successfully generated\033[m")
self.make_wrapped(unique_data, note, bde, change, create, verb=verb)
@@ -198,69 +196,334 @@ class Command(BaseCommand):
return
- def convert_to_note(self, user=None, club=None, verb=1):
- query = Q(pk=-1)
- if user:
- if 'custom' in user[0]:
- for u in user[1]:
- query |= Q(noteuser__user=u)
- elif user[0] == 'all':
- query |= Q(noteuser__user__pk__gte=-1)
- elif user[0] == 'adh':
- # TODO some complex query
- query |= query
- elif user[0] == 'superuser':
- query |= Q(noteuser__user__is_superuser=True)
- else:
- return
+ def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1):
+ N = []
+ for b in bde:
+ note = Note.objects.filter(pk__lte=-1)
+ if user:
+ if 'custom' in user[0]:
+ for u in user[1]:
+ query = Q(noteuser__user=u)
+ note |= Note.objects.filter(query)
+ elif user[0] == 'all':
+ query = Q(noteuser__user__pk__gte=-1)
+ note |= Note.objects.filter(query)
+ elif user[0] == 'adh':
+ M = Membership.objects.filter(club=1,
+ date_start__lt=b.date_end,
+ date_end__gt=b.date_start,
+ ).distinct('user')
+ for m in M:
+ note |= Note.objects.filter(noteuser__user=m.user)
- if club:
- if 'custom' in club[0]:
- for c in club[1]:
- query |= Q(noteclub__club=c)
- elif club[0] == 'all':
- query |= Q(noteclub__club__pk__gte=-1)
- elif club[0] == 'active':
- # TODO some complex query
- query |= query
- else:
- return
-
- if verb >= 3: print('\033[mQuery: ' + str(query))
- note = Note.objects.filter(query)
+ elif user[0] == 'superuser':
+ query |= Q(noteuser__user__is_superuser=True)
+ note |= Note.objects.filter(query)
- return note
+ if club:
+ if 'custom' in club[0]:
+ for c in club[1]:
+ query = Q(noteclub__club=c)
+ note |= Note.objects.filter(query)
+ elif club[0] == 'all':
+ query = Q(noteclub__club__pk__gte=-1)
+ note |= Note.objects.filter(query)
+ elif club[0] == 'active':
+ nc = Note.objects.filter(noteclub__club__pk__gte=-1)
+ for n in nc:
+ if Transaction.objects.filter(
+ Q(created_at__gte=b.date_start,
+ created_at__lte=b.date_end) &
+ (Q(source=n) | Q(destination=n))):
+ note |= Note.objects.filter(pk=n.pk)
+
+ note = self.filter_note(b, note, change, create, verb=verb)
+ N.append(note)
+ if verb >= 2:
+ print("\033[m{nb} note selectionned for bde {bde}".format(nb=len(note) ,bde=b.name))
+ return N
def global_data(self, bde, verb=1):
data = {}
for b in bde:
if b.name == 'Rave Part[list]':
- # TODO
- data = {}
+ if verb >= 2: print("Begin to make global data")
+ if verb >= 3: print('nb_transaction')
+ # nb total de transactions
+ data['nb_transaction'] = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True).count()
+
+ if verb >= 3: print('nb_vieux_con')
+ # nb total de vielleux con·ne·s derrière le bar
+ button_id = [2884,2585]
+ T = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ recurrenttransaction__template__pk__in=button_id)
+
+ q = 0
+ for t in T: q += t.quantity
+ data['nb_vieux_con'] = q
+
+ if verb >= 3: print('nb_soiree')
+ # nb total de soirée
+ a_type_id = [1, 2, 4, 5, 7, 10]
+ data['nb_soiree'] = Activity.objects.filter(
+ date_end__gte=b.date_start,
+ date_start__lte=b.date_end,
+ valid=True,
+ activity_type__pk__in=a_type_id).count()
+
+ if verb >= 3: print('pots, nb_entree_pot')
+ # nb d'entrée totale aux pots
+ pot_id = [1, 4, 10]
+ pots = Activity.objects.filter(
+ date_end__gte=b.date_start,
+ date_start__lte=b.date_end,
+ activity_type__pk__in=pot_id)
+ data['pots'] = pots # utile dans unique_data
+ data['nb_entree_pot'] = 0
+ for pot in pots:
+ data['nb_entree_pot'] += Entry.objects.filter(activity=pot).count()
+
+ if verb >= 3: print('top3_buttons')
+ # top 3 des boutons les plus cliqués
+ T = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ amount__gt=0,
+ recurrenttransaction__template__pk__gte=-1)
+
+ d = {}
+ for t in T:
+ if t.recurrenttransaction.template.name in d:
+ d[t.recurrenttransaction.template.name] += t.quantity
+ else : d[t.recurrenttransaction.template.name] = t.quantity
+
+ data['top3_buttons'] = list(sorted(d.items(), key=lambda item: item[1], reverse=True))[:3]
+
+ if verb >= 3: print('class_conso_all')
+ # le classement des plus gros consommateurs (BDE + club)
+ T = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ source__noteuser__user__pk__gte=-1,
+ destination__noteclub__club__pk__gte=-1)
+
+ d = {}
+ for t in T:
+ if t.source in d: d[t.source] += t.total
+ else : d[t.source] = t.total
+
+ data['class_conso_all'] = dict(sorted(d.items(), key=lambda item: item[1], reverse=True))
+
+ if verb >= 3: print('class_conso_bde')
+ # le classement des plus gros consommateurs BDE
+ T = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ source__noteuser__user__pk__gte=-1,
+ destination=5)
+
+ d = {}
+ for t in T:
+ if t.source in d: d[t.source] += t.total
+ else : d[t.source] = t.total
+
+ data['class_conso_bde'] = dict(sorted(d.items(), key=lambda item: item[1], reverse=True))
+
else:
# make your wrapped or reuse previous wrapped
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, change, create, global_data=None, verb=1):
- data = {}
+ def unique_data(self, bde, note, global_data=None, verb=1):
+ data = []
for i in range(len(bde)):
- if verb >= 2: print('\033[mlen(note) = {nb}'.format(nb=len(note)))
- note_filtered = self.filter_note(bde[i], note, change, create, verb=verb)
- if verb >= 2: print('\033[mlen(note_after_filter) = {nb}'.format(nb=len(note_filtered)))
+ data_bde = []
if bde[i].name == 'Rave Part[list]':
- # TODO
- data = {}
+ if verb >= 3:
+ total = len(note[i])
+ current = 0
+ 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__():
+ # première conso du mandat
+ T = Transaction.objects.filter(
+ valid=True,
+ recurrenttransaction__template__id__gte=-1,
+ created_at__gte=bde[i].date_start,
+ created_at__lte=bde[i].date_end,
+ source=n,
+ destination=5).order_by('created_at')
+ if T:
+ d['first_conso'] = T[0].template.name
+ else:
+ d['first_conso'] = ''
+ # Wei + bus
+ W = WEIClub.objects.filter(
+ date_start__lte=bde[i].date_end,
+ date_end__gte=bde[i].date_start)
+ if not W:
+ d['wei'] = ''
+ d['bus'] = ''
+ else:
+ w = W[0]
+ M = Membership.objects.filter(club=w, user=n.user)
+ if not M:
+ d['wei'] = ''
+ d['bus'] = ''
+ else :
+ A = []
+ for a in w.note.alias.iterator():
+ A.append(str(a))
+ d['wei'] = A[-1]
+ d['bus'] = M[0].weimembership.bus.name
+ # top3 conso
+ T = Transaction.objects.filter(
+ valid=True,
+ created_at__gte=bde[i].date_start,
+ created_at__lte=bde[i].date_end,
+ source=n,
+ amount__gt=0,
+ recurrenttransaction__template__id__gte=-1)
+ dt = {}
+ dc = {}
+ for t in T:
+ if t.template.name in dt: dt[t.template.name] += t.quantity
+ else : dt[t.template.name] = t.quantity
+ if t.template.category.name in dc: dc[t.template.category.name] += t.quantity
+ else : dc[t.template.category.name] = t.quantity
+
+ d['top3_conso'] = list(sorted(dt.items(), key=lambda item: item[1], reverse=True))[:3]
+ # catégorie de bouton préférée
+ if dc:
+ d['top_category'] = list(sorted(dc.items(), key=lambda item: item[1], reverse=True))[0][0]
+ else:
+ d['top_category'] = ''
+ # nombre de pot, et nombre d'entrée pot
+ pots = global_data['pots']
+ d['nb_pots'] = pots.count()
+
+ p = 0
+ for pot in pots:
+ if Entry.objects.filter(activity=pot,note=n):
+ p += 1
+ d['nb_pot_entry'] = p
+ # ton nombre de rechargement
+ d['nb_rechargement'] = Transaction.objects.filter(
+ valid=True,
+ created_at__gte=bde[i].date_start,
+ created_at__lte=bde[i].date_end,
+ destination=n,
+ source__pk__in=[1,2,3,4]).count()
+ # ajout info globale spécifique user
+ # classement et montant conso all
+ d['class_part_all'] = len(global_data['class_conso_all'])
+ if n in global_data['class_conso_all']:
+ d['class_conso_all'] = list(global_data['class_conso_all']).index(n) + 1
+ d['amount_conso_all'] = global_data['class_conso_all'][n]/100
+ else:
+ d['class_conso_all'] = 0
+ d['amount_conso_all'] = 0
+ # classement et montant conso bde
+ d['class_part_bde'] = len(global_data['class_conso_bde'])
+ if n in global_data['class_conso_bde']:
+ d['class_conso_bde'] = list(global_data['class_conso_bde']).index(n) + 1
+ d['amount_conso_bde'] = global_data['class_conso_bde'][n]/100
+ else:
+ d['class_conso_bde'] = 0
+ d['amount_conso_bde'] = 0
+
+ if 'club' in n.__dir__():
+ # plus gros consommateur
+ T = Transaction.objects.filter(
+ valid=True,
+ created_at__lte=bde[i].date_end,
+ created_at__gte=bde[i].date_start,
+ destination=n,
+ source__noteuser__user__pk__gte=-1)
+ dt = {}
+
+ for t in T:
+ if t.source.user.username in dt: dt[t.source.user.username] += t.total
+ else : dt[t.source.user.username] = t.total
+ if dt:
+ d['big_consumer'] = list(sorted(dt.items(), key=lambda item: item[1], reverse=True))[0]
+ d['big_consumer'] = (d['big_consumer'][0], d['big_consumer'][1]/100)
+ else:
+ d['big_consumer'] = ''
+ # plus gros créancier
+ T = Transaction.objects.filter(
+ valid=True,
+ created_at__lte=bde[i].date_end,
+ created_at__gte=bde[i].date_start,
+ source=n,
+ destination__noteuser__user__pk__gte=-1)
+ dt = {}
+
+ for t in T:
+ if t.destination.user.username in dt: dt[t.destination.user.username] += t.total
+ else : dt[t.destination.user.username] = t.total
+ if dt:
+ d['big_creancier'] = list(sorted(dt.items(), key=lambda item: item[1], reverse=True))[0]
+ d['big_creancier'] = (d['big_creancier'][0], d['big_creancier'][1]/100)
+ else:
+ d['big_creancier'] = ''
+ # nb de soirée organisée
+ d['nb_soiree_orga'] = Activity.objects.filter(
+ valid=True,
+ date_start__lte=bde[i].date_end,
+ date_end__gte=bde[i].date_start,
+ organizer=n.club).count()
+ # nb de membres cumulé
+ d['nb_member'] = Membership.objects.filter(
+ date_start__lte=bde[i].date_end,
+ date_end__gte=bde[i].date_start,
+ club=n.club).distinct('user').count()
+
+ # ajout info globale
+ # top3 button
+ d['glob_top3_conso'] = global_data['top3_buttons']
+ # nb entree pot
+ d['glob_nb_entree_pot'] = global_data['nb_entree_pot']
+ # nb soiree
+ d['glob_nb_soiree'] = global_data['nb_soiree']
+ # nb vieux con
+ d['glob_nb_vieux_con'] = global_data['nb_vieux_con']
+ # nb transaction
+ d['glob_nb_transaction'] = global_data['nb_transaction']
+
+ data_bde.append(json.dumps(d))
+ if verb >= 3:
+ current += 1
+ print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
+
else:
# make your wrapped or reuse previous wrapped
raise NotImplementedError("The BDE: {bde_name} has not personalized wrapped, make it !"
.format(bde_name=bde[i].name))
+ data.append(data_bde)
return data
def make_wrapped(self, unique_data, note, bde, change, create, verb=1):
+ if verb >= 3:
+ current = 0
+ total = 0
+ for l in note:
+ total += len(l)
+ print('\033[mMake {nb} wrapped'.format(nb=total))
for i in range(len(bde)):
- for j in len(note[i]):
+ for j in range(len(note[i])):
if create and not Wrapped.objects.filter(bde=bde[i], note=note[i][j]):
Wrapped(bde=bde[i],
note=note[i][j],
@@ -271,11 +534,14 @@ class Command(BaseCommand):
w = Wrapped.objects.get(bde=bde[i], note=note[i][j])
w.data_json = unique_data[i][j]
w.save()
+ if verb >= 3:
+ current += 1
+ print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
return
def filter_note(self, bde, note, change, create, verb=1):
if change and create:
- return note
+ return list(note)
if change and not create:
note_new = []
for n in note:
diff --git a/apps/wrapped/static/wrapped/css/1/custom.css b/apps/wrapped/static/wrapped/css/1/custom.css
new file mode 100644
index 00000000..50415ffb
--- /dev/null
+++ b/apps/wrapped/static/wrapped/css/1/custom.css
@@ -0,0 +1,69 @@
+:root {
+ --accent-primary: #FF0065;
+ --accent-secondary: #FFCB20;
+}
+body {
+ font-family: Arial, sans-serif;
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
+ color: white;
+ text-align: center;
+ padding: 50px;
+}
+#name {
+ font-size: 2em;
+ font-weight: bold;
+ text-shadow: 2px 2px 15px var(--accent-secondary);
+}
+.wrap-container {
+ max-width: 500px;
+ margin: auto;
+ padding: 20px;
+ background: rgba(0, 0, 0, 0.8);
+ border-radius: 10px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+}
+.category {
+ display: flex;
+ justify-content: space-between;
+ padding: 10px;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 5px;
+ margin: 10px 0;
+ padding: 10px;
+}
+h1 {
+ font-size: 2.5em;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
+ -webkit-background-clip: text;
+}
+.list {
+ list-style: none;
+ padding: 0;
+}
+.list li {
+ display: flex;
+ justify-content: space-between;
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
+ margin: 10px 0;
+ padding: 10px;
+ border-radius: 5px;
+ font-weight: bold;
+}
+.ranking-bar {
+ width: 100%;
+ height: 20px;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 10px;
+ overflow: hidden;
+ margin-top: 10px;
+ position: relative;
+}
+.ranking-progress {
+ height: 100%;
+ width: 0%;
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
+ border-radius: 10px;
+}
diff --git a/apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png b/apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png
new file mode 100644
index 00000000..40f01b34
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png b/apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png
new file mode 100644
index 00000000..6bd8616e
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png b/apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png
new file mode 100644
index 00000000..ccd657d9
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/browserconfig.xml b/apps/wrapped/static/wrapped/favicon/1/browserconfig.xml
new file mode 100644
index 00000000..eb5c9b5e
--- /dev/null
+++ b/apps/wrapped/static/wrapped/favicon/1/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
Default content...
+ {% endblock %} +