From c0a66f9a38272a6511187abe53840e8a1cf3abbe Mon Sep 17 00:00:00 2001 From: Skia Date: Sat, 13 Aug 2016 05:33:09 +0200 Subject: [PATCH] Finish profile of users --- core/management/commands/populate.py | 15 ++- core/migrations/0007_auto_20160813_0522.py | 119 +++++++++++++++++++++ core/models.py | 50 +++++---- core/static/core/style.css | 1 + core/templates/core/base.jinja | 2 +- core/templates/core/user_base.jinja | 2 +- core/templates/core/user_edit.jinja | 4 + core/views/forms.py | 45 ++++++-- requirements.txt | 1 + 9 files changed, 208 insertions(+), 31 deletions(-) create mode 100644 core/migrations/0007_auto_20160813_0522.py diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index e82f56b2..cb5b30b5 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -32,7 +32,7 @@ class Command(BaseCommand): for g in settings.SITH_GROUPS.values(): Group(id=g['id'], name=g['name']).save() self.reset_index("core", "auth") - root = User(username='root', last_name="", first_name="Bibou", + root = User(id=0, username='root', last_name="", first_name="Bibou", email="ae.info@utbm.fr", date_of_birth="1942-06-12", is_superuser=True, is_staff=True) @@ -76,6 +76,19 @@ class Command(BaseCommand): p.save() PageRev(page=p, title="Wiki index", author=root, content=""" Welcome to the wiki page! +""").save() + + p = Page(name="services") + p.set_lock(root) + p.save() + p.view_groups=[settings.SITH_GROUPS['public']['id']] + p.set_lock(root) + PageRev(page=p, title="Services", author=root, content=""" +| | | | +| :---: | :---: | :---: | :---: | +| [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | [Fichiers](/file) | +| SAS | Weekmail | Forum | | + """).save() p = Page(name="launderette") diff --git a/core/migrations/0007_auto_20160813_0522.py b/core/migrations/0007_auto_20160813_0522.py new file mode 100644 index 00000000..43b44e10 --- /dev/null +++ b/core/migrations/0007_auto_20160813_0522.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import phonenumber_field.modelfields +from django.utils.timezone import utc +import datetime +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_auto_20160811_0450'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='address', + field=models.CharField(default='', verbose_name='address', max_length=128, blank=True), + ), + migrations.AddField( + model_name='user', + name='last_update', + field=models.DateField(default=datetime.datetime(2016, 8, 13, 3, 22, 21, 699918, tzinfo=utc), verbose_name='last update', auto_now=True), + preserve_default=False, + ), + migrations.AddField( + model_name='user', + name='parent_address', + field=models.CharField(default='', verbose_name='parent address', max_length=128, blank=True), + ), + migrations.AddField( + model_name='user', + name='parent_phone', + field=phonenumber_field.modelfields.PhoneNumberField(blank=True, verbose_name='parent phone', max_length=128, null=True), + ), + migrations.AddField( + model_name='user', + name='phone', + field=phonenumber_field.modelfields.PhoneNumberField(blank=True, verbose_name='phone', max_length=128, null=True), + ), + migrations.AddField( + model_name='user', + name='second_email', + field=models.EmailField(blank=True, verbose_name='second email address', max_length=254, null=True), + ), + migrations.AlterField( + model_name='user', + name='avatar_pict', + field=models.OneToOneField(verbose_name='avatar', on_delete=django.db.models.deletion.SET_NULL, related_name='avatar_of', blank=True, to='core.SithFile', null=True), + ), + migrations.AlterField( + model_name='user', + name='department', + field=models.CharField(default='NA', verbose_name='department', max_length=15, choices=[('TC', 'TC'), ('IMSI', 'IMSI'), ('IMAP', 'IMAP'), ('INFO', 'INFO'), ('GI', 'GI'), ('E', 'E'), ('EE', 'EE'), ('GESC', 'GESC'), ('GMC', 'GMC'), ('MC', 'MC'), ('EDIM', 'EDIM'), ('HUMA', 'Humanities'), ('NA', 'N/A')], blank=True), + ), + migrations.AlterField( + model_name='user', + name='dpt_option', + field=models.CharField(default='', verbose_name='dpt option', max_length=32, blank=True), + ), + migrations.AlterField( + model_name='user', + name='first_name', + field=models.CharField(verbose_name='first name', max_length=64), + ), + migrations.AlterField( + model_name='user', + name='forum_signature', + field=models.TextField(default='', verbose_name='forum signature', max_length=256, blank=True), + ), + migrations.AlterField( + model_name='user', + name='last_name', + field=models.CharField(verbose_name='last name', max_length=64), + ), + migrations.AlterField( + model_name='user', + name='nick_name', + field=models.CharField(blank=True, verbose_name='nick name', max_length=64, null=True), + ), + migrations.AlterField( + model_name='user', + name='profile_pict', + field=models.OneToOneField(verbose_name='profile', on_delete=django.db.models.deletion.SET_NULL, related_name='profile_of', blank=True, to='core.SithFile', null=True), + ), + migrations.AlterField( + model_name='user', + name='quote', + field=models.CharField(default='', verbose_name='quote', max_length=256, blank=True), + ), + migrations.AlterField( + model_name='user', + name='role', + field=models.CharField(default='', verbose_name='role', max_length=15, choices=[('STUDENT', 'Student'), ('ADMINISTRATIVE', 'Administrative agent'), ('TEACHER', 'Teacher'), ('AGENT', 'Agent'), ('DOCTOR', 'Doctor'), ('FORMER STUDENT', 'Former student'), ('SERVICE', 'Service')], blank=True), + ), + migrations.AlterField( + model_name='user', + name='school', + field=models.CharField(default='', verbose_name='school', max_length=80, blank=True), + ), + migrations.AlterField( + model_name='user', + name='scrub_pict', + field=models.OneToOneField(verbose_name='scrub', on_delete=django.db.models.deletion.SET_NULL, related_name='scrub_of', blank=True, to='core.SithFile', null=True), + ), + migrations.AlterField( + model_name='user', + name='semester', + field=models.CharField(default='', verbose_name='semester', max_length=5, blank=True), + ), + migrations.AlterField( + model_name='user', + name='tshirt_size', + field=models.CharField(default='-', verbose_name='tshirt size', max_length=5, choices=[('-', '-'), ('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL'), ('XXXL', 'XXXL')]), + ), + ] diff --git a/core/models.py b/core/models.py index 15229a8a..5be5fb86 100644 --- a/core/models.py +++ b/core/models.py @@ -7,6 +7,8 @@ from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.conf import settings from django.db import transaction +from phonenumber_field.modelfields import PhoneNumberField + from datetime import datetime, timedelta, date import unicodedata @@ -82,11 +84,11 @@ class User(AbstractBaseUser): 'unique': _("A user with that username already exists."), }, ) - first_name = models.CharField(_('first name'), max_length=30) - last_name = models.CharField(_('last name'), max_length=30) + first_name = models.CharField(_('first name'), max_length=64) + last_name = models.CharField(_('last name'), max_length=64) email = models.EmailField(_('email address'), unique=True) date_of_birth = models.DateField(_('date of birth'), blank=True, null=True) - nick_name = models.CharField(_('nick name'), max_length=30, blank=True) + nick_name = models.CharField(_('nick name'), max_length=64, null=True, blank=True) is_staff = models.BooleanField( _('staff status'), default=False, @@ -101,6 +103,7 @@ class User(AbstractBaseUser): ), ) date_joined = models.DateField(_('date joined'), auto_now_add=True) + last_update = models.DateField(_('last update'), auto_now=True) is_superuser = models.BooleanField( _('superuser'), default=False, @@ -110,9 +113,12 @@ class User(AbstractBaseUser): ) groups = models.ManyToManyField(RealGroup, related_name='users', blank=True) home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True) - profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, blank=True) - avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, blank=True) - scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True, blank=True) + profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, + blank=True, on_delete=models.SET_NULL) + avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, + blank=True, on_delete=models.SET_NULL) + scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True, + blank=True, on_delete=models.SET_NULL) sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN") tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[ ("-", _("-")), @@ -123,7 +129,7 @@ class User(AbstractBaseUser): ("XL", _("XL")), ("XXL", _("XXL")), ("XXXL", _("XXXL")), - ], default="M") + ], default="-") role = models.CharField(_("role"), max_length=15, choices=[ ("STUDENT", _("Student")), ("ADMINISTRATIVE", _("Administrative agent")), @@ -132,7 +138,7 @@ class User(AbstractBaseUser): ("DOCTOR", _("Doctor")), ("FORMER STUDENT", _("Former student")), ("SERVICE", _("Service")), - ], default="STUDENT") + ], blank=True, default="") department = models.CharField(_("department"), max_length=15, choices=[ ("TC", _("TC")), ("IMSI", _("IMSI")), @@ -145,16 +151,20 @@ class User(AbstractBaseUser): ("GMC", _("GMC")), ("MC", _("MC")), ("EDIM", _("EDIM")), - ("HUMAN", _("Humanities")), + ("HUMA", _("Humanities")), ("NA", _("N/A")), - ], default="NA") - dpt_option = models.CharField(_("dpt option"), max_length=32, null=True, blank=True) - semester = models.CharField(_("semester"), max_length=5, null=True, blank=True) - quote = models.CharField(_("quote"), max_length=64, null=True, blank=True) - school = models.CharField(_("school"), max_length=32, null=True, blank=True) + ], default="NA", blank=True) + dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="") + semester = models.CharField(_("semester"), max_length=5, blank=True, default="") + quote = models.CharField(_("quote"), max_length=256, blank=True, default="") + school = models.CharField(_("school"), max_length=80, blank=True, default="") promo = models.IntegerField(_("promo"), validators=[validate_promo], null=True, blank=True) - forum_signature = models.TextField(_("forum signature"), max_length=256, null=True, blank=True) - # TODO: add phone numbers with https://github.com/stefanfoulis/django-phonenumber-field + forum_signature = models.TextField(_("forum signature"), max_length=256, blank=True, default="") + second_email = models.EmailField(_('second email address'), null=True, blank=True) + phone = PhoneNumberField(_("phone"), null=True, blank=True) + parent_phone = PhoneNumberField(_("parent phone"), null=True, blank=True) + address = models.CharField(_("address"), max_length=128, blank=True, default="") + parent_address = models.CharField(_("parent address"), max_length=128, blank=True, default="") objects = UserManager() @@ -217,7 +227,7 @@ class User(AbstractBaseUser): with transaction.atomic(): if self.id: old = User.objects.filter(id=self.id).first() - if old.username != self.username: + if old and old.username != self.username: self._change_username(self.username) super(User, self).save(*args, **kwargs) @@ -263,8 +273,8 @@ class User(AbstractBaseUser): Returns the display name of the user. A nickname if possible, otherwise, the full name """ - if self.nick_name != "": - return self.nick_name + if self.nick_name: + return "%s (%s)" % (self.get_full_name(), self.nick_name) return self.get_full_name() def email_user(self, subject, message, from_email=None, **kwargs): @@ -463,7 +473,7 @@ class SithFile(models.Model): if attr == "is_file": return not self.is_folder else: - return object.__getattribute__(self, attr) + return super(SithFile, self).__getattribute__(attr) def __str__(self): if self.is_folder: diff --git a/core/static/core/style.css b/core/static/core/style.css index 1f9244cd..3f333ea0 100644 --- a/core/static/core/style.css +++ b/core/static/core/style.css @@ -60,6 +60,7 @@ nav a:hover { margin: 0px auto; padding: 1em 1%; background: white; + overflow: auto; } h1, h2, h3, h4, h5, h6 { diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja index 815e60a0..6b57a300 100644 --- a/core/templates/core/base.jinja +++ b/core/templates/core/base.jinja @@ -37,7 +37,7 @@ {% trans %}Wiki{% endtrans %} {% trans %}Pages{% endtrans %} {% trans %}Clubs{% endtrans %} - {% trans %}Services{% endtrans %} + {% trans %}Services{% endtrans %} {% endif %} {% endblock %} diff --git a/core/templates/core/user_base.jinja b/core/templates/core/user_base.jinja index 9e2250c3..d6e3a356 100644 --- a/core/templates/core/user_base.jinja +++ b/core/templates/core/user_base.jinja @@ -2,6 +2,7 @@ {% block content %}
+
{{ profile.get_display_name() }}
{% trans %}Infos{% endtrans %} {% if can_edit(profile, request.user) or user.id == profile.id %} @@ -16,7 +17,6 @@ {% trans %}Account{% endtrans %} {% endif %}
-
{{ profile.get_display_name() }}

diff --git a/core/templates/core/user_edit.jinja b/core/templates/core/user_edit.jinja index d862c150..c50f28f5 100644 --- a/core/templates/core/user_edit.jinja +++ b/core/templates/core/user_edit.jinja @@ -1,5 +1,9 @@ {% extends "core/user_base.jinja" %} +{% block title %} +{% trans %}Edit user{% endtrans %} +{% endblock %} + {% block infos %}

{% trans %}Edit user profile{% endtrans %}

diff --git a/core/views/forms.py b/core/views/forms.py index 6e2fcaf4..92254d67 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -5,10 +5,13 @@ from django.core.exceptions import ValidationError from django.contrib.auth import logout, login, authenticate from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput from django.utils.translation import ugettext as _ +from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget + import logging from core.models import User, Page, RealGroup, SithFile + # Widgets class SelectSingle(Select): @@ -67,6 +70,26 @@ class RegisteringForm(UserCreationForm): user.save() return user +# Image utils + +from io import BytesIO +from PIL import Image + +def scale_dimension(width, height, long_edge): + if width > height: + ratio = long_edge * 1. / width + else: + ratio = long_edge * 1. / height + return int(width * ratio), int(height * ratio) + +def resize_image(im, edge, format): + from django.core.files.base import ContentFile + (w, h) = im.size + (width, height) = scale_dimension(w, h, long_edge=edge) + content = BytesIO() + im.resize((width, height), Image.ANTIALIAS).save(fp=content, format=format, dpi=[72, 72]) + return ContentFile(content.getvalue()) + class UserProfileForm(forms.ModelForm): """ Form handling the user profile, managing the files @@ -76,13 +99,16 @@ class UserProfileForm(forms.ModelForm): class Meta: model = User fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict', - 'scrub_pict', 'sex', 'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', - 'promo', 'forum_signature'] + 'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone', + 'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo', + 'forum_signature'] widgets = { 'date_of_birth': SelectDate, 'profile_pict': forms.ClearableFileInput, 'avatar_pict': forms.ClearableFileInput, 'scrub_pict': forms.ClearableFileInput, + 'phone': PhoneNumberInternationalFallbackWidget, + 'parent_phone': PhoneNumberInternationalFallbackWidget, } labels = { 'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"), @@ -111,13 +137,12 @@ class UserProfileForm(forms.ModelForm): parent = SithFile.objects.filter(parent=None, name="profiles").first() for field,f in files: with transaction.atomic(): - new_file = SithFile(parent=parent, name=self.generate_name(field, f), file=f, owner=self.instance, is_folder=False, - mime_type=f.content_type, size=f._size) try: - if not (f.content_type == "image/jpeg" or - f.content_type == "image/png" or - f.content_type == "image/gif"): - raise ValidationError(_("Bad image format, only jpeg, png, and gif are accepted")) + im = Image.open(BytesIO(f.read())) + new_file = SithFile(parent=parent, name=self.generate_name(field, f), + file=resize_image(im, 400, f.content_type.split('/')[-1]), + owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size) + new_file.file.name = new_file.name old = SithFile.objects.filter(parent=parent, name=new_file.name).first() if old: old.delete() @@ -129,6 +154,10 @@ class UserProfileForm(forms.ModelForm): self._errors.pop(field, None) self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': str(e.message)}) + except IOError: + self._errors.pop(field, None) + self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % + {'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")}) self._post_clean() class UserPropForm(forms.ModelForm): diff --git a/requirements.txt b/requirements.txt index 5b2c3451..f414561d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ django-jinja pyopenssl pytz djangorestframework +django-phonenumber-field