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

Compare commits

..

18 Commits

Author SHA1 Message Date
2a067070e4 Merge branch 'qrcode' into 'main'
Draft: Qrcode

See merge request bde/nk20!196
2025-03-19 12:37:47 +01:00
5ef019c5c2 Merge branch 'notekfet_wrapped' into 'main'
Rewrite script and add test

See merge request bde/nk20!300
2025-03-18 16:11:33 +01:00
8da62e62fb Rewrite script and add test 2025-03-18 15:53:02 +01:00
56a43396d4 Merge branch 'Add_some_permissions' into 'main'
Add some permissions

See merge request bde/nk20!296
2025-03-17 13:16:01 +01:00
cb61c511ce Merge branch 'notekfet_wrapped' into 'main'
Another tables and doc

See merge request bde/nk20!299
2025-03-14 00:44:57 +01:00
25bfa575ed Another tables and doc 2025-03-14 00:31:25 +01:00
e21d9fcfbe Merge branch 'notekfet_wrapped' into 'main'
Notekfet wrapped

See merge request bde/nk20!298
2025-03-14 00:11:04 +01:00
b293904525 Another tables and doc 2025-03-13 23:56:10 +01:00
bd7e6b8ad4 add table, add some translation 2025-03-13 21:08:52 +01:00
a208a4fa25 Merge branch 'bde_color' into 'main'
Resize and compress image, add shiny button

See merge request bde/nk20!297
2025-03-13 00:44:45 +01:00
4799b2c52d Resize and compress image, add shiny button 2025-03-12 23:42:37 +01:00
e6f3084588 Added a first pass for automatically entering an activity with a qrcode 2023-10-11 18:01:51 +02:00
145e55da75 remove useless comment 2022-03-22 15:06:04 +01:00
d3ba95cdca Insecable space for more clarity 2022-03-22 15:04:41 +01:00
8ffb0ebb56 Use DetailView 2022-03-22 14:59:01 +01:00
5038af9e34 Final html template 2022-03-22 14:58:26 +01:00
819b4214c9 Add QRCode View, URL and test template 2022-03-22 12:26:44 +01:00
b8a93b0b75 Add link to QR code 2022-03-19 16:25:15 +01:00
19 changed files with 576 additions and 146 deletions

View File

@ -38,6 +38,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</a> </a>
<input id="alias" type="text" class="form-control" placeholder="Nom/note ..."> <input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
<button id="trigger" class="btn btn-secondary">Click me !</button>
<hr> <hr>
@ -63,15 +64,46 @@ SPDX-License-Identifier: GPL-3.0-or-later
refreshBalance(); refreshBalance();
} }
function process_qrcode() {
let name = alias_obj.val();
$.get("/api/note/note?search=" + name + "&format=json").done(
function (res) {
let note = res.results[0];
$.post("/api/activity/entry/?format=json", {
csrfmiddlewaretoken: CSRF_TOKEN,
activity: {{ activity.id }},
note: note.id,
guest: null
}).done(function () {
addMsg(interpolate(gettext(
"Entry made for %s whose balance is %s €"),
[note.name, note.balance / 100]), "success", 4000);
reloadTable(true);
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}
alias_obj.keyup(function(event) { alias_obj.keyup(function(event) {
let code = event.originalEvent.keyCode let code = event.originalEvent.keyCode
if (65 <= code <= 122 || code === 13) { if (65 <= code <= 122 || code === 13) {
debounce(reloadTable)() debounce(reloadTable)()
} }
if (code === 0)
process_qrcode();
}); });
$(document).ready(init); $(document).ready(init);
alias_obj2 = document.getElementById("alias");
$("#trigger").click(function (e) {
addMsg("Clicked", "success", 1000);
alias_obj.val(alias_obj.val() + "\0");
alias_obj2.dispatchEvent(new KeyboardEvent('keyup'));
})
function init() { function init() {
$(".table-row").click(function (e) { $(".table-row").click(function (e) {
let target = e.target.parentElement; let target = e.target.parentElement;

View File

@ -60,7 +60,10 @@
{% if user_object.pk == user.pk %} {% if user_object.pk == user.pk %}
<div class="text-center"> <div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}"> <a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<i class="fa fa-cogs"></i>{% trans 'API token' %} <i class="fa fa-cogs"></i>&nbsp;{% trans 'API token' %}
</a>
<a class="small badge badge-secondary" href="{% url 'member:qr_code' user_object.pk %}">
<i class="fa fa-qrcode"></i>&nbsp;{% trans 'QR Code' %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -0,0 +1,36 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block content %}
<div class="card bg-light">
<h3 class="card-header text-center">
{% trans "QR Code for" %} {{ user_object.username }} ({{ user_object.first_name }} {{user_object.last_name }})
</h3>
<div class="text-center" id="qrcode">
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
var qrc = new QRCode(document.getElementById("qrcode"), {
text: "{{ user_object.pk }}\0",
width: 1024,
height: 1024
});
</script>
{% endblock %}
{% block extracss %}
<style>
img {
width: 100%
}
</style>
{% endblock %}

View File

@ -25,4 +25,5 @@ urlpatterns = [
path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"), path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"),
path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"), path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
path('user/<int:pk>/qr_code/', views.QRCodeView.as_view(), name='qr_code'),
] ]

View File

@ -402,6 +402,14 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
context['token'] = Token.objects.get_or_create(user=self.request.user)[0] context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
return context return context
class QRCodeView(LoginRequiredMixin, DetailView):
"""
Affiche le QR Code
"""
model = User
context_object_name = "user_object"
template_name = "member/qr_code.html"
extra_context = {"title": _("QR Code")}
# ******************************* # # ******************************* #
# CLUB # # CLUB #

View File

@ -294,3 +294,10 @@ searchbar.addEventListener("keyup", function (e) {
if (firstMatch && e.key === "Enter") if (firstMatch && e.key === "Enter")
firstMatch.click() 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

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

View File

@ -6,17 +6,24 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="row justify-content-center"> <div id="wrapped_tables">
<div class="col-md-10"> {% if tables|length > 0 %}
<div class="card card-border shadow"> <div class="card bg-light mb-3">
<div class="card-header text-center"> <h3 class="card-header text-center">
<h5> {{ title }}</h5> {% trans "My wrapped" %}
</div> </h3>
<div class="card-body px-0 py-0" id="wrapped_table"> {% render_table tables.1 %}
{% render_table table %}
</div>
</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> </div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}
@ -25,7 +32,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
let club_not_public = {{ club_not_public }}; 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'));} 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() { function refreshTable() {
$("#wrapped_table").load(location.pathname + " #wrapped_table"); $("#wrapped_tables").load(location.pathname + " #wrapped_tables");
} }
function copylink(id) { function copylink(id) {

View File

View File

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

118
docs/_static/img/graphs/wrapped.svg vendored Normal file
View File

@ -0,0 +1,118 @@
<?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>

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

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

108
docs/apps/wrapped.rst Normal file
View File

@ -0,0 +1,108 @@
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

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-25 13:47+0100\n" "POT-Creation-Date: 2025-03-13 21:08+0100\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: bleizi <bleizi@crans.org>\n" "Last-Translator: bleizi <bleizi@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n" "Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -865,7 +865,7 @@ msgstr "Taille maximale : 2 Mo"
msgid "This image cannot be loaded." msgid "This image cannot be loaded."
msgstr "Cette image ne peut pas être chargée." msgstr "Cette image ne peut pas être chargée."
#: apps/member/forms.py:154 apps/member/views.py:103 #: apps/member/forms.py:154 apps/member/views.py:117
#: apps/registration/forms.py:33 apps/registration/views.py:282 #: apps/registration/forms.py:33 apps/registration/views.py:282
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Un alias avec un nom similaire existe déjà." msgstr "Un alias avec un nom similaire existe déjà."
@ -1194,11 +1194,11 @@ msgstr "Adhésion de {user} pour le club {club}"
msgid "The role {role} does not apply to the 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}." msgstr "Le rôle {role} ne s'applique pas au club {club}."
#: apps/member/models.py:388 apps/member/views.py:745 #: apps/member/models.py:388 apps/member/views.py:759
msgid "User is already a member of the club" msgid "User is already a member of the club"
msgstr "L'utilisateur·rice est déjà membre du club" msgstr "L'utilisateur·rice est déjà membre du club"
#: apps/member/models.py:400 apps/member/views.py:754 #: apps/member/models.py:400 apps/member/views.py:768
msgid "User is not a member of the parent club" msgid "User is not a member of the parent club"
msgstr "L'utilisateur·rice n'est pas membre du club parent" msgstr "L'utilisateur·rice n'est pas membre du club parent"
@ -1251,7 +1251,7 @@ msgid "Account #"
msgstr "Compte n°" msgstr "Compte n°"
#: apps/member/templates/member/base.html:48 #: apps/member/templates/member/base.html:48
#: apps/member/templates/member/base.html:62 apps/member/views.py:60 #: apps/member/templates/member/base.html:62 apps/member/views.py:61
#: apps/registration/templates/registration/future_profile_detail.html:48 #: apps/registration/templates/registration/future_profile_detail.html:48
#: apps/wei/templates/wei/weimembership_form.html:117 #: apps/wei/templates/wei/weimembership_form.html:117
msgid "Update Profile" msgid "Update Profile"
@ -1312,8 +1312,8 @@ msgstr ""
"seront à nouveau possible." "seront à nouveau possible."
#: apps/member/templates/member/club_alias.html:10 #: apps/member/templates/member/club_alias.html:10
#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:304 #: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:318
#: apps/member/views.py:545 #: apps/member/views.py:559
msgid "Note aliases" msgid "Note aliases"
msgstr "Alias de la note" msgstr "Alias de la note"
@ -1505,51 +1505,51 @@ msgstr "Sauvegarder les changements"
msgid "Registrations" msgid "Registrations"
msgstr "Inscriptions" msgstr "Inscriptions"
#: apps/member/views.py:73 apps/registration/forms.py:23 #: apps/member/views.py:74 apps/registration/forms.py:23
msgid "This address must be valid." msgid "This address must be valid."
msgstr "Cette adresse doit être valide." msgstr "Cette adresse doit être valide."
#: apps/member/views.py:140 #: apps/member/views.py:154
msgid "Profile detail" msgid "Profile detail"
msgstr "Détails de l'utilisateur⋅rice" msgstr "Détails de l'utilisateur⋅rice"
#: apps/member/views.py:206 #: apps/member/views.py:220
msgid "Search user" msgid "Search user"
msgstr "Chercher un·e utilisateur·rice" msgstr "Chercher un·e utilisateur·rice"
#: apps/member/views.py:258 #: apps/member/views.py:272
msgid "Note friendships" msgid "Note friendships"
msgstr "Amitiés note" msgstr "Amitiés note"
#: apps/member/views.py:328 #: apps/member/views.py:342
msgid "Update note picture" msgid "Update note picture"
msgstr "Modifier la photo de la note" msgstr "Modifier la photo de la note"
#: apps/member/views.py:377 #: apps/member/views.py:391
msgid "Manage auth token" msgid "Manage auth token"
msgstr "Gérer les jetons d'authentification" msgstr "Gérer les jetons d'authentification"
#: apps/member/views.py:404 #: apps/member/views.py:418
msgid "Create new club" msgid "Create new club"
msgstr "Créer un nouveau club" msgstr "Créer un nouveau club"
#: apps/member/views.py:423 #: apps/member/views.py:437
msgid "Search club" msgid "Search club"
msgstr "Chercher un club" msgstr "Chercher un club"
#: apps/member/views.py:461 #: apps/member/views.py:475
msgid "Club detail" msgid "Club detail"
msgstr "Détails du club" msgstr "Détails du club"
#: apps/member/views.py:573 #: apps/member/views.py:587
msgid "Update club" msgid "Update club"
msgstr "Modifier le club" msgstr "Modifier le club"
#: apps/member/views.py:607 #: apps/member/views.py:621
msgid "Add new member to the club" msgid "Add new member to the club"
msgstr "Ajouter un·e nouvelleau membre au club" msgstr "Ajouter un·e nouvelleau membre au club"
#: apps/member/views.py:736 apps/wei/views.py:991 #: apps/member/views.py:750 apps/wei/views.py:991
msgid "" msgid ""
"This user don't have enough money to join this club, and can't have a " "This user don't have enough money to join this club, and can't have a "
"negative balance." "negative balance."
@ -1557,19 +1557,19 @@ msgstr ""
"Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et ne " "Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et ne "
"peut pas avoir un solde négatif." "peut pas avoir un solde négatif."
#: apps/member/views.py:758 #: apps/member/views.py:772
msgid "The membership must start after {:%m-%d-%Y}." msgid "The membership must start after {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}." msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}."
#: apps/member/views.py:763 #: apps/member/views.py:777
msgid "The membership must begin before {:%m-%d-%Y}." msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}." msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
#: apps/member/views.py:913 #: apps/member/views.py:927
msgid "Manage roles of an user in the club" msgid "Manage roles of an user in the club"
msgstr "Gérer les rôles d'un⋅e utilisateur⋅rice dans le club" msgstr "Gérer les rôles d'un⋅e utilisateur⋅rice dans le club"
#: apps/member/views.py:938 #: apps/member/views.py:952
msgid "Members of the club" msgid "Members of the club"
msgstr "Membres du club" msgstr "Membres du club"
@ -2084,7 +2084,7 @@ msgid "Button displayed"
msgstr "Bouton affiché" msgstr "Bouton affiché"
#: apps/note/templates/note/transactiontemplate_list.html:100 #: apps/note/templates/note/transactiontemplate_list.html:100
#: apps/wrapped/templates/wrapped/wrapped_list.html:63 #: apps/wrapped/templates/wrapped/wrapped_list.html:70
msgid "An error occured" msgid "An error occured"
msgstr "Une erreur s'est produite" msgstr "Une erreur s'est produite"
@ -3662,6 +3662,14 @@ msgstr "soirée·s organisée·s"
msgid "distinct members" msgid "distinct members"
msgstr "Membres distinct·e·s" 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 #: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13
msgid "You participate to the wei: " msgid "You participate to the wei: "
msgstr "Tu as participé au wei : " msgstr "Tu as participé au wei : "
@ -3699,7 +3707,15 @@ msgstr "avec"
msgid "Your expenses to BDE: " msgid "Your expenses to BDE: "
msgstr "Tes dépenses au BDE : " msgstr "Tes dépenses au BDE : "
#: apps/wrapped/templates/wrapped/wrapped_list.html:26 #: 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
msgid "" msgid ""
"Do not forget to ask permission to people who are in your wrapped before to " "Do not forget to ask permission to people who are in your wrapped before to "
"make them public" "make them public"
@ -3707,19 +3723,19 @@ msgstr ""
"N'oublies pas de demander la permission des personnes apparaissant dans un " "N'oublies pas de demander la permission des personnes apparaissant dans un "
"wrapped avant de le rendre public" "wrapped avant de le rendre public"
#: apps/wrapped/templates/wrapped/wrapped_list.html:33 #: apps/wrapped/templates/wrapped/wrapped_list.html:40
msgid "Link copied" msgid "Link copied"
msgstr "Lien copié" msgstr "Lien copié"
#: apps/wrapped/templates/wrapped/wrapped_list.html:58 #: apps/wrapped/templates/wrapped/wrapped_list.html:65
msgid "Wrapped is private" msgid "Wrapped is private"
msgstr "Le wrapped est privé" msgstr "Le wrapped est privé"
#: apps/wrapped/templates/wrapped/wrapped_list.html:59 #: apps/wrapped/templates/wrapped/wrapped_list.html:66
msgid "Wrapped is public" msgid "Wrapped is public"
msgstr "Le wrapped est public" msgstr "Le wrapped est public"
#: apps/wrapped/views.py:24 #: apps/wrapped/views.py:28
msgid "List of wrapped" msgid "List of wrapped"
msgstr "Liste des wrapped" msgstr "Liste des wrapped"

View File

@ -74,7 +74,7 @@ mark {
/* MODE VIEUXCON=ON */ /* MODE VIEUXCON=ON */
/* background-color: rgb(166, 0, 2) !important; */ /* background-color: rgb(166, 0, 2) !important; */
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
background-image: url('/static/wrapped/img/1/bg.png'); background-image: url('/static/img/rp_bg.png');
} }
html { html {
@ -136,33 +136,32 @@ body {
border-color: rgb(255, 0, 101); border-color: rgb(255, 0, 101);
} }
.btn-outline-dark:nth-child(even) { .btn-outline-dark-shiny {
color: rgba(255, 203, 32, 75%); background-color: #222;
border-color: #61605b;
color: rgba(255, 0, 101, 75%);
} }
.btn-outline-dark:nth-child(odd) { .btn-outline-dark-shiny:hover,
color: rgba(255, 0, 101, 75%); .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 { .btn-outline-dark {
background-color: #222; background-color: #222;
border-color: #61605b; border-color: #61605b;
color: rgba(255, 203, 32, 75%);
} }
.btn-outline-dark:hover:nth-child(even), .btn-outline-dark:hover,
.btn-outline-dark:not(:disabled):not(.disabled).active, .btn-outline-dark:not(:disabled):not(.disabled).active,
.btn-outline-dark:not(:disabled):not(.disabled):active { .btn-outline-dark:not(:disabled):not(.disabled):active {
color: rgb(0, 0, 0); color: rgb(0, 0, 0);
background-color: rgb(255, 203, 32); background-color: rgb(255, 0, 101);
border-color: rgb(255, 0, 101); border-color: rgb(255, 203, 32);
}
.btn-outline-dark:hover:nth-child(odd),
.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, 203, 32);
border-color: rgb(255, 0, 101);
} }
a { a {

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

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