feat(ernestoprofil): init user managment system

This commit is contained in:
sinavir 2024-06-23 19:34:46 +02:00
parent 9b1cb51aae
commit 8c2769b0b9
44 changed files with 1461 additions and 22528 deletions

View file

@ -40,9 +40,12 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"sass_processor",
"ernestoprofile",
"bulma",
"shared",
"sass_processor",
"authens",
"solo",
]
MIDDLEWARE = [
@ -103,35 +106,36 @@ DATABASES = {
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = (
[]
if DEBUG
else [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
)
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Authentication backends
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"authens.backends.ENSCASBackend",
"authens.backends.OldCASBackend",
]
LOGIN_URL = reverse_lazy("authens:login")
AUTHENS_USE_OLDCAS = False
LOGIN_REDIRECT_URL = reverse_lazy("home") # TODO change for agenda
LOGOUT_REDIRECT_URL = reverse_lazy("home")
# AUTHENS_USE_OLDCAS = True
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
@ -174,3 +178,7 @@ MESSAGE_TAGS = {
messages.WARNING: "is-warning",
messages.ERROR: "is-danger",
}
# Email
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

View file

@ -22,7 +22,9 @@ from django.views.generic import TemplateView
urlpatterns = [
path("admin/", admin.site.urls),
path("", TemplateView.as_view(template_name="home.html")),
path("authens/", include("authens.urls")),
path("", TemplateView.as_view(template_name="home.html"), name="home"),
path("account/", include("ernestoprofile.urls")),
]
if settings.DEBUG:
urlpatterns += [

3
ernestoprofile/README.md Normal file
View file

@ -0,0 +1,3 @@
# `ernestouser`
Application qui gère les profils utilisateurs et l'authentification

View file

23
ernestoprofile/admin.py Normal file
View file

@ -0,0 +1,23 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from solo.admin import SingletonModelAdmin
from .models import AuthConfiguration, Profile
# Register your models here.
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = [ProfileInline]
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.site.register(AuthConfiguration, SingletonModelAdmin)

6
ernestoprofile/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ErnestoprofileConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "ernestoprofile"

39
ernestoprofile/forms.py Normal file
View file

@ -0,0 +1,39 @@
from django import forms
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from .models import AuthConfiguration, Profile
class ProfileForm(forms.ModelForm):
class Meta: # pyright: ignore
model = Profile
# abstract = True
exclude = ["user"]
class UserForm(UserChangeForm):
class Meta: # pyright: ignore
model = User
fields = ["username", "email", "first_name", "last_name"]
class UserCreateForm(UserCreationForm):
class Meta: # pyright: ignore
model = User
fields = ["username", "email", "first_name", "last_name"]
class BecomeErnestoForm(forms.Form):
password = forms.CharField(
label=_("L'ernesto mot de passe"), widget=forms.PasswordInput
)
def clean_password(self):
data = self.cleaned_data["password"]
if data != AuthConfiguration.get_solo().password:
raise ValidationError(_("Ernesto mot de passe incorrect"))
return data

View file

@ -0,0 +1,84 @@
# Generated by Django 4.2.12 on 2024-06-18 05:51
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="AuthConfiguration",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"password",
models.CharField(
default="dummy",
max_length=255,
verbose_name="Mot de passe pour devenir fanfaron",
),
),
],
options={
"verbose_name": "Paramètres d'authentification",
},
),
migrations.CreateModel(
name="Profile",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"phone",
models.CharField(
blank=True,
help_text="seulement visible par les chef·fe·s",
max_length=20,
verbose_name="Numéro de téléphone",
),
),
(
"polls_pseudo",
models.CharField(
blank=True,
max_length=30,
verbose_name="Pseudo pour les sondages",
),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Ernestoprofil",
"verbose_name_plural": "Ernestoprofils",
},
),
]

View file

@ -0,0 +1,46 @@
# Generated by Django 4.2.12 on 2024-06-22 18:58
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("ernestoprofile", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="profile",
name="phone",
field=models.CharField(
blank=True,
default="",
help_text="seulement visible par les chef·fe·s",
max_length=20,
verbose_name="Numéro de téléphone",
),
),
migrations.AlterField(
model_name="profile",
name="polls_pseudo",
field=models.CharField(
blank=True,
default="",
max_length=30,
verbose_name="Pseudo pour les sondages",
),
),
migrations.AlterField(
model_name="profile",
name="user",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="profile",
to=settings.AUTH_USER_MODEL,
),
),
]

View file

@ -0,0 +1,26 @@
# Generated by Django 4.2.12 on 2024-06-22 19:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"ernestoprofile",
"0002_alter_profile_phone_alter_profile_polls_pseudo_and_more",
),
]
operations = [
migrations.AlterField(
model_name="profile",
name="polls_pseudo",
field=models.CharField(
help_text="Le pseudo qui s'affichera pour tes réponses aux sondages",
max_length=30,
unique=True,
verbose_name="Pseudo pour les sondages",
),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 4.2.12 on 2024-06-23 04:09
from django.db import migrations
def create_groups(apps, schema_editor):
Group = apps.get_model("auth", "Group")
Group.objects.update_or_create(name="Ernestophoniste")
Group.objects.update_or_create(name="Chef")
class Migration(migrations.Migration):
dependencies = [
("ernestoprofile", "0003_alter_profile_polls_pseudo"),
]
operations = [migrations.RunPython(create_groups)]

View file

22
ernestoprofile/mixins.py Normal file
View file

@ -0,0 +1,22 @@
from django.contrib import messages
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponseRedirect
class LocalUserOnlyMixin(AccessMixin):
"""Verify that the current user is authenticated."""
def dispatch(self, request, *args, **kwargs):
if hasattr(request.user, "cas_account"):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class LocalUserOnlySoftMixin(LocalUserOnlyMixin):
redirect_url = None
reject_message = ""
def handle_no_permission(self):
messages.warning(self.request, self.reject_message)
return HttpResponseRedirect(str(self.redirect_url))

59
ernestoprofile/models.py Normal file
View file

@ -0,0 +1,59 @@
# Create your models here.
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext_lazy as _
from solo.models import SingletonModel
class AuthConfiguration(SingletonModel):
password = models.CharField(
max_length=255,
default="dummy",
verbose_name=_("Mot de passe pour devenir fanfaron"),
)
def __str__(self):
return "Auth Configuration"
class Meta: # pyright: ignore
verbose_name = _("Paramètres d'authentification")
class Instrument(models.Model):
name = models.CharField(
_("Nom de l'instrument"),
max_length=50,
blank=False,
null=False,
unique=True,
)
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
phone = models.CharField(
_("Numéro de téléphone"),
max_length=20,
blank=True,
default="",
help_text=_("seulement visible par les chef·fe·s"),
)
polls_pseudo = models.CharField(
_("Pseudo pour les sondages"),
max_length=30,
help_text=_("Le pseudo qui s'affichera pour tes réponses aux sondages"),
unique=True,
blank=False,
null=False,
)
instruments = models.ManyToManyField(Instrument)
class Meta: # pyright: ignore
verbose_name = _("Ernestoprofil")
verbose_name_plural = _("Ernestoprofils")
def __str__(self):
return self.user.username

View file

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<section class="section">
<h1 class="title">{% trans "Choisissez la méthode de connexion" %}</h1>
<div class="buttons">
<a class="button signin-button is-primary is-fullwidth"
href="{% url "authens:login.cas" %}?next={{ next }}">
<span class="icon">
<i class="ti ti-school"></i>
</span>
<span>{% trans "Connexion via clipper" %}</span>
</a>
<a class="button signin-button is-link is-fullwidth"
href="{% url "authens:login.pwd" %}?next={{ next }}">
<span class="icon">
<i class="ti ti-key"></i>
</span>
<span>{% trans "Connexion par mot de passe" %}</span>
</a>
<a class="button signin-button is-secondary is-fullwidth"
href="{% url "authens:login.oldcas" %}?next={{ next }}">
<span class="icon">🦕</span>
<span>{% trans "Connexion dino" %}</span>
</a>
</div>
<div class="mb-6">
<label class="checkbox">
<input type="checkbox" />
{% trans "Se souvenir de mon choix" %}
</label>
</div>
<div class="content notification">
{% url "authens:reset.pwd" as url_reset %}
<p class="has-text-centered">
{% blocktrans %}Si votre fin de scolarité approche, créez un mot de passe pour votre compte pour bénéficier de la connexion dino: <a href="{{ url_reset }}">créer un mot de passe</a>.
<br>
<a href="{{ url_reset }}">Mot de passe oublié</a>{% endblocktrans %}
</p>
</div>
</section>
{% endblock %}

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{# TODO: Add hero #}
<section class="section">
<h1 class="title">{% trans "Connexion en tant que vieilleux" %}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True %}
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span>{% trans "Connection" %}</span>
</button>
</div>
</div>
</form>
</section>
{% endblock %}

View file

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{# TODO: Add hero #}
<section class="section">
<h1 class="title">{% trans "Connexion par mot de passe" %}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True %}
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span>{% trans "Connection" %}</span>
</button>
</div>
</div>
</form>
</section>
{% endblock %}

View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<section class="section">
<h1 class="title">{% trans "Réinitialisation du mot de passe" %}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True %}
<div class="field is-grouped">
<div class="control">
<button class="button is-fullwidth is-outlined is-primary is-light"
type="submit">
<span>{% trans "Envoyer un mail" %}</span>
</button>
</div>
<div class="control">
<a class="button is-primary"
href="{% url 'authens:login.pwd' %}?next={{ next }}">
<span>{% trans "Retour" %}</span>
</a>
</div>
</div>
</form>
</section>
{% endblock %}

View file

@ -0,0 +1,26 @@
{% extends "authens/base.html" %}
{% load i18n %}
{% block content %}
<section class="section">
<h1 class="title">{% trans "Réinitialisation du mot de passe" %}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True %}
<div class="field is-grouped is-centered">
<div class="control is-expanded">
<button class="button is-fullwidth is-outlined is-primary is-light"
type="submit">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{% trans "Enregistrer" %}</span>
</button>
</div>
</div>
</form>
</section>
{% endblock %}

View file

@ -0,0 +1,15 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}Cher·re Ernesto,
Quelqu'un (probablement vous) a demandé la réinitialisation du mot de passe associé à cette addresse sur l'ernestosite.{% endblocktrans %}
{% blocktrans %}S'il s'agit bien de vous, vous pouvez vous rendre à l'adresse suivante pour en choisir un nouveau : {% endblocktrans %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url "authens:reset.pwd.confirm" uidb64=uid token=token %}
{% endblock %}
{% blocktrans with username=user.base_username %}Pour information, votre nom d'ernesto est le suivant : {{ username }}{% endblocktrans %}
{% block signature %}{% blocktrans %}L'Ernestosite team{% endblocktrans %}{% endblock %}
{% endautoescape %}

View file

@ -0,0 +1,26 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{# TODO: Add hero #}
<section class="section">
<h1 class="title">{% trans "Créer un compte" %}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True form=user_form %}
{% include "bulma/form.html" with errors=True form=profile_form %}
{% include "bulma/form.html" with errors=True form=ernesto_form %}
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span>{% trans "Enregister" %}</span>
</button>
</div>
</div>
</form>
</section>
{% endblock %}

View file

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{# TODO: Add hero #}
<section class="section">
<div class="level">
<div class="level-left">
<h1 class="title level-item">{% trans "Modifier mon ernesto-profil" %}</h1>
</div>
<div class="level-right">
<a href="{% url "ernestoprofil:password-change" %}"
class="button is-primary is-outlined">{% trans "Modifier mon mot de passe" %}</a>
</div>
</div>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True form=user_form %}
{% include "bulma/form.html" with errors=True form=profile_form %}
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span>{% trans "Enregister" %}</span>
</button>
</div>
</div>
</form>
</section>
{% endblock %}

1
ernestoprofile/tests.py Normal file
View file

@ -0,0 +1 @@
# Create your tests here.

12
ernestoprofile/urls.py Normal file
View file

@ -0,0 +1,12 @@
from django.urls import path
from .views import BecomeErnesto, CreateUser, EditUser, LogoutView, PasswordChange
app_name = "ernestoprofil"
urlpatterns = [
path("edit-user", EditUser.as_view(), name="edit-user"),
path("create-user", CreateUser.as_view(), name="create-user"),
path("password-change", PasswordChange.as_view(), name="password-change"),
path("logout", LogoutView.as_view(), name="logout"),
path("become-ernesto", BecomeErnesto.as_view(), name="become-ernesto"),
]

176
ernestoprofile/views.py Normal file
View file

@ -0,0 +1,176 @@
from authens import views as authens_views
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import Group
from django.contrib.auth.views import PasswordChangeView
from django.core.exceptions import ImproperlyConfigured
from django.forms import ModelForm
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView, TemplateView
from shared.mixins import LocalUserOnlySoftMixin
from .forms import BecomeErnestoForm, ProfileForm, UserCreateForm, UserForm
from .models import Profile
# Create your views here.
class LogoutView(authens_views.LogoutView):
def get_success_url(self):
messages.success(self.request, _("Déconnecté avec succès"))
return super().get_success_url()
class PasswordChange(LocalUserOnlySoftMixin, PasswordChangeView):
redirect_url = reverse_lazy("ernestoprofil:edit-user")
reject_message = _(
"Vous ne pouvez pas changer votre mote de pass avec un compte CAS"
)
extra_context = {
"title": _("Changer mon mot de passe"),
"submit": _("Enregistrer"),
}
template_name = "simple_form.html"
success_url = reverse_lazy("ernestoprofil:edit-user")
class BecomeErnesto(LoginRequiredMixin, FormView):
template_name = "simple_form.html"
form_class = BecomeErnestoForm
success_url = reverse_lazy("home")
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx.update(
{
"title": _("Devenir fanfaron"),
"submit": _("Pouêt 🎶"),
}
)
return ctx
def form_valid(self, form):
group, created = Group.objects.get_or_create(name="Ernestophoniste")
group.user_set.add(self.request.user)
messages.success(
self.request, _("Bienvenue à la fanfare jeune ernestophoniste! 🎺")
)
return super().form_valid(form)
class BaseEditUser(TemplateView):
def get_instance(self, fname):
raise ImproperlyConfigured("")
def get_context_data(self, **kwargs):
inst_forms = self.get_forms()
for f in self.forms:
if f not in kwargs:
kwargs[f] = inst_forms[f]
return super().get_context_data(**kwargs)
def get_success_url(self):
return str(self.success_url)
def get_forms(self):
kwargs = {}
if self.request.method in ("POST", "PUT"):
kwargs.update(
{
"data": self.request.POST,
"files": self.request.FILES,
}
)
inst_forms = {}
for f, form_class in self.forms.items():
if issubclass(form_class, ModelForm):
inst_forms[f] = form_class(
prefix=f,
instance=self.get_instance(f),
**kwargs,
)
else:
inst_forms[f] = form_class(prefix=f, **kwargs)
return inst_forms
def valid(self, forms):
pass
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
forms = self.get_forms()
valid = True
for fname, form in forms.items():
valid &= form.is_valid()
if valid:
user = forms["user_form"].save()
forms["profile_form"].instance.user = user
print(forms["profile_form"].instance.polls_pseudo)
for fname, form in forms.items():
if isinstance(form, ModelForm):
form.save()
self.valid(forms)
return HttpResponseRedirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data(**forms))
class EditUser(LoginRequiredMixin, BaseEditUser):
template_name = "ernestoprofile/edit-user.html"
forms = {
"user_form": UserForm,
"profile_form": ProfileForm,
}
success_url = reverse_lazy("ernestoprofil:edit-user")
def get_instance(self, fname):
match fname:
case "user_form":
return self.request.user
case "profile_form":
if not hasattr(self.request.user, "profile"):
Profile.objects.create(
user=self.request.user, polls_pseudo=self.request.user.username
)
return self.request.user.profile
def get_success_url(self):
messages.success(self.request, _("Profil mis à jour avec succès"))
return super().get_success_url()
class CreateUser(BaseEditUser):
template_name = "ernestoprofile/create-user.html"
forms = {
"user_form": UserCreateForm,
"profile_form": ProfileForm,
"ernesto_form": BecomeErnestoForm,
}
success_url = reverse_lazy("home")
def get_instance(self, fname):
return None
def valid(self, forms):
user = forms["user_form"].instance
group, created = Group.objects.get_or_create(name="Ernestophoniste")
group.user_set.add(user)
def get_success_url(self):
messages.success(
self.request,
_(
"Bienvenue chèr.re fanfaron.ne! Tu peux maintenant te connecter pour découvrir ce merveilleux site."
),
) # TODO middot
return super().get_success_url()

View file

@ -0,0 +1,15 @@
diff --git a/authens/views.py b/authens/views.py
index 0478861..b1c93e9 100644
--- a/authens/views.py
+++ b/authens/views.py
@@ -138,8 +138,8 @@ class LogoutView(auth_views.LogoutView):
else:
self.cas_connected = False
- def get_next_page(self):
- next_page = super().get_next_page()
+ def get_success_url(self):
+ next_page = super().get_success_url()
if self.cas_connected:
cas_client = get_cas_client(self.request)

View file

@ -8,6 +8,7 @@ buildPythonPackage rec {
pname = "authens";
version = "v0.1b5";
doCheck = false;
patches = [ ./01-get-success_url.patch ];
src = builtins.fetchGit {
url = "https://git.eleves.ens.fr/klub-dev-ens/authens.git";
#rev = "master";

View file

@ -1,7 +1,7 @@
{
lib,
buildPythonPackage,
fetchgit,
#fetchgit,
setuptools,
setuptools-scm,
wheel,
@ -13,11 +13,12 @@ buildPythonPackage rec {
version = "0.1.4";
pyproject = true;
src = fetchgit {
url = "https://git.hubrecht.ovh/hubrecht/django-bulma-forms";
rev = "v${version}";
hash = "sha256-4KTMXx3YxDxB4/YH14pJnNYtpOGXeDmD+gcbrUHwD/w=";
};
src = ../../../django-bulma-forms;
#fetchgit {
# url = "https://git.hubrecht.ovh/hubrecht/django-bulma-forms";
# rev = "v${version}";
# hash = "sha256-4KTMXx3YxDxB4/YH14pJnNYtpOGXeDmD+gcbrUHwD/w=";
#};
nativeBuildInputs = [
setuptools

View file

@ -0,0 +1,38 @@
{
lib,
buildPythonPackage,
fetchFromGitHub,
setuptools,
wheel,
django,
}:
buildPythonPackage rec {
pname = "django-solo";
version = "2.2.0";
pyproject = true;
src = fetchFromGitHub {
owner = "lazybird";
repo = "django-solo";
rev = version;
hash = "sha256-utLwELFk5/oiYnfJCnnHQPlVTADSjt9cbMOOUKMtnfM=";
};
nativeBuildInputs = [
setuptools
wheel
];
dependencies = [ django ];
pythonImportsCheck = [ "solo" ];
meta = with lib; {
description = "Helps working with singletons - things like global settings that you want to edit from the admin site";
homepage = "https://github.com/lazybird/django-solo";
changelog = "https://github.com/lazybird/django-solo/blob/${src.rev}/CHANGES";
license = licenses.cc-by-30;
maintainers = with maintainers; [ ];
};
}

View file

@ -9,7 +9,7 @@ let
hooks = {
# JS hooks
eslint.enable = true;
# eslint.enable = true;
# Python hooks
ruff.enable = true;
@ -36,6 +36,7 @@ let
authens = self.callPackage ./authens { };
pythoncas = self.callPackage ./python-cas { };
django-browser-reload = self.callPackage ./django-browser-reload { };
django-solo = self.callPackage ./django-solo { };
django-bulma-forms = self.callPackage ./django-bulma-forms { };
django-sass-processor = self.callPackage ./django-sass-processor { };
django-sass-processor-dart-sass = self.callPackage ./django-sass-processor-dart-sass { };
@ -51,6 +52,7 @@ pkgs.mkShell {
ps.django
ps.gunicorn
ps.authens
ps.django-solo
ps.django-browser-reload
ps.django-bulma-forms
ps.django-sass-processor

30
pyproject.toml Normal file
View file

@ -0,0 +1,30 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "ernesto"
version = "1.0.0"
authors = [{name = "Maurice Debray", email = "sinavir@sinavir.fr"}]
description = ""
license = {file = "LICENSE"}
readme = "README.md"
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
"Programming Language :: Python :: 3",
]
[tool.djlint]
blank_line_after_tag = "load,extends"
format_js = true
indent = 2
max_blank_lines = 1
profile = "django"
[tool.djlint.js]
indent_size = 4
[tool.isort]
profile = "black"

22
shared/mixins.py Normal file
View file

@ -0,0 +1,22 @@
from django.contrib import messages
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponseRedirect
class LocalUserOnlyMixin(AccessMixin):
"""Verify that the current user is authenticated."""
def dispatch(self, request, *args, **kwargs):
if hasattr(request.user, "cas_account"):
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class LocalUserOnlySoftMixin(LocalUserOnlyMixin):
redirect_url = None
reject_message = ""
def handle_no_permission(self):
messages.warning(self.request, self.reject_message)
return HttpResponseRedirect(str(self.redirect_url))

1
shared/static/bulma/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.css

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,10 @@
/*! bulma.io v1.0.0 | MIT License | github.com/jgthms/bulma */
@use "sass" with (
$navbar-item-img-max-height: 2rem,
$family-sans-serif: '"Signika", sans-serif',
$primary: #ff4500,
$link: #730000
$link: #730000,
);
body {
@ -20,4 +21,8 @@ body {
justify-content: space-between;
}
:root {
scroll-behavior: smooth;
}
@import url("https://fonts.googleapis.com/css?family=Signika:400,700");

View file

@ -0,0 +1,379 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="217.9005"
height="61.852177"
viewBox="0 0 217.9005 61.85218"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
sodipodi:docname="ernesto-white-long-bis.svg"
inkscape:export-filename="ernesto_filled-64x64.png"
inkscape:export-xdpi="192"
inkscape:export-ydpi="192"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="2.1575361"
inkscape:cx="59.558679"
inkscape:cy="33.3714"
inkscape:window-width="1916"
inkscape:window-height="1055"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="g4"
showgrid="false" />
<defs
id="defs1">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath3">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-379.20901,-637.53812)"
id="path3" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-383.65581,-637.53812)"
id="path5" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-371.99561,-633.14062)"
id="path7" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath61">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-231.36961,-599.54491)"
id="path61" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath63">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-253.05321,-706.59232)"
id="path63" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath65">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-305.49411,-694.92041)"
id="path65" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath67">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-321.33011,-710.64162)"
id="path67" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath69">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-339.27001,-690.53912)"
id="path69" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath71">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-258.63231,-619.17772)"
id="path71" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath73">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-318.36281,-612.33982)"
id="path73" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath75">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-232.2529,-680.92871)"
id="path75" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath77">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-337.67291,-618.84521)"
id="path77" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath79">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-223.5034,-666.56542)"
id="path79" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath81">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-286.82281,-678.06791)"
id="path81" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath83">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-256.417,-629.66261)"
id="path83" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath85">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-179.78031,-703.79152)"
id="path85" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath9">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-659.84861,-589.57762)"
id="path9" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath11">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-686.02931,-730.88182)"
id="path11" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath13">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-751.05759,-695.40532)"
id="path13" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath15">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-767.67872,-718.45802)"
id="path15" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath17">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-801.15632,-706.96632)"
id="path17" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath19">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-699.35162,-614.75981)"
id="path19" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath21">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-779.83014,-605.59672)"
id="path21" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath23">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-658.15042,-696.48682)"
id="path23" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath25">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-804.58112,-617.13872)"
id="path25" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath27">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-646.06642,-677.34962)"
id="path27" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath29">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-731.28712,-692.65382)"
id="path29" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath31">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-695.64941,-630.91601)"
id="path31" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath33">
<path
d="M 0,841.89 H 1190.551 V 0 H 0 Z"
transform="translate(-586.13182,-724.30372)"
id="path33" />
</clipPath>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(55.737102,10.584888)">
<g
id="g2"
transform="matrix(0.33637415,0,0,0.33637531,-22.728233,-7.6664558)">
<g
id="g4"
transform="matrix(1,0.08748836,0,1,0,-13.741963)">
<g
id="g3"
transform="matrix(4.2285533,0.709314,0,3.9031162,-138.7291,-356.0536)">
<path
id="path8"
d="M 0,0 C -2.154,-1.758 -4.411757,-3.6103174 -4.654757,-6.6833174 -5.002757,-11.069317 -5.225,-26.661 -5.278,-28.301 c -0.156,-4.818 -0.91,-11.121 3.449,-11.455 2.497,-0.191 4.896,1.68 5.999,3.152 3.517,4.702 6.271,10.846 6.271,21.331 0,7.837 0,16.945 -7.097,16.945 C 2.318,1.672 0.683,0.556 0,0 m -8.541,14.641 c 1.224,0.128 2.603,-0.596 2.949,-1.627 C -5.16,11.74 -5.24,8.007 -5.388,6.608 -5.474,5.812 -5.386757,5.5585033 -5.366757,4.6026826 -4.911757,4.1026826 -1.422,9.842 7.684,9.842 c 1.785,0 3.602,-0.274 5.331,-1.097 5.65,-2.69 9.289,-9.932 9.289,-23.489 0,-13.451 -7.167,-23.544 -12.711,-27.113 -2.764,-1.781 -6.413,-2.786 -10.506,-2.982 -1.047,-0.052 -3.263,0.838 -4.068,-0.306 -0.808,-1.146 -0.752,-6.098 -0.784,-7.198 -0.636,-23.09 -2.436,-32.515 -8.061,-35.303 -1.462,-0.725 -3.279,-1.094 -3.462,-0.125 -0.119,0.627 0.223,1.005 1.326,2.159 1.936,2.025 1.936,4.089 2.237,8.845 0.394,6.211 -2.1,57.998 -2.745,66.803 -0.089,1.218 0.078,8.212 -1.932,8.337 -0.624,0.039 -2.932,-1.679 -4.981,-3.796 -0.623,-0.647 -2.892,-3.249 -3.318,-1.13 -0.591,2.961 9.664,17.473 15.822,20.483 0.737,0.36 1.523,0.626 2.338,0.711"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.35277776,0,0,-0.35277776,109.0012,80.514293)"
clip-path="url(#clipPath9)"
sodipodi:nodetypes="cccccsscccccscscccccccccccccc" />
<path
id="path18"
d="m -2.083847,-15.3782 c 1.09699997,-0.567 1.23199997,-2.768 1.35599997,-3.724 C -0.14484703,-23.5652 0.89,-40.161 0.719,-42.935 c -0.028,-0.459 -0.136,-2.855 0.426,-3.02 0.587,-0.174 1.477,1.438 2.047,2.239 1.744,2.46 4.534,6.067 8.753,6.367 5.169,0.367 6.771,-7.318 6.807,-12.595 0.127,-19.937 0.136,-25.568 0.732,-28.546 0.152,-0.75 0.616,-2.093 1.552,-2.246 2.217,-0.362 2.281,3.391 4.202,3.673 3.139,0.461 0.769,-4.914 0.063,-6.256 -1.578,-2.994 -3.399,-4.997 -5.822,-6.055 -2.622,-1.146 -6.428,-0.619 -7.95,1.036 -1.894,2.06 -2.807,6.581 -3.115,10.447 -0.715,8.994 0.133,17.165 -0.523,25.596 -0.127,1.624 -0.476,3.634 -1.259,3.827 -0.709,0.174 -1.691,-0.993 -2.232,-1.65 -3.386,-4.118 -4.613,-7.971 -4.553,-14.743 0.062,-7.55 0.086,-16.816 -0.479,-21.698 -0.266,-2.283 -0.892,-5.289 -2.92,-6.983 -1.235,-1.033 -3.257,-1.667 -3.637,-0.41 -0.252,0.826 1.022,1.665 1.258,3.462 0.314,2.402 0.364,5.293 0.202,8.039 -0.705,11.868 -3.4622748,53.263246 -3.7332748,55.978246 -0.115,1.164 -0.16,3.353 -1.0850002,3.338 -1.649,-0.027 -3.358,-2.846 -4.208,-2.611 -0.163,0.046 -1.549,1.423 -1.561,2.004 -0.017,0.789 6.531428,6.539554 11.652428,8.267554 0.854,0.288 1.842,0.477 2.58,0.096"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.35277776,0,0,-0.35277776,120.10394,58.023656)"
clip-path="url(#clipPath19)"
sodipodi:nodetypes="ccccccccccccccccccccccccccccc" />
<path
id="path20"
d="m 0,0 c -1.285,0.73 -2.063,-2.027 -3.419,-1.702 -1.206,0.289 -2.184,2.311 -2.261,5.547 -0.077,3.24 1.002,25.38 0.822,28.189 -0.314,4.956 -1.144,10.432 -5.613,9.541 -4.154,-0.829 -6.578,-4.075 -8.277,-7.174 -0.257,-0.464 -1.883,-4.205 -2.394,-4.161 -0.712,0.062 -0.831,1.552 -1.108,3.031 -0.293,1.569 -1.155,6.294 -3.83,6.813 C -35.842,41.98 -39.721803,15.721866 -40.709803,13.085866 -42.393803,8.5938655 -46.956,6.115 -50.109,2.892 c -1.552,-1.584 -4.103,-3.635 -6.301,-4.028 -2.929,-0.525 -6.1,0.741 -7.078,3.184 -2.903,7.235 5.263,11.141 10.071,7.326 2.052,-1.625 2.358556,-4.0246458 3.473556,-3.9396458 0.340399,0.025502 10.394963,7.4891328 7.819686,11.8289718 C -43.640979,20.124256 -45.396,22.592 -48.159,24.016 c -6.18,3.193 -14.641,1.58 -19.561,-3.837 -5.03,-5.541 -7.551,-19.725 3.871,-25.452 3.686,-1.851 8.626,-0.864 11.659,1.023 0.892,0.553 1.33,1.275 1.738,0.941 0.301,-0.246 0.144,-1.6 -0.045,-2.344 -1.936,-7.652 -5.925,-9.79 -9.519,-9.518 -5.771,0.435 -7.216,7.097 -7.772,8.415 -0.340893,0.2877357 -1.088666,0.153623 -2.434814,-0.097743 -1.352263,-0.2476709 -1.865247,-0.3457225 -1.897186,-1.233257 -0.184,-7.493 5.815,-17.814 16.77,-17.506 13.169,0.373 17.482977,19.0828386 16.152328,31.3184574 0.33679,2.5295247 2.181725,6.6436386 2.388869,8.1514086 0.925,3.881 1.274803,11.474134 3.059803,11.212134 1.188,-0.173 1.31,-2.994 1.436,-4.613 0.384,-4.847 1.755,-22.495 1.871,-24.551 0.411,-7.312 0.44,-13.968 -1.758,-19.643 -0.331,-0.855 -1.203,-2.766 -0.967,-3.181 0.656,-1.153 3.729,0.847 4.442,1.426 4.519,3.685 6.726,11.854 6.786,18.841 0.035,4.374 -0.394,14.162 0.303,22.633 0.35,4.24 2.105,9.62 4.342,12.434 0.401,0.503 0.754,1.131 1.602,0.927 0.402,-0.097 0.36,-1.291 0.376,-1.735 0.073,-2.347 -0.575,-23.962 -0.149,-31.126 0.089,-1.466 0.136,-2.773 0.562,-4.029 1.419,-4.188 6.357,-3.863 9.604,-1.672 1.88,1.268 4.968,4.757 5.574,6.775 C 0.748,-0.854 0.477,-0.271 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.35277776,0,0,-0.35277776,153.29405,73.277736)"
clip-path="url(#clipPath21)"
sodipodi:nodetypes="csscccccccccccccccccccccccccccccccccccccccccc" />
<path
id="path24"
d="m 0,0 c -1.097,0 -1.167,-0.584 -1.46,-1.951 -0.86,-4.029 -4.619,-9.789 -8.922,-9.647 -10.727,0.353 -10.364,26.953 -6.067,27.149 2.329,0.106 3.28,-4.098 3.124,-6.549 C -13.589,4.799 -16.91,0.3 -19.467,-1.146 l 1.011,-3.545 c 6.56,3.72 11.768,9.317 13.001,18.265 1.058,7.697 -2.329,13.892 -7.486,13.626 -11.015,-0.562 -15.465,-23.756 -14.121,-34.773 1.058,-8.686 6.676,-15.554 12.499,-15.781 7.202,-0.284 16.049,8.336 15.991,20.124 C 1.422,-1.986 1.058,0 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.35277776,0,0,-0.35277776,162.2917,71.32908)"
clip-path="url(#clipPath25)" />
</g>
<g
id="g1"
transform="matrix(3.5025645,0,0,3.5025645,-342.30908,-88.983443)">
<path
id="path10"
d="m 0,0 c 3.925,0.872 3.57,-3.354 3.57,-6.841 0,-1.635 -0.207,-5.524 0.96,-3.111 1.514,3.13 4.049,7.805 7.407,9.203 6.321,2.633 9.019,-2.697 9.073,-8.939 0.029,-3.784 -0.636,-8.132 -1.171,-11.964 -0.337,-2.416 -0.401,-4.539 -0.789,-7.06 -0.39,-2.543 -0.893,-6.621 -0.789,-9.198 0.029,-0.763 0.431,-2.613 1.143,-2.695 0.896,-0.104 1.727,1.684 2.888,1.357 0.999,-0.282 0.783,-2.523 0.481,-3.744 -0.783,-3.177 -3.162,-6.581 -6.137,-8.024 -4.126,-1.999 -7.421,0.581 -7.95,4.513 -0.364,2.725 -0.071,5.917 0,8.753 0.151,6.14 0.842,12.296 0.96,18.572 0.039,2.128 0.098,5.378 -0.106,6.611 -0.612,3.661 -2.412,0.272 -3.544,-1.536 -4.536,-7.246 -2.774,-23.704 -3.454,-31.568 -0.42,-4.839 -1.053,-9.615 -2.946,-13.167 -0.944,-1.77 -2.545,-4.254 -4.526,-5.506 -0.534,-0.337 -1.418,-0.718 -1.808,-0.492 -1.052,0.61 -0.192,1.993 0.065,2.955 2.105,7.91 1.599,18.081 0.57,26.9 -1.376,11.784 -2.337,17.567 -3.372,17.654 -1.808,0.151 -2.284,-2.109 -2.704,-3.18 -0.366,-0.942 -0.407,-2.674 -1.063,-3.308 -1.129,-1.085 -2.838,-0.647 -3,0.98 -0.171,1.74 0.633,4.583 1.244,6.063 0.834,2.015 1.738,4.759 3.328,7.071 C -8.798,-5.532 -5.843,-1.299 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.43319446,0,0,-0.43319446,123.83195,24.587673)"
clip-path="url(#clipPath11)" />
<path
id="path12"
d="m 0,0 c 0.021,-3.813 -1.282,-6.979 -4.593,-8.786 -0.916,-0.501 -3.96,-0.944 -4.139,0.475 -0.086,0.655 1.069,1.335 1.241,1.905 0.883,2.926 -1.549,7.341 -3.113,8.546 -4.113,3.172 -3.717,-4.172 -3.2,-6.491 1.936,-8.697 9.983,-13.42 12.696,-21.315 1.146,-3.334 1.383,-8.728 -0.733,-12.606 -2.438,-4.469 -7.202,-8.301 -12.93,-7.417 -3.315,0.511 -5.635,2.602 -7.291,5.136 -0.764,1.169 -1.772,3.593 -2.068,5.15 -0.608,3.218 -1.008,6.621 -0.017,10.545 0.283,1.121 1.385,2.693 2.712,1.326 0.908,-0.935 0.848,-2.884 1.176,-4.025 1.076,-3.76 3.535,-7.992 9.109,-8.007 2.027,-0.004 4.504,1.12 5.148,4.731 0.562,3.152 -1.105,5.334 -4.749,8.528 -4.803,4.208 -11.579,9.774 -12.634,17.418 -1.15,8.33 4.58,13.286 10.352,14.367 C -5.408,10.909 -0.029,5.173 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.48633305,0,0,-0.48633305,154.66817,37.004843)"
clip-path="url(#clipPath13)" />
<path
id="path14"
d="M 0,0 C 1.936,0.131 4.997,4.963 7.001,3.663 9.303,2.163 6.288,-1.032 5.124,-1.933 3.685,-3.047 -0.328,-5.33 -0.701,-5.708 c -0.609,-0.615 -0.712,-3.539 -1.023,-5.105 -0.372,-1.899 -0.573,-4.061 -0.709,-5.302 -0.458,-4.194 -0.78,-7.523 -1.111,-10.7 -0.36,-3.428 -0.812,-6.933 -1.022,-10.292 -0.485,-7.701 -0.314,-13.466 1.379,-13.498 0.84,-0.016 1.52,1.099 1.797,1.866 0.553,1.541 1.96,2.617 2.279,-0.049 0.328,-2.763 -0.39,-5.375 -2.831,-7.046 -2.696,-1.849 -7.025,-1.678 -9.129,1.021 -1.23,1.578 -2.075,4.362 -2.328,6.551 -1.363,11.791 -0.057,25.255 0.076,37.936 -1.259,-0.094 -4.793,-1.402 -4.92,0.381 -0.112,1.524 4.288,1.549 4.926,2.533 0.148,0.224 0.422,6.621 0.396,7.67 -0.083,3.513 -1.731,3.905 -4.282,0.629 -0.807,-1.036 -1.809,-3.11 -2.784,-3.208 -2.092,-0.207 -0.695,4.252 -0.405,5.078 1.838,5.275 6.552,10.067 11.756,12.636 C -6.438,16.479 1.657,19.04 1.858,14.274 1.965,11.734 0.188,2.172 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.48633305,0,0,-0.48633305,162.75156,30.886171)"
clip-path="url(#clipPath15)" />
<path
id="path16"
d="m 0,0 c 0.907,-3.057 -0.429,-4.106 -2.049,-5.872 -1.587,-1.732 -4.09,-3.366 -5.928,-4.575 -0.961,-0.633 -3.336,-2.313 -4.199,-1.741 -0.65,0.429 -0.603,1.89 -0.727,2.701 -1.454,9.661 -10.61,7.909 -12.147,3.303 -1.308,-3.934 0.305,-6.556 1.992,-7.888 5.598,-4.419 14.422,0.621 18.391,3.492 3.644,2.635 6.342,5.997 8.751,9.881 0.833,1.345 1.584,2.633 2.443,4.312 0.906,1.764 3.1501254,5.648724 4.642451,5.1105435 C 12.827451,8.3985435 11.156,3.583 10.477,1.804 9.202,-1.547 6.173,-5.297 3.874,-8.024 2.99,-9.068 2.216,-9.394 1.894,-10.168 c -0.798,-1.931 -1.09,-5.486 -1.666,-7.587 -1.584,-5.79 -5.43,-11.105 -9.688,-14.504 -3.842,-3.064 -11.771,-4.529 -17.126,-1.44 -3.853,2.223 -5.813,6.126 -7.185,10.803 -0.369,1.249 -1.096,4.241 -0.177,4.7 1.088,0.547 2.252,-1.482 3.63,-1.987 1.483,-0.547 2.345,-0.027 3.035,-1.001 0.236,-0.331 0.351,-0.916 0.472,-1.365 0.447,-1.637 2.131,-6.983 6.233,-7.195 3.094,-0.158 5.91,3.3 7.027,9.265 0.104,0.547 0.311,1.073 0.086,1.277 -0.316,0.282 -3.111,-0.816 -7.335,-0.433 -7.746,0.708 -13.293,6.581 -10.344,15.206 1.374,4.018 4.3,7.195 7.703,9.446 C -12.759,12.087 -2.258,7.568 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.48633305,0,0,-0.48633305,179.03284,40.929077)"
clip-path="url(#clipPath17)"
sodipodi:nodetypes="ccccccccccccccccccccccccccccc" />
<path
id="path22"
d="m 0,0 c 2.31,5.927 3.634,11.8 9.092,15.11 2.179,1.32 4.935,0.453 6.112,-5.339 0.686,-3.37 0.275,-8.924 -0.405,-11.782 -0.751,-3.165 -3.127,-8.504 -6.195,-9.697 -3.211,-1.247 -6.137,1.67 -4.778,4.874 0.228,0.542 1.112,1.807 1.545,2.163 1.033,0.853 3.296,1.327 3.287,2.814 C 8.649,-0.148 6.37,4.984 5.566,5 4.601,5.02 3.389,0.958 3.124,-0.031 2.127,-3.764 1.606,-8.298 1.521,-12.046 c -0.041,-1.781 -0.003,-3.696 0.112,-4.847 0.423,-4.227 0.884,-9.241 0.4,-13.801 -0.406,-3.833 -2.638,-8.263 -5.002,-9.983 -0.675,-0.49 -2.287,-1.346 -2.774,-0.641 -0.63,0.91 0.671,2.12 0.832,2.647 0.386,1.257 0.141,4.237 0.032,5.703 -0.353,4.775 -3.088,19.774 -3.838,22.974 -0.318,1.357 -1.08,4.493 -1.529,4.674 -1.383,0.56 -2.959,-2.576 -5.046,-7.089 -0.49,-1.064 -1.161,-2.907 -2.111,-3.182 -0.841,-0.243 -1.617,0.522 -1.285,2.004 0.148,0.651 0.38,1.209 0.63,2.091 1.605,5.649 6.118,13.458 10.881,16.508 0.947,0.606 2.344,1.164 3.34,1.478 C 0.446,7.837 -0.542,3.335 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.43319446,0,0,-0.43319446,111.75496,39.487396)"
clip-path="url(#clipPath23)" />
<path
id="path26"
d="m 0,0 c -1.37,-0.063 -1.423,-0.796 -1.707,-2.523 -0.839,-5.085 -5.201,-12.506 -10.585,-12.581 -13.434,-0.188 -14.537,33.084 -9.18,33.579 2.906,0.269 4.342,-4.931 4.291,-8.003 -0.086,-5.269 -3.971,-11.088 -7.084,-13.046 l 1.475,-4.371 c 7.979,5.034 14.162,12.337 15.176,23.592 0.875,9.683 -3.727,17.228 -10.155,16.596 -13.733,-1.352 -17.938,-30.601 -15.613,-44.294 1.834,-10.794 9.26,-19.05 16.549,-18.994 9.02,0.068 19.571,11.362 18.809,26.091 C 1.896,-2.399 1.324,0.062 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.43319446,0,0,-0.43319446,106.52025,47.777527)"
clip-path="url(#clipPath27)" />
<path
id="path28"
d="m 0,0 c -0.095,0.739 -0.497,2.052 -1.649,1.556 -0.505,-0.217 -0.976,-1.398 -1.28,-2.101 -0.71,-1.645 -2.326,-5.998 -6.2,-6.18 -3.109,-0.146 -6.517,2.627 -7.43,9.194 -1.471,10.57 1.017,18.32 4.945,18.505 2.961,0.139 3.468,-5.242 2.348,-9.432 -0.865,-3.236 -4.027,-9.097 -7.834,-9.593 0,0 -5.886,-5.648 0.934,-3.348 6.821,2.297 12.342,10.015 14.053,16.059 1.865,6.583 -0.406,15.166 -7.583,14.098 -1.8,-0.268 -3.588,-1.426 -4.962,-2.778 -5.888,-5.797 -10.353,-17.034 -8.952,-28.311 0.907,-7.287 4.855,-15.163 12.788,-14.686 6.661,0.402 9.805,9.514 10.577,13.543 C -0.03,-2.339 0.106,-0.842 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
transform="matrix(0.43319446,0,0,-0.43319446,143.43738,41.147831)"
clip-path="url(#clipPath29)" />
<path
id="path30"
d="m -8.7450691,0.39360726 c -1.245,-3.24299946 -6.5109999,-6.58699956 -6.9369999,-1.69599946 -0.198,2.28299946 3.104,6.984999 4.957,7.281999 2.0879999,0.337 3.2739999,-2.1989995 1.9799999,-5.58599954 M 1.754931,35.431907 c -0.713,0 -0.98999997,-0.774 -2.41799997,-2.621 -5.93900013,-7.685 -24.08500003,-20.733 -43.02900003,-23.0360002 -10.725,-1.305 -29.499,4.1817002 -46.279,-16.997299 -1.981,-2.4980001 -6.485812,-4.1358138 -9.711812,-5.1498138 -8.022999,-2.521 -8.204869,-2.555763 -14.225119,-2.206094 0.879,9.4250005 6.639,13.2043 9.023,30.1983 6.23,44.389 -3.518,65.259 -20.458,74.016 -3.267,1.689 -7.525,0.328 -10.192,-3.499 -4.341,-6.229 -15.647,-29.199 -14.462,-32.325 0.846,-2.239 2.822,-1.116 3.788,1.38 1.384,3.576 2.537,6.739 4.986,8.493 5.785,4.141 26.651,-3.384 24.812,-41.597 -1.806,-37.519 -14.669,-28.9442335 -34.264,-55.747234 -0.736,-1.008 -2.709,-3.678 -2.566,-5.321 0.09,-1.017 1.894,-2.054 2.926,-1.855 1.50524,0.295166 20.77475,14.471564 27.739,11.214701 3.003,-3.295 5.813,-8.161 7.137,-13.427 0.469,-1.872 1.357,-3.666 3.571,-2.263 0.738,0.469 0.923,2.082 0.873,3.684 -0.74438,20.931327 11.454466,22.107387 27.586223,23.165092 13.212,0.288 36.288708,-9.832952 57.855708,-0.08695 21.1369999,9.5529998 33.454,23.5632988 30.177,46.726299 -0.224,1.583 -1.06,7.253 -2.869,7.254"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.35277776,0.03086395,0,-0.35277776,123.77659,67.893825)"
clip-path="url(#clipPath31)"
sodipodi:nodetypes="ccccccccccccccscccccccccccsc" />
<path
id="path32"
d="m 0,0 c -2.698,2.946 -3.368,6.526 -0.642,9.179 4.794,4.657 13.982,-0.46 16.613,-8.334 1.477,-4.424 1.858,-13.089 -6.975,-20.765 -1.016,-0.819 -2.53,0.361 -1.537,1.751 1.289,1.796 2.894,6.835 0.36,11.53 C 5.872,-3.035 2.29,-2.505 0,0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0.35277776,0,0,-0.35277776,85.003826,32.951646)"
clip-path="url(#clipPath33)" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -2,9 +2,9 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48.156189"
height="19.813145"
viewBox="0 0 48.156189 19.813145"
width="150"
height="62"
viewBox="0 0 150 62.000003"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
@ -26,15 +26,16 @@
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="17.146626"
inkscape:cx="23.124083"
inkscape:cy="15.629897"
inkscape:zoom="3.292102"
inkscape:cx="-7.290175"
inkscape:cy="60.447701"
inkscape:window-width="1916"
inkscape:window-height="1009"
inkscape:window-height="1055"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="g1" />
inkscape:current-layer="g4"
showgrid="false" />
<defs
id="defs1">
<clipPath
@ -274,7 +275,7 @@
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-6.3894695,-9.7672879)">
transform="translate(43.610527,10.590344)">
<g
id="g2"
transform="matrix(0.33637415,0,0,0.33637531,-22.728233,-7.6664558)">
@ -283,7 +284,7 @@
transform="matrix(1,0.08748836,0,1,0,-13.741963)">
<g
id="g3"
transform="matrix(0.86859292,0.14570116,0,0.80174446,88.322932,-16.382563)">
transform="matrix(2.7055492,0.45383937,0,2.4973253,-56.59998,-220.77782)">
<path
id="path8"
d="m 0,0 c -2.154,-1.758 -4.433,-5.061 -4.676,-8.134 -0.348,-4.386 -0.549,-18.527 -0.602,-20.167 -0.156,-4.818 -0.91,-11.121 3.449,-11.455 2.497,-0.191 4.896,1.68 5.999,3.152 3.517,4.702 6.271,10.846 6.271,21.331 0,7.837 0,16.945 -7.097,16.945 C 2.318,1.672 0.683,0.556 0,0 m -8.541,14.641 c 1.224,0.128 2.603,-0.596 2.949,-1.627 0.432,-1.274 0.352,-5.007 0.204,-6.406 -0.086,-0.796 -0.02,-3.436 0,-3.456 0.455,-0.5 3.966,6.69 13.072,6.69 1.785,0 3.602,-0.274 5.331,-1.097 5.65,-2.69 9.289,-9.932 9.289,-23.489 0,-13.451 -7.167,-23.544 -12.711,-27.113 -2.764,-1.781 -6.413,-2.786 -10.506,-2.982 -1.047,-0.052 -3.263,0.838 -4.068,-0.306 -0.808,-1.146 -0.752,-6.098 -0.784,-7.198 -0.636,-23.09 -2.436,-32.515 -8.061,-35.303 -1.462,-0.725 -3.279,-1.094 -3.462,-0.125 -0.119,0.627 0.223,1.005 1.326,2.159 1.936,2.025 1.936,4.089 2.237,8.845 0.394,6.211 -2.1,57.998 -2.745,66.803 -0.089,1.218 0.078,8.212 -1.932,8.337 -0.624,0.039 -2.932,-1.679 -4.981,-3.796 -0.623,-0.647 -2.892,-3.249 -3.318,-1.13 -0.591,2.961 9.664,17.473 15.822,20.483 0.737,0.36 1.523,0.626 2.338,0.711"
@ -313,7 +314,7 @@
</g>
<g
id="g1"
transform="translate(16.849468,27.462558)">
transform="matrix(3.1148646,0,0,3.1148646,-279.23013,-84.206213)">
<path
id="path10"
d="m 0,0 c 3.925,0.872 3.57,-3.354 3.57,-6.841 0,-1.635 -0.207,-5.524 0.96,-3.111 1.514,3.13 4.049,7.805 7.407,9.203 6.321,2.633 9.019,-2.697 9.073,-8.939 0.029,-3.784 -0.636,-8.132 -1.171,-11.964 -0.337,-2.416 -0.401,-4.539 -0.789,-7.06 -0.39,-2.543 -0.893,-6.621 -0.789,-9.198 0.029,-0.763 0.431,-2.613 1.143,-2.695 0.896,-0.104 1.727,1.684 2.888,1.357 0.999,-0.282 0.783,-2.523 0.481,-3.744 -0.783,-3.177 -3.162,-6.581 -6.137,-8.024 -4.126,-1.999 -7.421,0.581 -7.95,4.513 -0.364,2.725 -0.071,5.917 0,8.753 0.151,6.14 0.842,12.296 0.96,18.572 0.039,2.128 0.098,5.378 -0.106,6.611 -0.612,3.661 -2.412,0.272 -3.544,-1.536 -4.536,-7.246 -2.774,-23.704 -3.454,-31.568 -0.42,-4.839 -1.053,-9.615 -2.946,-13.167 -0.944,-1.77 -2.545,-4.254 -4.526,-5.506 -0.534,-0.337 -1.418,-0.718 -1.808,-0.492 -1.052,0.61 -0.192,1.993 0.065,2.955 2.105,7.91 1.599,18.081 0.57,26.9 -1.376,11.784 -2.337,17.567 -3.372,17.654 -1.808,0.151 -2.284,-2.109 -2.704,-3.18 -0.366,-0.942 -0.407,-2.674 -1.063,-3.308 -1.129,-1.085 -2.838,-0.647 -3,0.98 -0.171,1.74 0.633,4.583 1.244,6.063 0.834,2.015 1.738,4.759 3.328,7.071 C -8.798,-5.532 -5.843,-1.299 0,0"
@ -371,7 +372,7 @@
clip-path="url(#clipPath33)" />
<text
xml:space="preserve"
style="font-size:7.92767px;line-height:1.25;font-family:sans-serif;text-align:end;text-anchor:end;white-space:pre;inline-size:102.756;fill:#ffffff;fill-opacity:1;stroke-width:2.97287"
style="font-size:7.92767px;line-height:1.25;font-family:sans-serif;text-align:end;text-anchor:end;white-space:pre;inline-size:102.756;display:inline;fill:#ffffff;fill-opacity:1;stroke-width:2.97287"
x="213.26958"
y="73.687592"
id="logo"
@ -379,16 +380,16 @@
class="logo-subtitle"><tspan
x="213.26958"
y="73.687592"
id="tspan2"><tspan
style="font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans'"
id="tspan1">La fanfare de l'École</tspan><tspan
y="73.687592"
id="tspan3"> </tspan></tspan><tspan
id="tspan7"><tspan
style="font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans'"
id="tspan5">La fanfare de l'École</tspan><tspan
y="73.687592"
id="tspan8"> </tspan></tspan><tspan
x="213.26958"
y="83.750981"
id="tspan6"><tspan
id="tspan10"><tspan
style="font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans'"
id="tspan4">normale supérieure de Paris</tspan></tspan></text>
id="tspan9">normale supérieure de Paris</tspan></tspan></text>
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before After
Before After

View file

@ -0,0 +1,38 @@
document.addEventListener('DOMContentLoaded', () => {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Add a click event on each of them
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target;
const $target = document.getElementById(target);
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
});
document.addEventListener('DOMContentLoaded', () => {
const $deleteElems = Array.prototype.slice.call(document.querySelectorAll('.delete'), 0);
// Add a click event on each of them
$deleteElems.forEach( el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
if ('target' in el.dataset) {
const target = el.dataset.target;
const $target = document.getElementById(target);
$target.remove();
}
});
});
});

View file

@ -1,7 +1,5 @@
{% load django_browser_reload %}
{% load i18n %}
<footer class="footer has-text-centered">
<b>{% translate "Logiciel développé pour et par l'Ernestophone." %}</b>
{% django_browser_reload_script %}
</footer>

View file

@ -1,9 +1,11 @@
{% load i18n %}
{% load static %}
{% load auth_extra %}
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="https://buma.io">
<img src="{% static "icons/ernesto-white-long.svg" %}"/>
<a class="navbar-item py-0" href="{% url "home" %}" >
{# TODO: Rename #}
<img src="{% static "icons/ernesto-white-long-bis.svg" %}"/>
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="mainNavbar">
@ -16,28 +18,38 @@
<div id="mainNavbar" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">
Home
<a href="{% url "home" %}" class="navbar-item">
{% trans "Accueil" %}
</a>
<a class="navbar-item">
Documentation
<a href="#{# TODO #}" class="navbar-item">
{% trans "Agenda" %}
</a>
<a href="#{# TODO #}" class="navbar-item">
{% trans "Répertoire" %}
</a>
<a href="{% url "home" %}#contact" class="navbar-item">
{% trans "Contact" %}
</a>
{% if user.is_authenticated %}
<a href="#{# TODO #}" class="navbar-item">
{% trans "Le pouvoir des ernestos" %}
</a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
Le pouvoir des chef&middot;fe&middot;s
{% trans "Le pouvoir des chef&middot;fe&middot;s" %}
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
Administration
<a href="{% url "admin:index" %}" class="navbar-item">
{% trans "Administration" %}
</a>
<a class="navbar-item is-selected">
Test
</a>
<a class="navbar-item">
Contact
<a href="#{# TODO #}" class="navbar-item">
{% trans "TODO" %}
</a>
<hr class="navbar-divider">
<a class="navbar-item">
@ -45,24 +57,51 @@
</a>
</div>
</div>
{% endif %}
</div>
<div class="navbar-end">
{% if user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link button is-link has-text-white">
<span class="icon"> <i class="ti ti-user"></i> </span>
<span>{{ user.username }}</span>
</a>
<div class="navbar-dropdown is-right">
<div class="dropdown-content">
<a href="{% url "ernestoprofil:edit-user" %}" class="navbar-item">{% trans "Modifier mon profil" %}</a>
{% if not user|has_group:"Ernestophoniste" %}
<a href="{% url "ernestoprofil:become-ernesto" %}" class="navbar-item">{% trans "Devenir fanfaron" %}</a>
{% endif %}
<hr class="navbar-divider" />
<form class="navbar-item" action="{% url "ernestoprofil:logout" %}">
{% csrf_token %}
<button class="button is-link is-size-6" type="submit">
<span>{% trans "Se déconnecter" %}</span>
</button>
</form>
</div>
</div>
</div>
{%else%}
<div class="navbar-item">
<div class="buttons">
<a class="button is-link has-text-white">
<strong>
Créer un compte
<span class="icon">
<i class="ti ti-door-enter"></i>
</span>
</strong>
<a href="{% url "ernestoprofil:create-user" %}" class="button is-link has-text-white icon-text">
<span>{% trans "Créer un compte" %}</span>
<span class="icon">
<i class="ti ti-user-plus"></i>
</span>
</a>
<a class="button is-light">
Se connecter
<a href="{% url "authens:login" %}" class="button is-light">
<span>{% trans "Se connecter" %}</span>
<span class="icon">
<i class="ti ti-door-enter"></i>
</span>
</a>
</div>
</div>
{% endif %}
</div>
</div>
</nav>

View file

@ -1,4 +1,7 @@
{% load i18n %}
{% load static %}
{% load auth_extra %}
{% load django_browser_reload %}
<!DOCTYPE html>
<html lang="fr">
<head>
@ -6,7 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="keywords" content="fanfare,Ernestophone,Brass Band,ENS,École Normale Supérieure" />
<meta name="description" content="{% blocktranslate %}Site web de l'Ernestophone, la fanfare de l'École Normale Supérieure de Paris{% endblocktranslate %}" />
<title>{% translate "L'Ernestophone" %}</title>
<title>{% block title %}{% translate "L'Ernestophone" %}{% endblock %}</title>
{% include "_links.html" %}
</head>
@ -14,9 +17,34 @@
<body>
{% include "_nav.html" %}
<section id="notifications" class="section py-0">
{% if user.is_authenticated and not user|has_group:"Ernestophoniste" and request.resolver_match.view_name != "ernestoprofil:become-ernesto" %}
<div class="content notification is-warning my-2" id="message-{{ forloop.counter0 }}">
<button class="delete" data-target="message-{{ forloop.counter0 }}"></button>
{% blocktranslate %}Vous n'êtes pas encore considérés comme fanfaron par le site. Pour le devenir, cliquez sur{% endblocktranslate %}
<a href="{% url "ernestoprofil:become-ernesto" %}">{% trans "ce lien" %}</a>
</div>
{% endif %}
{# TODO add warning for pupitre #}
{% for message in messages %}
<div class="notification {{ message.level_tag }} my-2" id="message-{{ forloop.counter0 }}">
<button class="delete" data-target="message-{{ forloop.counter0 }}"></button>
{% if 'safe' in message.tags %}
{{ message|safe }}
{% else %}
{{ message }}
{% endif %}
</div>
{% endfor %}
</section>
{% block content %}
{% endblock content %}
{% include "_footer.html" %}
<script src="{% static "js/common.js" %}" defer></script>
{% block extra_scripts %}
{% endblock extra_scripts %}
{# TODO: Add analytics #}
</body>
</html>

View file

@ -43,10 +43,11 @@
<section id="contact" class="section content">
<h1 class="has-text-centered">Nous contacter</h1>
<p>
Vous préparez un évenement et vous recherchez une fanfare dynamique ? N'hésitez pas à nous envoyer un message par mail ou sur Facebook. Nous serons ravis de venir fanfaronner pour vous !!
Vous préparez un évenement et vous recherchez une fanfare dynamique ? N'hésitez pas à nous envoyer un message par mail (<span class="tag">fanfare (at) ens.psl.eu</span>) ou sur Facebook. Nous serons ravis de venir fanfaronner pour vous !!
</p>
<hr/>
<div class="level">
{# TODO: Fill #}
<div class="level-item has-text-centered">
<a class="title">
<span class="icon">
@ -68,8 +69,8 @@
</span>
</a>
</div>
<div class="level-item has-text-centered">
<a class="title">
<div class="level-item has-text-centered" href="mailto:fanfare@ens.psl.eu">
<a class="title" href="mailto:fanfare@ens.psl.eu">
<span class="icon">
<i class="ti ti-mail"></i>
</span>

View file

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
{# TODO: Add hero #}
<section class="section">
<h1 class="title">{{ title }}</h1>
<form action="" method="post">
{% csrf_token %}
{% include "bulma/form.html" with errors=True %}
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span>{{ submit }}</span>
</button>
</div>
</div>
</form>
</section>
{% endblock %}

View file

@ -0,0 +1,8 @@
from django import template
register = template.Library()
@register.filter
def has_group(user, group_name):
return user.groups.filter(name=group_name).exists()