Compare commits
90 Commits
96539d262f
...
notekfet_w
Author | SHA1 | Date | |
---|---|---|---|
|
8da62e62fb | ||
|
25bfa575ed | ||
|
bd7e6b8ad4 | ||
|
bd9773a8af | ||
|
cdeb76d9f8 | ||
|
ac4574200d | ||
|
b17d31e8ee | ||
|
30d27459dd | ||
|
333f7aa284 | ||
|
587314e03c | ||
|
9f888a5281 | ||
|
88b1a25ca0 | ||
|
8cb50f58f2 | ||
|
041a8f20a9 | ||
|
b1ffb28532 | ||
|
6225fb51f1 | ||
|
1dd74e8024 | ||
|
1af9f5f23c | ||
|
83d5a7ceff | ||
|
a7cba0a4a3 | ||
|
ccd9a66ab9 | ||
|
c7a92fa4b2 | ||
|
5f1b698d58 | ||
|
0a5368d23f | ||
|
26b351a51c | ||
|
1836677c47 | ||
|
e7a98c86f0 | ||
|
eb5044490b | ||
|
983d7ec052 | ||
|
dc56deaf85 | ||
|
19d1ecfc66 | ||
|
694f54e1c4 | ||
|
b0c3eee699 | ||
|
cd942779ca | ||
|
0d0fdef363 | ||
|
7ed544b3ac | ||
|
821efbf78b | ||
|
a209e0d366 | ||
|
ef485e0628 | ||
|
1481aa0635 | ||
|
867bf9fd25 | ||
|
47fda0ea36 | ||
|
623290827a | ||
|
a87ce625f3 | ||
|
3559787fa7 | ||
|
bd6ed27ae5 | ||
|
43dc676747 | ||
|
caaeab6b0b | ||
|
54ba786884 | ||
|
80e109114f | ||
|
787005e60d | ||
|
414e103686 | ||
|
942d887c2e | ||
|
a63c34fe37 | ||
|
2be6133458 | ||
|
7975fe47a6 | ||
|
476fbceeea | ||
|
8fbaa0bdc8 | ||
|
a0de63effd | ||
|
09fb1d227e | ||
|
2e27d4f05c | ||
|
5d16dc4e7d | ||
|
3c34033bf5 | ||
|
131f508433 | ||
|
c1a353963a | ||
|
178ce2b579 | ||
|
9162319734 | ||
|
5d2a8e9b79 | ||
|
33c94d0720 | ||
|
5040e8e8ea | ||
|
c5697c4cb4 | ||
|
e188c5a153 | ||
|
94e1fdc93a | ||
|
d1ef367bab | ||
|
0fbb19c5fd | ||
|
21cbf2b21a | ||
|
185a2cabf2 | ||
|
7552e55c8d | ||
|
361de9f8b4 | ||
|
e2426bd6a6 | ||
|
7fea619a9f | ||
|
7b5eefcc0a | ||
|
e4aa16986f | ||
|
b92e6e4e10 | ||
|
dd675b3676 | ||
|
f50849b4f8 | ||
|
73ff35c232 | ||
|
a5df98224f | ||
|
2cb9ac8735 | ||
|
35d4849a28 |
@@ -7,21 +7,6 @@ stages:
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Debian Bullseye
|
||||
py39-django42:
|
||||
stage: test
|
||||
image: debian:bullseye
|
||||
before_script:
|
||||
- >
|
||||
apt-get update &&
|
||||
apt-get install --no-install-recommends -y
|
||||
python3-django python3-django-crispy-forms
|
||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
script: tox -e py39-django42
|
||||
|
||||
# Ubuntu 22.04
|
||||
py310-django42:
|
||||
stage: test
|
||||
@@ -54,8 +39,6 @@ py311-django42:
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
script: tox -e py311-django42
|
||||
|
||||
|
||||
|
||||
linters:
|
||||
stage: quality-assurance
|
||||
image: debian:bookworm
|
||||
|
@@ -265,12 +265,11 @@ class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
|
||||
# Keep only users that have a note
|
||||
note_qs = note_qs.filter(note__noteuser__isnull=False)
|
||||
|
||||
# Keep only members
|
||||
# Keep only valid members
|
||||
note_qs = note_qs.filter(
|
||||
note__noteuser__user__memberships__club=activity.attendees_club,
|
||||
note__noteuser__user__memberships__date_start__lte=timezone.now(),
|
||||
note__noteuser__user__memberships__date_end__gte=timezone.now(),
|
||||
)
|
||||
note__noteuser__user__memberships__date_end__gte=timezone.now()).exclude(note__inactivity_reason='forced')
|
||||
|
||||
# Filter with permission backend
|
||||
note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request, Alias, "view"))
|
||||
@@ -330,7 +329,7 @@ class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
|
||||
context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk
|
||||
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
|
||||
|
||||
activities_open = Activity.objects.filter(open=True).filter(
|
||||
activities_open = Activity.objects.filter(open=True, activity_type__manage_entries=True).filter(
|
||||
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
|
||||
context["activities_open"] = [a for a in activities_open
|
||||
if PermissionBackend.check_perm(self.request,
|
||||
|
@@ -47,6 +47,10 @@ if "wei" in settings.INSTALLED_APPS:
|
||||
from wei.api.urls import register_wei_urls
|
||||
register_wei_urls(router, 'wei')
|
||||
|
||||
if "wrapped" in settings.INSTALLED_APPS:
|
||||
from wrapped.api.urls import register_wrapped_urls
|
||||
register_wrapped_urls(router, 'wrapped')
|
||||
|
||||
app_name = 'api'
|
||||
|
||||
# Wire up our API using automatic URL routing.
|
||||
|
@@ -44,6 +44,7 @@ class ProfileForm(forms.ModelForm):
|
||||
"""
|
||||
A form for the extras field provided by the :model:`member.Profile` model.
|
||||
"""
|
||||
# Remove widget=forms.HiddenInput() if you want to use report frequency.
|
||||
report_frequency = forms.IntegerField(required=False, initial=0, label=_("Report frequency"))
|
||||
|
||||
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
|
||||
@@ -76,7 +77,8 @@ class ProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = '__all__'
|
||||
exclude = ('user', 'email_confirmed', 'registration_valid', )
|
||||
# Remove ml_[asso]_registration from exclude if the concerned association uses nk20 to manage its mailing list.
|
||||
exclude = ('user', 'email_confirmed', 'registration_valid', 'ml_sport_registration', )
|
||||
|
||||
|
||||
class ImageForm(forms.Form):
|
||||
|
@@ -42,12 +42,12 @@ class UserTable(tables.Table):
|
||||
"""
|
||||
alias = tables.Column()
|
||||
|
||||
section = tables.Column(accessor='profile__section')
|
||||
section = tables.Column(accessor='profile__section', orderable=False)
|
||||
|
||||
# Override the column to let replace the URL
|
||||
email = tables.EmailColumn(linkify=lambda record: "mailto:{}".format(record.email))
|
||||
|
||||
balance = tables.Column(accessor='note__balance', verbose_name=_("Balance"))
|
||||
balance = tables.Column(accessor='note__balance', verbose_name=_("Balance"), orderable=False)
|
||||
|
||||
def render_email(self, record, value):
|
||||
# Replace the email by a dash if the user can't see the profile detail
|
||||
|
@@ -11,7 +11,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note…">
|
||||
<input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note...">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="only_active">
|
||||
<input type="checkbox" class="checkboxinput form-check-input" id="only_active"
|
||||
@@ -66,4 +66,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
roles_obj.change(reloadTable);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@@ -26,6 +26,7 @@ from note_kfet.middlewares import _set_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.models import Role
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
from django import forms
|
||||
|
||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
|
||||
CustomAuthenticationForm, MembershipRolesForm
|
||||
@@ -72,11 +73,24 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
form.fields['email'].required = True
|
||||
form.fields['email'].help_text = _("This address must be valid.")
|
||||
|
||||
if PermissionBackend.check_perm(self.request, "member.change_profile", context['user_object'].profile):
|
||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||
data=self.request.POST if self.request.POST else None)
|
||||
if not self.object.profile.report_frequency:
|
||||
del context['profile_form'].fields["last_report"]
|
||||
profile_form = self.profile_form(instance=context['user_object'].profile,
|
||||
data=self.request.POST if self.request.POST else None)
|
||||
|
||||
if not self.object.profile.report_frequency:
|
||||
del profile_form.fields["last_report"]
|
||||
|
||||
fields_to_check = list(profile_form.fields.keys())
|
||||
fields_modifiable = False
|
||||
|
||||
# Delete the fields for which the user does not have the permission to modify
|
||||
for field_name in fields_to_check:
|
||||
if not PermissionBackend.check_perm(self.request, f"member.change_profile_{field_name}", context['user_object'].profile):
|
||||
profile_form.fields[field_name].widget = forms.HiddenInput()
|
||||
else:
|
||||
fields_modifiable = True
|
||||
|
||||
if fields_modifiable:
|
||||
context['profile_form'] = profile_form
|
||||
|
||||
return context
|
||||
|
||||
|
@@ -260,11 +260,13 @@ class ButtonTable(tables.Table):
|
||||
text=_('edit'),
|
||||
accessor='pk',
|
||||
verbose_name=_("Edit"),
|
||||
orderable=False,
|
||||
)
|
||||
|
||||
hideshow = tables.Column(
|
||||
verbose_name=_("Hide/Show"),
|
||||
accessor="pk",
|
||||
orderable=False,
|
||||
attrs={
|
||||
'td': {
|
||||
'class': 'col-sm-1',
|
||||
@@ -276,7 +278,8 @@ class ButtonTable(tables.Table):
|
||||
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
attrs={'td': {'class': 'col-sm-1'}},
|
||||
verbose_name=_("Delete"), )
|
||||
verbose_name=_("Delete"),
|
||||
orderable=False, )
|
||||
|
||||
def render_amount(self, value):
|
||||
return pretty_money(value)
|
||||
|
@@ -31,3 +31,4 @@ class RoleAdmin(admin.ModelAdmin):
|
||||
Admin customisation for Role
|
||||
"""
|
||||
list_display = ('name', )
|
||||
filter_horizontal = ('permissions',)
|
||||
|
@@ -135,18 +135,18 @@ class Permission(models.Model):
|
||||
|
||||
# A json encoded Q object with the following grammar
|
||||
# query -> [] | {} (the empty query representing all objects)
|
||||
# query -> ["AND", query, …] AND multiple queries
|
||||
# | ["OR", query, …] OR multiple queries
|
||||
# query -> ["AND", query, ...] AND multiple queries
|
||||
# | ["OR", query, ...] OR multiple queries
|
||||
# | ["NOT", query] Opposite of query
|
||||
# query -> {key: value, …} A list of fields and values of a Q object
|
||||
# query -> {key: value, ...} A list of fields and values of a Q object
|
||||
# key -> string A field name
|
||||
# value -> int | string | bool | null Literal values
|
||||
# | [parameter, …] A parameter. See compute_param for more details.
|
||||
# | [parameter, ...] A parameter. See compute_param for more details.
|
||||
# | {"F": oper} An F object
|
||||
# oper -> [string, …] A parameter. See compute_param for more details.
|
||||
# | ["ADD", oper, …] Sum multiple F objects or literal
|
||||
# oper -> [string, ...] A parameter. See compute_param for more details.
|
||||
# | ["ADD", oper, ...] Sum multiple F objects or literal
|
||||
# | ["SUB", oper, oper] Substract two F objects or literal
|
||||
# | ["MUL", oper, …] Multiply F objects or literals
|
||||
# | ["MUL", oper, ...] Multiply F objects or literals
|
||||
# | int | string | bool | null Literal values
|
||||
# | ["F", string] A field
|
||||
#
|
||||
|
@@ -300,9 +300,13 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
# join_bde = True
|
||||
# join_kfet = True
|
||||
|
||||
if not join_bde:
|
||||
if not (join_bde or any(b for _, b in join_clubs)):
|
||||
# This software belongs to the BDE.
|
||||
form.add_error('join_bde', _("You must join the BDE."))
|
||||
form.add_error('join_bde', _("You must join a club."))
|
||||
return super().form_invalid(form)
|
||||
|
||||
if join_kfet and not join_bde:
|
||||
form.add_error('join_bde', _("You must also join the parent club BDE."))
|
||||
return super().form_invalid(form)
|
||||
|
||||
# Calculate required registration fee
|
||||
|
@@ -37,6 +37,7 @@ class InvoiceTable(tables.Table):
|
||||
args=[A('id')],
|
||||
verbose_name=_("delete"),
|
||||
text=_("Delete"),
|
||||
orderable=False,
|
||||
attrs={
|
||||
'th': {
|
||||
'id': 'delete-membership-header'
|
||||
@@ -70,6 +71,7 @@ class RemittanceTable(tables.Table):
|
||||
verbose_name=_("View"),
|
||||
args=[A("pk")],
|
||||
text=_("View"),
|
||||
orderable=False,
|
||||
attrs={
|
||||
'a': {'class': 'btn btn-primary'}
|
||||
}, )
|
||||
@@ -97,6 +99,7 @@ class SpecialTransactionTable(tables.Table):
|
||||
verbose_name=_("Remittance"),
|
||||
args=[A("specialtransactionproxy__pk")],
|
||||
text=_("Add"),
|
||||
orderable=False,
|
||||
attrs={
|
||||
'a': {'class': 'btn btn-primary'}
|
||||
}, )
|
||||
@@ -105,6 +108,7 @@ class SpecialTransactionTable(tables.Table):
|
||||
verbose_name=_("Remittance"),
|
||||
args=[A("specialtransactionproxy__pk")],
|
||||
text=_("Remove"),
|
||||
orderable=False,
|
||||
attrs={
|
||||
'a': {'class': 'btn btn-primary btn-danger'}
|
||||
}, )
|
||||
@@ -130,10 +134,12 @@ class SogeCreditTable(tables.Table):
|
||||
|
||||
amount = tables.Column(
|
||||
verbose_name=_("Amount"),
|
||||
orderable=False,
|
||||
)
|
||||
|
||||
valid = tables.Column(
|
||||
verbose_name=_("Valid"),
|
||||
orderable=False,
|
||||
)
|
||||
|
||||
def render_amount(self, value):
|
||||
|
@@ -82,7 +82,7 @@ WORDS = {
|
||||
5: "La quoi ?"
|
||||
}],
|
||||
"kokarde": ["Qu'est-ce que le mot Kokarde t'évoque ?", {
|
||||
1: "Vraiment pas mon truc les soirées…",
|
||||
1: "Vraiment pas mon truc les soirées...",
|
||||
2: "Bof, je viens pour manger et je repars aussitôt",
|
||||
3: "Je kiffe, good vibes",
|
||||
4: "Perso, je ne m'arrêterai pas de danser sur la piste !",
|
||||
@@ -117,15 +117,15 @@ WORDS = {
|
||||
5: "Je pourrais en faire à n'importe qui. Pourquoi ne pas créer le club Câl[ENS] ?"
|
||||
}],
|
||||
"vomi": ["Quel est ton rapport au vomi ?", {
|
||||
1: "C'est compliqué…",
|
||||
1: "C'est compliqué...",
|
||||
2: "Jamais je ne vomis mais je nettoie quand mes potes vomissent",
|
||||
3: "Jamais je ne vomis et jamais je ne nettoie celui de quelqu'un d'autre",
|
||||
4: "Je vomis quelquefois, ça arrive, faites pas cette tête, mais je fins toujours par nettoyer !",
|
||||
5: "Je vomis à chaque soirée et ce n'est jamais moi qui nettoie"
|
||||
}],
|
||||
"kfet": ["Qu'est ce que la Kfet t'évoque ?", {
|
||||
1: "La Kfet, quel lieu de dépravé⋅es sérieux…",
|
||||
2: "C'est un endroit à l'hygiène plus que douteuse…",
|
||||
1: "La Kfet, quel lieu de dépravé⋅es sérieux...",
|
||||
2: "C'est un endroit à l'hygiène plus que douteuse...",
|
||||
3: "Téma les prix des boissons et des snacks, c'est aberrant !",
|
||||
4: "En vrai, c'est cool, petit billard, petit canapé, chill !",
|
||||
5: "Banger, j'y reste jusqu'à la fin de mes jours"
|
||||
@@ -147,7 +147,7 @@ WORDS = {
|
||||
"scolarite": ["Comment tu vois ton cursus à l'ENS ?", {
|
||||
1: "La tranquillité et le travail",
|
||||
2: "On va s'amuser tout en bossant",
|
||||
3: "Ça va profiter et réviser au dernier moment pour les exams…",
|
||||
3: "Ça va profiter et réviser au dernier moment pour les exams...",
|
||||
4: "Nous festoierons sans songer aux conséquences",
|
||||
5: "Je ne vois qu'une seule issue : la débauche"
|
||||
}]
|
||||
|
@@ -1,8 +1,6 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
from django import forms
|
||||
@@ -25,15 +23,23 @@ buses_descr = [
|
||||
],
|
||||
[
|
||||
"Va[car]me 🎷🍎🔊", "#fd7a28", 3,
|
||||
"""Ici c'est le bus du bruit. Que ce soit les groupes de musique du Bureau des Arts ou la fanfare, on sera là pour vous
|
||||
ambiancer ! On fera en sorte que vous vous souveniez de votre WEI et de son Vacarme !""",
|
||||
"""Ici, c'est le bus du bruit. Si vous voulez réveiller les autres bus en musique, apprendre de merveilleuses
|
||||
mélodies au kazoo tout le week-end, ou simplement profiter d'une bonne ambiance musicale, le BDA et la
|
||||
F[ENS]foire sont là pour vous. Vous pourrez également goûter au célèbre cocktail de la fanfare, concocté
|
||||
pour l'occasion par les tout nouveaux "meilleurs artisans v*********** de France" ! Alors que vous soyez artiste
|
||||
dans l'âme ou que vous souhaitiez juste faire le plus grand Vacarme, rejoignez-nous !""",
|
||||
],
|
||||
[
|
||||
"[Kar]aïbes", "#a5cfdd", 0,
|
||||
"""Ahoy, explorateurs du WEI ! Le bus Karaibes t’invite à une traversée sous les tropiques, où l’ambiance est toujours au beau fixe ! ☀️🍹 Ici, c’est soleil, rhum, et bonne humeur assurée : une atmosphère de vacances où l’on se laisse porter par la chaleur humaine et la fête. Que tu sois un pirate en quête de sensations fortes ou un amateur de chill avec un cocktail à la main, tu seras à ta place dans notre bus. Les soirées seront marquées par des rythmes tropicaux qui te feront vibrer jusqu’à l’aube. Prêt à embarquer pour une aventure inoubliable avec les meilleurs matelots du WEI ? On t’attend sur le pont du Karaibes pour lever l’ancre ensemble !""",
|
||||
"[Kar]aïbes 🏝️🏴☠️🥥", "#a5cfdd", 3,
|
||||
"""Ahoy, explorateurs du WEI ! Le bus Karaibes t’invite à une traversée sous les tropiques, où l’ambiance est
|
||||
toujours au beau fixe ! ☀️🍹 Ici, c’est soleil, rhum, et bonne humeur assurée : une atmosphère de vacances où
|
||||
l’on se laisse porter par la chaleur humaine et la fête. Que tu sois un pirate en quête de sensations fortes ou
|
||||
un amateur de chill avec un cocktail à la main, tu seras à ta place dans notre bus. Les soirées seront marquées
|
||||
par des rythmes tropicaux qui te feront vibrer jusqu’à l’aube. Prêt à embarquer pour une aventure inoubliable
|
||||
avec les meilleurs matelots du WEI ? On t’attend sur le pont du Karaibes pour lever l’ancre ensemble !""",
|
||||
],
|
||||
[
|
||||
"[Kar]di [Bus]", "#e46398", 0,
|
||||
"[Kar]di [Bus] 🎙️💅", "#e46398", 2.5,
|
||||
"""Bienvenue à bord du Kardi Bus, la seul, l’unique, l’inimitable pépite de ce weekend d’intégration ! Inspiré par les
|
||||
icônes suprêmes de la pop culture telles les Bratz, les Winx et autres Mean Girls, notre bus est un sanctuaire de style,
|
||||
d’audace et de pur plaisir. A nos cotés attends toi à siroter tes meilleurs Cosmo, sex on the Beach et autres cocktails
|
||||
@@ -42,14 +48,14 @@ buses_descr = [
|
||||
Bus !""",
|
||||
],
|
||||
[
|
||||
"Sparta[bus] 🐺🐒🏉", "#ebdac2", 0,
|
||||
"Sparta[bus] 🐺🐒🏉", "#ebdac2", 5,
|
||||
"""Dans notre bus, on vous donne un avant goût des plus grandes assos de l'ENS : les Kyottes et l'Aspique (clubs de rugby
|
||||
féminin et masculin, mais pas que). Bien entendu, qui dit rugby dit les copaings, le pastaga et la Pena Bayona, mais vous
|
||||
verrez par vous même qu'on est ouvert⋅e à toutes propositions quand il s'agit de faire la fête. Pour les casse-cous comme
|
||||
pour les plus calmes, vous trouverez au bus Aspique-Kyottes les 2A+ qui vous feront kiffer votre WEI.""",
|
||||
],
|
||||
[
|
||||
"Zanzo[Bus] 🤩👽🐔", "#FFFF", 3,
|
||||
"Zanzo[Bus] 🤯🚸🐒", "#FFFF", 3,
|
||||
"""Dans un entre-trois bien senti entre zinzinerie, enfance et vieillerie, le Zanzo[BUS] est un concentré de fun mêlé à
|
||||
de la dinguerie à gogo. N'hésitez plus et rejoignez-nous pour un WEI toujours plus déjanté !""",
|
||||
],
|
||||
@@ -62,7 +68,7 @@ buses_descr = [
|
||||
musique !""",
|
||||
],
|
||||
[
|
||||
"Techno [kar]ade", "#8065a3", 0,
|
||||
"Techno [kar]ade 🔊🚩", "#8065a3", 3,
|
||||
"""Avis à tous·tes les gauchos, amoureux·ses de la fête et des manifs : le Techno [kar]ade vous ouvre grand ses bras pour
|
||||
finir en beauté votre première inté. Préparez-vous à vous abreuver de cocktails (savamment élaborés) à la vibration d’un
|
||||
système son fabriqué pour l’occasion. Des sets technos à « Mon père était tellement de gauche » en passant par « Female
|
||||
@@ -72,7 +78,7 @@ buses_descr = [
|
||||
(nombreux⋅ses) 3A+ qui auront répondu à l’appel. Bref, rejoignez-nous, on est super cools :)"""
|
||||
],
|
||||
[
|
||||
"[Bus]ka-P", "#7c4768", 0,
|
||||
"[Bus]ka-P 🥇🍻🎤", "#7c4768", 4.5,
|
||||
"""Booska-p, c’est le « site N°1 du Rap français ». Le [Bus]ka-p ? Le bus N°1 sur l’ambiance au WEI. Les nuits vont être
|
||||
courtes, les cocktails vont couler à flots : tout sera réuni pour vivre un week-end dont tu te souviendras toute ta vie.
|
||||
Au programme pas un seul temps mort et un maximum de rencontres pour bien commencer ta première année à l’ENS. Et bien
|
||||
@@ -82,30 +88,12 @@ buses_descr = [
|
||||
]
|
||||
|
||||
|
||||
def print_survey_info(i):
|
||||
s = {"recap": {
|
||||
"1": 0,
|
||||
}}
|
||||
s_ = {f"bus{i}": {f"{i}": 0 for i in range(1, 5 + 1)} for i in range(len(buses_descr))}
|
||||
s.update(s_)
|
||||
s.update({f"bus{i}": {f"{join}": join for join in range(1, 5 + 1)}})
|
||||
s = {"scores": s}
|
||||
json_str = json.dumps(s)
|
||||
print(json_str)
|
||||
|
||||
|
||||
|
||||
|
||||
def print_bus(i):
|
||||
return f"""<h1 style="color:{buses_descr[i][1]};-webkit-text-stroke: 2px black;font-size: 50px;">{buses_descr[i][0]}</h1><br>
|
||||
<b>Alcoolomètre : {buses_descr[i][2]} / 5 🍻</b><br><br>{buses_descr[i][3]}<br>"""
|
||||
|
||||
|
||||
|
||||
|
||||
def print_all_buses():
|
||||
# for i in range(len(buses_descr)):
|
||||
# print_survey_info(i)
|
||||
liste = [print_bus(i) for i in range(len(buses_descr))]
|
||||
return "<br><br><br><br>".join(liste)
|
||||
|
||||
@@ -245,7 +233,6 @@ class WEISurvey2024(WEISurvey):
|
||||
raise ValueError("Survey is not ended, can't calculate score")
|
||||
|
||||
bus_info = self.get_algorithm_class().get_bus_information(bus)
|
||||
print(bus_info)
|
||||
# Score is the given score by the bus subtracted to the mid-score of the buses.
|
||||
s = 0
|
||||
for question in WORDS:
|
||||
@@ -289,11 +276,22 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
|
||||
surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys
|
||||
surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys
|
||||
# Don't manage hardcoded people
|
||||
surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
|
||||
# surveys = [s for s in surveys if s.bus_id != None]
|
||||
# surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
|
||||
|
||||
# surveys = [s for s in surveys if s.registration.user_id in free_users]
|
||||
|
||||
# hardcoded_first_year_mb = WEIMembership.objects.filter(bus != None,registration__first_year=True)
|
||||
# hardcoded_first_year = hardcoded_first_year_mb.values_list('user__id', 'bus__id')
|
||||
|
||||
hardcoded_first_year_mb = WEIMembership.objects.filter(registration__first_year=True)
|
||||
hardcoded_first_year = {mb.user.id if mb.bus else None: mb.bus.id if mb.bus else None for mb in hardcoded_first_year_mb}
|
||||
|
||||
# Reset previous algorithm run
|
||||
for survey in surveys:
|
||||
survey.free()
|
||||
if survey.registration.user_id in hardcoded_first_year.keys():
|
||||
survey.select_bus(hardcoded_first_year[survey.registration.user_id])
|
||||
survey.save()
|
||||
|
||||
non_men = [s for s in surveys if s.registration.gender != 'male']
|
||||
@@ -304,9 +302,7 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
|
||||
non_men_total = registrations.filter(~Q(gender='male')).count()
|
||||
for bus in self.get_buses():
|
||||
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
|
||||
# Remove hardcoded people
|
||||
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
|
||||
registration__information_json__icontains="hardcoded").count()
|
||||
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk)
|
||||
quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats)
|
||||
|
||||
tqdm_obj = None
|
||||
@@ -321,9 +317,6 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
|
||||
for bus in self.get_buses():
|
||||
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
|
||||
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk)
|
||||
# Remove hardcoded people
|
||||
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
|
||||
registration__information_json__icontains="hardcoded").count()
|
||||
quotas[bus] = free_seats
|
||||
|
||||
if display_tqdm:
|
||||
|
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.2.15 on 2024-08-29 20:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wei', '0009_weiregistration_specific_diet'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='weiregistration',
|
||||
name='specific_diet',
|
||||
),
|
||||
]
|
@@ -232,12 +232,6 @@ class WEIRegistration(models.Model):
|
||||
verbose_name=_("health issues"),
|
||||
)
|
||||
|
||||
specific_diet = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name=_("specific diet"),
|
||||
)
|
||||
|
||||
emergency_contact_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("emergency contact name"),
|
||||
|
@@ -25,12 +25,9 @@
|
||||
<dt class="col-xl-6">{% trans 'department'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ object.user.profile.get_department_display }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'health issues'|capfirst %}</dt>
|
||||
<dt class="col-xl-6">{% trans 'health issues or specific diet'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ object.health_issues|default:"—" }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'specific diet'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ object.specific_diet|default:"—" }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'suggested bus'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ survey.information.selected_bus_name }}</dd>
|
||||
</dl>
|
||||
|
@@ -64,12 +64,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<dt class="col-xl-6">{% trans 'birth date'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ registration.birth_date }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'health issues'|capfirst %}</dt>
|
||||
<dt class="col-xl-6">{% trans 'health issues or specific diet'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ registration.health_issues }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'specific diet'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ registration.specific_diet }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'emergency contact name'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ registration.emergency_contact_name }}</dd>
|
||||
|
||||
|
4
apps/wrapped/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'activity.apps.WrappedConfig'
|
17
apps/wrapped/admin.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
from note_kfet.admin import admin_site
|
||||
|
||||
from .models import Bde, Wrapped
|
||||
|
||||
|
||||
@admin.register(Bde, site=admin_site)
|
||||
class BdeAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Wrapped, site=admin_site)
|
||||
class WrappedAdmin(admin.ModelAdmin):
|
||||
pass
|
0
apps/wrapped/api/__init__.py
Normal file
28
apps/wrapped/api/serializers.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import Wrapped, Bde
|
||||
|
||||
|
||||
class WrappedSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Wrapped.
|
||||
The djangorestframework plugin will analyse the model `Wrapped` and parse all fields in the API.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Wrapped
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class BdeSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
REST API Serializer for Bde.
|
||||
The djangorestframework plugin will analyse the model `Bde` and parse all fields in the API.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Bde
|
||||
fields = '__all__'
|
12
apps/wrapped/api/urls.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import WrappedViewSet, BdeViewSet
|
||||
|
||||
|
||||
def register_wrapped_urls(router, path):
|
||||
"""
|
||||
Configure router for Wrapped REST API.
|
||||
"""
|
||||
router.register(path + '/wrapped', WrappedViewSet)
|
||||
router.register(path + '/bde', BdeViewSet)
|
35
apps/wrapped/api/views.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter
|
||||
|
||||
from .serializers import WrappedSerializer, BdeSerializer
|
||||
from ..models import Wrapped, Bde
|
||||
|
||||
|
||||
class WrappedViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `Wrapped` objects, serialize it to JSON with the given
|
||||
serializer, then render it on /api/wrapped/wrapped/
|
||||
"""
|
||||
queryset = Wrapped.objects.order_by('id')
|
||||
serializer_class = WrappedSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['note', 'bde', ]
|
||||
search_fields = ['$note', ]
|
||||
|
||||
|
||||
class BdeViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `Bde` objects, serialize it to JSON with the given
|
||||
serializer, then render it on /api/wrapped/bde/
|
||||
"""
|
||||
queryset = Bde.objects.order_by('id')
|
||||
serializer_class = BdeSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', ]
|
||||
search_fields = ['$name', ]
|
10
apps/wrapped/apps.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class WrappedConfig(AppConfig):
|
||||
name = 'wrapped'
|
||||
verbose_name = _('wrapped')
|
584
apps/wrapped/management/commands/generate_wrapped.py
Normal file
@@ -0,0 +1,584 @@
|
||||
# Copyright (C) 2028-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
from django.db.models import Q
|
||||
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
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Generate wrapper for the annual BDE change"
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser):
|
||||
parser.add_argument(
|
||||
'-b', '--bde',
|
||||
type=str,
|
||||
required=False,
|
||||
help="A list of BDE name, BDE1,BDE2,... (a BDE name cannot have ',')",
|
||||
dest='bde',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-i', '--id',
|
||||
type=str,
|
||||
required=False,
|
||||
help="A list of BDE id, id1,id2,...",
|
||||
dest='bde_id',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-u', '--users',
|
||||
type=str,
|
||||
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
|
||||
supersuser = all superusers
|
||||
custom user1,user2,... = a list of username,
|
||||
custom_id id1,id2,... = a list of user id""",
|
||||
dest='user',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c', '--club',
|
||||
type=str,
|
||||
required=False,
|
||||
help="""Club will have their(s) wrapped generated,
|
||||
all = all clubs,
|
||||
active = all clubs with at least one transaction during the BDE mandate considered,
|
||||
custom club1,club2,... = a list of club name,
|
||||
custom_id id1,id2,... = a list of club id""",
|
||||
dest='club',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-f', '--force-change',
|
||||
required=False,
|
||||
action='store_true',
|
||||
help="if wrapped already exist change data_json",
|
||||
dest='change',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-n', '--no-creation',
|
||||
required=False,
|
||||
action='store_false',
|
||||
help="if wrapped don't already exist, don't generate it",
|
||||
dest='create',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options): # NOQA
|
||||
# Traitement des paramètres
|
||||
verb = options['verbosity']
|
||||
bde = []
|
||||
if options['bde']:
|
||||
bde_list = options['bde'].split(',')
|
||||
bde = [Bde.objects.get(name=bde_name) for bde_name in bde_list]
|
||||
|
||||
if options['bde_id']:
|
||||
if bde:
|
||||
if verb >= 1:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
"WARNING\nYou already defined bde with their name !"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.ERROR("ABORT"))
|
||||
exit(1)
|
||||
bde_id = options['bde_id'].split(',')
|
||||
bde = [Bde.objects.get(pk=i) for i in bde_id]
|
||||
|
||||
user = []
|
||||
if options['user']:
|
||||
if options['user'] == 'all':
|
||||
user = ['all', None]
|
||||
elif options['user'] == 'adh':
|
||||
user = ['adh', None]
|
||||
elif options['user'] == 'superuser':
|
||||
user = ['superuser', None]
|
||||
elif options['user'].split(' ')[0] == 'custom':
|
||||
user_list = options['user'].split(' ')[1].split(',')
|
||||
user = ['custom', [User.objects.get(username=u) for u in user_list]]
|
||||
elif options['user'].split(' ')[0] == 'custom_id':
|
||||
user_id = options['user'].split(' ')[1].split(',')
|
||||
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"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.ERROR("ABORT"))
|
||||
exit(1)
|
||||
|
||||
club = []
|
||||
if options['club']:
|
||||
if options['club'] == 'all':
|
||||
club = ['all', None]
|
||||
elif options['club'] == 'active':
|
||||
club = ['active', None]
|
||||
elif options['club'].split(' ')[0] == 'custom':
|
||||
club_list = options['club'].split(' ')[1].split(',')
|
||||
club = ['custom', [Club.objects.get(name=club_name) for club_name in club_list]]
|
||||
elif options['club'].split(' ')[0] == 'custom_id':
|
||||
club_id = options['club'].split(' ')[1].split(',')
|
||||
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"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.ERROR("ABORT"))
|
||||
exit(1)
|
||||
|
||||
change = options['change']
|
||||
create = options['create']
|
||||
|
||||
# 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 !"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.ERROR("ABORT"))
|
||||
exit(1)
|
||||
if not (user or club):
|
||||
if verb >= 1:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
"WARNING\nNo club or user selected !"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.ERROR("ABORT"))
|
||||
exit(1)
|
||||
|
||||
if verb >= 3:
|
||||
self.stdout.write("Options:")
|
||||
bde_str = ''
|
||||
for b in bde:
|
||||
bde_str += str(b) + '\n'
|
||||
self.stdout.write("BDE: " + bde_str)
|
||||
if user:
|
||||
self.stdout.write('User: ' + user[0])
|
||||
if club:
|
||||
self.stdout.write('Club: ' + club[0])
|
||||
self.stdout.write('change: ' + str(change))
|
||||
self.stdout.write('create: ' + str(create) + '\n')
|
||||
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"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.ERROR("ABORT"))
|
||||
exit(1)
|
||||
if verb >= 1 and change:
|
||||
self.stdout.write(self.style.WARNING(
|
||||
"WARNING\nchange 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 !"))
|
||||
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)
|
||||
|
||||
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"))
|
||||
|
||||
global_data = self.global_data(bde, verb=verb)
|
||||
if verb >= 1:
|
||||
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)
|
||||
if verb >= 1:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
"Unique data has been successfully generated"))
|
||||
|
||||
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 !"))
|
||||
if verb >= 0:
|
||||
self.stdout.write(self.style.SUCCESS("SUCCESS"))
|
||||
exit(0)
|
||||
|
||||
def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1): # NOQA
|
||||
notes = []
|
||||
for b in bde:
|
||||
note_for_bde = Note.objects.filter(pk__lte=-1)
|
||||
if user:
|
||||
if 'custom' in user[0]:
|
||||
for u in user[1]:
|
||||
query = Q(noteuser__user=u)
|
||||
note_for_bde |= Note.objects.filter(query)
|
||||
elif user[0] == 'all':
|
||||
query = Q(noteuser__user__pk__gte=-1)
|
||||
note_for_bde |= 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 membership in m:
|
||||
note_for_bde |= Note.objects.filter(noteuser__user=membership.user)
|
||||
|
||||
elif user[0] == 'superuser':
|
||||
query |= Q(noteuser__user__is_superuser=True)
|
||||
note_for_bde |= Note.objects.filter(query)
|
||||
|
||||
if club:
|
||||
if 'custom' in club[0]:
|
||||
for c in club[1]:
|
||||
query = Q(noteclub__club=c)
|
||||
note_for_bde |= Note.objects.filter(query)
|
||||
elif club[0] == 'all':
|
||||
query = Q(noteclub__club__pk__gte=-1)
|
||||
note_for_bde |= Note.objects.filter(query)
|
||||
elif club[0] == 'active':
|
||||
nc = Note.objects.filter(noteclub__club__pk__gte=-1)
|
||||
for noteclub in nc:
|
||||
if Transaction.objects.filter(
|
||||
Q(created_at__gte=b.date_start,
|
||||
created_at__lte=b.date_end) & (Q(source=noteclub) | Q(destination=noteclub))):
|
||||
note_for_bde |= Note.objects.filter(pk=noteclub.pk)
|
||||
|
||||
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}")
|
||||
return notes
|
||||
|
||||
def global_data(self, bde, verb=1): # NOQA
|
||||
data = {}
|
||||
for b in bde:
|
||||
if b.name == 'Rave Part[list]':
|
||||
if verb >= 2:
|
||||
self.stdout.write("Begin to make global data")
|
||||
if verb >= 3:
|
||||
self.stdout.write("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:
|
||||
self.stdout.write("nb_vieux_con")
|
||||
# nb total de vielleux con·ne·s derrière le bar
|
||||
button_id = [2884, 2585]
|
||||
transactions = 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 transactions:
|
||||
q += t.quantity
|
||||
data['nb_vieux_con'] = q
|
||||
|
||||
if verb >= 3:
|
||||
self.stdout.write("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:
|
||||
self.stdout.write('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,
|
||||
valid=True,
|
||||
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:
|
||||
self.stdout.write('top3_buttons')
|
||||
# top 3 des boutons les plus cliqués
|
||||
transactions = 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 transactions:
|
||||
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:
|
||||
self.stdout.write('class_conso_all')
|
||||
# le classement des plus gros consommateurs (BDE + club)
|
||||
transactions = 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 transactions:
|
||||
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:
|
||||
self.stdout.write('class_conso_bde')
|
||||
# le classement des plus gros consommateurs BDE
|
||||
transactions = 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 transactions:
|
||||
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(f"The BDE: {b.name} has not personalized wrapped, make it !")
|
||||
return data
|
||||
|
||||
def unique_data(self, bde, note, global_data=None, verb=1): # NOQA
|
||||
data = []
|
||||
for i in range(len(bde)):
|
||||
data_bde = []
|
||||
if bde[i].name == 'Rave Part[list]':
|
||||
if verb >= 3:
|
||||
total = len(note[i])
|
||||
current = 0
|
||||
self.stdout.write(f"Make {total} data for wrapped sponsored by {bde[i].name}")
|
||||
for n in note[i]:
|
||||
d = {}
|
||||
if 'user' in n.__dir__():
|
||||
# première conso du mandat
|
||||
transactions = 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 transactions:
|
||||
d['first_conso'] = transactions[0].template.name
|
||||
else:
|
||||
d['first_conso'] = ''
|
||||
# Wei + bus
|
||||
wei = WEIClub.objects.filter(
|
||||
date_start__lte=bde[i].date_end,
|
||||
date_end__gte=bde[i].date_start)
|
||||
if not wei:
|
||||
d['wei'] = ''
|
||||
d['bus'] = ''
|
||||
else:
|
||||
w = wei[0]
|
||||
memberships = Membership.objects.filter(club=w, user=n.user)
|
||||
if not memberships:
|
||||
d['wei'] = ''
|
||||
d['bus'] = ''
|
||||
else:
|
||||
alias = []
|
||||
for a in w.note.alias.iterator():
|
||||
alias.append(str(a))
|
||||
d['wei'] = alias[-1]
|
||||
d['bus'] = memberships[0].weimembership.bus.name
|
||||
# top3 conso
|
||||
transactions = 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 transactions:
|
||||
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
|
||||
transactions = 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 transactions:
|
||||
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
|
||||
transactions = 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 transactions:
|
||||
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
|
||||
self.stdout.write("\033[2K" + f"({current}/{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 !")
|
||||
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 n in note:
|
||||
total += len(n)
|
||||
self.stdout.write(f"Make {total} wrapped")
|
||||
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]):
|
||||
Wrapped(bde=bde[i],
|
||||
note=note[i][j],
|
||||
data_json=unique_data[i][j],
|
||||
public=False,
|
||||
generated=True).save()
|
||||
elif change:
|
||||
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
|
||||
self.stdout.write("\033[2K" + f"({current}/{total})" + "\033[1A")
|
||||
return
|
||||
|
||||
def filter_note(self, bde, note, change, create, verb=1):
|
||||
if change and create:
|
||||
return list(note)
|
||||
if change and not create:
|
||||
note_new = []
|
||||
for n in note:
|
||||
if Wrapped.objects.filter(bde=bde, note=n):
|
||||
note_new.append(n)
|
||||
return note_new
|
||||
if not change and create:
|
||||
note_new = []
|
||||
for n in note:
|
||||
if not Wrapped.objects.filter(bde=bde, note=n):
|
||||
note_new.append(n)
|
||||
return note_new
|
86
apps/wrapped/migrations/0001_initial.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# Generated by Django 4.2.15 on 2025-02-13 01:38
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("note", "0007_alter_note_polymorphic_ctype_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Bde",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255, verbose_name="name")),
|
||||
("date_start", models.DateTimeField(verbose_name="date start")),
|
||||
("date_end", models.DateTimeField(verbose_name="date end")),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "BDE",
|
||||
"verbose_name_plural": "BDE",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Wrapped",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"generated",
|
||||
models.BooleanField(default=False, verbose_name="generated"),
|
||||
),
|
||||
("public", models.BooleanField(default=False, verbose_name="public")),
|
||||
(
|
||||
"data_json",
|
||||
models.TextField(
|
||||
default="{}",
|
||||
help_text="data in the wrapped and generated by the script generate_wrapped",
|
||||
verbose_name="data json",
|
||||
),
|
||||
),
|
||||
(
|
||||
"bde",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="+",
|
||||
to="wrapped.bde",
|
||||
verbose_name="bde",
|
||||
),
|
||||
),
|
||||
(
|
||||
"note",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="+",
|
||||
to="note.note",
|
||||
verbose_name="note",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Wrapped",
|
||||
"verbose_name_plural": "Wrappeds",
|
||||
"unique_together": {("note", "bde")},
|
||||
},
|
||||
),
|
||||
]
|
0
apps/wrapped/migrations/__init__.py
Normal file
80
apps/wrapped/models.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note.models import Note
|
||||
|
||||
|
||||
class Bde(models.Model):
|
||||
"""
|
||||
describe a BDE
|
||||
"""
|
||||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_('name'),
|
||||
)
|
||||
|
||||
date_start = models.DateTimeField(
|
||||
verbose_name=_('date start'),
|
||||
)
|
||||
|
||||
date_end = models.DateTimeField(
|
||||
verbose_name=_('date end'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('BDE')
|
||||
verbose_name_plural = _('BDE')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Wrapped(models.Model):
|
||||
"""
|
||||
A Wrapped is associated to a note, a BDE year,
|
||||
"""
|
||||
generated = models.BooleanField(
|
||||
verbose_name=_('generated'),
|
||||
default=False,
|
||||
)
|
||||
|
||||
public = models.BooleanField(
|
||||
verbose_name=_('public'),
|
||||
default=False,
|
||||
)
|
||||
|
||||
bde = models.ForeignKey(
|
||||
Bde,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
verbose_name=_('bde'),
|
||||
)
|
||||
|
||||
note = models.ForeignKey(
|
||||
Note,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
verbose_name=_('note'),
|
||||
)
|
||||
|
||||
data_json = models.TextField(
|
||||
default='{}',
|
||||
verbose_name=_('data json'),
|
||||
help_text=_('data in the wrapped and generated by the script generate_wrapped'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Wrapped')
|
||||
verbose_name_plural = _('Wrappeds')
|
||||
unique_together = ('note', 'bde')
|
||||
|
||||
def __str__(self):
|
||||
return 'NoteKfet Wrapped of {note} sponsored by {bde}'.format(bde=str(self.bde), note=str(self.note))
|
||||
|
||||
def makepublic(self):
|
||||
self.public = not self.public
|
||||
self.save()
|
||||
return
|
73
apps/wrapped/static/wrapped/css/1/custom.css
Normal file
@@ -0,0 +1,73 @@
|
||||
:root {
|
||||
--accent-primary: #FF0065;
|
||||
--accent-secondary: #FFCB20;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JEMROKtrial-Regular";
|
||||
src: url("/static/wrapped/fonts/1/JEMROKtrial-Regular.ttf");
|
||||
}
|
||||
body {
|
||||
font-family: "JEMROKtrial-Regular", sans-serif;
|
||||
background: url("/static/wrapped/img/1/bg.png");
|
||||
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: normal;
|
||||
}
|
||||
.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;
|
||||
}
|
BIN
apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 44 KiB |
9
apps/wrapped/static/wrapped/favicon/1/browserconfig.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/static/favicon/1/mstile-150x150.png"/>
|
||||
<TileColor>#00a300</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
apps/wrapped/static/wrapped/favicon/1/favicon-16x16.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
apps/wrapped/static/wrapped/favicon/1/favicon-32x32.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
apps/wrapped/static/wrapped/favicon/1/favicon.ico
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
apps/wrapped/static/wrapped/favicon/1/mstile-150x150.png
Normal file
After Width: | Height: | Size: 34 KiB |
503
apps/wrapped/static/wrapped/favicon/1/safari-pinned-tab.svg
Normal file
@@ -0,0 +1,503 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="933.000000pt" height="933.000000pt" viewBox="0 0 933.000000 933.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,933.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M4285 8909 c-1611 -113 -3013 -1115 -3640 -2600 -322 -763 -413
|
||||
-1617 -259 -2439 160 -853 598 -1670 1217 -2266 689 -665 1526 -1060 2497
|
||||
-1181 206 -25 734 -25 940 0 539 67 996 204 1455 436 609 308 1121 746 1528
|
||||
1304 368 504 642 1152 745 1762 47 277 57 397 57 730 0 335 -6 419 -51 695
|
||||
-189 1175 -882 2234 -1884 2882 -772 499 -1702 741 -2605 677z m336 -175 c88
|
||||
-16 130 -33 167 -67 29 -27 30 -110 2 -192 -30 -87 -35 -175 -16 -283 19 -107
|
||||
45 -165 141 -312 97 -148 117 -198 123 -301 4 -84 3 -87 -24 -118 -39 -44
|
||||
-100 -51 -336 -40 -255 12 -305 25 -371 98 -47 52 -68 124 -66 226 1 63 -1 80
|
||||
-12 78 -8 -1 -51 -7 -97 -13 l-82 -12 2 -142 c3 -126 1 -146 -18 -187 -42 -91
|
||||
-145 -159 -314 -210 -197 -58 -280 -8 -267 162 7 87 32 158 127 357 127 270
|
||||
164 434 141 631 -15 123 -9 188 20 225 44 56 188 100 384 116 145 11 393 4
|
||||
496 -16z m849 -107 c172 -44 281 -89 332 -138 39 -37 41 -40 34 -85 -3 -27
|
||||
-27 -85 -56 -138 -59 -110 -74 -171 -90 -372 -15 -181 -9 -236 30 -276 32 -31
|
||||
58 -35 98 -14 35 19 62 54 154 202 100 158 123 205 153 311 51 175 80 209 168
|
||||
200 104 -10 335 -126 424 -213 67 -63 71 -96 17 -148 -44 -43 -81 -61 -192
|
||||
-93 -114 -32 -167 -62 -235 -131 -78 -79 -120 -155 -168 -301 -47 -144 -74
|
||||
-195 -129 -245 -89 -80 -176 -70 -506 57 -253 98 -339 173 -351 305 -7 74 14
|
||||
158 66 260 61 122 75 180 68 292 -8 140 -32 201 -128 325 -63 81 -79 116 -79
|
||||
169 0 35 5 48 23 61 36 27 202 15 367 -28z m-2409 -257 c92 -26 148 -98 156
|
||||
-204 11 -135 -61 -297 -205 -459 -24 -27 -42 -50 -40 -52 2 -2 29 2 61 7 63
|
||||
11 129 0 174 -29 29 -19 55 -68 78 -148 13 -49 26 -69 70 -110 154 -146 134
|
||||
-194 -155 -372 -214 -132 -295 -164 -353 -140 -55 24 -71 70 -64 193 7 131 -9
|
||||
207 -52 246 -17 16 -40 28 -51 28 -56 0 -183 -72 -280 -159 l-45 -40 25 -21
|
||||
c14 -12 71 -44 127 -72 155 -78 243 -175 243 -268 0 -62 -43 -124 -191 -272
|
||||
-175 -174 -263 -225 -347 -197 -59 19 -91 49 -122 114 -25 49 -33 85 -45 200
|
||||
-18 164 -34 225 -83 318 -56 109 -110 161 -217 212 -101 48 -169 104 -180 148
|
||||
-18 71 74 200 269 379 386 355 806 642 1012 692 71 17 165 20 215 6z m3941
|
||||
-474 c91 -39 319 -242 542 -483 78 -84 151 -172 164 -195 46 -86 22 -175 -85
|
||||
-308 -146 -180 -264 -199 -360 -57 -79 117 -56 264 48 313 24 12 63 42 87 68
|
||||
52 57 55 91 12 134 -69 69 -185 48 -316 -57 -82 -65 -104 -135 -64 -209 33
|
||||
-62 45 -72 91 -76 47 -4 69 -25 69 -64 0 -41 -49 -112 -77 -112 -8 0 -41 23
|
||||
-75 51 -64 53 -104 76 -114 66 -3 -3 -10 -22 -14 -43 -21 -93 41 -237 127
|
||||
-297 53 -36 91 -35 129 4 44 46 61 53 89 35 60 -40 72 -184 22 -282 -50 -98
|
||||
-113 -130 -239 -121 -114 8 -195 49 -328 168 -58 52 -171 149 -252 216 -227
|
||||
191 -260 238 -222 320 29 62 129 153 223 203 46 25 105 64 131 87 81 73 161
|
||||
250 195 430 20 107 37 149 77 191 36 36 81 42 140 18z m-2454 -617 c3 -19 -23
|
||||
-49 -43 -49 -18 0 -45 27 -45 45 0 27 83 31 88 4z m382 -79 c8 -14 8 -26 0
|
||||
-40 -19 -35 -80 -20 -80 20 0 40 61 55 80 20z m409 -11 c42 -12 134 -44 205
|
||||
-71 501 -195 938 -549 1235 -1002 83 -126 202 -359 252 -493 19 -51 37 -93 40
|
||||
-93 12 0 58 98 90 193 18 53 37 97 43 97 6 0 47 -7 90 -15 117 -23 322 -29
|
||||
463 -16 171 17 327 62 452 132 29 17 59 28 66 26 22 -9 107 -131 150 -217 142
|
||||
-286 151 -529 30 -887 -19 -56 -40 -142 -46 -191 -11 -79 -10 -95 5 -142 25
|
||||
-77 65 -130 140 -186 80 -59 80 -49 -7 -247 -147 -334 -310 -554 -479 -650
|
||||
-131 -75 -197 -91 -363 -92 -164 0 -130 -18 -340 175 -200 183 -319 290 -325
|
||||
290 -3 0 -13 -24 -23 -52 -34 -98 -165 -352 -244 -473 -416 -633 -1062 -1051
|
||||
-1804 -1170 -151 -24 -382 -34 -554 -24 -465 28 -886 169 -1280 430 -92 61
|
||||
-143 102 -161 129 -62 94 -135 369 -137 516 -1 68 3 89 24 129 32 64 69 90
|
||||
124 89 61 -1 115 -37 221 -146 49 -52 119 -113 153 -136 107 -71 157 -53 182
|
||||
66 14 69 4 119 -45 231 -33 75 -66 209 -66 269 0 22 -14 47 -49 89 -138 166
|
||||
-233 361 -283 584 -31 138 -33 407 -4 538 104 479 427 851 879 1011 43 15 80
|
||||
30 82 32 5 4 -17 96 -30 131 -9 23 10 22 43 -2 15 -10 54 -34 87 -52 l60 -33
|
||||
202 -1 c164 0 204 2 208 13 3 8 2 63 -1 122 -8 128 -2 169 32 210 33 39 44 38
|
||||
44 -5 0 -50 29 -118 88 -209 50 -75 52 -81 52 -148 0 -39 -7 -84 -15 -106 -25
|
||||
-58 -34 -113 -28 -159 l5 -41 41 46 c69 80 87 131 87 250 0 56 3 102 8 102 17
|
||||
0 114 -95 132 -130 26 -51 34 -127 21 -190 -6 -29 -10 -55 -7 -57 7 -7 125 87
|
||||
150 120 25 33 49 104 40 119 -3 4 -23 8 -44 8 -98 0 -136 17 -217 98 -71 70
|
||||
-77 74 -89 57 -8 -10 -14 -25 -14 -32 0 -24 -18 -14 -63 37 -62 69 -82 154
|
||||
-53 224 27 65 76 106 191 162 122 58 230 131 240 163 9 28 -11 60 -41 67 -35
|
||||
9 -119 -1 -179 -21 -27 -9 -117 -49 -200 -88 -175 -82 -220 -91 -289 -57 -152
|
||||
73 -93 290 102 377 53 23 61 24 191 17 136 -7 136 -7 167 19 18 16 41 52 57
|
||||
94 15 37 37 80 49 95 28 33 134 98 162 98 11 0 54 -10 95 -21z m-901 -195 c37
|
||||
-26 23 -74 -23 -74 -43 0 -63 58 -27 79 23 14 24 14 50 -5z m-3516 -1078 c80
|
||||
-44 254 -103 344 -116 99 -14 316 -12 479 5 197 20 182 23 199 -32 30 -102 67
|
||||
-186 110 -253 27 -42 43 -76 39 -86 -14 -38 -131 125 -173 243 -13 35 -27 60
|
||||
-32 57 -30 -19 -116 -190 -151 -300 -48 -152 -60 -243 -54 -419 5 -169 15
|
||||
-238 74 -505 34 -156 36 -175 36 -355 1 -135 -4 -218 -16 -285 -22 -130 -61
|
||||
-277 -91 -344 -14 -31 -22 -56 -18 -56 4 1 104 93 222 205 197 188 229 212
|
||||
230 174 0 -6 -326 -312 -452 -422 -47 -42 -98 -81 -113 -87 -39 -15 -219 -12
|
||||
-300 5 -38 8 -112 35 -164 61 -121 59 -227 159 -320 299 -77 117 -216 389
|
||||
-251 493 l-25 73 35 27 c203 157 227 254 135 542 -103 320 -111 513 -33 745
|
||||
43 126 185 365 218 365 5 0 38 -16 72 -34z m1564 -2071 c26 -39 -31 -86 -64
|
||||
-53 -18 18 -15 55 6 67 23 14 42 10 58 -14z m434 -265 c8 -14 8 -26 0 -40 -19
|
||||
-35 -80 -20 -80 20 0 40 61 55 80 20z m-392 -162 c4 -34 -27 -54 -58 -38 -21
|
||||
11 -25 27 -14 55 5 11 16 15 38 13 25 -2 32 -8 34 -30z m168 -223 c11 -43 -34
|
||||
-73 -64 -43 -13 13 -16 50 -5 61 3 4 19 7 34 7 22 0 30 -6 35 -25z m4258 -79
|
||||
c42 -57 100 -125 128 -152 99 -89 238 -168 428 -244 190 -76 276 -126 364
|
||||
-215 92 -92 128 -197 88 -256 -34 -50 -96 -119 -108 -119 -7 0 -28 22 -47 48
|
||||
-55 77 -61 91 -48 119 16 35 -13 95 -80 162 -68 68 -126 106 -319 216 -192
|
||||
109 -304 185 -398 271 -59 54 -77 65 -97 60 -18 -5 -28 0 -44 20 -36 46 -37
|
||||
85 -1 142 17 29 37 52 44 52 8 0 48 -47 90 -104z m-4627 -91 c88 -132 97 -158
|
||||
91 -246 -7 -87 -45 -167 -112 -234 -72 -72 -133 -98 -216 -93 -75 4 -100 19
|
||||
-156 93 -19 25 -38 45 -42 45 -4 0 -33 -33 -66 -74 -83 -107 -238 -253 -292
|
||||
-278 -39 -17 -50 -18 -75 -7 -38 15 -95 81 -110 127 -37 111 96 236 358 335
|
||||
209 80 308 154 413 309 77 114 100 140 119 136 9 -2 48 -53 88 -113z m4568
|
||||
-154 c89 -67 204 -184 220 -223 14 -35 14 -39 -5 -58 -24 -24 -69 -26 -113 -4
|
||||
-40 21 -77 54 -77 70 0 13 -46 54 -61 54 -17 0 -8 -37 19 -81 64 -101 201
|
||||
-197 362 -255 167 -59 279 -144 315 -237 24 -63 11 -107 -51 -176 -79 -88
|
||||
-133 -102 -200 -53 -50 36 -133 139 -219 271 -77 117 -131 174 -257 269 -48
|
||||
36 -85 68 -82 71 6 7 132 -81 195 -138 30 -27 98 -109 149 -181 52 -73 125
|
||||
-166 163 -206 89 -96 129 -108 193 -59 43 33 39 40 -9 20 -20 -8 -45 -15 -56
|
||||
-15 -33 0 -138 107 -229 233 -96 133 -213 253 -300 308 -63 39 -127 66 -116
|
||||
47 5 -7 -1 -9 -20 -4 -22 5 -26 3 -26 -16 0 -24 26 -58 46 -58 16 0 73 -65 85
|
||||
-98 20 -52 -11 -102 -64 -102 -98 0 -220 130 -241 255 -10 58 4 87 85 174 39
|
||||
42 99 111 132 153 73 92 87 96 162 39z m-4283 -208 c64 -61 108 -124 115 -168
|
||||
7 -46 -17 -113 -76 -204 -84 -131 -140 -252 -162 -347 -61 -265 -99 -371 -154
|
||||
-429 -26 -28 -40 -35 -72 -35 -70 0 -179 86 -216 169 -38 87 5 218 110 332
|
||||
l56 60 -23 24 c-13 14 -29 25 -35 25 -6 0 -55 -47 -109 -105 -54 -58 -115
|
||||
-115 -137 -125 -81 -42 -154 -16 -185 65 -16 43 -16 47 1 92 37 96 120 161
|
||||
345 271 193 93 259 148 363 305 93 139 101 142 179 70z m3835 -224 c72 -35
|
||||
133 -128 133 -203 0 -36 -5 -48 -26 -65 -32 -25 -55 -26 -94 -6 -34 18 -45 35
|
||||
-54 90 -8 44 -40 85 -67 85 -27 0 -59 -48 -59 -89 0 -48 42 -132 86 -171 45
|
||||
-39 92 -40 189 -1 140 57 189 52 298 -30 77 -58 139 -132 176 -209 21 -46 26
|
||||
-69 25 -135 0 -68 -4 -89 -29 -138 -36 -72 -131 -164 -210 -203 -160 -78 -292
|
||||
-16 -437 205 -27 41 -47 82 -46 90 2 12 21 18 76 22 130 12 237 89 212 153
|
||||
-18 49 -49 52 -136 13 -102 -45 -134 -51 -194 -37 -69 17 -130 73 -175 162
|
||||
-62 125 -45 241 51 347 43 48 128 108 180 126 51 18 51 18 101 -6z m-3403
|
||||
-138 c50 -32 129 -80 176 -106 153 -85 210 -162 210 -285 0 -117 -76 -226
|
||||
-174 -251 l-45 -11 27 -40 c36 -56 42 -149 13 -232 -12 -33 -21 -82 -21 -110
|
||||
0 -75 -19 -177 -39 -211 -34 -58 -115 -62 -230 -10 -79 37 -121 72 -142 120
|
||||
-33 74 -6 145 96 253 65 70 115 152 115 192 0 40 -32 90 -76 120 -38 27 -64
|
||||
37 -64 26 0 -46 -46 -282 -65 -332 -71 -188 -137 -228 -272 -165 -80 38 -147
|
||||
101 -169 157 -40 105 44 246 222 375 159 114 218 207 259 405 25 121 44 164
|
||||
72 164 9 0 57 -26 107 -59z m2891 -157 c26 -10 44 -56 61 -156 18 -106 63
|
||||
-210 119 -275 20 -23 73 -69 119 -102 141 -102 212 -199 223 -305 10 -94 -39
|
||||
-155 -174 -218 -81 -38 -156 -47 -201 -24 -72 37 -123 186 -139 404 -10 126
|
||||
-12 137 -51 215 -22 45 -70 120 -105 167 -36 47 -70 102 -77 124 -11 34 -10
|
||||
41 7 65 20 27 115 84 168 100 17 5 31 10 32 10 1 1 9 -2 18 -5z m-2249 -154
|
||||
c107 -37 231 -70 384 -100 83 -16 161 -37 173 -45 45 -29 71 -93 75 -186 3
|
||||
-51 0 -101 -7 -122 -17 -50 -73 -97 -115 -97 -68 0 -136 74 -136 148 0 19 6
|
||||
35 14 38 21 8 27 31 16 60 -7 18 -16 25 -32 22 -48 -7 -99 -156 -99 -289 0
|
||||
-47 9 -103 32 -181 61 -212 35 -383 -64 -435 -41 -21 -140 -21 -226 2 -160 41
|
||||
-212 104 -198 238 9 89 44 163 133 279 105 139 119 177 119 325 0 104 -2 114
|
||||
-23 135 -23 23 -23 23 -38 4 -17 -23 -18 -58 -3 -73 14 -14 -17 -88 -49 -113
|
||||
-13 -11 -40 -20 -59 -20 -104 0 -147 101 -107 253 20 78 47 130 80 156 33 26
|
||||
56 26 130 1z m1218 -43 c14 -54 -1 -87 -44 -101 -54 -17 -62 -43 -55 -176 9
|
||||
-168 53 -559 76 -675 32 -159 70 -225 128 -225 45 0 61 -31 61 -116 0 -98 -6
|
||||
-102 -163 -119 -185 -19 -230 -5 -268 81 -43 96 -45 360 -3 544 36 161 29 358
|
||||
-22 565 -27 112 -31 202 -10 230 17 23 80 33 201 31 l90 -1 9 -38z m406 -57
|
||||
c22 -22 22 -22 24 -200 2 -100 6 -136 23 -182 61 -163 189 -247 293 -193 35
|
||||
18 41 38 38 120 l-3 60 36 3 c73 6 177 -107 220 -241 27 -84 21 -166 -17 -244
|
||||
-35 -72 -70 -98 -182 -132 -231 -72 -428 -110 -497 -97 -52 10 -100 58 -122
|
||||
119 -26 75 -23 227 5 334 41 151 24 263 -65 434 -36 70 -44 94 -41 130 4 53
|
||||
29 70 133 92 98 21 131 21 155 -3z"/>
|
||||
<path d="M4450 8721 c14 -4 54 -14 90 -20 36 -7 88 -23 116 -35 41 -19 54 -32
|
||||
72 -70 12 -25 22 -57 22 -71 0 -40 16 -29 30 19 31 111 -57 174 -255 182 -65
|
||||
3 -91 1 -75 -5z"/>
|
||||
<path d="M3914 8686 c-144 -47 -171 -81 -160 -206 3 -42 12 -96 19 -121 l13
|
||||
-44 12 108 c7 65 19 122 31 145 21 41 103 103 164 122 20 6 37 13 37 16 0 10
|
||||
-50 1 -116 -20z"/>
|
||||
<path d="M4157 8572 c-26 -28 -45 -82 -67 -191 -48 -235 -66 -471 -37 -471 8
|
||||
0 58 7 113 15 54 8 104 15 111 15 17 0 53 115 60 190 9 109 -44 368 -89 433
|
||||
-23 32 -66 37 -91 9z"/>
|
||||
<path d="M4336 8308 c32 -150 3 -361 -53 -382 -9 -4 -58 -12 -109 -18 -127
|
||||
-16 -122 -33 7 -23 l102 7 -9 -39 c-5 -21 -9 -72 -8 -114 0 -62 6 -87 28 -132
|
||||
46 -95 129 -148 228 -147 33 1 33 1 -12 17 -69 24 -107 59 -142 131 -30 60
|
||||
-33 75 -32 152 0 58 9 118 27 187 37 139 35 214 -10 358 -23 74 -33 76 -17 3z"/>
|
||||
<path d="M3566 7668 c-31 -47 -78 -184 -83 -239 -7 -68 10 -124 42 -145 32
|
||||
-21 104 -20 162 2 68 25 74 34 17 28 -66 -8 -130 10 -151 43 -27 41 -22 150
|
||||
10 248 28 83 29 102 3 63z"/>
|
||||
<path d="M5163 8646 c-17 -8 -36 -23 -42 -35 -25 -46 -9 -84 95 -216 57 -73
|
||||
51 -41 -11 60 -48 78 -52 90 -42 121 9 26 48 44 119 53 l53 7 -50 8 c-27 4
|
||||
-59 9 -70 12 -11 2 -34 -3 -52 -10z"/>
|
||||
<path d="M6211 8246 c-12 -13 -37 -71 -55 -128 -41 -130 -64 -175 -176 -342
|
||||
-88 -132 -136 -184 -182 -198 -20 -5 -19 -6 8 -7 81 -3 211 152 331 394 22 44
|
||||
46 96 53 115 21 55 59 101 98 117 31 13 46 13 128 -1 101 -17 183 -46 229 -80
|
||||
39 -29 55 -38 55 -32 0 8 -111 81 -167 110 -79 39 -200 76 -253 76 -36 0 -51
|
||||
-5 -69 -24z"/>
|
||||
<path d="M5263 7797 c-63 -150 -77 -200 -69 -250 15 -92 64 -145 196 -212 91
|
||||
-45 126 -54 68 -16 -68 43 -168 149 -191 200 -25 56 -17 148 23 293 16 57 28
|
||||
104 26 105 -1 1 -25 -53 -53 -120z"/>
|
||||
<path d="M2820 8326 c-74 -26 -126 -52 -223 -109 -77 -45 -97 -66 -40 -41 193
|
||||
83 294 114 373 114 74 0 145 -26 174 -63 l26 -32 0 35 c0 52 -32 97 -77 109
|
||||
-62 18 -161 12 -233 -13z"/>
|
||||
<path d="M2400 7949 c-100 -41 -150 -172 -150 -395 0 -121 22 -279 41 -299 7
|
||||
-7 40 20 97 80 133 138 189 270 180 430 -5 87 -27 147 -63 171 -30 19 -76 25
|
||||
-105 13z"/>
|
||||
<path d="M2562 7933 c33 -64 42 -98 46 -164 7 -135 -36 -256 -135 -379 -34
|
||||
-41 -59 -77 -57 -79 2 -2 18 8 37 22 139 106 207 240 207 410 0 87 -15 134
|
||||
-56 172 -25 24 -50 34 -42 18z"/>
|
||||
<path d="M1698 7492 c-106 -109 -113 -211 -20 -276 20 -14 74 -39 119 -56 46
|
||||
-17 99 -43 119 -56 20 -14 38 -23 40 -21 6 6 -91 98 -166 157 -36 28 -73 66
|
||||
-83 83 -26 45 -18 115 18 167 15 22 26 42 23 45 -2 2 -25 -17 -50 -43z"/>
|
||||
<path d="M2710 7365 c0 -3 15 -19 34 -36 51 -44 66 -100 66 -243 0 -102 3
|
||||
-126 19 -153 35 -57 90 -56 184 3 32 20 57 38 55 40 -2 2 -32 -4 -66 -14 -112
|
||||
-30 -146 3 -136 129 11 127 -29 224 -108 265 -28 14 -48 18 -48 9z"/>
|
||||
<path d="M2086 6674 c10 -194 22 -236 83 -287 76 -64 161 -39 279 81 99 102
|
||||
110 125 32 72 -130 -88 -213 -109 -277 -70 -41 24 -45 33 -81 169 -17 62 -33
|
||||
116 -36 118 -3 3 -3 -34 0 -83z"/>
|
||||
<path d="M6902 7869 c-19 -6 -40 -22 -51 -39 -18 -31 -57 -208 -47 -217 3 -4
|
||||
19 25 36 63 62 143 59 139 117 142 64 4 123 -24 209 -97 63 -54 93 -68 53 -25
|
||||
-32 36 -164 141 -197 158 -40 20 -83 26 -120 15z"/>
|
||||
<path d="M7470 7455 c0 -3 31 -42 70 -88 38 -45 80 -102 92 -127 28 -56 33
|
||||
-139 14 -210 -8 -30 -13 -56 -11 -58 1 -1 17 19 34 47 20 30 34 67 37 97 7 58
|
||||
-1 72 -138 237 -84 100 -98 115 -98 102z"/>
|
||||
<path d="M6560 7189 c-14 -11 -56 -36 -95 -56 -77 -40 -190 -144 -201 -187 -3
|
||||
-14 -3 -38 1 -53 8 -33 85 -108 146 -142 l43 -25 -22 30 c-105 136 -98 179 52
|
||||
334 107 111 135 147 76 99z"/>
|
||||
<path d="M6980 7095 c0 -28 6 -60 15 -71 19 -28 75 -54 114 -54 27 0 32 -4 38
|
||||
-31 7 -38 23 -19 23 29 0 31 -1 32 -43 32 -49 0 -78 23 -121 100 l-26 45 0
|
||||
-50z"/>
|
||||
<path d="M6850 6833 c0 -194 171 -375 302 -320 19 8 44 27 56 42 28 35 54 29
|
||||
69 -15 l12 -35 0 40 c1 88 -47 120 -96 64 -15 -17 -39 -35 -51 -40 -34 -13
|
||||
-96 6 -137 43 -40 34 -97 132 -122 204 -23 71 -33 76 -33 17z"/>
|
||||
<path d="M4656 6262 c-14 -28 -16 -56 -14 -177 l3 -145 32 -6 c36 -7 50 -31
|
||||
30 -51 -7 -7 -33 -19 -57 -26 -42 -11 -47 -17 -80 -81 -30 -60 -34 -79 -34
|
||||
-140 1 -44 8 -93 21 -129 22 -64 14 -89 -10 -29 -29 74 -40 153 -27 212 6 30
|
||||
14 67 17 82 7 39 -15 44 -76 17 -48 -20 -51 -24 -52 -58 0 -20 -4 -44 -8 -54
|
||||
-4 -12 7 -45 31 -95 21 -42 41 -99 44 -127 8 -63 -8 -51 -25 19 -17 68 -67
|
||||
165 -106 203 l-33 32 -39 -29 c-39 -29 -40 -32 -47 -103 -7 -81 1 -115 47
|
||||
-189 16 -27 27 -54 25 -60 -3 -7 -17 6 -32 30 -25 40 -38 68 -65 135 -12 31
|
||||
-58 54 -81 39 -8 -5 -19 -38 -25 -76 -9 -55 -9 -72 4 -98 13 -27 73 -80 104
|
||||
-91 7 -2 9 -8 5 -12 -12 -12 -98 44 -119 78 -10 18 -24 50 -30 72 l-11 40 -22
|
||||
-27 c-11 -15 -33 -49 -48 -76 l-28 -49 28 -54 c20 -40 38 -60 70 -77 38 -21
|
||||
55 -42 34 -42 -22 0 -101 64 -118 95 -22 42 -31 44 -40 8 -10 -40 41 -133 83
|
||||
-153 40 -18 43 -35 6 -25 -38 9 -50 21 -83 79 l-29 50 -11 -30 c-16 -41 -50
|
||||
-185 -50 -211 0 -23 45 -56 93 -67 15 -4 25 -11 22 -16 -6 -10 -58 3 -91 25
|
||||
-12 8 -25 11 -27 7 -8 -12 1 -95 12 -124 9 -24 16 -28 51 -28 25 0 39 -4 35
|
||||
-10 -3 -5 -19 -10 -36 -10 -17 0 -29 -5 -29 -12 0 -7 20 -44 44 -82 l43 -69 6
|
||||
119 c6 142 22 209 81 329 96 194 252 330 461 401 115 39 295 45 415 15 274
|
||||
-68 483 -268 570 -546 34 -110 39 -278 11 -398 -66 -287 -275 -508 -558 -589
|
||||
-113 -33 -306 -32 -413 1 -100 31 -211 90 -287 152 -66 54 -157 166 -192 237
|
||||
l-22 43 -30 -26 c-17 -14 -41 -28 -55 -31 -13 -3 -24 -8 -24 -9 0 -2 9 -24 20
|
||||
-49 l19 -46 37 15 c21 8 43 20 50 26 8 7 14 7 18 0 4 -6 -16 -22 -43 -36 l-50
|
||||
-25 20 -35 c24 -40 54 -45 122 -21 20 8 37 9 37 4 0 -12 -63 -38 -95 -38 -14
|
||||
0 -25 -6 -25 -12 0 -7 24 -44 53 -83 l53 -70 49 3 c35 1 65 11 99 33 27 16 51
|
||||
27 55 24 8 -8 -34 -40 -87 -66 -64 -32 -68 -49 -23 -93 37 -36 40 -37 87 -31
|
||||
70 11 81 19 96 72 7 25 20 49 27 51 10 3 12 0 6 -14 -4 -11 -13 -41 -21 -69
|
||||
-9 -35 -24 -59 -49 -82 l-37 -32 69 -45 c37 -25 73 -46 79 -46 7 0 34 17 61
|
||||
38 52 41 97 114 107 175 4 20 11 37 16 37 11 0 4 -48 -15 -105 -7 -22 -16 -60
|
||||
-19 -83 -4 -24 -15 -61 -26 -82 l-19 -38 37 -15 c36 -14 38 -14 79 15 50 36
|
||||
52 41 63 137 6 53 4 86 -5 118 -15 51 -3 67 16 21 17 -41 14 -149 -6 -234 -24
|
||||
-102 -19 -109 83 -128 47 -9 88 -14 90 -11 3 3 0 16 -6 30 -19 41 -14 85 19
|
||||
191 30 96 33 149 14 197 -4 9 -3 17 3 17 24 0 35 -70 22 -145 -12 -70 -12 -75
|
||||
10 -107 46 -66 99 -113 84 -73 -10 25 17 114 43 145 25 30 25 30 8 86 -9 32
|
||||
-27 73 -40 93 -27 39 -25 53 4 27 40 -36 63 -113 67 -225 3 -58 9 -120 14
|
||||
-137 13 -40 74 -99 125 -120 44 -18 95 -34 95 -30 0 2 -15 19 -33 38 -33 35
|
||||
-57 94 -57 139 0 14 9 57 20 97 37 135 20 239 -56 339 -18 23 -21 33 -11 33
|
||||
34 0 107 -148 107 -218 0 -21 7 -48 16 -60 18 -25 74 -56 121 -67 l33 -7 -15
|
||||
43 c-9 24 -15 77 -15 127 0 79 -2 89 -31 134 -31 48 -105 98 -144 98 -8 0 -15
|
||||
5 -15 10 0 28 105 -13 146 -57 14 -15 45 -63 69 -108 49 -89 88 -136 131 -158
|
||||
16 -8 71 -17 126 -20 53 -3 106 -10 117 -16 25 -14 26 -9 7 27 -19 36 -63 76
|
||||
-111 100 -76 38 -99 77 -134 227 -33 136 -66 177 -169 204 -55 15 -46 32 10
|
||||
18 74 -18 114 -51 145 -117 23 -49 35 -62 66 -75 39 -16 94 -19 146 -9 36 7
|
||||
40 20 10 29 -52 17 -116 97 -133 168 -9 37 -62 84 -110 98 -23 6 -63 8 -95 4
|
||||
-44 -5 -53 -4 -44 6 13 13 114 18 154 7 15 -4 62 -37 105 -73 121 -100 166
|
||||
-105 310 -34 59 29 96 41 133 42 28 1 51 4 51 7 0 9 -70 61 -100 74 -21 8 -47
|
||||
10 -87 4 -32 -5 -81 -11 -110 -14 l-52 -6 -58 55 c-32 30 -76 64 -98 75 -44
|
||||
21 -129 42 -175 42 -17 0 -30 5 -30 11 0 19 128 0 195 -28 33 -14 70 -25 82
|
||||
-25 41 0 153 99 153 135 0 3 -18 0 -40 -5 -55 -14 -95 -4 -170 42 -36 22 -75
|
||||
40 -88 40 -12 0 -46 -16 -74 -35 -29 -19 -54 -32 -57 -29 -10 10 50 54 114 85
|
||||
54 26 70 29 162 29 144 0 173 14 239 117 18 29 52 66 75 83 66 47 20 36 -92
|
||||
-22 -111 -58 -99 -57 -249 -14 -86 25 -196 21 -255 -9 -27 -13 -51 -22 -53
|
||||
-20 -11 11 24 35 71 51 29 9 61 25 73 36 12 10 63 30 115 44 52 14 104 31 116
|
||||
39 22 15 43 69 43 113 0 26 -2 26 -38 8 -15 -7 -47 -17 -72 -20 -25 -4 -67
|
||||
-13 -95 -21 -27 -8 -77 -22 -109 -31 -44 -11 -69 -25 -93 -52 -18 -20 -33 -42
|
||||
-33 -49 0 -7 -4 -13 -10 -13 -33 0 21 84 70 109 19 10 71 28 115 40 142 39
|
||||
177 74 206 208 19 88 58 184 86 215 15 17 14 18 -8 18 -80 0 -154 -53 -226
|
||||
-162 -30 -45 -60 -77 -88 -93 -38 -23 -65 -32 -186 -65 -20 -5 -57 -26 -82
|
||||
-45 -54 -41 -64 -32 -14 12 18 16 42 44 54 63 12 20 37 46 55 60 75 55 118
|
||||
102 124 137 7 35 -10 114 -24 112 -4 0 -21 -19 -37 -42 -40 -55 -108 -100
|
||||
-186 -123 -56 -17 -64 -23 -80 -59 -11 -22 -22 -57 -25 -77 -7 -39 -24 -53
|
||||
-24 -20 0 28 37 129 62 171 12 20 52 68 89 107 93 98 104 122 102 216 -1 42
|
||||
-9 100 -17 129 -18 60 -20 107 -6 143 12 33 2 33 -47 -3 -47 -34 -73 -90 -73
|
||||
-160 0 -59 -20 -130 -50 -171 -12 -17 -53 -54 -91 -81 -92 -67 -137 -122 -157
|
||||
-191 -19 -64 -38 -79 -27 -21 6 35 18 60 64 141 11 19 25 65 32 103 18 100 2
|
||||
162 -58 229 -24 27 -49 50 -53 50 -4 0 -10 -42 -12 -92 -4 -107 -23 -155 -95
|
||||
-239 -48 -56 -49 -61 -28 -139 18 -66 17 -60 5 -60 -13 0 -38 88 -51 185 -10
|
||||
78 -9 86 15 157 17 48 26 96 26 134 0 52 -5 69 -36 120 -70 112 -86 145 -98
|
||||
192 l-13 47 -17 -33z"/>
|
||||
<path d="M4060 6014 c0 -2 5 -25 10 -52 l11 -48 54 11 c29 5 56 13 59 15 2 3
|
||||
-27 21 -65 41 -38 20 -69 35 -69 33z"/>
|
||||
<path d="M5650 5960 c-19 -5 -70 -14 -112 -21 l-76 -13 1 -97 2 -97 -42 -62
|
||||
c-24 -34 -43 -72 -43 -85 0 -29 -24 -49 -50 -42 -13 3 -25 -2 -35 -16 -24 -35
|
||||
-18 -37 44 -16 72 25 143 84 175 147 l23 46 16 -25 c9 -13 21 -45 28 -71 19
|
||||
-71 2 -116 -68 -175 -113 -95 -138 -127 -82 -104 13 5 57 19 97 31 l72 21 0
|
||||
47 c0 67 34 130 90 167 27 18 93 44 160 63 183 52 224 79 237 155 7 45 -30 96
|
||||
-92 127 -44 22 -64 25 -180 27 -71 1 -146 -2 -165 -7z"/>
|
||||
<path d="M4315 5909 c-520 -53 -966 -396 -1139 -877 -73 -203 -91 -472 -47
|
||||
-681 52 -243 173 -471 345 -646 196 -199 467 -342 741 -389 119 -21 353 -21
|
||||
474 0 110 19 197 45 194 59 -1 6 -23 13 -48 17 -275 40 -649 307 -843 602 -64
|
||||
98 -132 241 -132 281 0 11 10 15 35 15 45 0 92 23 100 49 11 36 -14 98 -75
|
||||
183 -33 45 -75 112 -93 148 -31 63 -32 68 -31 185 1 86 7 142 22 197 74 275
|
||||
235 506 467 670 67 48 221 129 295 155 21 8 31 16 25 22 -15 15 -189 21 -290
|
||||
10z m194 -23 c-2 -2 -38 -22 -79 -43 -405 -209 -664 -603 -665 -1009 0 -96 2
|
||||
-104 37 -175 21 -41 64 -112 97 -158 62 -85 83 -143 62 -164 -6 -6 -42 -15
|
||||
-79 -20 -37 -4 -86 -10 -109 -13 -52 -7 -66 13 -40 58 21 36 21 48 3 48 -15 0
|
||||
-46 -58 -46 -87 0 -37 34 -56 88 -51 l49 5 18 -55 c9 -30 34 -88 55 -128 l38
|
||||
-74 -42 0 c-29 0 -65 11 -107 33 -61 31 -64 33 -61 66 5 42 -17 39 -31 -5 -5
|
||||
-16 -20 -40 -33 -55 -13 -14 -24 -28 -24 -32 0 -14 24 -7 45 13 l22 20 62 -32
|
||||
c46 -24 80 -33 127 -36 l64 -4 45 -63 c164 -231 436 -436 695 -526 l95 -33
|
||||
-55 -13 c-343 -76 -732 -3 -1030 193 -460 303 -676 860 -535 1382 142 526 610
|
||||
908 1170 955 64 6 169 7 164 3z"/>
|
||||
<path d="M3481 5234 c-50 -25 -71 -47 -57 -61 3 -3 23 6 44 20 74 52 151 48
|
||||
207 -10 19 -20 38 -33 42 -29 13 13 -22 60 -62 83 -54 30 -108 29 -174 -3z"/>
|
||||
<path d="M3630 5144 c0 -3 11 -20 25 -38 24 -32 50 -129 39 -147 -3 -5 -15 -7
|
||||
-28 -3 -27 6 -74 -11 -103 -38 l-22 -21 -31 27 c-31 25 -70 35 -70 17 0 -5 15
|
||||
-18 34 -29 38 -23 122 -120 162 -188 15 -25 30 -42 35 -37 7 7 22 80 49 243
|
||||
12 71 -8 157 -48 203 -14 16 -42 24 -42 11z m48 -222 c19 -13 4 -67 -28 -97
|
||||
l-27 -26 -31 37 c-30 36 -31 39 -14 57 28 32 74 45 100 29z"/>
|
||||
<path d="M3406 4541 c-4 -7 6 -32 23 -59 82 -128 130 -278 134 -412 3 -107 21
|
||||
-109 25 -1 4 107 -23 214 -83 336 -51 103 -88 155 -99 136z"/>
|
||||
<path d="M8240 5861 c-66 -35 -213 -81 -304 -96 -109 -18 -448 -20 -579 -4
|
||||
-49 6 -91 8 -94 6 -3 -3 8 -25 25 -49 79 -115 152 -309 177 -473 13 -80 16
|
||||
-147 12 -269 -4 -142 -9 -179 -41 -316 -21 -85 -45 -186 -53 -225 -24 -101
|
||||
-24 -476 -1 -597 30 -154 60 -248 112 -352 l51 -101 85 -3 c103 -4 224 12 298
|
||||
40 137 51 324 212 427 368 58 89 139 246 184 359 l32 82 -69 67 c-78 76 -117
|
||||
148 -127 232 -7 60 23 208 71 343 108 311 80 633 -77 892 -79 129 -73 125
|
||||
-129 96z m76 -75 c64 -64 29 -136 -47 -96 -20 11 -49 60 -49 84 0 19 27 46 47
|
||||
46 8 0 30 -15 49 -34z m-855 -115 c41 -41 49 -70 24 -91 -36 -30 -125 33 -125
|
||||
88 0 35 6 42 38 42 15 0 39 -15 63 -39z m544 -106 c99 -28 219 -124 263 -212
|
||||
72 -141 1 -334 -158 -435 -66 -42 -111 -51 -177 -38 -47 10 -65 21 -108 64
|
||||
-84 84 -122 218 -112 398 8 148 43 209 134 234 50 13 75 12 158 -11z m212
|
||||
-760 c72 -30 113 -86 113 -153 0 -49 -68 -395 -109 -557 -62 -242 -110 -332
|
||||
-220 -408 -42 -29 -53 -32 -121 -32 -64 0 -85 5 -140 32 -134 66 -238 177
|
||||
-292 312 -20 49 -23 74 -23 191 1 150 14 201 77 308 100 166 294 300 467 322
|
||||
87 11 202 4 248 -15z m271 -557 c17 -17 15 -79 -4 -106 -21 -30 -61 -29 -75 2
|
||||
-14 30 -5 70 21 96 23 23 41 25 58 8z m-898 -635 c31 -21 48 -41 61 -73 40
|
||||
-97 -89 -92 -136 6 -32 68 14 109 75 67z"/>
|
||||
<path d="M7860 5458 c-48 -32 -64 -70 -68 -159 -6 -115 42 -253 110 -316 36
|
||||
-33 98 -39 154 -13 97 42 139 92 164 193 34 134 -103 290 -275 312 -43 6 -55
|
||||
3 -85 -17z"/>
|
||||
<path d="M7980 4739 c-171 -49 -284 -126 -386 -262 -58 -79 -84 -163 -84 -278
|
||||
0 -176 81 -321 217 -389 37 -19 64 -24 119 -24 63 0 76 3 123 33 62 40 86 70
|
||||
139 176 49 100 95 248 132 425 39 190 26 245 -69 298 -48 27 -137 37 -191 21z
|
||||
m45 -203 c27 -12 36 -23 46 -58 31 -115 -67 -339 -172 -394 -79 -40 -166 3
|
||||
-194 99 -19 64 -19 81 1 147 12 39 30 69 72 113 92 96 169 125 247 93z"/>
|
||||
<path d="M4550 5820 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
|
||||
-10 -4 -10 -10z"/>
|
||||
<path d="M4358 5736 c-23 -17 -23 -29 1 -50 17 -15 19 -15 25 -1 8 23 8 65 -1
|
||||
65 -5 0 -16 -7 -25 -14z"/>
|
||||
<path d="M7205 5720 c-9 -45 -65 -166 -98 -215 -24 -35 -25 -40 -12 -76 22
|
||||
-61 63 -254 81 -384 12 -84 17 -185 17 -340 0 -292 -33 -521 -111 -772 l-29
|
||||
-89 195 -185 c107 -101 198 -185 203 -187 5 -2 -2 20 -15 50 -56 127 -93 312
|
||||
-105 533 -11 194 -1 306 42 490 104 438 88 759 -55 1051 -21 45 -54 99 -72
|
||||
120 l-33 39 -8 -35z"/>
|
||||
<path d="M4170 5590 l-35 -29 28 -10 c43 -17 47 -14 47 29 0 22 -1 40 -2 40
|
||||
-2 0 -19 -14 -38 -30z"/>
|
||||
<path d="M4065 5470 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||
-8 -4 -11 -10z"/>
|
||||
<path d="M4610 5384 c-232 -44 -437 -200 -541 -409 -62 -125 -82 -225 -76
|
||||
-364 15 -328 211 -584 527 -691 89 -30 275 -38 380 -16 113 23 261 101 348
|
||||
181 159 148 241 335 242 551 0 217 -70 387 -224 539 -87 86 -192 149 -310 187
|
||||
-60 19 -285 33 -346 22z m319 -45 c119 -30 223 -91 321 -189 144 -143 213
|
||||
-309 213 -510 0 -130 -18 -204 -80 -330 -88 -179 -260 -318 -460 -371 -107
|
||||
-29 -259 -29 -366 0 -204 54 -376 196 -467 386 -52 109 -70 190 -70 315 0 125
|
||||
18 206 70 315 71 148 186 263 335 335 166 80 321 95 504 49z"/>
|
||||
<path d="M4320 4910 c-89 -41 -160 -140 -160 -222 0 -34 3 -38 38 -47 20 -6
|
||||
119 -11 220 -11 104 0 182 -4 182 -9 0 -5 -7 -37 -16 -71 -30 -119 -1 -163
|
||||
113 -174 76 -7 180 7 200 27 31 31 30 128 -2 210 -7 16 6 17 191 17 236 0 250
|
||||
4 247 68 -2 57 -49 139 -101 179 -146 111 -345 39 -398 -145 -16 -53 -16 -57
|
||||
6 -110 13 -30 25 -77 28 -103 4 -44 1 -51 -27 -80 -31 -30 -34 -31 -103 -27
|
||||
-59 3 -73 8 -89 27 -25 31 -25 96 1 161 26 66 25 114 -4 175 -44 96 -118 146
|
||||
-221 152 -45 3 -71 -2 -105 -17z"/>
|
||||
<path d="M4976 4334 c-3 -9 -6 -26 -6 -39 0 -13 -10 -31 -22 -40 -57 -40 -256
|
||||
-77 -307 -58 -14 6 -28 23 -35 43 -18 55 -36 50 -29 -9 4 -38 2 -57 -11 -75
|
||||
-20 -31 -20 -46 -2 -46 8 0 20 13 27 28 12 27 14 28 98 28 90 0 187 21 254 53
|
||||
36 18 38 18 47 1 11 -20 47 -36 58 -26 3 4 -7 19 -23 36 -24 25 -27 35 -23 74
|
||||
4 33 2 46 -7 46 -7 0 -16 -7 -19 -16z"/>
|
||||
<path d="M5890 5289 c-24 -16 -49 -29 -56 -29 -6 0 -18 -8 -25 -17 -13 -15
|
||||
-11 -16 20 -10 19 4 49 18 66 32 18 13 35 22 37 20 9 -9 -11 -135 -26 -163
|
||||
-18 -35 -58 -55 -152 -77 -40 -9 -80 -23 -90 -31 -18 -15 -18 -15 6 -9 14 3
|
||||
59 15 101 25 101 25 126 38 149 75 20 33 46 176 36 201 -7 19 -14 17 -66 -17z"/>
|
||||
<path d="M5720 4771 c0 -5 33 -29 73 -51 63 -36 78 -40 119 -36 26 3 58 12 72
|
||||
21 33 22 39 6 15 -42 -24 -49 -77 -103 -124 -129 -19 -10 -35 -22 -35 -27 0
|
||||
-13 58 24 112 72 54 48 73 78 83 133 8 44 -8 58 -38 30 -68 -61 -131 -55 -252
|
||||
24 -14 9 -25 12 -25 5z"/>
|
||||
<path d="M5780 4285 c0 -3 6 -20 14 -38 19 -47 74 -95 128 -115 27 -9 48 -20
|
||||
48 -24 0 -4 -25 -15 -56 -24 -55 -16 -139 -15 -191 1 -13 4 -23 3 -23 -3 0
|
||||
-17 88 -35 151 -29 66 6 154 44 154 67 0 9 -14 17 -37 21 -60 9 -129 58 -153
|
||||
107 -17 36 -35 55 -35 37z"/>
|
||||
<path d="M5537 3858 c-9 -33 8 -129 28 -168 9 -18 14 -35 10 -38 -11 -12 -119
|
||||
18 -157 43 -29 19 -38 22 -38 10 0 -21 62 -50 149 -69 81 -19 108 -11 78 22
|
||||
-28 31 -47 89 -47 144 0 62 -14 94 -23 56z"/>
|
||||
<path d="M4165 3851 c-6 -11 9 -23 19 -14 9 9 7 23 -3 23 -6 0 -12 -4 -16 -9z"/>
|
||||
<path d="M4301 3721 c-11 -7 -10 -11 7 -20 16 -9 25 -7 44 9 l23 19 -30 1
|
||||
c-16 0 -36 -4 -44 -9z"/>
|
||||
<path d="M5135 3640 c-14 -26 -19 -52 -18 -103 l1 -67 -39 31 c-21 17 -53 51
|
||||
-71 75 -36 51 -44 46 -54 -30 l-7 -46 51 0 c45 0 59 -5 104 -40 38 -29 54 -36
|
||||
56 -26 2 8 -2 24 -7 35 -14 25 -14 64 0 78 5 5 9 37 7 69 l-3 59 -20 -35z"/>
|
||||
<path d="M4585 3605 c-29 -66 -31 -83 -11 -65 17 13 51 108 42 117 -3 3 -17
|
||||
-20 -31 -52z"/>
|
||||
<path d="M4710 3500 c-19 -15 -21 -20 -8 -20 9 0 24 7 32 16 31 30 13 33 -24
|
||||
4z"/>
|
||||
<path d="M4913 3338 c-29 -11 -53 -25 -53 -30 0 -5 10 -28 22 -51 11 -23 24
|
||||
-67 27 -97 l7 -55 31 49 c49 74 69 199 33 202 -8 1 -39 -7 -67 -18z"/>
|
||||
<path d="M4080 3287 c0 -6 11 -30 25 -53 34 -57 102 -99 149 -90 50 10 86 42
|
||||
86 77 l0 29 -63 -35 c-85 -47 -118 -47 -82 1 28 37 19 49 -50 66 -41 10 -65
|
||||
11 -65 5z"/>
|
||||
<path d="M4229 3243 c-5 -13 -9 -26 -9 -28 0 -8 37 7 59 24 l24 18 -32 5 c-28
|
||||
4 -34 1 -42 -19z"/>
|
||||
<path d="M796 5833 c-45 -62 -118 -210 -144 -293 -31 -98 -42 -172 -42 -291 0
|
||||
-129 18 -227 70 -390 54 -168 74 -267 65 -335 -10 -82 -56 -164 -131 -232
|
||||
l-64 -59 15 -44 c32 -90 135 -289 203 -390 198 -295 449 -437 729 -414 l82 7
|
||||
39 81 c52 106 97 242 119 359 13 72 17 143 17 313 0 236 3 214 -70 515 -37
|
||||
152 -38 159 -38 355 -1 224 7 284 61 439 38 110 80 198 125 264 17 24 28 46
|
||||
25 48 -3 3 -49 0 -104 -6 -54 -7 -188 -12 -298 -13 -168 0 -216 3 -301 21
|
||||
-102 22 -203 56 -274 92 -22 11 -42 20 -45 20 -2 0 -20 -21 -39 -47z m88 -29
|
||||
c9 -8 16 -22 16 -30 0 -24 -29 -73 -49 -84 -76 -40 -111 32 -47 96 38 38 57
|
||||
42 80 18z m874 -132 c3 -25 -3 -39 -26 -62 -35 -35 -76 -47 -97 -30 -25 21
|
||||
-17 50 25 92 30 30 45 39 67 36 23 -2 29 -8 31 -36z m-482 -97 c54 -16 80 -39
|
||||
110 -95 16 -29 19 -57 19 -175 0 -196 -30 -291 -118 -372 -98 -90 -233 -74
|
||||
-350 44 -80 79 -123 192 -111 288 16 133 151 264 314 307 81 21 76 21 136 3z
|
||||
m-23 -780 c103 -35 173 -81 263 -171 97 -96 155 -202 181 -329 19 -94 9 -201
|
||||
-27 -297 -88 -235 -361 -407 -523 -329 -95 46 -176 167 -226 336 -50 170 -131
|
||||
571 -131 648 0 84 60 145 162 167 69 15 224 2 301 -25z m-563 -555 c39 -39 35
|
||||
-111 -6 -127 -27 -10 -64 40 -64 86 0 59 32 79 70 41z m920 -625 c26 -31 -19
|
||||
-117 -73 -140 -65 -27 -95 16 -57 83 35 62 101 91 130 57z"/>
|
||||
<path d="M1145 5471 c-97 -24 -189 -91 -225 -166 -72 -149 54 -350 219 -347
|
||||
55 2 84 20 121 80 94 149 93 364 -2 422 -34 21 -61 23 -113 11z"/>
|
||||
<path d="M986 4736 c-86 -32 -126 -86 -126 -169 0 -120 89 -445 161 -589 44
|
||||
-88 70 -120 130 -159 45 -29 60 -33 118 -33 51 -1 77 5 115 24 62 32 147 126
|
||||
189 210 28 57 32 76 35 162 3 78 0 112 -17 167 -25 81 -91 176 -172 247 -128
|
||||
111 -333 177 -433 140z m251 -205 c64 -30 132 -98 164 -166 81 -170 -23 -348
|
||||
-169 -290 -97 39 -192 218 -192 362 0 47 4 59 29 84 25 24 38 29 78 29 26 0
|
||||
67 -9 90 -19z"/>
|
||||
<path d="M6831 3169 c-27 -43 -27 -100 -1 -130 22 -27 24 -20 10 32 -8 27 -6
|
||||
46 5 79 19 54 12 63 -14 19z"/>
|
||||
<path d="M7575 2578 c70 -52 169 -156 191 -201 19 -36 23 -56 18 -82 -5 -29
|
||||
-1 -42 20 -70 15 -19 36 -40 47 -45 20 -11 20 -11 0 29 -12 22 -21 53 -21 69
|
||||
0 62 -32 124 -103 198 -71 75 -112 107 -157 118 -24 7 -24 6 5 -16z"/>
|
||||
<path d="M2191 3077 c-26 -43 -43 -87 -32 -87 5 0 24 20 42 45 36 47 47 53 67
|
||||
33 15 -15 16 -3 2 23 -17 31 -55 24 -79 -14z"/>
|
||||
<path d="M2141 2810 c-68 -54 -126 -101 -129 -104 -11 -11 32 -46 56 -46 87 0
|
||||
232 144 232 230 0 37 -34 21 -159 -80z"/>
|
||||
<path d="M2295 2825 c-27 -56 -126 -154 -164 -161 -17 -4 -31 -11 -31 -16 0
|
||||
-13 64 -1 100 17 44 23 108 100 115 139 11 54 0 65 -20 21z"/>
|
||||
<path d="M1629 2646 c-78 -32 -125 -62 -187 -120 -81 -76 -100 -128 -68 -185
|
||||
13 -24 14 -24 21 17 15 93 134 225 248 278 53 23 68 34 50 34 -5 0 -33 -11
|
||||
-64 -24z"/>
|
||||
<path d="M6770 2840 c-36 -46 -32 -51 12 -15 35 30 40 30 98 4 l45 -20 -34 35
|
||||
c-48 49 -80 48 -121 -4z"/>
|
||||
<path d="M6898 2704 c12 -8 28 -30 36 -48 10 -23 28 -40 60 -55 25 -13 51 -21
|
||||
58 -19 7 3 -6 13 -27 24 -24 11 -46 31 -54 49 -21 46 -42 65 -70 65 l-25 0 22
|
||||
-16z"/>
|
||||
<path d="M6581 2625 c-45 -49 -50 -76 -28 -148 11 -34 74 -126 88 -127 3 0
|
||||
-13 36 -34 79 -39 78 -39 80 -27 130 6 28 21 62 32 76 35 45 13 37 -31 -10z"/>
|
||||
<path d="M2500 2650 c-24 -46 -19 -50 22 -15 23 19 34 23 48 15 35 -19 45 -9
|
||||
17 15 -41 36 -63 32 -87 -15z"/>
|
||||
<path d="M2494 2468 c-32 -40 -108 -124 -168 -186 l-110 -113 35 -35 36 -35
|
||||
51 43 c69 58 122 130 182 250 47 93 62 148 41 148 -5 0 -35 -33 -67 -72z"/>
|
||||
<path d="M2455 2253 c-35 -56 -142 -163 -163 -163 -8 0 -32 14 -54 32 -29 23
|
||||
-38 26 -30 12 5 -10 20 -28 31 -38 12 -11 21 -24 21 -30 0 -6 -29 -40 -65 -76
|
||||
-85 -86 -135 -181 -135 -257 0 -65 18 -108 66 -154 l35 -34 -30 59 c-24 45
|
||||
-31 71 -31 115 0 78 33 131 176 282 111 117 193 226 208 277 11 33 4 27 -29
|
||||
-25z"/>
|
||||
<path d="M2000 2226 c-126 -63 -217 -133 -246 -187 -31 -61 -10 -135 47 -165
|
||||
34 -17 36 -11 9 23 -25 32 -25 66 -1 119 28 61 72 105 171 174 117 80 121 87
|
||||
20 36z"/>
|
||||
<path d="M6239 2366 c-80 -59 -139 -174 -139 -270 0 -58 36 -144 78 -189 65
|
||||
-69 163 -91 256 -58 80 28 90 40 24 30 -144 -24 -273 56 -308 191 -24 89 8
|
||||
184 92 276 28 30 48 54 47 54 -2 0 -25 -16 -50 -34z"/>
|
||||
<path d="M6380 2342 c0 -4 5 -13 11 -19 6 -6 19 -35 29 -64 10 -32 27 -61 43
|
||||
-73 23 -17 30 -18 54 -6 15 7 24 14 18 15 -5 2 -22 6 -37 9 -20 5 -29 15 -38
|
||||
43 -7 21 -18 48 -26 60 -15 24 -54 49 -54 35z"/>
|
||||
<path d="M6606 1752 c-29 -33 -110 -71 -179 -84 -37 -7 -67 -17 -67 -23 0 -24
|
||||
64 -118 114 -170 133 -136 286 -141 426 -15 l45 41 -48 -31 c-143 -94 -298
|
||||
-73 -416 56 -69 76 -67 98 11 123 45 15 113 63 128 92 15 28 7 34 -14 11z"/>
|
||||
<path d="M2915 2300 c-15 -17 -20 -39 -23 -98 l-3 -77 20 35 c11 19 20 42 21
|
||||
51 0 9 8 27 18 40 19 22 19 22 68 5 87 -30 90 -29 32 13 -72 52 -106 60 -133
|
||||
31z"/>
|
||||
<path d="M3260 2146 c0 -3 18 -22 39 -41 26 -24 50 -61 70 -108 30 -70 30 -71
|
||||
31 -34 0 47 -22 95 -58 129 -32 30 -82 63 -82 54z"/>
|
||||
<path d="M3105 2099 c-35 -31 -165 -216 -165 -236 0 -15 46 -33 83 -33 63 0
|
||||
126 65 153 158 19 63 12 117 -16 132 -15 8 -26 4 -55 -21z"/>
|
||||
<path d="M3181 1949 c-30 -66 -84 -126 -121 -134 -15 -3 -47 -1 -70 5 -54 15
|
||||
-59 8 -18 -21 42 -30 127 -32 159 -3 32 29 88 154 89 197 0 21 -18 1 -39 -44z"/>
|
||||
<path d="M2586 1739 c-140 -108 -169 -147 -174 -234 -5 -76 13 -119 69 -170
|
||||
41 -36 75 -49 47 -17 -9 9 -26 37 -38 61 -19 37 -22 56 -18 112 7 89 36 137
|
||||
139 230 69 62 110 109 96 109 -2 0 -56 -41 -121 -91z"/>
|
||||
<path d="M2948 1426 c-103 -110 -131 -173 -108 -242 12 -34 86 -101 99 -88 3
|
||||
3 -1 11 -9 17 -34 26 -50 62 -50 115 0 63 20 106 84 181 95 109 83 122 -16 17z"/>
|
||||
<path d="M5835 2098 c-79 -40 -105 -65 -105 -100 0 -36 13 -62 56 -111 19 -20
|
||||
34 -42 34 -49 0 -6 2 -9 5 -6 3 3 -8 31 -25 63 -34 65 -36 84 -15 125 15 29
|
||||
82 82 127 100 21 8 21 9 3 9 -11 0 -47 -14 -80 -31z"/>
|
||||
<path d="M5978 1422 c15 -152 25 -197 54 -258 40 -87 112 -104 226 -54 62 27
|
||||
62 36 1 20 -63 -17 -121 -5 -160 34 -40 38 -69 117 -99 266 -11 57 -24 108
|
||||
-28 112 -4 4 -2 -50 6 -120z"/>
|
||||
<path d="M3591 1977 c-38 -19 -59 -56 -81 -145 -15 -59 -20 -97 -15 -131 10
|
||||
-72 20 -76 21 -7 2 80 34 155 85 201 39 35 58 40 132 36 20 -2 37 -1 37 2 0 4
|
||||
-94 40 -135 51 -11 3 -31 0 -44 -7z"/>
|
||||
<path d="M4040 1861 c8 -5 49 -16 90 -26 65 -15 81 -24 118 -62 45 -47 55 -41
|
||||
21 14 -27 44 -63 62 -152 73 -88 12 -96 12 -77 1z"/>
|
||||
<path d="M3781 1648 c0 -85 -19 -175 -47 -230 -9 -17 -45 -68 -80 -114 -77
|
||||
-101 -88 -122 -113 -211 -13 -49 -17 -82 -12 -109 10 -51 36 -101 61 -114 18
|
||||
-10 17 -7 -4 25 -35 51 -40 86 -21 151 27 95 73 179 132 245 73 83 114 170
|
||||
121 259 4 66 -11 165 -28 175 -5 3 -8 -32 -9 -77z"/>
|
||||
<path d="M4109 1701 c18 -33 13 -78 -10 -94 -19 -14 -19 -18 -8 -57 14 -47 50
|
||||
-80 86 -80 24 0 24 0 -6 25 -38 33 -46 59 -27 87 29 42 17 106 -25 129 -19 9
|
||||
-19 9 -10 -10z"/>
|
||||
<path d="M4665 1958 c-50 -27 -57 -76 -25 -163 l18 -50 1 87 c1 78 3 88 23
|
||||
102 12 9 42 16 66 16 26 0 41 4 37 10 -8 13 -95 12 -120 -2z"/>
|
||||
<path d="M4650 875 c0 -178 11 -236 52 -270 27 -23 38 -25 118 -25 102 0 128
|
||||
15 43 25 -92 10 -110 15 -128 35 -22 24 -41 107 -50 208 -10 130 -17 172 -26
|
||||
172 -5 0 -9 -65 -9 -145z"/>
|
||||
<path d="M5138 1866 c-98 -21 -119 -61 -78 -151 22 -49 46 -73 30 -30 -6 15
|
||||
-10 46 -10 70 0 52 23 73 110 100 30 9 60 18 65 20 28 9 -68 2 -117 -9z"/>
|
||||
<path d="M5717 1463 c-4 -3 -7 -33 -7 -65 0 -70 -23 -100 -90 -118 -36 -10
|
||||
-50 -9 -85 5 -24 8 -51 25 -60 35 -10 11 -23 20 -29 20 -14 0 54 -69 84 -85
|
||||
52 -27 96 -29 145 -5 55 27 68 48 75 125 l5 60 35 -3 35 -2 -25 20 c-24 19
|
||||
-70 27 -83 13z"/>
|
||||
<path d="M5147 1215 c-46 -206 2 -360 114 -371 l44 -4 -41 19 c-58 27 -80 60
|
||||
-94 143 -9 53 -10 92 -1 158 13 104 14 130 3 130 -5 0 -16 -34 -25 -75z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 34 KiB |
19
apps/wrapped/static/wrapped/favicon/1/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/favicon/1/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/favicon/1/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
BIN
apps/wrapped/static/wrapped/fonts/1/JEMROKtrial-Regular.ttf
Normal file
BIN
apps/wrapped/static/wrapped/img/1/bg.png
Normal file
After Width: | Height: | Size: 732 KiB |
87
apps/wrapped/tables.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.middlewares import get_current_request
|
||||
import django_tables2 as tables
|
||||
from django_tables2 import A
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .models import Wrapped
|
||||
|
||||
|
||||
class WrappedTable(tables.Table):
|
||||
"""
|
||||
List all wrapped
|
||||
"""
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table-condensed table-striped table-hover',
|
||||
'id': 'wrapped_table'
|
||||
}
|
||||
row_attrs = {
|
||||
'class': lambda record: 'bg-danger' if not record.generated else '',
|
||||
}
|
||||
model = Wrapped
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
fields = ('note', 'bde', 'public', )
|
||||
|
||||
view = tables.LinkColumn(
|
||||
'wrapped:wrapped_detail',
|
||||
args=[A('pk')],
|
||||
attrs={
|
||||
'td': {'class': 'col-sm-2'},
|
||||
'a': {
|
||||
'class': 'btn btn-sm btn-primary',
|
||||
'data-turbolinks': 'false',
|
||||
}
|
||||
},
|
||||
text=_('view the wrapped'),
|
||||
accessor='pk',
|
||||
verbose_name=_('View'),
|
||||
orderable=False,
|
||||
)
|
||||
|
||||
public = tables.Column(
|
||||
accessor="pk",
|
||||
orderable=False,
|
||||
attrs={
|
||||
"td": {
|
||||
"id": lambda record: "makepublic_" + str(record.pk),
|
||||
"class": 'col-sm-1',
|
||||
"data-toggle": "tooltip",
|
||||
"title": lambda record:
|
||||
(_("Click to make this wrapped private") if record.public else
|
||||
_("Click to make this wrapped public")) if PermissionBackend.check_perm(
|
||||
get_current_request(), "wrapped.change_wrapped_public", record) else None,
|
||||
"onclick": lambda record:
|
||||
'makepublic(' + str(record.id) + ', ' + str(not record.public).lower() + ')'
|
||||
if PermissionBackend.check_perm(get_current_request(), "wrapped.change_wrapped_public",
|
||||
record) else None
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
share = tables.Column(
|
||||
verbose_name=_("Share"),
|
||||
accessor="pk",
|
||||
orderable=False,
|
||||
attrs={
|
||||
"td": {
|
||||
"class": 'col-sm-2',
|
||||
"title": _("Click to copy the link in the press paper"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def render_share(self, value, record):
|
||||
val = '<a class="btn btn-sm btn-primary" data-turbolinks="false" '
|
||||
val += 'onclick="copylink(' + str(record.id) + ')">'
|
||||
val += _('Copy link')
|
||||
val += '</a>'
|
||||
return format_html(val)
|
||||
|
||||
def render_public(self, value, record):
|
||||
val = "✔" if record.public else "✖"
|
||||
return val
|
82
apps/wrapped/templates/wrapped/1/wrapped_base.html
Normal file
@@ -0,0 +1,82 @@
|
||||
{% load static i18n pretty_money getenv %}
|
||||
{% comment %}
|
||||
Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
<!DOCTYPE html>
|
||||
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
|
||||
<html lang="{{ LANGUAGE_CODE|default:"en" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %} class="postition-relative h-100">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>
|
||||
{% block title %}{{ title }}{% endblock title %} - {{ request.site.name }}
|
||||
</title>
|
||||
<meta name="description" content="{% trans "The ENS Paris-Saclay BDE note." %}">
|
||||
|
||||
{# Favicon #}
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static "wrapped/favicon/1/apple-touch-icon.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{% static "wrapped/favicon/1/favicon-32x32.png" %}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{% static "wrapped/favicon/1/favicon-16x16.png" %}">
|
||||
<link rel="manifest" href="{% static "wrapped/favicon/1/site.webmanifest" %}">
|
||||
<link rel="mask-icon" href="{% static "wrapped/favicon/1/safari-pinned-tab.svg" %}" color="#5bbad5">
|
||||
<link rel="shorcut icon" href="{% static "wrapped/favicon/1/favicon.ico" %}">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-config" content="{% static "wrapped/favicon/1/browserconfig.xml" %}">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
{# Bootstrap, Font Awesome and custom CSS #}
|
||||
<link rel="stylesheet" href="{% static "bootstrap4/css/bootstrap.min.css" %}">
|
||||
<link rel="stylesheet" href="{% static "font-awesome/css/font-awesome.min.css" %}">
|
||||
<link rel="stylesheet" href="{% static "wrapped/css/1/custom.css" %}">
|
||||
|
||||
{# JQuery, Bootstrap and Turbolinks JavaScript #}
|
||||
<script src="{% static "jquery/jquery.min.js" %}"></script>
|
||||
<script src="{% static "popper.js/umd/popper.min.js" %}"></script>
|
||||
<script src="{% static "bootstrap4/js/bootstrap.min.js" %}"></script>
|
||||
<script src="{% static "js/turbolinks.js" %}"></script>
|
||||
<script src="{% static "js/base.js" %}"></script>
|
||||
<script src="{% static "js/konami.js" %}"></script>
|
||||
|
||||
{# Translation in javascript files #}
|
||||
<script src="{% static "js/jsi18n/"|add:LANGUAGE_CODE|add:".js" %}"></script>
|
||||
|
||||
{# If extra ressources are needed for a form, load here #}
|
||||
{% if form.media %}
|
||||
{{ form.media }}
|
||||
{% endif %}
|
||||
|
||||
{% block extracss %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
<p>Default content...</p>
|
||||
{% endblock %}
|
||||
<br>
|
||||
<div class="wrap-container">
|
||||
<h2>{% trans "The NoteKfet this year it's also" %}</h2>
|
||||
<ul class="list" id="glob_top3_conso">
|
||||
<li>{{ glob_nb_transaction }} {% trans " transactions" %}</li>
|
||||
<li>{{ glob_nb_soiree }} {% trans " parties" %}</li>
|
||||
<li>{{ glob_nb_entree_pot }} {% trans " Pot entries" %}</li>
|
||||
<script>
|
||||
let liste = {{ glob_top3_conso | safe }};
|
||||
let ul = document.getElementById("glob_top3_conso");
|
||||
liste.forEach(item => {
|
||||
let li = document.createElement("li");
|
||||
li.textContent = item[1] + " " + item[0];
|
||||
ul.appendChild(li);
|
||||
});
|
||||
</script>
|
||||
<li>{{ glob_nb_vieux_con }} {% trans " old dickhead behind the bar" %} </li>
|
||||
</ul>
|
||||
</div>
|
||||
<script>
|
||||
CSRF_TOKEN = "{{ csrf_token }}";
|
||||
$(".invalid-feedback").addClass("d-block");
|
||||
</script>
|
||||
|
||||
{% block extrajavascript %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
31
apps/wrapped/templates/wrapped/1/wrapped_view_club.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends "wrapped/1/wrapped_base.html" %}
|
||||
{% comment %}
|
||||
COPYRIGHT (C) 2018-2025 BDE ENS Paris-Saclay
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n pretty_money %}
|
||||
{% block content %}
|
||||
<div class="wrap-container">
|
||||
<h2>{% trans "NoteKfet Wrapped" %}</h2>
|
||||
<h1 id="name">{{ wrapped.note.club.name }}</h1>
|
||||
{% trans "Your best consumer:" %}
|
||||
<div class="category" id="consumer"></div>
|
||||
{% trans "Your worst creditor:" %}
|
||||
<div class="category" id="creditor"></div>
|
||||
<ul class="list">
|
||||
<li>{{ nb_soiree_orga }} {% trans "party·ies organised" %}</li>
|
||||
<li>{{ nb_member }} {% trans "distinct members" %}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<script>
|
||||
let con = Boolean({{ big_consumer | safe }});
|
||||
let cre = Boolean({{ big_creancier | safe }});
|
||||
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" %}");};
|
||||
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 !" %}"); };
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
69
apps/wrapped/templates/wrapped/1/wrapped_view_user.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends "wrapped/1/wrapped_base.html" %}
|
||||
{% comment %}
|
||||
COPYRIGHT (C) 2018-2024 BDE ENS Paris-Saclay
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load i18n pretty_money %}
|
||||
{% block content %}
|
||||
<div class="wrap-container">
|
||||
<h2>{% trans "NoteKfet Wrapped" %}</h2>
|
||||
<h1 id="name">{{ wrapped.note.user.username }}</h1>
|
||||
{% if wei %}
|
||||
<div class="category" id="wei">
|
||||
{% trans "You participate to the wei: " %} {{ wei }} {% trans "in the" %} {{ bus }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="ranking-bar">
|
||||
<div class="ranking-progress" id="pot_bar">
|
||||
{{ nb_pot_entry }}/{{ nb_pots }} {% trans "pots !" %}
|
||||
</div>
|
||||
<script>
|
||||
const percentage = ({{ nb_pot_entry }} / {{ nb_pots }}) *100;
|
||||
document.getElementById("pot_bar").style.width = percentage + '%';
|
||||
</script>
|
||||
</div>
|
||||
{% if first_conso %}
|
||||
<ul class="list" id="user_conso">
|
||||
<li>{% trans "Your first conso of the year: " %} {{ first_conso }}</li>
|
||||
<li>{% trans "Your prefered consumtion category: " %} {{ top_category }}</li>
|
||||
<script>
|
||||
let top3 = {{ top3_conso | safe }};
|
||||
let l = document.getElementById("user_conso");
|
||||
top3.forEach(item => {
|
||||
let li = document.createElement("li");
|
||||
li.textContent = item[1] + " " + item[0];
|
||||
l.appendChild(li);
|
||||
});
|
||||
</script>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="category">
|
||||
{{ nb_rechargement }} {% trans ": it's the number of time your reload your note" %}
|
||||
</div>
|
||||
{% if class_conso_all > 0 %}
|
||||
{% trans "Your overall expenses: " %}
|
||||
<div class="ranking-bar">
|
||||
<div class="ranking-progress" id="all_bar">
|
||||
{{ class_conso_all }}/{{ class_part_all }} {% trans "with" %} {{ amount_conso_all }}€
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const p_all = 100 - (({{ class_conso_all }} - 1) / {{ class_part_all }}) * 100;
|
||||
document.getElementById("all_bar").style.width = p_all + '%';
|
||||
</script>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if class_conso_bde > 0 %}
|
||||
{% trans "Your expenses to BDE: " %}
|
||||
<div class="ranking-bar">
|
||||
<div class="ranking-progress" id="bde_bar">
|
||||
{{ class_conso_bde }}/{{ class_part_bde }} {% trans "with" %} {{ amount_conso_bde }}€
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const p_bde = 100 - (({{ class_conso_bde }} - 1) / {{ class_part_all }}) * 100;
|
||||
document.getElementById("bde_bar").style.width = p_bde + '%';
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
76
apps/wrapped/templates/wrapped/wrapped_list.html
Normal file
@@ -0,0 +1,76 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% 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>
|
||||
{% 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 %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script type="text/javascript">
|
||||
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");
|
||||
}
|
||||
|
||||
function copylink(id) {
|
||||
navigator.clipboard.writeText({{ request.get_full_path }} + id)
|
||||
.then(() => { addMsg("{% trans "Link copied" %}", 'success', 1000);});
|
||||
}
|
||||
|
||||
function makepublic(id, isprivate) {
|
||||
const makepublic_obj = $('#makepublic_'+id)
|
||||
|
||||
if (makepublic_obj.data('pending'))
|
||||
// The button is already clicked
|
||||
{ return }
|
||||
|
||||
makepublic_obj.html('<strong style="font-size: 16pt;">⟳</strong>')
|
||||
makepublic_obj.data('pending', true)
|
||||
|
||||
$.ajax({
|
||||
url: '/api/wrapped/wrapped/' + id + '/',
|
||||
type: 'PATCH',
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
'X-CSRFTOKEN': CSRF_TOKEN
|
||||
},
|
||||
data: {
|
||||
public: isprivate
|
||||
},
|
||||
success: function() {
|
||||
if(!isprivate)
|
||||
addMsg("{% trans "Wrapped is private" %}", 'success', 2000)
|
||||
else addMsg("{% trans "Wrapped is public" %}", 'success', 2000)
|
||||
refreshTable()
|
||||
},
|
||||
error: function (err) {
|
||||
addMsg("{% trans "An error occured" %}", 'danger')
|
||||
refreshTable()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
0
apps/wrapped/tests/__init__.py
Normal file
91
apps/wrapped/tests/test_wrapped.py
Normal 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/")
|
13
apps/wrapped/urls.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'wrapped'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.WrappedListView.as_view(), name='wrapped_list'),
|
||||
path('<int:pk>/', views.WrappedDetailView.as_view(), name='wrapped_detail'),
|
||||
]
|
71
apps/wrapped/views.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
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 permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
|
||||
from .models import Wrapped
|
||||
from .tables import WrappedTable
|
||||
|
||||
|
||||
class WrappedListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
|
||||
"""
|
||||
Display all Wrapped, and classify by year
|
||||
"""
|
||||
model = Wrapped
|
||||
tables = [
|
||||
lambda data: WrappedTable(data, prefix="public-"),
|
||||
lambda data: WrappedTable(data, prefix="personnal-"),
|
||||
]
|
||||
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_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
w = self.object_list.filter(note__noteclub__club__pk__gte=-1, public=False)
|
||||
if w:
|
||||
context['club_not_public'] = 'true'
|
||||
else:
|
||||
context['club_not_public'] = 'false'
|
||||
return context
|
||||
|
||||
|
||||
class WrappedDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
"""
|
||||
View a wrapped
|
||||
"""
|
||||
model = Wrapped
|
||||
template_name = 'wrapped/0/wrapped_view.html' # by default
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
bde_id = Wrapped.objects.get(pk=kwargs['pk']).bde.id
|
||||
note_type = 'user' if 'user' in Wrapped.objects.get(pk=kwargs['pk']).note.__dir__() else 'club'
|
||||
self.template_name = 'wrapped/' + str(bde_id) + '/wrapped_view_' + note_type + '.html'
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
d = json.loads(self.object.data_json)
|
||||
for key in d:
|
||||
context[key] = d[key]
|
||||
context['title'] = str(self.object)
|
||||
return context
|
118
docs/_static/img/graphs/wrapped.svg
vendored
Normal 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">    </text>
|
||||
<text text-anchor="start" x="62" y="-65.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Bde    </text>
|
||||
<text text-anchor="start" x="11" y="-49.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="77" y="-49.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="11" y="-36.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="77" y="-36.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="11" y="-23.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="77" y="-23.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="11" y="-10.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="77" y="-10.1" font-family="Roboto" font-size="8.00">    </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">    </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">    </text>
|
||||
<text text-anchor="start" x="113" y="-219.5" font-family="Roboto" font-weight="bold" font-size="10.00" fill="white">    Wrapped    </text>
|
||||
<text text-anchor="start" x="70" y="-203.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="137" y="-203.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="70" y="-190.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="137" y="-190.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="70" y="-177.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="137" y="-177.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="70" y="-164.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="137" y="-164.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="70" y="-151.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="137" y="-151.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="70" y="-138.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<text text-anchor="start" x="137" y="-138.1" font-family="Roboto" font-size="8.00">    </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">    </text>
|
||||
<polygon fill="none" stroke="black" points="67,-132 67,-233 231,-233 231,-132 67,-132"/>
|
||||
</g>
|
||||
<!-- wrapped_models_Wrapped->wrapped_models_Bde -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>wrapped_models_Wrapped->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">  </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">  </text>
|
||||
</g>
|
||||
<!-- wrapped_models_Wrapped->note_models_notes_Note -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>wrapped_models_Wrapped->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 |
@@ -14,6 +14,7 @@ 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,
|
||||
@@ -32,7 +33,7 @@ Applications indispensables
|
||||
* `Note <note>`_ :
|
||||
Les notes associées à des utilisateur⋅rices ou des clubs.
|
||||
* `Activity <activity>`_ :
|
||||
La gestion des activités (créations, gestion, entrées,…)
|
||||
La gestion des activités (créations, gestion, entrées, ...)
|
||||
* `Permission <permission>`_ :
|
||||
Backend de droits, limites les pouvoirs des utilisateur⋅rices
|
||||
* `API <../api>`_ :
|
||||
@@ -64,9 +65,11 @@ Applications facultatives
|
||||
* ``cas-server``
|
||||
Serveur central d'authentification, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client.
|
||||
* `Scripts <https://gitlab.crans.org/bde/nk20-scripts>`_
|
||||
Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc…
|
||||
Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc...
|
||||
* `Treasury <treasury>`_ :
|
||||
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>`_ :
|
||||
Interface de gestion du WEI.
|
||||
* `Wrapped <wrapped>`_ :
|
||||
Récapitulatif personnalisé annuel de statitiques globales et personnelles.
|
||||
|
||||
|
@@ -43,7 +43,7 @@ l'utilisateur⋅rice, utiles pour l'adhésion au BDE :
|
||||
* ``address`` : ``CharField``, adresse physique de l'utilisateur⋅rice
|
||||
* ``paid`` : ``BooleanField``, indique si l'utilisateur⋅rice normalien⋅ne est rémunéré⋅e ou non (utile pour différencier les montants d'adhésion aux clubs)
|
||||
* ``phone_number`` : ``CharField``, numéro de téléphone de l'utilisateur⋅rice
|
||||
* ``section`` : ``CharField``, section de l'ENS à laquelle appartient l'utilisateur⋅rice (exemple : 1A0,…)
|
||||
* ``section`` : ``CharField``, section de l'ENS à laquelle appartient l'utilisateur⋅rice (exemple : 1A0, ...)
|
||||
|
||||
Clubs
|
||||
~~~~~
|
||||
@@ -101,7 +101,7 @@ Adhésions
|
||||
|
||||
La Note Kfet offre la possibilité aux clubs de gérer l'adhésion de leurs membres. En plus de réguler les cotisations
|
||||
des adhérent⋅es, des permissions sont octroyées sur la note en fonction des rôles au sein des clubs. Un rôle est une
|
||||
fonction occupée au sein d'un club (Trésorièr⋅e de club, président⋅e de club, GC Kfet, Res[pot], respo info,…).
|
||||
fonction occupée au sein d'un club (Trésorièr⋅e de club, président⋅e de club, GC Kfet, Res[pot], respo info, ...).
|
||||
Une adhésion attribue à un⋅e adhérent⋅e ses rôles. Les rôles fournissent les permissions. Par exemple, læ trésorièr⋅e d'un
|
||||
club a le droit de faire des transferts de et vers la note du club, tant que la source reste au-dessus de -50 €.
|
||||
Une adhésion est considérée comme valide si la date du jour est comprise (au sens large) entre les dates de début et
|
||||
|
@@ -49,7 +49,7 @@ Une fois l'inscription validée, détail de ce qu'il se passe :
|
||||
lui octroyant un faible nombre de permissions de base, telles que la visualisation de son compte.
|
||||
* On adhère la personne au club Kfet si cela est demandé, l'adhésion commence aujourd'hui. Iel dispose d'un unique rôle :
|
||||
« Adhérent⋅e Kfet » , lui octroyant un nombre un peu plus conséquent de permissions basiques, telles que la possibilité de
|
||||
faire des transactions, d'accéder aux activités, au WEI,…
|
||||
faire des transactions, d'accéder aux activités, au WEI, ...
|
||||
* Si læ nouvelleau membre a indiqué avoir ouvert un compte à la société générale, alors les transactions sont invalidées,
|
||||
la note n'est pas débitée (commence alors à 0 €).
|
||||
|
||||
|
108
docs/apps/wrapped.rst
Normal 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.
|
@@ -3744,8 +3744,8 @@ msgid "FAQ (FR)"
|
||||
msgstr "FAQ (FR)"
|
||||
|
||||
#: note_kfet/templates/base_search.html:15
|
||||
msgid "Search by attribute such as name…"
|
||||
msgstr "Suche nach Attributen wie Name…"
|
||||
msgid "Search by attribute such as name..."
|
||||
msgstr "Suche nach Attributen wie Name..."
|
||||
|
||||
#: note_kfet/templates/base_search.html:23
|
||||
msgid "There is no results."
|
||||
|
@@ -3694,8 +3694,8 @@ msgid "FAQ (FR)"
|
||||
msgstr "FAQ (FR)"
|
||||
|
||||
#: note_kfet/templates/base_search.html:15
|
||||
msgid "Search by attribute such as name…"
|
||||
msgstr "Buscar con atributo, como el nombre…"
|
||||
msgid "Search by attribute such as name..."
|
||||
msgstr "Buscar con atributo, como el nombre..."
|
||||
|
||||
#: note_kfet/templates/base_search.html:23
|
||||
msgid "There is no results."
|
||||
|
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-10-07 09:07+0200\n"
|
||||
"POT-Creation-Date: 2025-02-25 13:27+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -17,11 +17,11 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: apps/member/static/member/js/alias.js:17
|
||||
#: apps/activity/static/activity/js/opener.js:31
|
||||
msgid "Opener successfully added"
|
||||
msgstr "Ouvreureuse ajouté avec succès"
|
||||
|
||||
#: apps/member/static/member/js/alias.js:17
|
||||
#: apps/activity/static/activity/js/opener.js:47
|
||||
msgid "Opener successfully deleted"
|
||||
msgstr "Ouvreureuse supprimé avec succès"
|
||||
|
||||
|
@@ -9,6 +9,7 @@ MAILTO=notekfet2020@lists.crans.org
|
||||
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -c 1 -v 0
|
||||
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -c 1 -v 0
|
||||
00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -v 0
|
||||
00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log -r failure 30 -v 0
|
||||
# Faire une sauvegarde de la base de données
|
||||
00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db
|
||||
# Vérifier la cohérence de la base et mailer en cas de problème
|
||||
@@ -25,3 +26,6 @@ MAILTO=notekfet2020@lists.crans.org
|
||||
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons -v 0
|
||||
# Vider les tokens Oauth2
|
||||
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0
|
||||
# Envoyer la liste des abonnés à la NL BDA
|
||||
00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art
|
||||
|
@@ -79,6 +79,7 @@ INSTALLED_APPS = [
|
||||
'scripts',
|
||||
'treasury',
|
||||
'wei',
|
||||
'wrapped',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -225,6 +226,7 @@ MEDIA_URL = '/media/'
|
||||
# Use mailer in production to place emails in a queue before sending them to avoid spam
|
||||
EMAIL_BACKEND = 'mailer.backend.DbBackend'
|
||||
MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
MAILER_EMAIL_MAX_BATCH = 10
|
||||
EMAIL_USE_SSL = os.getenv('EMAIL_USE_SSL', False)
|
||||
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.org')
|
||||
EMAIL_PORT = os.getenv('EMAIL_PORT', 25)
|
||||
@@ -265,11 +267,9 @@ OAUTH2_PROVIDER = {
|
||||
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
|
||||
'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)
|
||||
}
|
||||
|
||||
# PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
|
||||
PKCE_REQUIRED = False
|
||||
|
||||
# Take control on how widget templates are sourced
|
||||
# See https://docs.djangoproject.com/en/2.2/ref/forms/renderers/#templatessetting
|
||||
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
||||
|
@@ -66,13 +66,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
{% url 'food:food_list' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-cutlery"></i> {% trans 'Food' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated and user|is_member:"Kfet" %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-cutlery"></i> {% trans 'Food' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
{% url 'note:transfer' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a>
|
||||
@@ -107,6 +107,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% url 'wei:current_wei_detail' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-bus"></i> {% trans 'WEI' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if "wrapped.wrapped"|model_list_length >= 1 %}
|
||||
<li class="nav-item">
|
||||
{% url 'wrapped:wrapped_list' as url %}
|
||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-gift"></i> {% trans 'Wrapped' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
@@ -158,7 +164,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</nav>
|
||||
<div class="{% block containertype %}container{% endblock %} my-3">
|
||||
<div id="messages">
|
||||
<div id="messages">
|
||||
{% if user.is_authenticated %}
|
||||
{% if not user|is_member:"BDE" %}
|
||||
<div class="alert alert-danger">
|
||||
|
@@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<input id="searchbar" type="text" class="form-control"
|
||||
placeholder="{% trans "Search by attribute such as name…" %}">
|
||||
placeholder="{% trans "Search by attribute such as name..." %}">
|
||||
</div>
|
||||
<div id="dynamic-table">
|
||||
{% if table.data %}
|
||||
@@ -75,4 +75,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
init_table();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@@ -22,6 +22,7 @@ urlpatterns = [
|
||||
path('treasury/', include('treasury.urls')),
|
||||
path('wei/', include('wei.urls')),
|
||||
path('food/',include('food.urls')),
|
||||
path('wrapped/',include('wrapped.urls')),
|
||||
|
||||
# Include Django Contrib and Core routers
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
|
9
tox.ini
@@ -1,14 +1,14 @@
|
||||
[tox]
|
||||
envlist =
|
||||
# Debian Bullseye Python
|
||||
py39-django42
|
||||
|
||||
# Ubuntu 22.04 Python
|
||||
py310-django42
|
||||
|
||||
# Debian Bookworm Python
|
||||
py311-django42
|
||||
|
||||
# Ubuntu 24.04 Python
|
||||
py312-django42
|
||||
|
||||
linters
|
||||
skipsdist = True
|
||||
|
||||
@@ -32,7 +32,8 @@ deps =
|
||||
pep8-naming
|
||||
pyflakes
|
||||
commands =
|
||||
flake8 apps --extend-exclude apps/scripts
|
||||
flake8 apps --extend-exclude apps/scripts,apps/wrapped/management/commands
|
||||
flake8 apps/wrapped/management/commands --extend-ignore=C901
|
||||
|
||||
[flake8]
|
||||
ignore = W503, I100, I101, B019
|
||||
|