diff --git a/manifeste_velo/package-lock.json b/manifeste_velo/package-lock.json new file mode 100644 index 0000000..0352dab --- /dev/null +++ b/manifeste_velo/package-lock.json @@ -0,0 +1,73 @@ +{ + "name": "manifeste_velo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "bootstrap": "5.3.*", + "bootstrap-icons": "1.13.*", + "htmx.org": "2.0.*", + "leaflet": "~1.9.4" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz", + "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, + "node_modules/htmx.org": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.8.tgz", + "integrity": "sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q==", + "license": "0BSD" + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + } + } +} diff --git a/manifeste_velo/package.json b/manifeste_velo/package.json new file mode 100644 index 0000000..a82223c --- /dev/null +++ b/manifeste_velo/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "bootstrap": "5.3.*", + "bootstrap-icons": "1.13.*", + "htmx.org": "2.0.*", + "leaflet": "~1.9.4" + } +} \ No newline at end of file diff --git a/manifeste_velo/questionnaire/admin.py b/manifeste_velo/questionnaire/admin.py index 4fb097d..ab2e625 100644 --- a/manifeste_velo/questionnaire/admin.py +++ b/manifeste_velo/questionnaire/admin.py @@ -1,9 +1,24 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.models import User from questionnaire import models # Register your models here. + +class UtilisateurInline(admin.StackedInline): + model = models.Utilisateur + can_delete = False + + +class UserAdmin(BaseUserAdmin): + inlines = [UtilisateurInline] + + +admin.site.unregister(User) +admin.site.register(User, UserAdmin) + admin.site.register(models.Ville) admin.site.register(models.PointManifeste) admin.site.register(models.Liste) diff --git a/manifeste_velo/questionnaire/migrations/0002_utilisateur.py b/manifeste_velo/questionnaire/migrations/0002_utilisateur.py new file mode 100644 index 0000000..89eea63 --- /dev/null +++ b/manifeste_velo/questionnaire/migrations/0002_utilisateur.py @@ -0,0 +1,24 @@ +# Generated by Django 6.0.1 on 2026-01-23 09:35 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questionnaire', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Utilisateur', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('ville', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='questionnaire.ville')), + ], + ), + ] diff --git a/manifeste_velo/questionnaire/migrations/0003_candidat_email.py b/manifeste_velo/questionnaire/migrations/0003_candidat_email.py new file mode 100644 index 0000000..6c65187 --- /dev/null +++ b/manifeste_velo/questionnaire/migrations/0003_candidat_email.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.1 on 2026-01-23 09:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('questionnaire', '0002_utilisateur'), + ] + + operations = [ + migrations.AddField( + model_name='candidat', + name='email', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/manifeste_velo/questionnaire/models.py b/manifeste_velo/questionnaire/models.py index d2b672c..84c1f7f 100644 --- a/manifeste_velo/questionnaire/models.py +++ b/manifeste_velo/questionnaire/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import User from django.db import models from django_extensions.db.fields import AutoSlugField from django_extensions.db.models import TimeStampedModel @@ -14,6 +15,14 @@ class Ville(models.Model): return f"{self.nom} ({self.code_insee})" +class Utilisateur(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") + ville = models.ForeignKey(Ville, on_delete=models.SET_NULL, blank=True, null=True) + + def __str__(self): + return self.user + + class PointManifeste(models.Model): titre = models.CharField(max_length=255) texte = models.TextField() @@ -32,9 +41,10 @@ class PointManifeste(models.Model): class Candidat(TimeStampedModel, models.Model): nom = models.CharField(max_length=255) prenom = models.CharField(max_length=255, verbose_name="Prénom") + email = models.CharField(max_length=255, blank=True, null=True) def __str__(self): - return f"{self.prenom} {self.nom} - {self.ville.nom}" + return f"{self.prenom} {self.nom}" class Liste(TimeStampedModel, models.Model): @@ -50,10 +60,11 @@ class Liste(TimeStampedModel, models.Model): responsable_mobilites = models.OneToOneField( Candidat, on_delete=models.PROTECT, verbose_name="Responsable mobilités de la liste" ) - email_ouvert = models.DateTimeField(blank=True, null=True, verbose_name="L'e-mail au candidat a été ouvert") + email_envoye = models.DateTimeField(blank=True, null=True, verbose_name="Date et heure de l'envoi de l'e-mail") + email_ouvert = models.DateTimeField(blank=True, null=True, verbose_name="Date et heure d'ouverture de l'e-mail") def __str__(self): - return f"{self.nom} - {self.ville.nom} - {self.tete_liste.prenom} {self.tete_liste.nom}" + return f"{self.nom} - {self.ville.nom} - {self.tete_liste}" class Meta: ordering = ["ville", "nom"] @@ -64,6 +75,10 @@ class ReponseListe(TimeStampedModel, models.Model): expression_libre = models.TextField(verbose_name="Expression libre") email_confirmation = models.BooleanField(verbose_name="E-mail de confirmation envoyé") finalise = models.BooleanField(verbose_name="Réponse finalisée") + date_heure_finalisation = models.DateTimeField(blank=True, null=True) + + def __str__(self): + return f"Réponse pour la liste {self.liste}" class EngagementChoices(models.TextChoices): diff --git a/manifeste_velo/questionnaire/templates/questionnaire/admin.html b/manifeste_velo/questionnaire/templates/questionnaire/admin.html new file mode 100644 index 0000000..b467657 --- /dev/null +++ b/manifeste_velo/questionnaire/templates/questionnaire/admin.html @@ -0,0 +1,37 @@ +{% extends "base.html" %}{% load static %} +{% block body %} +
Bienvenue sur la page d'administration, vous pouvez sélectionner une fonction dans le menu
+{{ question.texte|truncatewords_html:10 }}
+ {% else %} +{{ question.texte|safe }}
+ {% if question.exemple %} +