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\-]+)/desist/$', views.talk_acknowledgment, {'confirm': False}, name='talk-desist'),
# End backward compatibility
url(r'^volunteer/$', 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\-]+)/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/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\-]+)/)?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'^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'^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():
return redirect(reverse('volunteer-home'))
elif not request.POST:
# TODO: import biography, phone number and sms_prefered from User profile
initial.update({
'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)
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():
return redirect(reverse('proposal-dashboard'))
elif not request.POST:
# TODO: import biography from User profile
initial.update({
'name': request.user.get_full_name(),
'biography': request.user.profile.biography,
})
fields.remove('email')
NewSpeakerForm = modelform_factory(Participant, form=ParticipantForm, fields=fields)

View File

@ -37,8 +37,8 @@ INSTALLED_APPS = [
'django.contrib.sites',
# our apps
#'accounts',
'ponyconf',
'accounts',
'cfp',
'mailing',
#'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 admintab %}{% endblock %}><a href="{% url 'admin' %}"><span class="glyphicon glyphicon-cog"></span>&nbsp;{% trans "Administration" %}</a></li>
{% 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>
{% endcomment %}
<li><a href="{% url 'logout' %}"><span class="glyphicon glyphicon-log-out"></span>&nbsp;{% trans "Logout" %}</a></li>
{% else %}
<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.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
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 = [
url(r'^admin/django/', admin.site.urls),
url(r'accounts/login/', EmailLoginView.as_view()),
url(r'accounts/', include('django.contrib.auth.urls')),
url(r'^accounts/', include('accounts.urls')),
url(r'^', include('cfp.urls')),
]