Compare commits

...
Sign in to create a new pull request.

47 commits

Author SHA1 Message Date
sinavir
f886f3c98f
fix: Sort poll answers by creationDate 2025-03-02 17:53:49 +01:00
sinavir
02d07cb83d
fix(trombonoscope): Breaking changes in django-avatar 2024-12-19 16:22:20 +01:00
sinavir
fbb7cbe11a
feat: update and switch to dgnum django framework 2024-12-19 16:22:16 +01:00
Sinavir
98a6f98fda Fireworks hotfix 2024-11-13 10:50:46 +01:00
sinavir
0aae767a3e
fireworks 2024-11-13 10:35:37 +01:00
sinavir
d88768311e
cvec 2024-11-08 14:33:16 +01:00
sinavir
abc6c62a89 make forgotten migration 2024-06-15 15:03:37 +02:00
sinavir
3e065f30c0 Bugfix 2024-06-15 14:57:39 +02:00
sinavir
0ee798a64e Improve requirements.txt 2024-06-15 14:28:41 +02:00
sinavir
1e8b993739 Revert "add poll for 10y anniversary"
This reverts commit 56d7490879.
2022-10-26 21:57:01 +02:00
Lucie Galland
8398805b54 proposition migration 2022-09-14 12:45:30 +02:00
sinavir
56d7490879 add poll for 10y anniversary 2022-09-14 10:24:32 +02:00
Lucie Galland
aa430fa4d2 add propositions back 2022-03-22 15:05:51 +01:00
Lucie Galland
5eb27e2171 Merge branch 'mdebray/date_des_reponses_aux_sondages' into 'master'
Date des reponses aux sondages

See merge request klub-dev-ens/Ernesto!22
2022-03-15 11:29:37 +01:00
Lucie Galland
31f6dc0961 bug fix 2022-03-15 11:28:44 +01:00
Maurice Debray
764ffea61f on est moins laxiste 2022-03-15 11:10:55 +01:00
Maurice Debray
7122c1c0c4 black puis isort 2022-03-15 01:08:18 +01:00
Maurice Debray
14e8d963d6 Ajout de la date de création/mise à jour dans le modèle Participants 2022-03-15 01:04:01 +01:00
Lucie Galland
99e3438ead Merge branch 'mdebray/linting' into 'master'
Linting

See merge request klub-dev-ens/Ernesto!20
2022-02-21 11:13:02 +01:00
Maurice Debray
8a36b051a1 Merge branch 'master' into mdebray/linting 2022-02-21 10:29:47 +01:00
Lucie Galland
07d2e30b32 Merge branch 'mdebray/bugfix_respo_instru' into 'master'
Bugfix : Le chef instru peut maintenant changer les infos d'instru

See merge request klub-dev-ens/Ernesto!19
2022-02-21 09:55:25 +01:00
Maurice Debray
f88ed9ece6 Chef instru peut maintenant changer les infos d'instru 2022-02-21 00:47:03 +01:00
Maurice Debray
5062a1e84e linting 2022-02-05 16:18:00 +01:00
Lucie Galland
bc7430cb5d fix 2022-01-18 15:47:50 +01:00
Lucie Galland
a7851ff123 fix morceau ordering 2022-01-18 15:44:08 +01:00
Lucie Galland
50f888a07c add partitions to admin 2022-01-18 15:36:19 +01:00
Lucie Galland
343eaeb72e add partitions to admin 2022-01-18 15:36:04 +01:00
Lucie Galland
5aebd7bbe1 Merge branch 'mdebray/bug_affichage_musescore' into 'master'
bugfix affichage tableau partitions

See merge request klub-dev-ens/Ernesto!18
2022-01-17 14:18:20 +01:00
Maurice Debray
cb2577c04b bugfix affichage tableau partitions 2022-01-16 11:15:20 +01:00
Lucie Galland
bd42fe6fb9 add respo mu 2022-01-11 16:21:22 +01:00
Lucie Galland
bbff984513 remove partcipants from admin 2022-01-09 20:47:58 +01:00
Lucie Galland
9aed5d1758 Merge branch 'setlist_repet' into 'master'
Setlist de repet

See merge request klub-dev-ens/Ernesto!17
2022-01-09 20:46:50 +01:00
Maurice Debray
f961722fa8 black+isort 2022-01-09 19:40:58 +01:00
Maurice Debray
904f9fc74a migration 2022-01-09 19:39:48 +01:00
Maurice Debray
280e0c625d suppresion migrations 2022-01-09 19:39:27 +01:00
Maurice Debray
8e6ca0cc86 internationnalisation 2022-01-09 18:03:36 +01:00
Maurice Debray
0673e64083 Modifications de l'affichage parce que j'avais fais ça n'importe comment 2022-01-09 17:43:19 +01:00
Maurice Debray
82bc6c1e1b Setlists de repet 2022-01-09 01:18:50 +01:00
Maurice Debray
26bd2484a2 Merge branch 'master' into setlist_repet 2022-01-06 17:42:45 +01:00
Lucie Galland
4d8b97c2cb Merge branch 'actu_chef_arc_en_ciel' into 'master'
Actu chef arc en ciel

See merge request klub-dev-ens/Ernesto!16
2022-01-06 14:51:16 +01:00
Maurice Debray
b62d6f47a4 Migrations + black/isort 2022-01-06 13:11:16 +01:00
Maurice Debray
10159393f1 Set list de repet: front end random fanfaron 2022-01-06 00:27:53 +01:00
Maurice Debray
d217a74da5 Diclaimer: Ne pas mettre d'émoji car c'est moche 2021-12-26 16:12:58 +01:00
Maurice Debray
079e0d1c3f Changement de champ rainbow du modèle : Bool > Choice 2021-12-24 00:40:17 +01:00
Maurice Debray
45fdd4683b Amélioration mineure du gradient de couleur 2021-12-23 23:39:23 +01:00
Maurice Debray
95cf80c234 Affichage des arcs-en-ciel 2021-12-23 23:36:23 +01:00
Maurice Debray
e6f9173c62 Modification du modèle et des vues 2021-12-23 23:25:32 +01:00
81 changed files with 1941 additions and 502 deletions

1
.credentials/SECRET_KEY Normal file
View file

@ -0,0 +1 @@
insecure-secret_key

View file

@ -1,40 +1,28 @@
import os from pathlib import Path
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from loadcredential import Credentials
try: credentials = Credentials(env_prefix="ERNESTOPHONE_")
from . import secret
except ImportError: BASE_DIR = Path(__file__).resolve().parent.parent
raise ImportError(
"The secret.py file is missing.\n" DEBUG = credentials.get_json("DEBUG", False)
"For a development environment, simply copy secret_example.py"
) ALLOWED_HOSTS = credentials.get_json("ALLOWED_HOSTS", [])
SECRET_KEY = credentials["SECRET_KEY"]
ADMINS = credentials.get_json("ADMINS", [])
SERVER_EMAIL = credentials.get("SERVER_EMAIL", "ernesto@localhost")
def import_secret(name): ACCOUNT_CREATION_PASS = credentials.get("ACCOUNT_CREATION_PASS", "dummy")
"""
Shorthand for importing a value from the secret module and raising an
informative exception if a secret is missing.
"""
try:
return getattr(secret, name)
except AttributeError:
raise RuntimeError("Secret missing: {}".format(name))
SECRET_KEY = import_secret("SECRET_KEY")
ADMINS = import_secret("ADMINS")
SERVER_EMAIL = import_secret("SERVER_EMAIL")
DBNAME = import_secret("DBNAME")
DBUSER = import_secret("DBUSER")
DBPASSWD = import_secret("DBPASSWD")
ACCOUNT_CREATION_PASS = import_secret("ACCOUNT_CREATION_PASS")
BASE_DIR = os.path.join(os.path.dirname(__file__), "..", "..")
INSTALLED_APPS = [ INSTALLED_APPS = [
"propositions",
"trombonoscope", "trombonoscope",
"actu", "actu",
"colorful", "colorful",
@ -89,15 +77,22 @@ TEMPLATES = [
WSGI_APPLICATION = "Ernestophone.wsgi.application" WSGI_APPLICATION = "Ernestophone.wsgi.application"
DATABASES = {
###
# Database configuration
# -> https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
DATABASES = credentials.get_json(
"DATABASES",
{
"default": { "default": {
"ENGINE": "django.db.backends.postgresql", "ENGINE": "django.db.backends.sqlite3",
"NAME": DBNAME, "NAME": BASE_DIR / "db.sqlite3",
"USER": DBUSER,
"PASSWORD": DBPASSWD,
"HOST": "localhost",
} }
} },
)
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
@ -128,7 +123,7 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) LOCALE_PATHS = (BASE_DIR / "locale",)
AUTH_PROFILE_MODEL = "gestion.ErnestoUser" AUTH_PROFILE_MODEL = "gestion.ErnestoUser"
@ -149,3 +144,18 @@ AVATAR_PROVIDERS = (
"avatar.providers.DefaultAvatarProvider", "avatar.providers.DefaultAvatarProvider",
) )
AVATAR_THUMB_FORMAT = "JPEG" AVATAR_THUMB_FORMAT = "JPEG"
###
# Staticfiles configuration
STATIC_ROOT = credentials["STATIC_ROOT"]
STATIC_URL = "/static/"
MEDIA_ROOT = credentials.get("MEDIA_ROOT", BASE_DIR / "media")
MEDIA_URL = "/media/"
if DEBUG:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE

View file

@ -1,23 +0,0 @@
import os
from .common import * # noqa
from .common import BASE_DIR, INSTALLED_APPS, MIDDLEWARE
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEBUG = True
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
STATIC_URL = "/static/"
STATIC_ROOT = "static"
MEDIA_URL = "/media/"
MEDIA_ROOT = "media"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}

View file

@ -1,17 +0,0 @@
import os
from .common import * # noqa
from .common import BASE_DIR
DEBUG = False
ALLOWED_HOSTS = [
"ernestophone.ens.fr",
"www.ernestophone.ens.fr",
]
STATIC_URL = "/static/"
STATIC_ROOT = os.path.join(BASE_DIR, "..", "..", "public", "ernesto", "static")
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "..", "media")

View file

@ -1,9 +0,0 @@
SECRET_KEY = "vpb%-1@$ha98w5^ji#@9v2_kxj)zdk5+e!9!fqniu2$#eg+46="
ADMINS = None
SERVER_EMAIL = "ernesto@localhost"
DBNAME = "ernesto"
DBUSER = "ernesto"
DBPASSWD = "YNp2rrXowJnDAFF3"
ACCOUNT_CREATION_PASS = "dummy"

View file

@ -13,13 +13,13 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf import settings from django.conf import settings
from django.conf.urls.i18n import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.urls import include, path from django.urls import include, path
from gestion import views as gestion_views from gestion import views as gestion_views
urlpatterns = [] urlpatterns = []
@ -69,4 +69,5 @@ urlpatterns += i18n_patterns(
), ),
prefix_default_language=False, prefix_default_language=False,
) )
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.25 on 2022-01-06 12:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("actu", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="actu",
name="rainbow",
field=models.CharField(
choices=[("y", "Oui"), ("n", "Non")],
default="n",
max_length=1,
verbose_name="Actu en arc-en-ciel (ne pas mettre d'émoji, il prennent aussi la couleur et c'est moche)",
),
),
]

View file

@ -7,6 +7,15 @@ class Actu(models.Model):
text = models.TextField(_("Info"), null=True, blank=False) text = models.TextField(_("Info"), null=True, blank=False)
text_en = models.TextField(("Info en anglais"), null=True, blank=True) text_en = models.TextField(("Info en anglais"), null=True, blank=True)
order = models.IntegerField(verbose_name=_("ordre")) order = models.IntegerField(verbose_name=_("ordre"))
rainbow = models.CharField(
verbose_name=_(
"Actu en arc-en-ciel (ne pas mettre d'émoji, il prennent aussi la couleur et c'est moche)"
),
max_length=1,
choices=(("y", "Oui"), ("n", "Non")),
default="n",
blank=False,
)
def __str__(self): def __str__(self):
return self.text return self.text

View file

@ -14,7 +14,7 @@ class ActuList(ChefRequiredMixin, ListView):
class ActuCreate(ChefRequiredMixin, CreateView): class ActuCreate(ChefRequiredMixin, CreateView):
model = Actu model = Actu
fields = ["text", "order", "text_en"] fields = ["text", "order", "text_en", "rainbow"]
template_name = "actu/create_actu.html" template_name = "actu/create_actu.html"
success_url = reverse_lazy("actu:liste") success_url = reverse_lazy("actu:liste")
@ -26,7 +26,7 @@ class ActuCreate(ChefRequiredMixin, CreateView):
class ActuUpdate(ChefRequiredMixin, UpdateView): class ActuUpdate(ChefRequiredMixin, UpdateView):
model = Actu model = Actu
fields = ["text", "order", "text_en"] fields = ["text", "order", "text_en", "rainbow"]
template_name = "actu/update_actu.html" template_name = "actu/update_actu.html"
success_url = reverse_lazy("actu:liste") success_url = reverse_lazy("actu:liste")

View file

@ -2,6 +2,27 @@ from django.contrib import admin
from .models import Event, Participants from .models import Event, Participants
class ParticipantsAdmin(admin.ModelAdmin):
fields = [
"event",
"participant",
"reponse",
"instrument",
"instrument_autre",
"dont_play_main",
"details",
"creationDate",
"updateDate",
]
readonly_fields = ["creationDate", "updateDate"]
list_display = ["participant", "event", "reponse", "creationDate", "updateDate"]
def has_add_permission(self, req):
return False
def has_change_permission(self,obj, change=False):
return False
# Add event by admin page return a 502 error # Add event by admin page return a 502 error
admin.site.register(Event) admin.site.register(Event)
admin.site.register(Participants) admin.site.register(Participants, ParticipantsAdmin)

View file

@ -22,11 +22,11 @@ class EventCalendar(HTMLCalendar):
for ev in self.events[day]: for ev in self.events[day]:
body.append('<a href="/agenda/' + '%s"' % ev.id) body.append('<a href="/agenda/' + '%s"' % ev.id)
if ev.calendrier == "C": if ev.calendrier == "C":
body.append('style="color:#160083">'+esc(ev.nom)) body.append('style="color:#160083">' + esc(ev.nom))
elif ev.calendrier == "D": elif ev.calendrier == "D":
body.append('style="color:#770083">'+esc(ev.nom)) body.append('style="color:#770083">' + esc(ev.nom))
else: else:
body.append('>'+esc(ev.nom)) body.append(">" + esc(ev.nom))
body.append("</a><br/>") body.append("</a><br/>")
return self.day_cell( return self.day_cell(
cssclass, cssclass,

View file

@ -1,8 +1,8 @@
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from gestion.models import ErnestoUser
from calendrier.models import Event, Participants from calendrier.models import Event, Participants
from gestion.models import ErnestoUser
class ModifEventForm(forms.ModelForm): class ModifEventForm(forms.ModelForm):
@ -37,7 +37,13 @@ class EventForm(forms.ModelForm):
class ParticipantsForm(forms.ModelForm): class ParticipantsForm(forms.ModelForm):
class Meta: class Meta:
model = Participants model = Participants
fields = ("reponse", "details", "dont_play_main", "instrument","instrument_autre") fields = (
"reponse",
"details",
"dont_play_main",
"instrument",
"instrument_autre",
)
widgets = { widgets = {
"details": forms.Textarea(attrs={"placeholder": _("50 caractères max")}), "details": forms.Textarea(attrs={"placeholder": _("50 caractères max")}),
} }

View file

@ -6,23 +6,41 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('calendrier', '0004_auto_20210606_1640'), ("calendrier", "0004_auto_20210606_1640"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='event', model_name="event",
name='calendrier', name="calendrier",
field=models.CharField(choices=[('F', 'Visible seulement par les fanfarons'), ('T', 'Afficher dans le calendrier pour tous'), ('H', 'Hall of fame'), ('C', 'Visible seulement par les cheff·e·s'), ('D', "Visible seulement par les cheff·e·s et sur l'agenda public")], default='F', max_length=1), field=models.CharField(
choices=[
("F", "Visible seulement par les fanfarons"),
("T", "Afficher dans le calendrier pour tous"),
("H", "Hall of fame"),
("C", "Visible seulement par les cheff·e·s"),
("D", "Visible seulement par les cheff·e·s et sur l'agenda public"),
],
default="F",
max_length=1,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name="event",
name='desc_users', name="desc_users",
field=models.TextField(blank=True, null=True, verbose_name='Infos (visible seulement des fanfaron·ne·s)'), field=models.TextField(
blank=True,
null=True,
verbose_name="Infos (visible seulement des fanfaron·ne·s)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name="event",
name='desc_users_en', name="desc_users_en",
field=models.TextField(blank=True, null=True, verbose_name='Infos en anglais (visible seulement des fanfaron·ne·s'), field=models.TextField(
blank=True,
null=True,
verbose_name="Infos en anglais (visible seulement des fanfaron·ne·s",
),
), ),
] ]

View file

@ -6,18 +6,36 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('calendrier', '0005_auto_20210726_0949'), ("calendrier", "0005_auto_20210726_0949"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='participants', model_name="participants",
name='instrument_autre', name="instrument_autre",
field=models.CharField(blank=True, max_length=50, null=True), field=models.CharField(blank=True, max_length=50, null=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='participants', model_name="participants",
name='instrument', name="instrument",
field=models.CharField(blank=True, choices=[('Clarinette', 'Clarinette'), ('Euphonium', 'Euphonium'), ('Percussion', 'Percussion'), ('Piccolo', 'Piccolo'), ('Saxophone Alto', 'Saxophone Alto'), ('Saxophone Ténor', 'Saxophone Ténor'), ('Saxophone Baryton', 'Saxophone Baryton'), ('Souba', 'Souba'), ('Trombone', 'Trombone'), ('Trompette', 'Trompette'), ('Autre', 'Autre'), ('ne sais pas', 'Je ne sais pas encore')], max_length=50, null=True), field=models.CharField(
blank=True,
choices=[
("Clarinette", "Clarinette"),
("Euphonium", "Euphonium"),
("Percussion", "Percussion"),
("Piccolo", "Piccolo"),
("Saxophone Alto", "Saxophone Alto"),
("Saxophone Ténor", "Saxophone Ténor"),
("Saxophone Baryton", "Saxophone Baryton"),
("Souba", "Souba"),
("Trombone", "Trombone"),
("Trompette", "Trompette"),
("Autre", "Autre"),
("ne sais pas", "Je ne sais pas encore"),
],
max_length=50,
null=True,
),
), ),
] ]

View file

@ -0,0 +1,31 @@
# Generated by Django 2.2.25 on 2022-03-14 23:20
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("calendrier", "0006_auto_20210929_1629"),
]
operations = [
migrations.AddField(
model_name="participants",
name="creationDate",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="Date de création",
),
preserve_default=False,
),
migrations.AddField(
model_name="participants",
name="updateDate",
field=models.DateTimeField(
auto_now=True, verbose_name="Dernière mise à jour"
),
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 2.2.27 on 2022-03-22 13:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('calendrier', '0007_auto_20220314_2320'),
]
operations = [
migrations.AddConstraint(
model_name='participants',
constraint=models.UniqueConstraint(fields=('event', 'participant'), name='reponse unique aux event'),
),
]

View file

@ -2,9 +2,7 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from gestion.models import INSTRU_CHOICES, ErnestoUser
from gestion.models import INSTRU_CHOICES
from gestion.models import ErnestoUser
ANSWERS = ( ANSWERS = (
("oui", _("Oui")), ("oui", _("Oui")),
@ -62,7 +60,9 @@ class Participants(models.Model):
reponse = models.CharField( reponse = models.CharField(
_("Réponse"), max_length=20, default="non", choices=ANSWERS _("Réponse"), max_length=20, default="non", choices=ANSWERS
) )
instrument = models.CharField(max_length=50, blank=True, null=True, choices=INSTRU_CHOICES) instrument = models.CharField(
max_length=50, blank=True, null=True, choices=INSTRU_CHOICES
)
instrument_autre = models.CharField(max_length=50, blank=True, null=True) instrument_autre = models.CharField(max_length=50, blank=True, null=True)
dont_play_main = models.CharField( dont_play_main = models.CharField(
_("Je veux jouer d'un instrument different de mon instrument principal:"), _("Je veux jouer d'un instrument different de mon instrument principal:"),
@ -72,3 +72,11 @@ class Participants(models.Model):
choices=[("Non", _("Non")), ("Oui", _("Oui"))], choices=[("Non", _("Non")), ("Oui", _("Oui"))],
) )
details = models.CharField(max_length=50, blank=True) details = models.CharField(max_length=50, blank=True)
creationDate = models.DateTimeField(
auto_now_add=True, verbose_name=_("Date de création")
)
updateDate = models.DateTimeField(
auto_now=True, verbose_name=_("Dernière mise à jour")
)
class Meta:
constraints = [ models.UniqueConstraint(fields=['event', 'participant'], name='reponse unique aux event') ]

View file

@ -1,5 +1,5 @@
{% extends "gestion/base.html" %} {% extends "gestion/base.html" %}
{% load staticfiles %} {% load static %}
{% load frenchmonth %} {% load frenchmonth %}
{% load i18n %} {% load i18n %}
{% load translate %} {% load translate %}
@ -33,7 +33,7 @@
<h4> <span class="ernestocouleur">{% blocktrans count counter=actu|length %}Actualité des chef·fe·s:{% plural %}Actualités des chef·fe·s:{% endblocktrans %}</span></h4> <h4> <span class="ernestocouleur">{% blocktrans count counter=actu|length %}Actualité des chef·fe·s:{% plural %}Actualités des chef·fe·s:{% endblocktrans %}</span></h4>
<ul> <ul>
{% for a in actu %} {% for a in actu %}
<li>{% autotranslate current_language a.text a.text_en %}</li> <li>{% if a.rainbow == 'y' %}<span class="ernestocouleur font-weight-bold">{% endif %}{% autotranslate current_language a.text a.text_en %}{% if a.rainbow %}</span>{% endif %}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
@ -43,35 +43,35 @@
<tr> <tr>
<td width="20%" align="left"> <td width="20%" align="left">
&lt;&lt; &lt;&lt;
{% ifequal current_language "fr" %} {% if current_language == "fr" %}
<a href="{% url "calendrier:view-month" PreviousYear PreviousMonth %}">{{PreviousMonthName|frenchmonth}} {{PreviousYear}}</a> <a href="{% url "calendrier:view-month" PreviousYear PreviousMonth %}">{{PreviousMonthName|frenchmonth}} {{PreviousYear}}</a>
{% else %} {% else %}
<a href="{% url "calendrier:view-month" PreviousYear PreviousMonth %}">{{PreviousMonthName}} {{PreviousYear}}</a> <a href="{% url "calendrier:view-month" PreviousYear PreviousMonth %}">{{PreviousMonthName}} {{PreviousYear}}</a>
{% endifequal %} {% endif %}
</td> </td>
<td width="20%" align="center"><a href="{% url "calendrier:home" %}">{% trans "Aujourd'hui" %}</a></td> <td width="20%" align="center"><a href="{% url "calendrier:home" %}">{% trans "Aujourd'hui" %}</a></td>
<td width="20%" align="right"> <td width="20%" align="right">
{% ifequal current_language "fr" %} {% if current_language == "fr" %}
<a href="{% url "calendrier:view-month" NextYear NextMonth %}">{{NextMonthName|frenchmonth}} {{NextYear}}</a> &gt;&gt; <a href="{% url "calendrier:view-month" NextYear NextMonth %}">{{NextMonthName|frenchmonth}} {{NextYear}}</a> &gt;&gt;
{% else %} {% else %}
<a href="{% url "calendrier:view-month" NextYear NextMonth %}">{{NextMonthName}} {{NextYear}}</a> &gt;&gt; <a href="{% url "calendrier:view-month" NextYear NextMonth %}">{{NextMonthName}} {{NextYear}}</a> &gt;&gt;
{% endifequal %} {% endif %}
</td> </td>
</tr> </tr>
</table> </table>
<div id="calendar"> <div id="calendar">
{% if user.profile.is_chef_event or user.profile.is_chef %} {% if user.profile.is_chef_event or user.profile.is_chef %}
{% ifequal current_language "fr" %} {% if current_language == "fr" %}
{{Calendar_chef|translate}} {{Calendar_chef|translate}}
{% else %} {% else %}
{{Calendar_chef}} {{Calendar_chef}}
{% endifequal %} {% endif %}
{% else %} {% else %}
{% ifequal current_language "fr" %} {% if current_language == "fr" %}
{{Calendar|translate}} {{Calendar|translate}}
{% else %} {% else %}
{{Calendar}} {{Calendar}}
{% endifequal %} {% endif %}
{% endif%} {% endif%}
</div> </div>
{% if user.profile.is_chef_event or user.profile.is_chef %} {% if user.profile.is_chef_event or user.profile.is_chef %}

View file

@ -223,6 +223,9 @@
</div> </div>
</section> </section>
</div> </div>
{% if event.id == 573 %}
<div class="fireworks" style="pointer-events: none; position:fixed; top: 0; left: 0; right: 0; bottom: 0; height: 100%; weight: 100%;"></div>
{% endif %}
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script> <script>
@ -258,11 +261,19 @@ singleEvent.createWidget('#single-normal', function() {
console.log('#single-normal widget has been created'); console.log('#single-normal widget has been created');
}); });
{% ifequal current_language "fr" %} {% if current_language == "fr" %}
singleEvent.setOption({ lang: 'fr' }); singleEvent.setOption({ lang: 'fr' });
{% else %} {% else %}
singleEvent.setOption({ lang: 'en' }); singleEvent.setOption({ lang: 'en' });
{% endifequal %} {% endif %}
</script> </script>
{% if event.id == 573 %}
<script src="https://unpkg.com/fireworks-js@2.x/dist/index.umd.js"></script>
<script>
const container = document.querySelector('.fireworks');
const fireworks = new Fireworks.default(container);
fireworks.updateOptions({ acceleration: 1.01, traceSpeed: 5 });
fireworks.start();
</script>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -4,7 +4,6 @@ from django.contrib.auth import get_user_model
from django.template.defaultfilters import urlencode from django.template.defaultfilters import urlencode
from django.test import Client, TestCase from django.test import Client, TestCase
from django.utils import timezone from django.utils import timezone
from gestion.models import ErnestoUser from gestion.models import ErnestoUser
from ..models import Event from ..models import Event

View file

@ -4,20 +4,20 @@ from calendar import monthrange
from collections import defaultdict from collections import defaultdict
from datetime import date, datetime from datetime import date, datetime
from actu.models import Actu
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views.generic import DeleteView, TemplateView, UpdateView from django.views.generic import DeleteView, TemplateView, UpdateView
from gestion.mixins import ChefEventRequiredMixin
from gestion.models import Photo
from actu.models import Actu
from calendrier.calend import EventCalendar from calendrier.calend import EventCalendar
from calendrier.forms import (ChangeDoodleName, EventForm, ModifEventForm, from calendrier.forms import (ChangeDoodleName, EventForm, ModifEventForm,
ParticipantsForm) ParticipantsForm)
from calendrier.models import Event, Participants from calendrier.models import Event, Participants
from gestion.mixins import ChefRequiredMixin, ChefEventRequiredMixin
from gestion.models import Photo
def generer(*args): def generer(*args):
@ -76,9 +76,13 @@ class Calendar(LoginRequiredMixin, TemplateView):
lMonth = self.pMonth lMonth = self.pMonth
lCalendarFromMonth = datetime(lYear, lMonth, 1) lCalendarFromMonth = datetime(lYear, lMonth, 1)
lCalendarToMonth = datetime(lYear, lMonth, monthrange(lYear, lMonth)[1]) lCalendarToMonth = datetime(lYear, lMonth, monthrange(lYear, lMonth)[1])
lEvents = Event.objects.filter( lEvents = (
Event.objects.filter(
date__gte=lCalendarFromMonth, date__lte=lCalendarToMonth date__gte=lCalendarFromMonth, date__lte=lCalendarToMonth
).exclude(calendrier__iexact="C").exclude(calendrier__iexact="D") )
.exclude(calendrier__iexact="C")
.exclude(calendrier__iexact="D")
)
lEvents_chef = Event.objects.filter( lEvents_chef = Event.objects.filter(
date__gte=lCalendarFromMonth, date__lte=lCalendarToMonth date__gte=lCalendarFromMonth, date__lte=lCalendarToMonth
) )
@ -170,7 +174,6 @@ class Calendar(LoginRequiredMixin, TemplateView):
class Home(Calendar): class Home(Calendar):
@property @property
def pYear(self): def pYear(self):
lToday = datetime.now() lToday = datetime.now()
@ -209,6 +212,7 @@ class ViewEvent(LoginRequiredMixin, TemplateView):
else: else:
instru = participant.instrument instru = participant.instrument
instru = "" if instru is None else instru
sure, maybe, namesoui, namespe, namesnon = instrument_count[instru] sure, maybe, namesoui, namespe, namesnon = instrument_count[instru]
if participant.reponse == "oui": if participant.reponse == "oui":
@ -234,34 +238,61 @@ class ViewEvent(LoginRequiredMixin, TemplateView):
namesnon += [participant.participant.get_doodlename()] namesnon += [participant.participant.get_doodlename()]
instrument_count[instru] = (sure, maybe, namesoui, namespe, namesnon) instrument_count[instru] = (sure, maybe, namesoui, namespe, namesnon)
instrument_count_l = [] instrument_count_l = []
instru_order = ["Clarinette","Piccolo","Flute","Glockenspiel","Saxophone Alto","Trompette","Trombone","Cor","Saxophone Ténor","Saxophone Baryton","Clarinette Basse","Euphonium","Souba","Percussion"] instru_order = [
"Clarinette",
"Piccolo",
"Flute",
"Glockenspiel",
"Saxophone Alto",
"Trompette",
"Trombone",
"Cor",
"Saxophone Ténor",
"Saxophone Baryton",
"Clarinette Basse",
"Euphonium",
"Souba",
"Percussion",
]
for instrument in instru_order: for instrument in instru_order:
if instrument in instrument_count.keys(): if instrument in instrument_count.keys():
(sure,maybe,namesoui,namespe,namesnon) =instrument_count[instrument] (sure, maybe, namesoui, namespe, namesnon) = instrument_count[
instrument_count_l.append(( instrument, sure, instrument
]
instrument_count_l.append(
(
instrument,
sure,
maybe, maybe,
namesoui, namesoui,
namespe, namespe,
namesnon, namesnon,
)) )
)
for instrument in sorted(instrument_count.keys()): for instrument in sorted(instrument_count.keys()):
if instrument not in instru_order: if instrument not in instru_order:
(sure,maybe,namesoui,namespe,namesnon) =instrument_count[instrument] (sure, maybe, namesoui, namespe, namesnon) = instrument_count[
instrument_count_l.append(( instrument, sure, instrument
]
instrument_count_l.append(
(
instrument,
sure,
maybe, maybe,
namesoui, namesoui,
namespe, namespe,
namesnon, namesnon,
)) )
)
context["event"] = event context["event"] = event
context["instrument_count"] = instrument_count_l context["instrument_count"] = instrument_count_l
context["participants"] = participants context["participants"] = participants.order_by('-creationDate')
context["nboui"] = len(participants.filter(reponse="oui")) context["nboui"] = len(participants.filter(reponse="oui"))
context["nbpe"] = len(participants.filter(reponse="pe")) context["nbpe"] = len(participants.filter(reponse="pe"))
context["nbnon"] = len(participants.filter(reponse="non")) context["nbnon"] = len(participants.filter(reponse="non"))
context["multi_instrumentistes"] = multi_instrumentistes context["multi_instrumentistes"] = multi_instrumentistes
context["chef_only"] = (event.calendrier == "C")|(event.calendrier == "D") context["chef_only"] = (event.calendrier == "C") | (event.calendrier == "D")
return context return context
@ -337,20 +368,22 @@ class ReponseEvent(LoginRequiredMixin, TemplateView):
context["form"] = self.form_class() context["form"] = self.form_class()
context["ev"] = get_object_or_404(Event, id=self.kwargs["id"]) context["ev"] = get_object_or_404(Event, id=self.kwargs["id"])
context["id"] = self.kwargs["id"] context["id"] = self.kwargs["id"]
context["chef_only"] = (context["ev"].calendrier == "C")|(context["ev"].calendrier == "D") context["chef_only"] = (context["ev"].calendrier == "C") | (
context["ev"].calendrier == "D"
)
return context return context
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
ev = get_object_or_404(Event, id=self.kwargs["id"]) ev = get_object_or_404(Event, id=self.kwargs["id"])
part = request.user.profile part = request.user.profile
if form.is_valid():
try: try:
p = Participants.objects.get(event=ev, participant=part) p = Participants.objects.get(event=ev, participant=part)
p.delete()
except Participants.DoesNotExist: except Participants.DoesNotExist:
pass p = None
form = self.form_class(request.POST, instance=p)
if form.is_valid():
obj = form.save(commit=False) obj = form.save(commit=False)
# Si la participation existe déjà, ces 2 ligne sont redondantes
obj.event = ev obj.event = ev
obj.participant = part obj.participant = part
obj.save() obj.save()

View file

@ -1,19 +1,24 @@
# Generated by Django 2.2.17 on 2021-06-08 10:29 # Generated by Django 2.2.17 on 2021-06-08 10:29
from django.db import migrations, models from django.db import migrations, models
import gestion.models import gestion.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('gestion', '0005_auto_20210427_1834'), ("gestion", "0005_auto_20210427_1834"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='photo', model_name="photo",
name='image', name="image",
field=models.ImageField(default=None, upload_to='trombonoscope/deco', validators=[gestion.models.Photo.validate_image]), field=models.ImageField(
default=None,
upload_to="trombonoscope/deco",
validators=[gestion.models.Photo.validate_image],
),
), ),
] ]

View file

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('gestion', '0006_auto_20210608_1029'), ("gestion", "0006_auto_20210608_1029"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='ernestouser', model_name="ernestouser",
name='is_chef_event', name="is_chef_event",
field=models.BooleanField(default=False, verbose_name='Respo événement Fanfare'), field=models.BooleanField(
default=False, verbose_name="Respo événement Fanfare"
),
), ),
] ]

View file

@ -6,23 +6,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('gestion', '0007_ernestouser_is_chef_event'), ("gestion", "0007_ernestouser_is_chef_event"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='ernestouser', model_name="ernestouser",
name='is_chef_com', name="is_chef_com",
field=models.BooleanField(default=False, verbose_name='Respo com'), field=models.BooleanField(default=False, verbose_name="Respo com"),
), ),
migrations.AddField( migrations.AddField(
model_name='ernestouser', model_name="ernestouser",
name='is_chef_instru', name="is_chef_instru",
field=models.BooleanField(default=False, verbose_name='Respo instruments'), field=models.BooleanField(default=False, verbose_name="Respo instruments"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='ernestouser', model_name="ernestouser",
name='is_chef_event', name="is_chef_event",
field=models.BooleanField(default=False, verbose_name='Respo événements'), field=models.BooleanField(default=False, verbose_name="Respo événements"),
), ),
] ]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.24 on 2022-01-11 15:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("gestion", "0008_auto_20211022_1923"),
]
operations = [
migrations.AddField(
model_name="ernestouser",
name="is_chef_mu",
field=models.BooleanField(default=False, verbose_name="Respo musique"),
),
]

View file

@ -6,32 +6,75 @@ class ChefRequiredMixin(UserPassesTestMixin):
user = self.request.user user = self.request.user
return (user is not None) and hasattr(user, "profile") and user.profile.is_chef return (user is not None) and hasattr(user, "profile") and user.profile.is_chef
class ChefEventRequiredMixin(UserPassesTestMixin): class ChefEventRequiredMixin(UserPassesTestMixin):
def test_func(self): def test_func(self):
user = self.request.user user = self.request.user
is_chef = (user is not None) and hasattr(user, "profile") and user.profile.is_chef is_chef = (
is_chef_event = (user is not None) and hasattr(user, "profile") and user.profile.is_chef_event (user is not None) and hasattr(user, "profile") and user.profile.is_chef
)
is_chef_event = (
(user is not None)
and hasattr(user, "profile")
and user.profile.is_chef_event
)
return is_chef or is_chef_event return is_chef or is_chef_event
class ChefInstruRequiredMixin(UserPassesTestMixin): class ChefInstruRequiredMixin(UserPassesTestMixin):
def test_func(self): def test_func(self):
user = self.request.user user = self.request.user
is_chef = (user is not None) and hasattr(user, "profile") and user.profile.is_chef is_chef = (
is_chef_instru = (user is not None) and hasattr(user, "profile") and user.profile.is_chef_instru (user is not None) and hasattr(user, "profile") and user.profile.is_chef
)
is_chef_instru = (
(user is not None)
and hasattr(user, "profile")
and user.profile.is_chef_instru
)
return is_chef or is_chef_instru return is_chef or is_chef_instru
class ChefComRequiredMixin(UserPassesTestMixin): class ChefComRequiredMixin(UserPassesTestMixin):
def test_func(self): def test_func(self):
user = self.request.user user = self.request.user
is_chef = (user is not None) and hasattr(user, "profile") and user.profile.is_chef is_chef = (
is_chef_com = (user is not None) and hasattr(user, "profile") and user.profile.is_chef_com (user is not None) and hasattr(user, "profile") and user.profile.is_chef
)
is_chef_com = (
(user is not None) and hasattr(user, "profile") and user.profile.is_chef_com
)
return is_chef or is_chef_com return is_chef or is_chef_com
class ChefMuRequiredMixin(UserPassesTestMixin):
def test_func(self):
user = self.request.user
is_chef = (
(user is not None) and hasattr(user, "profile") and user.profile.is_chef
)
is_chef_mu = (
(user is not None) and hasattr(user, "profile") and user.profile.is_chef_mu
)
return is_chef or is_chef_mu
class AllChefRequiredMixin(UserPassesTestMixin): class AllChefRequiredMixin(UserPassesTestMixin):
def test_func(self): def test_func(self):
user = self.request.user user = self.request.user
is_chef = (user is not None) and hasattr(user, "profile") and user.profile.is_chef is_chef = (
(user is not None) and hasattr(user, "profile") and user.profile.is_chef
)
is_su = (user is not None) and user.is_superuser is_su = (user is not None) and user.is_superuser
is_chef_com = (user is not None) and hasattr(user, "profile") and user.profile.is_chef_com is_chef_com = (
is_chef_event = (user is not None) and hasattr(user, "profile") and user.profile.is_chef_event (user is not None) and hasattr(user, "profile") and user.profile.is_chef_com
return is_chef or is_chef_com or is_chef_event or is_su )
is_chef_event = (
(user is not None)
and hasattr(user, "profile")
and user.profile.is_chef_event
)
is_chef_mu = (
(user is not None) and hasattr(user, "profile") and user.profile.is_chef_mu
)
return is_chef or is_chef_com or is_chef_event or is_su or is_chef_mu

View file

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
from colorful.fields import RGBColorField from colorful.fields import RGBColorField
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import os
from django.conf import settings
INSTRU_CHOICES = [ INSTRU_CHOICES = [
("Clarinette", _("Clarinette")), ("Clarinette", _("Clarinette")),
@ -21,7 +22,9 @@ INSTRU_CHOICES = [
("Trompette", _("Trompette")), ("Trompette", _("Trompette")),
("Autre", _("Autre")), ("Autre", _("Autre")),
("ne sais pas", _("Je ne sais pas encore")), ("ne sais pas", _("Je ne sais pas encore")),
] ]
class Photo(models.Model): class Photo(models.Model):
PHOTO_PLACEMENT = ( PHOTO_PLACEMENT = (
("home_join", _("Rejoignez nous")), ("home_join", _("Rejoignez nous")),
@ -84,6 +87,7 @@ class ErnestoUser(models.Model):
is_chef_event = models.BooleanField(_("Respo événements"), default=False) is_chef_event = models.BooleanField(_("Respo événements"), default=False)
is_chef_com = models.BooleanField(_("Respo com"), default=False) is_chef_com = models.BooleanField(_("Respo com"), default=False)
is_chef_instru = models.BooleanField(_("Respo instruments"), default=False) is_chef_instru = models.BooleanField(_("Respo instruments"), default=False)
is_chef_mu = models.BooleanField(_("Respo musique"), default=False)
phone = models.CharField( phone = models.CharField(
_("Téléphone"), _("Téléphone"),
max_length=20, max_length=20,
@ -91,7 +95,6 @@ class ErnestoUser(models.Model):
help_text=_("seulement visible par les chef·fe·s"), help_text=_("seulement visible par les chef·fe·s"),
) )
COLORS_CHOICES = [ COLORS_CHOICES = [
("#e4522f#ffffff", _("Orange et Blanc")), ("#e4522f#ffffff", _("Orange et Blanc")),
("#ffffff#000000", _("Blanc et Noir")), ("#ffffff#000000", _("Blanc et Noir")),

View file

@ -3704,10 +3704,11 @@ div.spoiler
hsl(220, 100%, 50%), hsl(220, 100%, 50%),
hsl(230, 100%, 50%), hsl(230, 100%, 50%),
hsl(240, 100%, 50%), hsl(240, 100%, 50%),
hsl(250, 100%, 50%), hsl(250, 100%, 50%)
hsl(260, 100%, 50%),
hsl(270, 100%, 50%),
hsl(280, 100%, 50%)
); );
color: transparent; color: transparent;
} }
select[multiple] {
height: 15em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -67,7 +67,7 @@
<a class="dropdown-item" href="https://heyzine.com/flip-book/b2cf4809b7.html" target="_blank">{% trans "Year Book 2021" %}</a> <a class="dropdown-item" href="https://heyzine.com/flip-book/b2cf4809b7.html" target="_blank">{% trans "Year Book 2021" %}</a>
</div> </div>
</li> </li>
{% if user.is_superuser or user.profile.is_chef or user.profile.is_chef_event or user.profile.is_chef_com %} {% if user.is_superuser or user.profile.is_chef or user.profile.is_chef_event or user.profile.is_chef_com or user.profile.is_chef_mu %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="{% url 'chef' %}" id="navbardrop" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="{% url 'chef' %}" id="navbardrop" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<b>{% trans 'Le pouvoir des cheff·e·s'%}</b> <b>{% trans 'Le pouvoir des cheff·e·s'%}</b>
@ -77,18 +77,18 @@
<a class="dropdown-item" href="/admin/">{% trans "Administration" %}</a> <a class="dropdown-item" href="/admin/">{% trans "Administration" %}</a>
{% endif %} {% endif %}
{% if user.profile.is_chef %} {% if user.profile.is_chef %}
<a class="dropdown-item" href="{% url 'calendrier:create_event' %}">{% trans "Ajouter un événement" %}</a>
<a class="dropdown-item" href="{% url 'actu:liste' %}">{% trans "Modifier les actualités" %}</a> <a class="dropdown-item" href="{% url 'actu:liste' %}">{% trans "Modifier les actualités" %}</a>
<a class="dropdown-item" href="{% url 'liste_photo' %}">{% trans "Modifier les photos" %}</a> {% endif %}
<a class="dropdown-item" href="{% url 'liste_video' %}">{% trans "Modifier les vidéos" %}</a> {% if user.profile.is_chef or user.profile.is_chef_event %}
{% elif user.profile.is_chef_event %}
<a class="dropdown-item" href="{% url 'calendrier:create_event' %}">{% trans "Ajouter un événement" %}</a> <a class="dropdown-item" href="{% url 'calendrier:create_event' %}">{% trans "Ajouter un événement" %}</a>
{% endif %}
{% elif user.profile.is_chef_com %} {% if user.profile.is_chef or user.profile.is_chef_com %}
<a class="dropdown-item" href="{% url 'liste_photo' %}">{% trans "Modifier les photos" %}</a> <a class="dropdown-item" href="{% url 'liste_photo' %}">{% trans "Modifier les photos" %}</a>
<a class="dropdown-item" href="{% url 'liste_video' %}">{% trans "Modifier les vidéos" %}</a> <a class="dropdown-item" href="{% url 'liste_video' %}">{% trans "Modifier les vidéos" %}</a>
{% endif %} {% endif %}
{% if user.profile.is_chef or user.profile.is_chef_mu %}
<a class="dropdown-item" href="{% url 'partitions:list_setlist' %}">{% trans "Gérer les programmes de répétition" %}</a>
{% endif %}
</div> </div>
</li> </li>
@ -124,11 +124,11 @@
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
{% ifequal current_language "fr" %} {% if current_language == "fr" %}
<a class="nav-link" href="{% changelang "en" %}" ><img src="{% static 'images\en_flag.jpg' %}" width="60" height="40" style="vertical-align: middle"></a> <a class="nav-link" href="{% changelang "en" %}" ><img src="{% static 'images\en_flag.jpg' %}" width="60" height="40" style="vertical-align: middle"></a>
{% else %} {% else %}
<a class="nav-link" href="{% changelang "fr" %}" ><img src="{% static 'images\fr_flag.jpg' %}" width="60" height="40" style="vertical-align: middle"></a> <a class="nav-link" href="{% changelang "fr" %}" ><img src="{% static 'images\fr_flag.jpg' %}" width="60" height="40" style="vertical-align: middle"></a>
{% endifequal %} {% endif %}
</li> </li>
</ul> </ul>
@ -145,6 +145,10 @@
<!-- Footer --> <!-- Footer -->
<footer id="footer" style="background-color:rgb(228, 82, 47);"> <footer id="footer" style="background-color:rgb(228, 82, 47);">
<div class="copyright"> <div class="copyright">
<ul class="icons">
<li><a target="_blank" href="https://cvec.etudiant.gouv.fr/"><img alt="Logo de la CVEC" src='{% static "images/cvec.png" %}' width="100px"/></a></li>
</ul>
<ul class="icons"> <ul class="icons">
<li><a target="_blank" href="https://www.facebook.com/ernestophone" <li><a target="_blank" href="https://www.facebook.com/ernestophone"

View file

@ -13,6 +13,7 @@
{% if user.profile.is_chef or user.is_superuser %} {% if user.profile.is_chef or user.is_superuser %}
<li> <a href="/admin/">{% trans "Administration" %}</a></li> <li> <a href="/admin/">{% trans "Administration" %}</a></li>
{% endif %} {% endif %}
{% if user.profile.is_chef %} {% if user.profile.is_chef %}
<li><a href="{% url 'actu:liste' %}">{% trans "Modifier les actualités" %}</a></li> <li><a href="{% url 'actu:liste' %}">{% trans "Modifier les actualités" %}</a></li>
@ -24,6 +25,9 @@
<li><a href="{% url 'liste_photo' %}">{% trans "Modifier les photos" %}</a></li> <li><a href="{% url 'liste_photo' %}">{% trans "Modifier les photos" %}</a></li>
<li><a href="{% url 'liste_video' %}">{% trans "Modifier les vidéos" %}</a></li> <li><a href="{% url 'liste_video' %}">{% trans "Modifier les vidéos" %}</a></li>
{% endif %} {% endif %}
{% if user.profile.is_chef or user.profile.is_chef_mu %}
<li> <a href="{% url 'partitions:list_setlist' %}">{% trans "Gérer les programmes de répétition" %}</a> </li>
{% endif %}
</ul> </ul>
</div> </div>

View file

@ -1,3 +1,4 @@
import os
import random import random
import string import string
@ -11,12 +12,12 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import (CreateView, DeleteView, ListView, from django.views.generic import (CreateView, DeleteView, ListView,
TemplateView, UpdateView) TemplateView, UpdateView)
import os
from calendrier.forms import ChangeDoodleName from calendrier.forms import ChangeDoodleName
from gestion.forms import (ChangeFormUser, ChangeMembreForm, from gestion.forms import (ChangeFormUser, ChangeMembreForm,
InscriptionMembreForm, RegistrationFormUser) InscriptionMembreForm, RegistrationFormUser)
from gestion.mixins import ChefRequiredMixin, AllChefRequiredMixin, ChefComRequiredMixin from gestion.mixins import (AllChefRequiredMixin, ChefComRequiredMixin,
ChefRequiredMixin)
from gestion.models import ErnestoUser, Photo, VideoGallery from gestion.models import ErnestoUser, Photo, VideoGallery
from partitions.models import Category from partitions.models import Category
@ -79,6 +80,7 @@ class Profil(LoginRequiredMixin, TemplateView):
class Chef(AllChefRequiredMixin, TemplateView): class Chef(AllChefRequiredMixin, TemplateView):
template_name = "gestion/chef.html" template_name = "gestion/chef.html"
class YearBook2021(TemplateView): class YearBook2021(TemplateView):
template_name = "gestion/yearbook2021.html" template_name = "gestion/yearbook2021.html"

View file

@ -15,9 +15,9 @@
<div class="7u 12u$(small)"> <div class="7u 12u$(small)">
<p>{% trans "Propriétaire : "%} {% if instru.owner %}{{instru.owner}} {% else %}-{% endif %}<br> <p>{% trans "Propriétaire : "%} {% if instru.owner %}{{instru.owner}} {% else %}-{% endif %}<br>
{% trans "Statut : "%} {{instru.statut}} <br> {% trans "Statut : "%} {{instru.statut}} <br>
{% ifequal instru.statut 'Prêté' %} {% if instru.statut == 'Prêté' %}
{% trans "Utilisateur : "%} {% if instru.user %}{{instru.user}} {% else %}-{% endif %}<br> {% trans "Utilisateur : "%} {% if instru.user %}{{instru.user}} {% else %}-{% endif %}<br>
{% endifequal %} {% endif %}
{% trans "Marque : "%} {% if instru.marque %}{{instru.marque}} {% else %}-{% endif %} <br> {% trans "Marque : "%} {% if instru.marque %}{{instru.marque}} {% else %}-{% endif %} <br>
{% trans "Modele : "%} {% if instru.model %}{{instru.model}} {% else %}-{% endif %}<br> {% trans "Modele : "%} {% if instru.model %}{{instru.model}} {% else %}-{% endif %}<br>
{% trans "Numéro de série : "%} {% if instru.serial %}{{instru.serial}}{% else %}-{% endif %} <br> {% trans "Numéro de série : "%} {% if instru.serial %}{{instru.serial}}{% else %}-{% endif %} <br>
@ -55,7 +55,7 @@
<tr> <tr>
<td> {{ rep.date }} </td> <td> {{ rep.date }} </td>
<td> {% ifequal current_language "fr" %} <td> {% if current_language == "fr" %}
{{ rep.description }} {{ rep.description }}
{% else %} {% else %}
{% if instru.description_en %} {% if instru.description_en %}
@ -63,7 +63,7 @@
{% else %} {% else %}
{{ rep.description }} {{ rep.description }}
{% endif %} {% endif %}
{% endifequal %} </td> {% endif %} </td>
<td> {{ rep.prix }} </td> <td> {{ rep.prix }} </td>
<td> {{ rep.lieux }} </td> <td> {{ rep.lieux }} </td>
{%if user.profile.is_chef or user.profile.is_chef_instru %} {%if user.profile.is_chef or user.profile.is_chef_instru %}

View file

@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
from django.views.generic import (CreateView, DeleteView, TemplateView, from django.views.generic import (CreateView, DeleteView, TemplateView,
UpdateView) UpdateView)
from gestion.mixins import ChefRequiredMixin, ChefInstruRequiredMixin from gestion.mixins import ChefInstruRequiredMixin, ChefRequiredMixin
from gestion.models import Photo from gestion.models import Photo
from instruments.forms import ChefEditInstrumentForm, ChefReparationForm from instruments.forms import ChefEditInstrumentForm, ChefReparationForm
from instruments.models import Instrument, Reparation from instruments.models import Instrument, Reparation
@ -28,7 +28,17 @@ class ListeInstru(LoginRequiredMixin, TemplateView):
class CreateInstru(ChefInstruRequiredMixin, CreateView): class CreateInstru(ChefInstruRequiredMixin, CreateView):
model = Instrument model = Instrument
fields = ["owner","user", "etat", "type", "marque", "model", "serial", "annee", "prix"] fields = [
"owner",
"user",
"etat",
"type",
"marque",
"model",
"serial",
"annee",
"prix",
]
template_name = "instruments/create_instru.html" template_name = "instruments/create_instru.html"
success_url = reverse_lazy("instruments:liste") success_url = reverse_lazy("instruments:liste")
@ -95,11 +105,12 @@ class FicheInstru(LoginRequiredMixin, TemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
instru = get_object_or_404(self.model, id=self.kwargs["pk"]) instru = get_object_or_404(self.model, id=self.kwargs["pk"])
form = ChefEditInstrumentForm(request.POST, instance=instru) form = ChefEditInstrumentForm(request.POST, instance=instru)
if request.user.profile.is_chef: if request.user.profile.is_chef or request.user.profile.is_chef_instru:
if form.is_valid(): if form.is_valid():
form.save() form.save()
context = self.get_context_data() context = self.get_context_data()
context["form"] = form context["form"] = form
print(instru.user)
return render(request, self.template_name, context) return render(request, self.template_name, context)

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ import os
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Ernestophone.settings.local") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Ernestophone.settings")
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line

80
npins/default.nix Normal file
View file

@ -0,0 +1,80 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource =
{
repository,
revision,
url ? null,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else
assert repository.type == "Git";
let
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName repository.url revision;
in
builtins.fetchGit {
url = repository.url;
rev = revision;
inherit name;
# hash = hash;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

22
npins/sources.json Normal file
View file

@ -0,0 +1,22 @@
{
"pins": {
"nix-pkgs": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.hubrecht.ovh/hubrecht/nix-pkgs.git"
},
"branch": "main",
"revision": "cc01e1c2a6ecb1e38fde35ee54995a6a639fb057",
"url": null,
"hash": "17a9vlwrk9365ccyl7a5xspqsn9wizcpwdpvr3qdimvq4fpwhjal"
},
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.05pre723402.4989a246d7a3/nixexprs.tar.xz",
"hash": "0hjng6rhkjiql1dqbanjm6jl6npik29q2lmba032j897fhyzin91"
}
},
"version": 3
}

View file

@ -4,7 +4,7 @@ from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.views.generic import CreateView, DeleteView, ListView, UpdateView from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from gestion.mixins import ChefRequiredMixin, ChefEventRequiredMixin from gestion.mixins import ChefEventRequiredMixin, ChefRequiredMixin
from pads.models import Pad from pads.models import Pad

View file

@ -1,6 +1,13 @@
from django.contrib import admin from django.contrib import admin
from .models import Category, PartitionSet from .models import Category, Partition, PartitionSet, SetList
class PartitionAdmin(admin.ModelAdmin):
list_filter = ("morceau",)
admin.site.register(Category) admin.site.register(Category)
admin.site.register(PartitionSet) admin.site.register(PartitionSet)
admin.site.register(SetList)
admin.site.register(Partition, PartitionAdmin)

View file

@ -0,0 +1,44 @@
# Generated by Django 2.2.25 on 2022-01-09 18:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("partitions", "0004_auto_20210331_1350"),
]
operations = [
migrations.CreateModel(
name="SetList",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("date", models.DateField(verbose_name="Date de la répétition")),
(
"is_current",
models.CharField(
choices=[("y", "Oui"), ("n", "Non")],
default="y",
max_length=1,
verbose_name="Afficher le programme de répétition (les répétition vieilles de plus d'une semaine ne sont pas affiché d'office)",
),
),
(
"morceaux",
models.ManyToManyField(
to="partitions.PartitionSet",
verbose_name="Morceaux de la répétition (ctrl ou cmd pour en selectionner plusieurs)",
),
),
],
),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 2.2.24 on 2022-01-18 14:25
import django.db.models.functions.text
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("partitions", "0005_setlist"),
]
operations = [
migrations.AlterModelOptions(
name="partition",
options={
"ordering": (django.db.models.functions.text.Lower("nom"),),
"verbose_name": "Partition",
"verbose_name_plural": "Partitions",
},
),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 2.2.24 on 2022-01-18 14:42
import django.db.models.functions.text
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("partitions", "0006_auto_20220118_1525"),
]
operations = [
migrations.AlterModelOptions(
name="partitionset",
options={
"ordering": ("category", django.db.models.functions.text.Lower("nom")),
"verbose_name": "Morceau",
"verbose_name_plural": "Morceaux",
},
),
]

View file

@ -1,6 +1,7 @@
import os import os
from django.conf import settings from django.conf import settings
from django.contrib import admin
from django.db import models from django.db import models
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -33,8 +34,8 @@ class Partition(models.Model):
super(Partition, self).delete(*args, **kwargs) super(Partition, self).delete(*args, **kwargs)
class Meta: class Meta:
verbose_name = _("Morceau") verbose_name = _("Partition")
verbose_name_plural = _("Morceaux") verbose_name_plural = _("Partitions")
ordering = (Lower("nom"),) ordering = (Lower("nom"),)
@ -69,4 +70,43 @@ class PartitionSet(models.Model):
class Meta: class Meta:
verbose_name = _("Morceau") verbose_name = _("Morceau")
verbose_name_plural = _("Morceaux") verbose_name_plural = _("Morceaux")
ordering = (Lower("nom"),) ordering = (
"category",
Lower("nom"),
)
from datetime import date as ddate
from datetime import timedelta
class SetList(models.Model):
"""
Modèle qui stocke les setlists de répétition (date et morceaux)
"""
date = models.DateField(_("Date de la répétition"))
is_current = models.CharField(
verbose_name=_(
"Afficher le programme de répétition (les répétition vieilles de plus d'une semaine ne sont pas affiché d'office)"
),
max_length=1,
choices=(("y", "Oui"), ("n", "Non")),
default="y",
blank=False,
)
morceaux = models.ManyToManyField(
"PartitionSet",
verbose_name=_(
"Morceaux de la répétition (ctrl ou cmd pour en selectionner plusieurs)"
),
)
def __str__(self):
return "%s - (%s)" % (
self.date,
", ".join(self.morceaux.all().values_list("nom", flat=True)),
)
def is_visible(self):
return self.is_current == "y" and self.date > ddate.today() - timedelta(days=7)

View file

@ -23,7 +23,7 @@
<a href="{% url "partitions:listepart" partition.nom partition.auteur %}" <a href="{% url "partitions:listepart" partition.nom partition.auteur %}"
class="fichier">{{ partition.nom }} - {{ partition.auteur }}</a> class="fichier">{{ partition.nom }} - {{ partition.auteur }}</a>
{% endif %} {% endif %}
{% if user.profile.is_chef %} {% if user.profile.is_chef or user.profile.is_chef_mu %}
<a href="{% url "partitions:conf_delete_morc" partition.nom partition.auteur %}" <a href="{% url "partitions:conf_delete_morc" partition.nom partition.auteur %}"
class="supprimer">Supprimer</a> class="supprimer">Supprimer</a>
{% endif %} {% endif %}

View file

@ -10,7 +10,7 @@
{% endif %} {% endif %}
<div class="info_part"> <div class="info_part">
{% if user.profile.is_chef %} {% if user.profile.is_chef or user.profile.is_chef_mu %}
<form action="{% url "partitions:listepart" nom auteur %}" id="chef-edit-form" method="post"> <form action="{% url "partitions:listepart" nom auteur %}" id="chef-edit-form" method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
@ -40,7 +40,7 @@
<a href="{% url "partitions:download" nom auteur p.id %}" class="telecharger">Télécharger</a> <a href="{% url "partitions:download" nom auteur p.id %}" class="telecharger">Télécharger</a>
{% endif %} {% endif %}
{% if user.profile.is_chef %} {% if user.profile.is_chef or user.profile.is_chef_mu %}
<a href="{% url "partitions:conf_delete" nom auteur p.pk %}" class="supprimer">Supprimer</a> <a href="{% url "partitions:conf_delete" nom auteur p.pk %}" class="supprimer">Supprimer</a>
{% endif %} {% endif %}

View file

@ -28,15 +28,17 @@
<tbody> <tbody>
{% for p in part %} {% for p in part %}
{% if user.is_authenticated and ".mscz" in p.part.url %} {% if user.is_authenticated and ".mscz" in p.part.url %}
<tr>
<td><p class="fichier">{{ p.nom }}</p></td> <td><p class="fichier">{{ p.nom }}</p></td>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<td> <a href="{% url "partitions:download" nom auteur p.id %}" class="button icon fa-download">{% trans "Télécharger" %}</a></td> <td> <a href="{% url "partitions:download" nom auteur p.id %}" class="button icon fa-download">{% trans "Télécharger" %}</a></td>
{% endif %} {% endif %}
{% if user.profile.is_chef %} <td> {% if user.profile.is_chef or user.profile.is_chef_mu %} <td>
<a href="{% url "partitions:conf_delete" nom auteur p.pk %}" class="button icon fa-deleate">{% trans "Supprimer" %}</a></td> <a href="{% url "partitions:conf_delete" nom auteur p.pk %}" class="button icon fa-deleate">{% trans "Supprimer" %}</a></td>
{% endif %} {% endif %}
</tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% for p in part %} {% for p in part %}
@ -54,7 +56,7 @@
{% if user.is_authenticated %} {% if user.is_authenticated %}
<td> <a href="{% url "partitions:download" nom auteur p.id %}" class="button icon fa-download">{% trans "Télécharger" %}</a></td> <td> <a href="{% url "partitions:download" nom auteur p.id %}" class="button icon fa-download">{% trans "Télécharger" %}</a></td>
{% endif %} {% endif %}
{% if user.profile.is_chef %} <td> {% if user.profile.is_chef or user.profile.is_chef_mu %} <td>
<a href="{% url "partitions:conf_delete" nom auteur p.pk %}" class="button icon fa-deleate">{% trans "Supprimer" %}</a></td> <a href="{% url "partitions:conf_delete" nom auteur p.pk %}" class="button icon fa-deleate">{% trans "Supprimer" %}</a></td>
{% endif %} {% endif %}
</tr> </tr>
@ -65,7 +67,7 @@
</tbody> </tbody>
</table> </table>
{% if user.profile.is_chef %} {% if user.profile.is_chef or user.profile.is_chef_mu %}
<p><a href="{% url "partitions:upload" p.nom p.auteur %}" class='button'>{% trans "Ajouter un média" %}</a></p> <p><a href="{% url "partitions:upload" p.nom p.auteur %}" class='button'>{% trans "Ajouter un média" %}</a></p>
{% if infos or infos_en %} {% if infos or infos_en %}
@ -86,7 +88,7 @@
<p></p> <p></p>
</div> </div>
<div class="6u 12u$(small)"> <div class="6u 12u$(small)">
{% if user.profile.is_chef %} {% if user.profile.is_chef or user.profile.is_chef_mu %}
<form action="{% url "partitions:listepart" nom auteur %}" id="chef-edit-form" method="post"> <form action="{% url "partitions:listepart" nom auteur %}" id="chef-edit-form" method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}

View file

@ -7,6 +7,20 @@
{% block content %} {% block content %}
<div id="main"> <div id="main">
<section class="wrapper style1"> <section class="wrapper style1">
{% if user.is_authenticated and setlists %}
<div class="inner">
<div class="box" style="background-color:rgba(228,82,47,0.5)">
{% for set_list in setlists %}
<h4>{% blocktrans with set_list_date=set_list.date %}Programme de répétition de la semaine du {{ set_list_date }}: {% endblocktrans %}</h4>
<ul class="pl-5">
{% for morceau in set_list.morceaux.all %}
<li><a href="{% url "partitions:listepart" morceau.nom morceau.auteur %}">{{ morceau.nom }}</a></li>
{% endfor %}
</ul>
{% endfor %}
</div>
</div>
{% endif %}
<div class="inner"> <div class="inner">
<span class="image fit"> <span class="image fit">
@ -23,7 +37,7 @@
<div class="icon fa-copyright" style="color:#000000"> Lucas Gierzack</div></div> <div class="icon fa-copyright" style="color:#000000"> Lucas Gierzack</div></div>
{% endif %} {% endif %}
</span> </span>
{% if user.profile.is_chef %} {% if user.profile.is_chef or user.profile.is_chef_mu %}
<a href="{% url "partitions:ajouter_morceau" %}" class="button alt big">{% trans "Ajouter un morceau" %}</a> &nbsp <a href="{% url "partitions:download_musecores" %}" class="button alt big">{% trans "Télécharger tous les musecores actifs" %}</a> <a href="{% url "partitions:ajouter_morceau" %}" class="button alt big">{% trans "Ajouter un morceau" %}</a> &nbsp <a href="{% url "partitions:download_musecores" %}" class="button alt big">{% trans "Télécharger tous les musecores actifs" %}</a>
{% elif user.is_authenticated %} {% elif user.is_authenticated %}
<a href="{% url "partitions:download_musecores" %}" class="button alt big">{% trans "Télécharger tous les musecores actifs" %}</a> <a href="{% url "partitions:download_musecores" %}" class="button alt big">{% trans "Télécharger tous les musecores actifs" %}</a>
@ -66,7 +80,7 @@
<td> <u><a href="{% url "partitions:listepart" partition.nom partition.auteur %}" <td> <u><a href="{% url "partitions:listepart" partition.nom partition.auteur %}"
class="fichier">{{ partition.nom }}</a> </td> class="fichier">{{ partition.nom }}</a> </td>
<td> {{ partition.auteur }} </a></u> </td> <td> {{ partition.auteur }} </a></u> </td>
{% if user.profile.is_chef %}<td> {% if user.profile.is_chef or user.profile.is_chef_mu %}<td>
<a href="{% url "partitions:conf_delete_morc" partition.nom partition.auteur %}" <a href="{% url "partitions:conf_delete_morc" partition.nom partition.auteur %}"
class="button small icon fa-trash">{% trans "Supprimer" %}</a></td> class="button small icon fa-trash">{% trans "Supprimer" %}</a></td>

View file

@ -0,0 +1,21 @@
{% extends "gestion/base.html" %}
{% load i18n %}
{% get_current_language as current_language %}
{% load autotranslate %}
{% block titre %}{% trans "Supprimer un programme de répétition" %}{% endblock %}
{% block content %}
<div id="main">
<section class="wrapper style1">
<div class="inner">
<h4>{% blocktrans with set_list_date=setlist.date %} Supprimer le programme de répétition du {{ set_list_date }} :{% endblocktrans %}</h4>
<p>{% blocktrans with set_list=setlist %}Êtes-vous sûr.e de vouloir supprimer cette répétition : {{ set_list }}?{% endblocktrans %}</p>
<form action="" method="POST">
{% csrf_token %}
<input class="button alt" type="submit" value="{% trans "Oui" %}">
<a class="button alt" href="{% url 'partitions:list_setlist' %}">{% trans "Retour" %}</a>
</form>
</div>
</section>
</div>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "gestion/base.html" %}
{% load i18n %}
{%block titre %}{% trans "Ajout/modification d'un programme de répétition" %}{% endblock %}
{% block content %}
<div id="main">
<section class="wrapper style1">
<div class="inner">
<p><a href="{% url "partitions:list_setlist" %}" class="button alt">{% trans "Retour à la liste" %}</a></p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="{% trans "Enregistrer" %}" />
</form>
</div>
</section>
</div>
{% endblock %}

View file

@ -0,0 +1,31 @@
{% extends "gestion/base.html" %}
{% load i18n %}
{% get_current_language as current_language %}
{% load autotranslate %}
{% block titre %}{% trans "Liste des programmes de répétition" %}{% endblock %}
{% block content %}
<div id="main">
<section class="wrapper style1">
<div class="inner">
<h4>{% trans "Liste des programmes de répétition" %} :</h4>
<p><a href="{% url 'partitions:create_setlist' %}" class="button">{% trans "Ajouter un programme de répétition" %}</a></p>
<ul class="filelist">
{% for a in setlist_list %}
<li>
<p>{% if a.is_visible %}[VISIBLE] - {% endif %}{{ a }}
<a class="button alt" href="{% url 'partitions:update_setlist' a.pk %}">{% trans "Modifier" %}</a>
<a class="button alt" href="{% url 'partitions:delete_setlist' a.pk %}">{% trans "Supprimer" %}</a></p>
</li>
{% empty %}
<p>{% trans "Pas de programme de répétition pour le moment" %}</p>
{% endfor %}
</ul>
</div>
</section>
</div>
{% endblock %}

View file

@ -6,6 +6,14 @@ app_name = "partitions"
urlpatterns = [ urlpatterns = [
path("", views.Repertoire.as_view(), name="liste"), path("", views.Repertoire.as_view(), name="liste"),
path("download", views.download_musecores, name="download_musecores"), path("download", views.download_musecores, name="download_musecores"),
path("setlist/", views.SetListListView.as_view(), name="list_setlist"),
path("setlist/create", views.SetListCreate.as_view(), name="create_setlist"),
path(
"setlist/<int:pk>/update", views.SetListUpdate.as_view(), name="update_setlist"
),
path(
"setlist/<int:pk>/delete", views.SetListDelete.as_view(), name="delete_setlist"
),
path("<str:nom>/<str:auteur>/upload", views.Upload.as_view(), name="upload"), path("<str:nom>/<str:auteur>/upload", views.Upload.as_view(), name="upload"),
path("<str:nom>/<str:auteur>", views.Morceau.as_view(), name="listepart"), path("<str:nom>/<str:auteur>", views.Morceau.as_view(), name="listepart"),
path("<str:nom>/<str:auteur>/see/<int:partition_id>", views.see, name="see"), path("<str:nom>/<str:auteur>/see/<int:partition_id>", views.see, name="see"),

View file

@ -7,15 +7,17 @@ from django.core.files import File
from django.db.models import Q from django.db.models import Q
from django.http import Http404 from django.http import Http404
from django.shortcuts import HttpResponse, get_object_or_404, redirect, render from django.shortcuts import HttpResponse, get_object_or_404, redirect, render
from django.urls import reverse_lazy
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView from django.views.generic import (CreateView, DeleteView, ListView,
TemplateView, UpdateView)
from gestion.mixins import ChefRequiredMixin from gestion.mixins import ChefMuRequiredMixin
from gestion.models import Photo from gestion.models import Photo
from partitions.forms import UploadFileForm, UploadMorceauForm from partitions.forms import UploadFileForm, UploadMorceauForm
from partitions.models import Category, Partition, PartitionSet from partitions.models import Category, Partition, PartitionSet, SetList
from .forms import ChefEditMorceauForm from .forms import ChefEditMorceauForm
@ -69,11 +71,21 @@ def download_musecores(request):
return resp return resp
from datetime import date, timedelta
class Repertoire(TemplateView): class Repertoire(TemplateView):
template_name = "partitions/repertoire.html" template_name = "partitions/repertoire.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["setlists"] = (
SetList.objects.filter(
is_current="y", date__gt=(date.today() - timedelta(days=7))
)
.order_by("date")
.prefetch_related("morceaux")
)
context["categories"] = Category.objects.prefetch_related( context["categories"] = Category.objects.prefetch_related(
"partitionset_set" "partitionset_set"
).order_by("order") ).order_by("order")
@ -94,7 +106,6 @@ class Morceau(LoginRequiredMixin, TemplateView):
form = self.form_class(instance=p) form = self.form_class(instance=p)
infos = mark_safe(p.infos) infos = mark_safe(p.infos)
infos_en = mark_safe(p.infos_en) infos_en = mark_safe(p.infos_en)
context["p"] = p context["p"] = p
context["infos"] = infos context["infos"] = infos
context["infos_en"] = infos_en context["infos_en"] = infos_en
@ -117,7 +128,7 @@ class Morceau(LoginRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class Upload(ChefRequiredMixin, TemplateView): class Upload(ChefMuRequiredMixin, TemplateView):
form_class = UploadFileForm form_class = UploadFileForm
template_name = "partitions/upload.html" template_name = "partitions/upload.html"
@ -197,7 +208,7 @@ def see(request, nom, auteur, partition_id):
return redirect("login") return redirect("login")
class DeletePart(ChefRequiredMixin, TemplateView): class DeletePart(ChefMuRequiredMixin, TemplateView):
model = PartitionSet model = PartitionSet
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -214,7 +225,7 @@ class DeletePart(ChefRequiredMixin, TemplateView):
) )
class CreateMorc(ChefRequiredMixin, TemplateView): class CreateMorc(ChefMuRequiredMixin, TemplateView):
form_class = UploadMorceauForm form_class = UploadMorceauForm
template_name = "partitions/new.html" template_name = "partitions/new.html"
@ -259,7 +270,7 @@ class CreateMorc(ChefRequiredMixin, TemplateView):
return render(request, self.template_name, context) return render(request, self.template_name, context)
class ConfDelete(ChefRequiredMixin, TemplateView): class ConfDelete(ChefMuRequiredMixin, TemplateView):
template_name = "partitions/conf_delete.html" template_name = "partitions/conf_delete.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -270,7 +281,7 @@ class ConfDelete(ChefRequiredMixin, TemplateView):
return context return context
class DeleteMorc(ChefRequiredMixin, TemplateView): class DeleteMorc(ChefMuRequiredMixin, TemplateView):
model = PartitionSet model = PartitionSet
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -284,7 +295,7 @@ class DeleteMorc(ChefRequiredMixin, TemplateView):
return redirect("partitions:liste") return redirect("partitions:liste")
class ConfDeleteMorc(ChefRequiredMixin, TemplateView): class ConfDeleteMorc(ChefMuRequiredMixin, TemplateView):
template_name = "partitions/conf_delete_morc.html" template_name = "partitions/conf_delete_morc.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -313,3 +324,27 @@ def download(request, nom, auteur, partition_id):
return response return response
else: else:
return redirect("login") return redirect("login")
class SetListListView(ChefMuRequiredMixin, ListView):
model = SetList
def get_queryset(self):
return SetList.objects.all().order_by("-date")
class SetListCreate(ChefMuRequiredMixin, CreateView):
model = SetList
fields = ["date", "morceaux", "is_current"]
success_url = reverse_lazy("partitions:list_setlist")
class SetListUpdate(ChefMuRequiredMixin, UpdateView):
model = SetList
fields = ["date", "morceaux", "is_current"]
success_url = reverse_lazy("partitions:list_setlist")
class SetListDelete(ChefMuRequiredMixin, DeleteView):
model = SetList
success_url = reverse_lazy("partitions:list_setlist")

0
propositions/admin.py Normal file
View file

View file

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestion', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Prop',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('nom', models.CharField(max_length=100)),
('artiste', models.CharField(max_length=100, blank=True)),
('lien', models.URLField(blank=True)),
('nboui', models.IntegerField(verbose_name='oui', default=0)),
('nbnon', models.IntegerField(verbose_name='non', default=0)),
('user', models.ForeignKey(verbose_name='Proposé par', to='gestion.ErnestoUser', on_delete=models.CASCADE)),
],
options={
'verbose_name': 'Proposition',
},
),
migrations.CreateModel(
name='Reponses',
fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
('reponse', models.CharField(verbose_name='Réponse', choices=[('oui', 'Oui'), ('non', 'Non')], max_length=20, blank=True)),
('part', models.ForeignKey(to='gestion.ErnestoUser', on_delete=models.CASCADE)),
('prop', models.ForeignKey(to='propositions.Prop', on_delete=models.CASCADE)),
],
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.9 on 2020-01-04 23:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('propositions', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='prop',
name='nom',
field=models.CharField(max_length=100, verbose_name='nom du morceau'),
),
]

View file

@ -0,0 +1,75 @@
# Generated by Django 2.2.9 on 2020-01-05 13:32
from django.conf import settings
from django.db import migrations, models
def move_profile_to_user(apps, schema_editor):
Reponses = apps.get_model("propositions", "reponses")
for answer in Reponses.objects.all():
answer.user = answer.part.user
answer.save()
def move_user_to_profile(apps, schema_editor):
# One should do something similar to ``move_profile_to_user`` AND make the
# ``part`` field temporarily nullable in the operations below.
# => Grosse flemme
raise NotImplementedError("Who uses migrations backwards anyway?")
class Migration(migrations.Migration):
dependencies = [
("gestion", "0001_initial"),
("propositions", "0002_nom_verbose_name"),
]
operations = [
migrations.AlterModelOptions(
name="reponses",
options={
"verbose_name": "Réponse à une proposition",
"verbose_name_plural": "Réponses à une proposition",
},
),
migrations.RenameField(
model_name="reponses", old_name="prop", new_name="proposition",
),
migrations.AddField(
model_name="reponses",
name="user",
field=models.ForeignKey(
on_delete=models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
null=True,
),
),
migrations.RunPython(move_profile_to_user, move_user_to_profile),
migrations.AlterField(
model_name="reponses",
name="user",
field=models.ForeignKey(
on_delete=models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
null=False,
),
),
migrations.RemoveField(model_name="reponses", name="part"),
migrations.AddField(
model_name="reponses",
name="answer",
field=models.CharField(
choices=[("oui", "Oui"), ("non", "Non")],
default="non",
max_length=3,
verbose_name="Réponse",
),
preserve_default=False,
),
migrations.AlterUniqueTogether(
name="reponses", unique_together={("proposition", "user")},
),
migrations.RemoveField(model_name="reponses", name="reponse",),
migrations.RenameModel(old_name="reponses", new_name="answer"),
]

View file

@ -0,0 +1,43 @@
# Generated by Django 2.2.9 on 2020-01-05 14:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("propositions", "0003_reponse_renaming_and_cleaning"),
]
operations = [
migrations.AlterModelOptions(
name="prop",
options={
"verbose_name": "Proposition de morceau",
"verbose_name_plural": "Propositions de morceaux",
},
),
migrations.RenameField(
model_name="prop", old_name="artiste", new_name="artist",
),
migrations.RenameField(model_name="prop", old_name="lien", new_name="link"),
migrations.RenameField(model_name="prop", old_name="nom", new_name="name"),
migrations.RenameField(model_name="prop", old_name="nbnon", new_name="nb_no"),
migrations.RenameField(model_name="prop", old_name="nboui", new_name="nb_yes"),
migrations.AlterField(
model_name="prop",
name="nb_no",
field=models.IntegerField(default=0, verbose_name="nombre de réponses non"),
),
migrations.AlterField(
model_name="prop",
name="nb_yes",
field=models.IntegerField(default=0, verbose_name="nombre de réponses oui"),
),
migrations.RenameModel(old_name="prop", new_name="proposition"),
migrations.AlterField(
model_name='answer',
name='proposition',
field=models.ForeignKey(on_delete=models.deletion.CASCADE, to='propositions.Proposition'),
),
]

View file

@ -0,0 +1,24 @@
# Generated by Django 2.2.9 on 2020-01-05 15:26
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("propositions", "0004_prop_renaming_and_cleaning"),
]
operations = [
migrations.RemoveField(model_name="proposition", name="nb_no",),
migrations.RemoveField(model_name="proposition", name="nb_yes",),
migrations.AlterField(
model_name="answer",
name="proposition",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="propositions.Proposition",
),
),
]

View file

@ -0,0 +1,53 @@
# Generated by Django 2.2.9 on 2020-01-05 16:28
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def move_profile_to_user(apps, schema_editor):
Proposition = apps.get_model("propositions", "Proposition")
for proposition in Proposition.objects.all():
proposition.user = proposition.profile.user
proposition.save()
def move_user_to_profile(apps, schema_editor):
# One should do something similar to ``move_profile_to_user`` AND make the
# ``profile`` field temporarily nullable in the operations below.
# => Grosse flemme
raise NotImplementedError("Who uses migrations backwards anyway?")
class Migration(migrations.Migration):
dependencies = [
("propositions", "0005_remove_nb_yes_no_fields"),
]
operations = [
migrations.RenameField(
model_name="proposition", old_name="user", new_name="profile"
),
migrations.AddField(
model_name="proposition",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Proposé par",
null=True,
),
),
migrations.RunPython(move_profile_to_user, move_user_to_profile),
migrations.RemoveField(model_name="proposition", name="profile"),
migrations.AlterField(
model_name="proposition",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
verbose_name="Proposé par",
),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.2.27 on 2022-03-22 13:55
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('propositions', '0006_proposition_profile_to_user'),
]
operations = [
migrations.AlterField(
model_name='answer',
name='proposition',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='propositions.Proposition'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.2.27 on 2022-09-14 10:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('propositions', '0007_auto_20220322_1455'),
]
operations = [
migrations.AlterField(
model_name='answer',
name='proposition',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='propositions.Proposition'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.2.28 on 2024-06-15 13:03
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('propositions', '0008_auto_20220914_1244'),
]
operations = [
migrations.AlterField(
model_name='answer',
name='proposition',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='propositions.Proposition'),
),
]

View file

34
propositions/models.py Normal file
View file

@ -0,0 +1,34 @@
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class Proposition(models.Model):
name = models.CharField(max_length=100, verbose_name="nom du morceau")
artist = models.CharField(blank=True, max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="Proposé par")
link = models.URLField(blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name = "Proposition de morceau"
verbose_name_plural = "Propositions de morceaux"
class Answer(models.Model):
YES = "oui"
NO = "non"
REP_CHOICES = [(YES, "Oui"), (NO, "Non")]
proposition = models.ForeignKey(Proposition, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
answer = models.CharField("Réponse", max_length=3, choices=REP_CHOICES)
class Meta:
unique_together = ("proposition", "user")
verbose_name = "Réponse à une proposition"
verbose_name_plural = "Réponses à une proposition"

View file

@ -0,0 +1,14 @@
{% extends "gestion/base.html" %}
{% block titre %}Proposition de morceau{% endblock %}
{% block content %}
<p><a href="{% url "propositions:list" %}">Retour aux propositions</a></p>
<form action="{% url "propositions:create" %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Enregistrer" />
</form>
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends "gestion/base.html" %}
{% block titre %}Suppression d'une proposition{% endblock %}
{% block content %}<form action="" method="post">
{% csrf_token %}
<p><a href="{% url "propositions:list" %}">Retour aux propositions</a></p>
<p>Voulez vous vraiment supprimer la proposition {{ object }}?</p>
<input type="submit" value="Oui" />
</form>
{% endblock %}

View file

@ -0,0 +1,44 @@
{% extends "gestion/base.html" %}
{% block titre %}Propositions de morceau{% endblock %}
{% block content %}
<h1>Liste des propositions</h1>
<p><a href="{% url "propositions:create" %}">Proposer un morceau</a></p>
{% if propositions.exists %}
<table>
<tr>
<th></th>
<th>Oui</th>
<th>Non</th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
{% for p in propositions %}
<tr class="prop">
<td>
{% if p.link %}<a href={{ p.link }}>{% endif %}
<b>{{ p.name }}</b>{% if p.artist %} - {{ p.artist }}{% endif %}
{% if p.link %}</a>{% endif %}
</td>
<td>{{ p.nb_yes }}</td>
<td>{{ p.nb_no }}</td>
<td><a href="{% url "propositions:oui" p.id %}">Oui</a></td>
<td><a href="{% url "propositions:non" p.id %}">Non</a></td>
<td>{% if p.user_answer %}Vous avez voté {{ p.user_answer }}{% endif %}</td>
<td>
{% if p.user == request.user or request.user.profile.is_chef %}
<a class="supprimer" href="{% url "propositions:delete" p.id %}">Supprimer</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
Pas de proposition pour le moment
{% endif %}
{% endblock %}

165
propositions/tests.py Normal file
View file

@ -0,0 +1,165 @@
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse_lazy, reverse
from gestion.models import ErnestoUser
from propositions.models import Answer, Proposition
User = get_user_model()
def new_user(username):
u = User.objects.create_user(username=username)
ErnestoUser.objects.create(user=u, slug=username, is_ernesto=True)
return u
class PropositionCreateTest(TestCase):
url = reverse_lazy("propositions:create")
def test_anonymous_cannot_get(self):
response = Client().get(self.url)
self.assertRedirects(response, "/login?next={}".format(self.url))
def test_ernesto_user_can_get(self):
user = new_user("toto")
client = Client()
client.force_login(user)
response = client.get(self.url)
self.assertEqual(response.status_code, 200)
def test_ernesto_user_can_post(self):
user = new_user("toto")
client = Client()
client.force_login(user)
data = {"name": "foo", "artist": "bar", "link": "example.com"}
client.post(self.url, data)
proposition = Proposition.objects.all()
self.assertEqual(1, proposition.count())
proposition = proposition.get()
self.assertEqual(proposition.name, "foo")
self.assertEqual(proposition.artist, "bar")
self.assertEqual(proposition.link, "http://example.com")
self.assertEqual(proposition.user, user)
class PropositionDeleteTest(TestCase):
def setUp(self):
self.owner = new_user("owner")
self.random_user = new_user("toto")
self.chef = new_user("chef")
self.chef.profile.is_chef = True
self.chef.profile.save()
proposition = Proposition.objects.create(name="prop", user=self.owner)
self.url = reverse("propositions:delete", args=(proposition.id,))
def test_anonymous_cannot_get(self):
response = Client().get(self.url)
self.assertRedirects(response, "/login?next={}".format(self.url))
def test_anonymous_cannot_post(self):
response = Client().post(self.url, {})
self.assertRedirects(response, "/login?next={}".format(self.url))
self.assertTrue(Proposition.objects.exists())
def test_random_user_cannot_get(self):
client = Client()
client.force_login(self.random_user)
response = client.get(self.url)
self.assertEqual(response.status_code, 403)
def test_not_owner_cannot_post(self):
client = Client()
client.force_login(self.random_user)
response = client.post(self.url, {})
self.assertEqual(response.status_code, 403)
self.assertTrue(Proposition.objects.exists())
def test_chef_can_get(self):
client = Client()
client.force_login(self.chef)
response = client.get(self.url)
self.assertEqual(response.status_code, 200)
def test_chef_can_post(self):
client = Client()
client.force_login(self.chef)
client.post(self.url, {})
self.assertFalse(Proposition.objects.exists())
def test_owner_can_get(self):
client = Client()
client.force_login(self.owner)
response = client.get(self.url)
self.assertEqual(response.status_code, 200)
def test_owner_can_post(self):
client = Client()
client.force_login(self.owner)
client.post(self.url, {})
self.assertFalse(Proposition.objects.exists())
class PropositionListTest(TestCase):
url = reverse_lazy("propositions:list")
def setUp(self):
self.user = new_user("toto")
for name in ["foo", "bar", "baz"]:
p = Proposition.objects.create(name=name, user=self.user)
Answer.objects.create(proposition=p, user=self.user, answer=Answer.YES)
for name in ["oof", "rab", "zab"]:
p = Proposition.objects.create(name=name, user=self.user)
Answer.objects.create(proposition=p, user=self.user, answer=Answer.NO)
def test_anonymous_cannot_get(self):
response = Client().get(self.url)
self.assertRedirects(response, "/login?next={}".format(self.url))
def test_ernesto_user_can_get(self):
client = Client()
client.force_login(self.user)
response = client.get(self.url)
self.assertEqual(response.status_code, 200)
class ReponseTest(TestCase):
def setUp(self):
self.user = new_user("toto")
self.prop = Proposition.objects.create(name="foo", user=self.user)
def _url(self, rep):
assert rep in Answer.REP_CHOICES
return reverse("propositions:{}".format(rep), args=(self.prop.id,))
def test_anonymous_cannot_get(self):
client = Client()
url = reverse("propositions:oui", args=(self.prop.id,))
response = client.get(url)
self.assertRedirects(response, "/login?next={}".format(url))
url = reverse("propositions:non", args=(self.prop.id,))
response = client.get(url)
self.assertRedirects(response, "/login?next={}".format(url))
def test_ernesto_user_can_get(self):
client = Client()
client.force_login(self.user)
client.get(reverse("propositions:oui", args=(self.prop.id,)))
self.prop.refresh_from_db()
self.assertEqual(
list(self.prop.answer_set.values_list("answer", flat=True)), [Answer.YES],
)
client.get(reverse("propositions:non", args=(self.prop.id,)))
self.prop.refresh_from_db()
self.assertEqual(
list(self.prop.answer_set.values_list("answer", flat=True)), [Answer.NO]
)

13
propositions/urls.py Normal file
View file

@ -0,0 +1,13 @@
from django.urls import path
from propositions import views
from propositions.models import Answer
app_name = "propositions"
urlpatterns = [
path("", views.PropositionList.as_view(), name="list"),
path("new", views.PropositionCreate.as_view(), name="create"),
path("<int:id>/oui", views.answer, {"ans": "oui"}, name=Answer.YES),
path("<int:id>/non", views.answer, {"ans": "non"}, name=Answer.NO),
path("<int:pk>/supprimer", views.PropositionDelete.as_view(), name="delete"),
]

8
propositions/utils.py Normal file
View file

@ -0,0 +1,8 @@
import string
import random
def generer(*args):
caracteres = string.ascii_letters + string.digits
aleatoire = [random.choice(caracteres) for _ in range(6)]
return ''.join(aleatoire)

63
propositions/views.py Normal file
View file

@ -0,0 +1,63 @@
from django.shortcuts import redirect, get_object_or_404
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db.models import Count, OuterRef, Q, Subquery
from django.views.generic import CreateView, DeleteView, ListView
from django.http import HttpResponseRedirect
from propositions.models import Answer, Proposition
class PropositionCreate(LoginRequiredMixin, CreateView):
template_name = "propositions/create.html"
success_url = reverse_lazy("propositions:list")
model = Proposition
fields = ["name", "artist", "link"]
def form_valid(self, form):
proposition = form.save(commit=False)
proposition.user = self.request.user
proposition.save()
return HttpResponseRedirect(self.success_url)
class PropositionList(LoginRequiredMixin, ListView):
template_name = "propositions/liste.html"
context_object_name = "propositions"
model = Proposition
def get_queryset(self):
user = self.request.user
user_answers = (
Answer.objects
.filter(proposition=OuterRef("id"), user=user)
.values_list("answer", flat=True)
)
return (
Proposition.objects
.annotate(nb_yes=Count("answer", filter=Q(answer__answer=Answer.YES)))
.annotate(nb_no=Count("answer", filter=Q(answer__answer=Answer.NO)))
.annotate(user_answer=Subquery(user_answers[:1]))
.order_by("-nb_yes", "nb_no", "name")
)
class PropositionDelete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Proposition
template_name = "propositions/delete.html"
success_url = reverse_lazy("propositions:list")
def test_func(self):
proposition = self.get_object()
user = self.request.user
return (proposition.user == user or user.profile.is_chef)
@login_required
def answer(request, id, ans):
proposition = get_object_or_404(Proposition, id=id)
user = request.user
Answer.objects.filter(proposition=proposition, user=user).delete()
Answer.objects.create(proposition=proposition, user=user, answer=ans)
return redirect("propositions:list")

View file

@ -1,4 +0,0 @@
-r requirements.txt
django-debug-toolbar
ipython

View file

@ -1,8 +0,0 @@
Django==2.2.*
# Pour la prod
#psycopg2
gunicorn
django-colorful
Pillow
django-avatar==5.0.*

46
shell.nix Normal file
View file

@ -0,0 +1,46 @@
{
sources ? import ./npins,
pkgs ? import sources.nixpkgs {
overlays = [
(import "${sources.nix-pkgs}/overlay.nix").default
];
},
dev ? true,
}:
pkgs.mkShell {
packages = [
(pkgs.python3.withPackages (
ps:
[
ps.django
ps.django-avatar
ps.django-colorful
ps.gunicorn
ps.pillow
ps.loadcredential
]
++ (
if dev then
[
ps.ipython
ps.django-debug-toolbar
]
else
[ ]
)
))
];
env = {
DJANGO_SETTINGS_MODULE = "Ernestophone.settings";
CREDENTIALS_DIRECTORY = builtins.toString ./.credentials;
ERNESTOPHONE_DEBUG = builtins.toJSON true;
ERNESTOPHONE_STATIC_ROOT = builtins.toString ./.static;
};
shellHook = ''
if [ ! -d .static ]; then
mkdir .static
fi
'';
}

View file

@ -12,7 +12,7 @@
<p>{% trans "Avatar courant: " %}</p> <p>{% trans "Avatar courant: " %}</p>
{% avatar user %} {% avatar user %}
<form enctype="multipart/form-data" method="POST" action="{% url 'avatar_add' %}"> <form enctype="multipart/form-data" method="POST" action="{% url 'avatar:add' %}">
{{ upload_avatar_form.as_p }} {{ upload_avatar_form.as_p }}
<p>{% csrf_token %}<input type="submit" value="{% trans "Ajouter une nouvelle image" %}" /></p> <p>{% csrf_token %}<input type="submit" value="{% trans "Ajouter une nouvelle image" %}" /></p>
</form> </form>

View file

@ -19,7 +19,7 @@
<p>{% trans "Tu n'as pas encore ajouté d'avatar. Le logo Ernestophone sera utilisé par defaut." %}</p> <p>{% trans "Tu n'as pas encore ajouté d'avatar. Le logo Ernestophone sera utilisé par defaut." %}</p>
{% endif %} {% endif %}
<form enctype="multipart/form-data" method="POST" action="{% url 'avatar_add' %}"> <form enctype="multipart/form-data" method="POST" action="{% url 'avatar:add' %}">
{{ upload_avatar_form.as_p }} {{ upload_avatar_form.as_p }}
<p>{% csrf_token %}<input type="submit" value="{% trans "Modifier mon avatar" %}" /></p> <p>{% csrf_token %}<input type="submit" value="{% trans "Modifier mon avatar" %}" /></p>
</form> </form>
@ -27,7 +27,7 @@
<a href="{% url "trombonoscope:change" %}" class="button alt">{% trans "Retour au profil"%}</a> <a href="{% url "trombonoscope:change" %}" class="button alt">{% trans "Retour au profil"%}</a>
{% if avatars %} {% if avatars %}
&nbsp;<a class="button alt" href="{% url 'avatar_delete' %}">{% trans "Supprimer l'avatar "%}</a> &nbsp;<a class="button alt" href="{% url 'avatar:delete' %}">{% trans "Supprimer l'avatar "%}</a>
{% endif %} {% endif %}
</div></div></div></section></div> </div></div></div></section></div>
{% endblock %} {% endblock %}

View file

@ -26,9 +26,9 @@
{% endif %} {% endif %}
<p> <p>
<a class="button alt" href="{% url 'avatar_change' %}">{% trans "Changer l'avatar "%}</a> <a class="button alt" href="{% url 'avatar:change' %}">{% trans "Changer l'avatar "%}</a>
{% if request.user|has_avatar %} {% if request.user|has_avatar %}
&nbsp;<a class="button alt" href="{% url 'avatar_delete' %}">{% trans "Supprimer l'avatar "%}</a> &nbsp;<a class="button alt" href="{% url 'avatar:delete' %}">{% trans "Supprimer l'avatar "%}</a>
{% endif %} {% endif %}
</p> </p>

View file

@ -10,12 +10,12 @@
<div class="6u 12u$(small)"> <div class="6u 12u$(small)">
<h2>{% trans "Suppression de l'avatar" %} :</h2> <h2>{% trans "Suppression de l'avatar" %} :</h2>
{% if not avatars %} {% if not avatars %}
{% url 'avatar_change' as avatar_change_url %} {% url 'avatar:change' as avatar_change_url %}
<p>{% trans "Vous n'avez pas d'avatar à supprimer."%}</p> <p>{% trans "Vous n'avez pas d'avatar à supprimer."%}</p>
<a href="{{ avatar_change_url }}" class="button">{% trans "Télécharger un avatar" %}</a>&nbsp;<a href="{% url "trombonoscope:change" %}" class="button alt">{% trans "Retour au profil"%}</a> <a href="{{ avatar_change_url }}" class="button">{% trans "Télécharger un avatar" %}</a>&nbsp;<a href="{% url "trombonoscope:change" %}" class="button alt">{% trans "Retour au profil"%}</a>
{% else %} {% else %}
<p>{% trans "Séléctione l'avatar pour le supprimer" %}</p> <p>{% trans "Séléctione l'avatar pour le supprimer" %}</p>
<form method="POST" action="{% url 'avatar_delete' %}"> <form method="POST" action="{% url 'avatar:delete' %}">
{% for field in delete_avatar_form %} {% for field in delete_avatar_form %}