user registration, ponyconf{user, speaker} models

This commit is contained in:
Guilhem Saurel 2016-06-11 15:42:40 +02:00
parent e778f87bf6
commit 21d65e936d
19 changed files with 214 additions and 176 deletions

View File

@ -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)

View File

@ -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'])

View File

@ -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)),
],
),
]

View File

@ -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'),
),
]

View File

@ -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')]),
),
]

View File

@ -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')

View File

@ -15,27 +15,14 @@
<h3>Update profile</h3>
</div>
<div class="panel-body">
<form method="post" class="col-md-4" role="form">
<form method="post" class="form-horizontal" role="form">
{% csrf_token %}
{% bootstrap_form profileform %}
<div class="form-group">
<button type="submit" name='update-profile' class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3>Update password</h3>
</div>
<div class="panel-body">
<form method="post" class="col-md-4" role="form">
{% csrf_token %}
{% bootstrap_form passwordform %}
<div class="form-group">
<button type="submit" name='update-password' class="btn btn-primary">Update</button>
</div>
{% for form in forms %}
{% bootstrap_form form layout="horizontal" %}
{% endfor %}
{% buttons layout="horizontal" %}
<button type="submit" class="btn btn-primary">Update</button>
{% endbuttons %}
</form>
</div>
</div>

View File

@ -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})

33
doc/requirements.md Normal file
View File

@ -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 davion é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

View File

@ -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 davion
é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

View File

@ -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

View File

@ -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')),
]

View File

@ -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'),
),
]

View File

@ -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'),

View File

@ -17,7 +17,7 @@
<ul>
{% for speaker in talk.speakers.all %}
<li>
<a href="{% url 'show-user' speaker.username %}">
<a href="{% url 'show-user' speaker.user.user.username %}">
{% firstof speaker.get_full_name speaker.username %}
</a>
</li>

View File

@ -14,7 +14,7 @@
<a href="{% url 'show-talk' talk.slug %}">{{ talk }}</a>
<i>by</i>
{% for speach in talk.speach_set.all %}
<a href="{% url 'show-user' speach.user.username %}">{% firstof speach.user.get_full_name speach.user.username %}</a>
<a href="{% url 'show-user' speach.user.user.user.username %}">{% firstof speach.user.user.user.get_full_name speach.user.user.user.username %}</a>
{% if forloop.revcounter == 2 %} and {% elif not forloop.last %}, {% endif %}
{% endfor %}
<a class="btn btn-primary" href="{% url 'edit-talk' talk.slug %}">edit</a>

View File

@ -7,6 +7,6 @@
<b>Biography:</b>
<p>{{ user.biography }}</p>
<a href="{% url 'list-talks-by-speaker' user.username %}">See the list of talks with this speaker</a>
<a href="{% url 'list-talks-by-speaker' user.user.username %}">See the list of talks with this speaker</a>
{% endblock %}

View File

@ -14,9 +14,14 @@ def home(request):
@login_required
def talk_list(request):
talks = Talk.on_site.all()
mine = talks.filter(speakers=request.user)
others = talks.exclude(speakers=request.user)
speaker = PonyConfSpeaker.on_site.filter(user=request.user.ponyconfuser)
if speaker.exists():
speaker = speaker.first()
mine = Talk.on_site.filter(speakers=speaker)
others = Talk.on_site.exclude(speakers=speaker)
else:
mine = []
others = Talk.on_site.all()
return render(request, 'proposals/talks.html', {
'my_talks': mine,
'other_talks': others,
@ -33,7 +38,7 @@ def talk_list_by_topic(request, topic):
@login_required
def talk_list_by_speaker(request, speaker):
speaker = get_object_or_404(User, username=speaker)
speaker = get_object_or_404(PonyConfSpeaker, user__user__username=speaker)
talks = Talk.on_site.filter(speakers=speaker)
return render(request, 'proposals/talk_list.html', {
'title': 'Talks with %s:' % (speaker.get_full_name() or speaker.username),
@ -50,14 +55,16 @@ def talk_edit(request, talk=None):
raise PermissionDenied()
form = TalkForm(request.POST or None, instance=talk)
if request.method == 'POST' and form.is_valid():
if talk:
if hasattr(talk, 'id'):
talk = form.save()
messages.success(request, 'Talk modified successfully!')
else:
site = get_current_site(request)
talk = form.save(commit=False)
talk.site = get_current_site(request)
talk.site = site
talk.save()
speach = Speach(user=request.user,talk=talk,order=1)
speaker = PonyConfSpeaker.on_site.get_or_create(user=request.user.ponyconfuser, site=site)[0]
speach = Speach(user=speaker, talk=talk, order=1)
speach.save()
messages.success(request, 'Talk proposed successfully!')
return redirect('show-talk', talk.slug)
@ -81,15 +88,14 @@ def topic_list(request):
@login_required
def speaker_list(request):
talks = Talk.on_site.all()
speakers = User.objects.filter(talks__in=talks) # FIXME
speakers = PonyConfSpeaker.on_site.all()
return render(request, 'proposals/speaker_list.html', {
'speaker': speakers,
})
@login_required
def user_details(request, username):
user = get_object_or_404(User, username=username)
user = get_object_or_404(PonyConfUser, user__username=username)
return render(request, 'proposals/user_details.html', {
'user': user,
})

View File

@ -2,3 +2,4 @@ django<1.10
django-bower
django-bootstrap3
django-autoslug
django-registration-redux