From 21d65e936d6fad01a5a69983bd822fd9436a2e29 Mon Sep 17 00:00:00 2001 From: Guilhem Saurel Date: Sat, 11 Jun 2016 15:42:40 +0200 Subject: [PATCH] user registration, ponyconf{user, speaker} models --- accounts/admin.py | 4 +- accounts/forms.py | 15 +++---- accounts/migrations/0001_initial.py | 34 ++++----------- .../migrations/0002_auto_20160602_2128.py | 24 ----------- .../migrations/0002_auto_20160611_1305.py | 40 ++++++++++++++++++ accounts/models.py | 42 +++++++++++++++++-- accounts/templates/accounts/profile.html | 27 ++++-------- accounts/views.py | 41 ++++++++---------- doc/requirements.md | 33 +++++++++++++++ doc/requirements.txt | 41 ------------------ ponyconf/settings.py | 3 +- ponyconf/urls.py | 1 + .../migrations/0004_auto_20160611_1305.py | 30 +++++++++++++ proposals/models.py | 22 +++++----- .../templates/proposals/talk_details.html | 2 +- proposals/templates/proposals/talks.html | 2 +- .../templates/proposals/user_details.html | 2 +- proposals/views.py | 26 +++++++----- requirements.txt | 1 + 19 files changed, 214 insertions(+), 176 deletions(-) delete mode 100644 accounts/migrations/0002_auto_20160602_2128.py create mode 100644 accounts/migrations/0002_auto_20160611_1305.py create mode 100644 doc/requirements.md delete mode 100644 doc/requirements.txt create mode 100644 proposals/migrations/0004_auto_20160611_1305.py diff --git a/accounts/admin.py b/accounts/admin.py index 458515d..26b4847 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from accounts.models import * +from accounts.models import PonyConfUser -admin.site.register(User) +admin.site.register(PonyConfUser) diff --git a/accounts/forms.py b/accounts/forms.py index e5e302e..bac3a1c 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -1,17 +1,14 @@ from django.forms.models import modelform_factory -from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.models import User -from accounts.models import User +from .models import PonyConfUser -__all__ = ['ProfileForm'] +__all__ = ['ProfileForm', 'PonyConfUserForm'] ProfileForm = modelform_factory(User, - fields=['first_name', 'last_name', 'email', 'biography']) + fields=['first_name', 'last_name', 'email', 'username']) -class CustomUserCreationForm(UserCreationForm): - - class Meta(UserCreationForm.Meta): - model = User - fields = UserCreationForm.Meta.fields + ('biography',) +PonyConfUserForm = modelform_factory(PonyConfUser, + fields=['biography']) diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py index b7d4642..d1ad576 100644 --- a/accounts/migrations/0001_initial.py +++ b/accounts/migrations/0001_initial.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.6 on 2016-06-02 21:26 +# Generated by Django 1.9.7 on 2016-06-11 12:23 from __future__ import unicode_literals -import django.contrib.auth.models -import django.core.validators +from django.conf import settings from django.db import migrations, models -import django.utils.timezone +import django.db.models.deletion class Migration(migrations.Migration): @@ -13,35 +12,16 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0007_alter_validators_add_error_messages'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='User', + name='PonyConfUser', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('bio', models.TextField(blank=True)), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'abstract': False, - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ('biography', models.TextField(blank=True, verbose_name='Biography')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/accounts/migrations/0002_auto_20160602_2128.py b/accounts/migrations/0002_auto_20160602_2128.py deleted file mode 100644 index 76d1c14..0000000 --- a/accounts/migrations/0002_auto_20160602_2128.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.6 on 2016-06-02 21:28 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='user', - name='bio', - ), - migrations.AddField( - model_name='user', - name='biography', - field=models.TextField(blank=True, verbose_name='Biography'), - ), - ] diff --git a/accounts/migrations/0002_auto_20160611_1305.py b/accounts/migrations/0002_auto_20160611_1305.py new file mode 100644 index 0000000..0d3c9d3 --- /dev/null +++ b/accounts/migrations/0002_auto_20160611_1305.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-06-11 13:05 +from __future__ import unicode_literals + +import django.contrib.sites.managers +from django.db import migrations, models +import django.db.models.deletion +import django.db.models.manager + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='PonyConfSpeaker', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('arrival', models.DateTimeField(blank=True, null=True)), + ('departure', models.DateTimeField(blank=True, null=True)), + ('transport', models.IntegerField(blank=True, choices=[(1, 'train'), (2, 'plane')], null=True)), + ('connector', models.IntegerField(blank=True, choices=[(1, 'VGA'), (2, 'HDMI'), (3, 'miniDP')], null=True)), + ('constraints', models.TextField()), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.PonyConfUser')), + ], + managers=[ + ('objects', django.db.models.manager.Manager()), + ('on_site', django.contrib.sites.managers.CurrentSiteManager()), + ], + ), + migrations.AlterUniqueTogether( + name='ponyconfspeaker', + unique_together=set([('site', 'user')]), + ), + ] diff --git a/accounts/models.py b/accounts/models.py index c50f133..0419700 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,11 +1,47 @@ +from enum import IntEnum + from django.db import models +from django.contrib.sites.models import Site +from django.contrib.sites.managers import CurrentSiteManager -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import User -__all__ = [ 'User' ] +__all__ = [ 'PonyConfUser', 'PonyConfSpeaker' ] -class User(AbstractUser): +def enum_to_choices(enum): + return ((item.value, item.name) for item in list(enum)) +class PonyConfUser(models.Model): + + user = models.OneToOneField(User) biography = models.TextField(blank=True, verbose_name='Biography') + + +class PonyConfSpeaker(models.Model): + + TRANSPORTS = IntEnum('Transport', 'train plane') + CONNECTORS = IntEnum('Connector', 'VGA HDMI miniDP') + + site = models.ForeignKey(Site, on_delete=models.CASCADE) + + user = models.ForeignKey(PonyConfUser) + arrival = models.DateTimeField(blank=True, null=True) + departure = models.DateTimeField(blank=True, null=True) + transport = models.IntegerField(choices=enum_to_choices(TRANSPORTS), blank=True, null=True) + connector = models.IntegerField(choices=enum_to_choices(CONNECTORS), blank=True, null=True) + constraints = models.TextField() + + objects = models.Manager() + on_site = CurrentSiteManager() + + class Meta: + unique_together = ('site', 'user') + + +def create_ponyconfuser(sender, instance, created, **kwargs): + if created: + PonyConfUser.objects.create(user=instance) + +models.signals.post_save.connect(create_ponyconfuser, sender=User, weak=False, dispatch_uid='create_ponyconfuser') diff --git a/accounts/templates/accounts/profile.html b/accounts/templates/accounts/profile.html index 63ba175..4f5391f 100644 --- a/accounts/templates/accounts/profile.html +++ b/accounts/templates/accounts/profile.html @@ -15,27 +15,14 @@

Update profile

-
+ {% csrf_token %} - {% bootstrap_form profileform %} -
- -
-
-
- - -
-
-

Update password

-
-
-
- {% csrf_token %} - {% bootstrap_form passwordform %} -
- -
+ {% for form in forms %} + {% bootstrap_form form layout="horizontal" %} + {% endfor %} + {% buttons layout="horizontal" %} + + {% endbuttons %}
diff --git a/accounts/views.py b/accounts/views.py index b14b30a..b27d83f 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,36 +1,29 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import PasswordChangeForm +from django.contrib.auth.views import password_change from django.contrib import messages -from accounts.forms import * +from .forms import ProfileForm, PonyConfUserForm + + +@login_required +def password(request): + return password_change(request, post_change_redirect='profile') @login_required def profile(request): - profileform = None - passwordform = None + + forms = [ProfileForm(request.POST or None, instance=request.user), + PonyConfUserForm(request.POST or None, instance=request.user.ponyconfuser)] if request.method == 'POST': - if 'update-profile' in request.POST: - profileform = ProfileForm(request.POST, instance=request.user) - if profileform.is_valid(): - profileform.save() - messages.success(request, 'Profile updated successfully.') - return redirect('profile') - elif 'update-password' in request.POST: - passwordform = PasswordChangeForm(user=request.user, data=request.POST) - if passwordform.is_valid(): - passwordform.save() - messages.success(request, 'Password updated successfully.') - return redirect('profile') + if all(form.is_valid() for form in forms): + for form in forms: + form.save() + messages.success(request, 'Profile updated successfully.') + else: + messages.error(request, 'Please correct those errors.') - if not profileform: - profileform = ProfileForm(None, instance=request.user) - if not passwordform: - passwordform = PasswordChangeForm(None) - - return render(request, 'accounts/profile.html', { - 'profileform': profileform, - 'passwordform': passwordform, - }) + return render(request, 'accounts/profile.html', {'forms': forms}) diff --git a/doc/requirements.md b/doc/requirements.md new file mode 100644 index 0000000..4102c94 --- /dev/null +++ b/doc/requirements.md @@ -0,0 +1,33 @@ +- [x] enregistrement sur le site (pas d'enregistrement fb/linkedin: on aime le libre ici) +- [ ] permettre de proposer une participation (conférence/atelier/small talk/etc.). Un speaker rentre un abstract et + peur associer un ou des co-speakers. +- [ ] l'équipe organisatrice peut voter pour les conférences (avec un commentaire visible uniquement de l'équipe) et + les accepter/refuser +- [ ] pour chaque conférence/conférencier, on permet d'échanger avec le conférencier (lui vois ça comme un mail, mais + côté orga c'est centralisé sur l'UI web... c'est mieux pour le suivi). +- [ ] Notification (par mail) pour les administrateurs des ajout/modification de conférence et des échanges avec les + intervenants. +- [ ] Gestion du planning des salles (on affecte une track à une salle, mais cela permet surtout de signaler les + doublons d'intervenants/salle/etc.) +- [ ] Gestion du planning des bénévoles (ils s'inscrivent) +- [ ] Générer un planning XML pour être ingérée par l'appli Android +- [ ] Gestion de différents profils: speaker, bénévole, responsable de track, kernel +- [ ] Génération des badges +- [x] Un speaker et un bénévole peuvent être enregistré par un tiers (responsable de track ou kernel). +- [ ] Gestion des sponsors : niveaux de sponsorings et personnes présentes pour la génération des badges +- [ ] Gestion des différentes thématiques (CRUD) +- [ ] Vues à réaliser: listing des propositions, conférence, bénévoles, speakers (permet d'avoir une vue d'ensemble) +- [ ] Champ de note interne sur un speaker (pour noter des préférences par exemple) et sur une proposition de + conférence +- [ ] Gestion de la date d'ouverture/fermeture de l'appel à conférence (différente selon le type de participation - + i.e. conférence/atelier/stand). +- [ ] On propose par défaut des propositions du type: talk, workshop, booth +- [x] Profil des intervenenants: nom, prénom, mail, date et heure d'arrivée, date et heure de départ, moyen de + transport, type de connectique, hébergement, +- [ ] Possibilité aux intervenants de récupérer leur billet d’avion électronique +- [ ] Aide au planning : + - [x] affichage des contraintes des intervenants + - [ ] qui est disponible pour un time slot donnée +- [ ] Stand ? + +Le code de l'année dernière est là : https://github.com/toulibre/cdl-site diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index 9e6c41d..0000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,41 +0,0 @@ -- enregistrement sur le site (pas d'enregistrement fb/linkedin: on aime -le libre ici) -- permettre de proposer une participation (conférence/atelier/small -talk/etc.). Un speaker rentre un abstract et peur associer un ou des -co-speakers. -- l'équipe organisatrice peut voter pour les conférences (avec un -commentaire visible uniquement de l'équipe) et les accepter/refuser -- pour chaque conférence/conférencier, on permet d'échanger avec le -conférencier (lui vois ça comme un mail, mais côté orga c'est centralisé -sur l'UI web... c'est mieux pour le suivi). -- Notification (par mail) pour les administrateurs des -ajout/modification de conférence et des échanges avec les intervenants. -- Gestion du planning des salles (on affecte une track à une salle, mais -cela permet surtout de signaler les doublons d'intervenants/salle/etc.) -- Gestion du planning des bénévoles (ils s'inscrivent) -- Générer un planning XML pour être ingérée par l'appli Android -- Gestion de différents profils: speaker, bénévole, responsable de -track, kernel -- Génération des badges -- Un speaker et un bénévole peuvent être enregistré par un tiers -(responsable de track ou kernel). -- Gestion des sponsors : niveaux de sponsorings et personnes présentes -pour la génération des badges -- Gestion des différentes thématiques (CRUD) -- Vues à réaliser: listing des propositions, conférence, bénévoles, -speakers (permet d'avoir une vue d'ensemble) -- Champ de note interne sur un speaker (pour noter des préférences par -exemple) et sur une proposition de conférence -- Gestion de la date d'ouverture/fermeture de l'appel à conférence -(différente selon le type de participation - i.e. conférence/atelier/stand). -- On propose par défaut des propositions du type: talk, workshop, booth -- Profil des intervenenants: nom, prénom, mail, date et heure d'arrivée, -date et heure de départ, moyen de transport, type de connectique, -hébergement, -- Possibilité aux intervenants de récupérer leur billet d’avion -électronique -- Aide au planning : affichage des contraintes des intervenants, qui est -disponible pour un time slot donnée, … -- Stand ? - -Le code de l'annéed ernière est là : https://github.com/toulibre/cdl-site diff --git a/ponyconf/settings.py b/ponyconf/settings.py index 1a8ab32..c4a540a 100644 --- a/ponyconf/settings.py +++ b/ponyconf/settings.py @@ -32,6 +32,7 @@ ALLOWED_HOSTS = [] INSTALLED_APPS = [ 'django.contrib.admin', + 'registration', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -146,8 +147,6 @@ LOGIN_REDIRECT_URL = 'home' SITE_ID = 1 -AUTH_USER_MODEL = 'accounts.User' - BOOTSTRAP3 = { # The URL to the jQuery JavaScript file diff --git a/ponyconf/urls.py b/ponyconf/urls.py index 032df4e..f0d67e0 100644 --- a/ponyconf/urls.py +++ b/ponyconf/urls.py @@ -20,5 +20,6 @@ from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^accounts/', include('accounts.urls')), + url(r'^registration/', include('registration.backends.default.urls')), url(r'^', include('proposals.urls')), ] diff --git a/proposals/migrations/0004_auto_20160611_1305.py b/proposals/migrations/0004_auto_20160611_1305.py new file mode 100644 index 0000000..01451ed --- /dev/null +++ b/proposals/migrations/0004_auto_20160611_1305.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-06-11 13:05 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('proposals', '0003_auto_20160608_2002'), + ] + + operations = [ + migrations.AlterModelOptions( + name='speach', + options={'ordering': ['talk', 'order']}, + ), + migrations.AlterField( + model_name='speach', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.PonyConfSpeaker'), + ), + migrations.AlterField( + model_name='talk', + name='speakers', + field=models.ManyToManyField(through='proposals.Speach', to='accounts.PonyConfSpeaker'), + ), + ] diff --git a/proposals/models.py b/proposals/models.py index 3f06e88..49dcdf7 100644 --- a/proposals/models.py +++ b/proposals/models.py @@ -4,7 +4,7 @@ from django.contrib.sites.managers import CurrentSiteManager from autoslug import AutoSlugField -from accounts.models import User +from accounts.models import PonyConfSpeaker __all__ = [ 'Topic', 'Talk', 'Speach' ] @@ -14,12 +14,12 @@ class Topic(models.Model): site = models.ForeignKey(Site, on_delete=models.CASCADE) - objects = models.Manager() - on_site = CurrentSiteManager() - name = models.CharField(max_length=128, verbose_name='Name', unique=True) slug = AutoSlugField(populate_from='name', unique=True) + objects = models.Manager() + on_site = CurrentSiteManager() + def __str__(self): return self.name @@ -28,29 +28,29 @@ class Talk(models.Model): site = models.ForeignKey(Site, on_delete=models.CASCADE) - objects = models.Manager() - on_site = CurrentSiteManager() - - speakers = models.ManyToManyField(User, through='Speach') + speakers = models.ManyToManyField(PonyConfSpeaker, through='Speach') title = models.CharField(max_length=128, verbose_name='Title') slug = AutoSlugField(populate_from='title', unique=True) description = models.TextField(blank=True, verbose_name='Description') topics = models.ManyToManyField(Topic, blank=True) + objects = models.Manager() + on_site = CurrentSiteManager() + def __str__(self): return self.title class Speach(models.Model): - SPEAKER_NO = ((1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "5"), (6, "6"), (7, "7"),) + SPEAKER_NO = tuple((i, str(i)) for i in range(1, 8)) - user = models.ForeignKey(User, on_delete=models.CASCADE) + user = models.ForeignKey(PonyConfSpeaker, on_delete=models.CASCADE) talk = models.ForeignKey(Talk, on_delete=models.CASCADE) order = models.IntegerField(choices=SPEAKER_NO) class Meta: - ordering = [ 'order' ] + ordering = ['talk', 'order'] unique_together = ( ('user', 'talk'), ('order', 'talk'), diff --git a/proposals/templates/proposals/talk_details.html b/proposals/templates/proposals/talk_details.html index c288d03..51361a9 100644 --- a/proposals/templates/proposals/talk_details.html +++ b/proposals/templates/proposals/talk_details.html @@ -17,7 +17,7 @@