accounts profile

This commit is contained in:
Élie Bouttier 2017-11-19 21:42:45 +01:00
parent ebec3575c4
commit 6610f86801
15 changed files with 236 additions and 27 deletions

1
accounts/__init__.py Normal file
View File

@ -0,0 +1 @@
default_app_config = 'accounts.apps.AccountsConfig'

8
accounts/apps.py Normal file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'
def ready(self):
import accounts.signals # noqa

20
accounts/forms.py Normal file
View File

@ -0,0 +1,20 @@
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext_lazy as _
from django.forms.models import modelform_factory
from .models import User, Profile
# email MUST be validated, we do not allow to edit it
UserForm = modelform_factory(User, fields=['first_name', 'last_name', 'username'])
ProfileForm = modelform_factory(Profile, fields=[
'phone_number', 'biography', 'twitter', 'website',
'linkedin', 'facebook', 'mastodon'])
class EmailAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].label = _('Email address')

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-11-18 20:14
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def profile_forward(apps, schema_editor):
User = apps.get_model(settings.AUTH_USER_MODEL)
Profile = apps.get_model("accounts", "Profile")
db_alias = schema_editor.connection.alias
for user in User.objects.using(db_alias).all():
Profile.objects.using(db_alias).get_or_create(user=user)
def profile_backward(apps, schema_editor):
pass
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', models.CharField(blank=True, default='', max_length=16, verbose_name='Phone number')),
('sms_prefered', models.BooleanField(default=False, verbose_name='SMS prefered')),
('biography', models.TextField(blank=True, verbose_name='Biography')),
('twitter', models.CharField(blank=True, default='', max_length=100, verbose_name='Twitter')),
('linkedin', models.CharField(blank=True, default='', max_length=100, verbose_name='LinkedIn')),
('github', models.CharField(blank=True, default='', max_length=100, verbose_name='Github')),
('website', models.CharField(blank=True, default='', max_length=100, verbose_name='Website')),
('facebook', models.CharField(blank=True, default='', max_length=100, verbose_name='Facebook')),
('mastodon', models.CharField(blank=True, default='', max_length=100, verbose_name='Mastodon')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.RunPython(profile_forward, profile_backward),
]

View File

25
accounts/models.py Normal file
View File

@ -0,0 +1,25 @@
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Profile(models.Model):
user = models.OneToOneField(User)
phone_number = models.CharField(max_length=16, blank=True, default='', verbose_name=_('Phone number'))
sms_prefered = models.BooleanField(default=False, verbose_name=_('SMS prefered'))
biography = models.TextField(blank=True, verbose_name=_('Biography'))
twitter = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Twitter'))
linkedin = models.CharField(max_length=100, blank=True, default='', verbose_name=_('LinkedIn'))
github = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Github'))
website = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Website'))
facebook = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Facebook'))
mastodon = models.CharField(max_length=100, blank=True, default='', verbose_name=_('Mastodon'))
def __str__(self):
return self.user.get_full_name() or self.user.username
def get_absolute_url(self):
return reverse('profile')

35
accounts/signals.py Normal file
View File

@ -0,0 +1,35 @@
from django.contrib import messages
#from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in, user_logged_out
#from django.contrib.sites.shortcuts import get_current_site
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
#from django.utils.translation import ugettext_noop
from ponyconf.decorators import disable_for_loaddata
from .models import User, Profile
@receiver(user_logged_in)
def on_user_logged_in(sender, request, user, **kwargs):
#participation, created = Participation.objects.get_or_create(user=user, site=get_current_site(request))
#if user.is_superuser:
# participation.orga = True
# participation.save()
#if created:
# messages.info(request, "Please check your profile!\n", fail_silently=True) # FIXME
messages.success(request, _('Welcome!'), fail_silently=True) # FIXME
@receiver(user_logged_out)
def on_user_logged_out(sender, request, **kwargs):
messages.success(request, _('Goodbye!'), fail_silently=True) # FIXME
@receiver(post_save, sender=User, weak=False, dispatch_uid='create_profile')
@disable_for_loaddata
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)

View File

@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load bootstrap3 i18n %}
{% block profiletab %} class="active"{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3>{% trans "Profile" %}</h3>
</div>
<div class="panel-body">
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% for form in forms %}
{% bootstrap_form form layout="horizontal" %}
{% endfor %}
{% buttons layout="horizontal" %}
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
{% for url, class, text in buttons %}
<a href="{% url url %}" class="btn btn-{{ class }}">{{ text }}</a>
{% endfor %}
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% url 'home' %}{% endif %}" class="btn btn-default">{% trans "Cancel" %}</a>
{% endbuttons %}
</form>
</div>
</div>
{% endblock %}
{% block css %}
{{ block.super }}
{% for form in forms %}{{ form.media.css }}{% endfor %}
{% endblock %}
{% block js_end %}
{{ block.super }}
{% for form in forms %}{{ form.media.js }}{% endfor %}
{% endblock %}

15
accounts/urls.py Normal file
View File

@ -0,0 +1,15 @@
from django.conf import settings
from django.conf.urls import include, url
from django.contrib.auth import views as auth_views
from . import views
urlpatterns = [
url(r'^profile/$', views.profile, name='profile'),
url(r'accounts/login/', views.EmailLoginView.as_view(), {'extra_context': {'buttons': [views.RESET_PASSWORD_BUTTON]}}, name='login'),
#url(r'^login/$', auth_views.login, {'extra_context': {'buttons': [views.RESET_PASSWORD_BUTTON]}}, name='login'),
url(r'^logout/$', auth_views.logout, {'next_page': settings.LOGOUT_REDIRECT_URL}, name='logout'),
#url(r'^avatar/', include('avatar.urls')),
url(r'', include('django.contrib.auth.urls')),
#url(r'', include('registration.backends.default.urls')),
]

34
accounts/views.py Normal file
View File

@ -0,0 +1,34 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import LoginView
from django.utils.translation import ugettext as _
from django.shortcuts import redirect, render
from django.contrib import messages
from accounts.models import User, Profile
from accounts.forms import UserForm, ProfileForm, EmailAuthenticationForm
RESET_PASSWORD_BUTTON = ('password_reset', 'warning', _('Reset your password'))
CHANGE_PASSWORD_BUTTON = ('password_change', 'warning', _('Change password'))
class EmailLoginView(LoginView):
authentication_form = EmailAuthenticationForm
@login_required
def profile(request):
user_form = UserForm(request.POST or None, instance=request.user)
profile_form = ProfileForm(request.POST or None, instance=request.user.profile)
forms = [user_form, profile_form]
if request.method == 'POST':
if all(map(lambda form: form.is_valid(), forms)):
for form in forms:
form.save()
messages.success(request, _('Profile updated successfully.'))
else:
messages.error(request, _('Please correct those errors.'))
return render(request, 'accounts/profile.html', {
'forms': forms,
'buttons': [CHANGE_PASSWORD_BUTTON],
})

View File

@ -27,10 +27,10 @@ urlpatterns = [
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/confirm/$', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm'), url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/confirm/$', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm'),
url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/desist/$', views.talk_acknowledgment, {'confirm': False}, name='talk-desist'), url(r'^cfp/(?P<talk_id>[\w\-]+)/(?P<participant_id>[\w\-]+)/desist/$', views.talk_acknowledgment, {'confirm': False}, name='talk-desist'),
# End backward compatibility # End backward compatibility
url(r'^volunteer/$', views.volunteer_enrole, name='volunteer-enrole'), url(r'^volunteer/enrole/$', views.volunteer_enrole, name='volunteer-enrole'),
url(r'^volunteer/(?P<volunteer_token>[\w\-]+)/$', views.volunteer_home, name='volunteer-home'), url(r'^volunteer/(?:(?P<volunteer_token>[\w\-]+)/)?$', views.volunteer_home, name='volunteer-home'),
url(r'^volunteer/(?P<volunteer_token>[\w\-]+)/join/(?P<activity>[\w\-]+)/$', views.volunteer_update_activity, {'join': True}, name='volunteer-join'), url(r'^volunteer/(?:(?P<volunteer_token>[\w\-]+)/)?join/(?P<activity>[\w\-]+)/$', views.volunteer_update_activity, {'join': True}, name='volunteer-join'),
url(r'^volunteer/(?P<volunteer_token>[\w\-]+)/quit/(?P<activity>[\w\-]+)/$', views.volunteer_update_activity, {'join': False}, name='volunteer-quit'), url(r'^volunteer/(?:(?P<volunteer_token>[\w\-]+)/)?quit/(?P<activity>[\w\-]+)/$', views.volunteer_update_activity, {'join': False}, name='volunteer-quit'),
#url(r'^talk/(?P<talk_id>[\w\-]+)/$', views.talk_show, name='show-talk'), #url(r'^talk/(?P<talk_id>[\w\-]+)/$', views.talk_show, name='show-talk'),
#url(r'^speaker/(?P<participant_id>[\w\-]+)/$', views.speaker_show, name='show-speaker'), #url(r'^speaker/(?P<participant_id>[\w\-]+)/$', views.speaker_show, name='show-speaker'),
url(r'^staff/$', views.staff, name='staff'), url(r'^staff/$', views.staff, name='staff'),

View File

@ -49,9 +49,10 @@ def volunteer_enrole(request):
if Volunteer.objects.filter(site=request.conference.site, email=request.user.email).exists(): if Volunteer.objects.filter(site=request.conference.site, email=request.user.email).exists():
return redirect(reverse('volunteer-home')) return redirect(reverse('volunteer-home'))
elif not request.POST: elif not request.POST:
# TODO: import biography, phone number and sms_prefered from User profile
initial.update({ initial.update({
'name': request.user.get_full_name(), 'name': request.user.get_full_name(),
'phone_number': request.user.profile.phone_number,
'sms_prefered': request.user.profile.sms_prefered,
}) })
form = VolunteerForm(request.POST or None, initial=initial, conference=request.conference) form = VolunteerForm(request.POST or None, initial=initial, conference=request.conference)
if request.user.is_authenticated(): if request.user.is_authenticated():
@ -174,9 +175,9 @@ def proposal_home(request):
if Participant.objects.filter(site=request.conference.site, email=request.user.email).exists(): if Participant.objects.filter(site=request.conference.site, email=request.user.email).exists():
return redirect(reverse('proposal-dashboard')) return redirect(reverse('proposal-dashboard'))
elif not request.POST: elif not request.POST:
# TODO: import biography from User profile
initial.update({ initial.update({
'name': request.user.get_full_name(), 'name': request.user.get_full_name(),
'biography': request.user.profile.biography,
}) })
fields.remove('email') fields.remove('email')
NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=fields) NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=fields)

View File

@ -37,8 +37,8 @@ INSTALLED_APPS = [
'django.contrib.sites', 'django.contrib.sites',
# our apps # our apps
#'accounts',
'ponyconf', 'ponyconf',
'accounts',
'cfp', 'cfp',
'mailing', 'mailing',
#'planning', #'planning',

View File

@ -43,12 +43,7 @@
<li{% block stafftab %}{% endblock %}><a href="{% url 'staff' %}"><span class="glyphicon glyphicon-blackboard"></span>&nbsp;{% trans "Organisation" %}</a></li> <li{% block stafftab %}{% endblock %}><a href="{% url 'staff' %}"><span class="glyphicon glyphicon-blackboard"></span>&nbsp;{% trans "Organisation" %}</a></li>
<li{% block admintab %}{% endblock %}><a href="{% url 'admin' %}"><span class="glyphicon glyphicon-cog"></span>&nbsp;{% trans "Administration" %}</a></li> <li{% block admintab %}{% endblock %}><a href="{% url 'admin' %}"><span class="glyphicon glyphicon-cog"></span>&nbsp;{% trans "Administration" %}</a></li>
{% endif %} {% endif %}
{% comment %}
<li{% block talkstab %}{% endblock %}><a href="{% url 'talk-list' %}"><span class="glyphicon glyphicon-blackboard"></span>&nbsp;{% trans "Talks" %}</a></li>
<li{% block speakerstab %}{% endblock %}><a href="{% url 'participant-list' %}"><span class="glyphicon glyphicon-bullhorn"></span>&nbsp;{% trans "Speakers" %}</a></li>
<li{% block inboxtab %}{% endblock %}><a href="{% url 'inbox' %}"><span class="glyphicon glyphicon-envelope"></span>&nbsp;Inbox</a></li>
<li{% block profiletab %}{% endblock %}><a href="{% url 'profile' %}"><span class="glyphicon glyphicon-user"></span>&nbsp;{% trans "Profile" %}</a></li> <li{% block profiletab %}{% endblock %}><a href="{% url 'profile' %}"><span class="glyphicon glyphicon-user"></span>&nbsp;{% trans "Profile" %}</a></li>
{% endcomment %}
<li><a href="{% url 'logout' %}"><span class="glyphicon glyphicon-log-out"></span>&nbsp;{% trans "Logout" %}</a></li> <li><a href="{% url 'logout' %}"><span class="glyphicon glyphicon-log-out"></span>&nbsp;{% trans "Logout" %}</a></li>
{% else %} {% else %}
<li{% block logintab %}{% endblock %}><a href="{% url 'staff' %}"><span class="glyphicon glyphicon-log-in"></span>&nbsp;{% trans "Staff" %}</a></li> <li{% block logintab %}{% endblock %}><a href="{% url 'staff' %}"><span class="glyphicon glyphicon-log-in"></span>&nbsp;{% trans "Staff" %}</a></li>

View File

@ -15,26 +15,12 @@ Including another URLconf
""" """
from django.conf.urls import include, url from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.views import LoginView
from django.contrib.auth.forms import AuthenticationForm
from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
class EmailAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].label = _('Email address')
class EmailLoginView(LoginView):
authentication_form = EmailAuthenticationForm
urlpatterns = [ urlpatterns = [
url(r'^admin/django/', admin.site.urls), url(r'^admin/django/', admin.site.urls),
url(r'accounts/login/', EmailLoginView.as_view()), url(r'^accounts/', include('accounts.urls')),
url(r'accounts/', include('django.contrib.auth.urls')),
url(r'^', include('cfp.urls')), url(r'^', include('cfp.urls')),
] ]