Commit 43b709bf authored by Skia's avatar Skia

Make file modale chooser and complete user profile

parent a8858fa7
Pipeline #110 failed with stage
in 1 minute and 52 seconds
......@@ -53,7 +53,7 @@
{% endif %}
<td>{{ o.remark }}</td>
{% if o.invoice %}
<td><a href="{{ url('core:download') + '?file=' + o.invoice.name }}">{{ o.invoice.name }}</a></td>
<td><a href="{{ url('core:download', file_id=o.invoice.id) }}">{{ o.invoice.name }}</a></td>
{% else %}
<td>-</td>
{% endif %}
......
......@@ -4,10 +4,9 @@ from django.shortcuts import render
from django.core.urlresolvers import reverse_lazy
from django.forms.models import modelform_factory
from django.forms import HiddenInput
from django.forms.extras.widgets import SelectDateWidget
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
from core.views.forms import SelectFile
from core.views.forms import SelectFile, SelectDate
from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company
# Accounting types
......@@ -168,7 +167,7 @@ class OperationCreateView(CanCreateMixin, CreateView):
form_class = modelform_factory(Operation,
fields=['amount', 'label', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode',
'cheque_number', 'invoice', 'accounting_type', 'done'],
widgets={'journal': HiddenInput, 'date': SelectDateWidget})
widgets={'journal': HiddenInput, 'date': SelectDate})
template_name = 'core/create.jinja'
def get_initial(self):
......@@ -188,7 +187,7 @@ class OperationEditView(CanEditMixin, UpdateView):
form_class = modelform_factory(Operation,
fields = ['amount', 'label', 'remark', 'target_type', 'target_id', 'target_label', 'date', 'mode', 'cheque_number',
'invoice', 'accounting_type', 'done'],
widgets={'date': SelectDateWidget, 'invoice': SelectFile})
widgets={'date': SelectDate, 'invoice': SelectFile})
template_name = 'core/edit.jinja'
# Company views
......
......@@ -38,6 +38,7 @@ class Command(BaseCommand):
is_superuser=True, is_staff=True)
root.set_password("plop")
root.save()
SithFile(parent=None, name="profiles", is_folder=True, owner=root).save()
home_root = SithFile(parent=None, name="users", is_folder=True, owner=root)
home_root.save()
club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root)
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_user_home'),
]
operations = [
migrations.AddField(
model_name='user',
name='avatar_pict',
field=models.OneToOneField(related_name='avatar_of', verbose_name='avatar', to='core.SithFile', null=True, blank=True),
),
migrations.AddField(
model_name='user',
name='profile_pict',
field=models.OneToOneField(related_name='profile_of', verbose_name='profile', to='core.SithFile', null=True, blank=True),
),
migrations.AddField(
model_name='user',
name='scrub_pict',
field=models.OneToOneField(related_name='scrub_of', verbose_name='scrub', to='core.SithFile', null=True, blank=True),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0003_auto_20160810_1949'),
]
operations = [
migrations.AlterField(
model_name='user',
name='nick_name',
field=models.CharField(verbose_name='nick name', max_length=30, blank=True),
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import core.models
class Migration(migrations.Migration):
dependencies = [
('core', '0004_auto_20160811_0206'),
]
operations = [
migrations.AddField(
model_name='user',
name='department',
field=models.CharField(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'), ('HUMAN', 'Humanities'), ('NA', 'N/A')], default='NA', verbose_name='department'),
),
migrations.AddField(
model_name='user',
name='dpt_option',
field=models.CharField(max_length=32, default='', verbose_name='dpt option'),
),
migrations.AddField(
model_name='user',
name='forum_signature',
field=models.TextField(max_length=256, default='', verbose_name='forum signature'),
),
migrations.AddField(
model_name='user',
name='promo',
field=models.IntegerField(blank=True, null=True, validators=[core.models.validate_promo], verbose_name='promo'),
),
migrations.AddField(
model_name='user',
name='quote',
field=models.CharField(max_length=64, default='', verbose_name='quote'),
),
migrations.AddField(
model_name='user',
name='role',
field=models.CharField(max_length=15, choices=[('STUDENT', 'Student'), ('ADMINISTRATIVE', 'Administrative agent'), ('TEACHER', 'Teacher'), ('AGENT', 'Agent'), ('DOCTOR', 'Doctor'), ('FORMER STUDENT', 'Former student'), ('SERVICE', 'Service')], default='STUDENT', verbose_name='role'),
),
migrations.AddField(
model_name='user',
name='school',
field=models.CharField(max_length=32, default='', verbose_name='school'),
),
migrations.AddField(
model_name='user',
name='semester',
field=models.CharField(max_length=5, default='', verbose_name='semester'),
),
migrations.AddField(
model_name='user',
name='sex',
field=models.CharField(max_length=10, choices=[('MAN', 'Man'), ('WOMAN', 'Woman')], default='MAN', verbose_name='sex'),
),
migrations.AddField(
model_name='user',
name='tshirt_size',
field=models.CharField(max_length=5, choices=[('-', '-'), ('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL'), ('XXXL', 'XXXL')], default='M', verbose_name='tshirt size'),
),
]
......@@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.conf import settings
from django.db import transaction
from datetime import datetime, timedelta
from datetime import datetime, timedelta, date
import unicodedata
......@@ -46,6 +46,15 @@ class RealGroup(Group):
class Meta:
proxy = True
def validate_promo(value):
start_year = settings.SITH_SCHOOL_START_YEAR
delta = (date.today()+timedelta(days=180)).year - start_year
if value < 0 or delta < value:
raise ValidationError(
_('%(value)s is not a valid promo (between 0 and %(end)s)'),
params={'value': value, 'end': delta},
)
class User(AbstractBaseUser):
"""
Defines the base user class, useable in every app
......@@ -77,7 +86,7 @@ class User(AbstractBaseUser):
last_name = models.CharField(_('last name'), max_length=30)
email = models.EmailField(_('email address'), unique=True)
date_of_birth = models.DateField(_('date of birth'), blank=True, null=True)
nick_name = models.CharField(max_length=30, blank=True)
nick_name = models.CharField(_('nick name'), max_length=30, blank=True)
is_staff = models.BooleanField(
_('staff status'),
default=False,
......@@ -101,6 +110,51 @@ 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)
sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN")
tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[
("-", _("-")),
("XS", _("XS")),
("S", _("S")),
("M", _("M")),
("L", _("L")),
("XL", _("XL")),
("XXL", _("XXL")),
("XXXL", _("XXXL")),
], default="M")
role = models.CharField(_("role"), max_length=15, choices=[
("STUDENT", _("Student")),
("ADMINISTRATIVE", _("Administrative agent")),
("TEACHER", _("Teacher")),
("AGENT", _("Agent")),
("DOCTOR", _("Doctor")),
("FORMER STUDENT", _("Former student")),
("SERVICE", _("Service")),
], default="STUDENT")
department = models.CharField(_("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")),
("HUMAN", _("Humanities")),
("NA", _("N/A")),
], default="NA")
dpt_option = models.CharField(_("dpt option"), max_length=32, default="")
semester = models.CharField(_("semester"), max_length=5, default="")
quote = models.CharField(_("quote"), max_length=64, default="")
school = models.CharField(_("school"), max_length=32, default="")
promo = models.IntegerField(_("promo"), validators=[validate_promo], null=True, blank=True)
forum_signature = models.TextField(_("forum signature"), max_length=256, default="")
# TODO: add phone numbers with https://github.com/stefanfoulis/django-phonenumber-field
objects = UserManager()
......@@ -341,6 +395,15 @@ class SithFile(models.Model):
def is_owned_by(self, user):
return user.id == self.owner.id
def can_be_viewed_by(self, user):
if hasattr(self, 'profile_of'):
return user.can_view(self.profile_of)
if hasattr(self, 'avatar_of'):
return user.can_view(self.avatar_of)
if hasattr(self, 'scrub_of'):
return user.can_view(self.scrub_of)
return False
def delete(self):
for c in self.children.all():
c.delete()
......@@ -422,6 +485,9 @@ class SithFile(models.Model):
def get_display_name(self):
return self.name
def get_download_url(self):
return reverse('core:download', kwargs={'file_id': self.id})
class LockError(Exception):
"""There was a lock error on the object"""
pass
......
......@@ -2,6 +2,11 @@
Super Form Reset
----------------------------------------------------------------------------------------------------*/
form {
margin: 0px auto;
width: 60%;
}
input,
label,
select,
......@@ -22,6 +27,10 @@ textarea
font-family: Arial;
}
label {
min-width: 50%;
}
/* Remove the stupid outer glow in Webkit */
input:focus
{
......@@ -70,7 +79,8 @@ input[type=tel],
input[type=text],
input[type=time],
input[type=url],
input[type=week]
input[type=week],
textarea
{
background-color: white;
border: 1px solid black;
......
console.log('Guy');
$( function() {
dialog = $( ".choose_file_widget" ).dialog({
buttons = $(".choose_file_button");
popups = $(".choose_file_widget");
popups.dialog({
autoOpen: false,
modal: true,
width: "80%",
minHeight: "300",
buttons: {
"Choose": function() {
console.log($("#file_id"));
$("input[name="+$(this).attr('name')+"]").attr('value', $("#file_id").attr('value'));
$( this ).dialog( "close" );
}
}
});
$('.select_date').datepicker({
changeMonth: true,
changeYear: true
});
$( ".choose_file_button" ).button().on( "click", function() {
dialog.dialog( "open" );
popup = popups.filter("[name="+$(this).attr('name')+"]");
console.log(popup);
popup.html('<iframe src="/file/popup" width="95%"></iframe><span id="file_id" value="null" />');
popup.dialog({title: $(this).attr('name')}).dialog( "open" );
});
} );
......@@ -118,12 +118,16 @@ ul, ol {
}
table {
width: 100%;
font-size: 0.90em;
}
td {
padding: 4px;
border: solid 1px black;
border-collapse: collapse;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
max-width: 0;
}
td>ul {
margin-top: 0px;
......@@ -152,7 +156,27 @@ tbody>tr:hover {
}
/*-----------------------------USER PROFILE----------------------------*/
.user_profile {
#user_profile {
width: 80%;
margin: 0px auto;
padding: 10px;
overflow: auto;
}
#user_profile h4 { border-bottom: 1px solid grey; max-width: 60%; }
#user_profile #pictures {
width: 30%;
float: right;
font-style: italic;
}
#user_profile #nickname {
font-style: italic;
}
#user_profile #pictures img {
max-width: 96%;
max-height: 96%;
}
#user_profile .promo_pict {
height: 45px;
}
/*---------------------------------PAGE--------------------------------*/
.page_content {
......
......@@ -60,23 +60,25 @@
{{ tests }}
{% endblock %}
-->
{% block script %}
<script src="{{ static('core/js/jquery-3.1.0.min.js') }}"></script>
<script src="{{ static('core/js/ui/jquery-ui.min.js') }}"></script>
<script src="{{ static('core/js/multiple-select.js') }}"></script>
<script src="{{ static('core/js/script.js') }}"></script>
<script>
$('select:not([multiple])').multipleSelect({
single: true,
{% if not popup %}
position: 'top',
{% endif %}
});
$('select[multiple=multiple]').multipleSelect({
filter: true,
{% if not popup %}
position: 'top',
{% endif %}
});
$('.select_single').multipleSelect({
single: true,
{% if not popup %}
position: 'top',
{% endif %}
});
$('.select_multiple').multipleSelect({
filter: true,
{% if not popup %}
position: 'top',
{% endif %}
});
</script>
{% endblock %}
</body>
</html>
{% extends "core/base.jinja" %}
{% block title %}
{% trans obj=object %}Edit {{ obj }}{% endtrans %}
{% endblock %}
{% block content %}
<h2>{% trans obj=object %}Edit {{ obj }}{% endtrans %}</h2>
<form action="" method="post">
......
......@@ -42,8 +42,18 @@
{% if not file.home_of and not file.home_of_club and file.parent %}
<p><a href="{{ url('core:file_delete', file_id=file.id, popup=popup) }}">{% trans %}Delete{% endtrans %}</a></p>
{% endif %}
{% if popup %}
{% endif %}
{% endblock %}
{% block script %}
{{ super() }}
{% if popup and file.is_file %}
<script>
parent.$("#file_id").replaceWith('<span id="file_id" value="{{ file.id }}">{{ file.name }}</span>');
</script>
{% endif %}
{% endblock %}
......@@ -21,7 +21,9 @@
<form method="post" action="{{ url('core:login') }}">
{% csrf_token %}
{{ form.as_p() }}
<p>{{ form.username.errors }}<label for="{{ form.username.name }}">{{ form.username.label }}
</label><input id="id_username" maxlength="254" name="username" type="text" autofocus="autofocus" /></p>
<p>{{ form.password.errors }}<label for="{{ form.password.name }}">{{ form.password.label }}</label>{{ form.password }}</p>
<input type="hidden" name="next" value="{{ next }}">
<p><input type="submit" value="{% trans %}login{% endtrans %}"></p>
</form>
......
......@@ -5,12 +5,26 @@
{% endblock %}
{% block infos %}
<h3>{% trans %}User Profile{% endtrans %}</h3>
<div class="user_profile">
<div id="user_profile">
<div id="pictures">
{% if profile.profile_pict %}
<img src="{{ profile.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}" />
{% endif %}
<p><em>{{ profile.quote }}</em></p>
</div>
<h4>{{ profile.get_full_name() }}</h4>
<p>{{ profile.nick_name }}</p>
<p id="nickname">&laquo; {{ profile.nick_name }} &raquo;</p>
<p>{% trans %}Born: {% endtrans %}{{ profile.date_of_birth|date("d/m/Y") }}</p>
<p>{{ profile.department }}{{ profile.semester }}
{% if profile.dpt_option %}
<br>{% trans %}Option: {% endtrans %}{{ profile.dpt_option }}
{% endif %}
</p>
{% if profile.promo %}
<p><img src="{{ static('core/img/promo_%02d.png' % profile.promo) }}" alt="Promo {{ profile.promo }}" class="promo_pict" />
{% trans %}Promo: {% endtrans %}{{ profile.promo }}</p>
{% endif %}
</div>
{% if user.membership.filter(end_date=None).exists() or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) %}
......
{% extends "core/base.jinja" %}
{% extends "core/user_base.jinja" %}
{% block content %}
{% block infos %}
<h2>{% trans %}Edit user profile{% endtrans %}</h2>
<form action="" method="post">
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p() }}
{% for field in form %}
<p>{{ field.errors }}<label for="{{ field.name }}">{{ field.label }}
{%- if field.name == "profile_pict" and form.instance.profile_pict -%}
<br>{% trans %}Current profile: {% endtrans %}
<img src="{{ form.instance.profile_pict.get_download_url() }}" title="{% trans %}Profile{% endtrans %}" width="50px" /><br>
{%- elif field.name == "avatar_pict" and form.instance.avatar_pict -%}
<br>{% trans %}Current avatar: {% endtrans %}
<img src="{{ form.instance.avatar_pict.get_download_url() }}" title="{% trans %}Avatar{% endtrans %}" width="50px" /><br>
{%- elif field.name == "scrub_pict" and form.instance.scrub_pict -%}
<br>{% trans %}Current scrub: {% endtrans %}
<img src="{{ form.instance.scrub_pict.get_download_url() }}" title="{% trans %}Scrub{% endtrans %}" width="50px" /><br>
{%- endif %}</label> {{ field }}</p>
{% endfor %}
<p><input type="submit" value="{% trans %}Update{% endtrans %}" /></p>
<p>{% trans %}Username: {% endtrans %}{{ form.instance.username }}</p>
{% if form.instance.customer %}
<p>{% trans %}Account number: {% endtrans %}{{ form.instance.customer.account_id }}</p>
{% endif %}
{% if form.instance == user %}
<p><a href="{{ url('core:password_change') }}">{% trans %}Change my password{% endtrans %}</a></p>
{% endif %}
</form>
{% if form.instance == user %}
<p><a href="{{ url('core:password_change') }}">{% trans %}Change my password{% endtrans %}</a></p>
{% endif %}
{% endblock %}
......
{% extends "core/base.jinja" %}
{% extends "core/user_base.jinja" %}
{% block content %}
{% block infos %}
<h2>{% trans user_name=profile.get_full_name() %}Edit user groups for {{ user_name }}{% endtrans %}</h2>
<form action="" method="post">
{% csrf_token %}
......
......@@ -8,8 +8,11 @@ from django.contrib.auth.forms import AuthenticationForm
from core.models import Group
def forbidden(request):
return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form':
AuthenticationForm(), 'popup': request.resolver_match.kwargs['popup'] or ""}))
try:
return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form':
AuthenticationForm(), 'popup': request.resolver_match.kwargs['popup'] or ""}))
except:
return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': AuthenticationForm()}))
def not_found(request):
return HttpResponseNotFound(render(request, "core/404.jinja"))
......
......@@ -37,7 +37,7 @@ def send_file(request, file_id):
response['Content-Disposition'] = 'inline; filename="%s"' % f.name
return response
class AddFileForm(forms.Form):
class AddFilesForm(forms.Form):
folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False)
file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"),
required=False)
......@@ -128,7 +128,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
pk_url_kwarg = "file_id"
template_name = 'core/file_detail.jinja'
context_object_name = "file"
form_class = AddFileForm
form_class = AddFilesForm
def get(self, request, *args, **kwargs):
self.form = self.get_form()
......
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, UserChangeForm
from django import forms
from django.db import transaction
from django.core.exceptions import ValidationError
from django.contrib.auth import logout, login, authenticate
from django.forms import CheckboxSelectMultiple, Select
from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput
from django.utils.translation import ugettext as _
import logging
from core.models import User, Page, RealGroup
from core.models import User, Page, RealGroup, SithFile
# Widgets
class SelectSingle(Select):
def render(self, name, value, attrs=None):
if attrs:
attrs['class'] = "select_single"
else:
attrs = {'class': "select_single"}
return super(SelectSingle, self).render(name, value, attrs)
class SelectMultiple(Select):
def render(self, name, value, attrs=None):
if attrs:
attrs['class'] = "select_multiple"
else:
attrs = {'class': "select_multiple"}
return super(SelectMultiple, self).render(name, value, attrs)
class SelectDate(DateInput):
def render(self, name, value, attrs=None):
if attrs:
attrs['class'] = "select_date"
else:
attrs = {'class': "select_date"}
return super(SelectDate, self).render(name, value, attrs)
class SelectFile(TextInput):
def render(self, name, value, attrs=None):
if attrs:
attrs['class'] = "select_file"
else:
attrs = {'class': "select_file"}
output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % {
'content': super(SelectFile, self).render(name, value, attrs),
'title': _("Choose file"),
'name': name,
}
output += '<span name="' + name + '" class="choose_file_button">' + _("Choose file") + '</span>'
print(output)
return output
# Forms
class RegisteringForm(UserCreationForm):
error_css_class = 'error'
......@@ -22,6 +67,69 @@ class RegisteringForm(UserCreationForm):
user.save()
return user
class UserProfileForm(forms.ModelForm):
"""
Form handling the user profile, managing the files
This form is actually pretty bad and was made in the rush before the migration. It should be refactored.
TODO: refactor this form
"""
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']
widgets = {
'date_of_birth': SelectDate,
'profile_pict': forms.ClearableFileInput,
'avatar_pict': forms.ClearableFileInput,
'scrub_pict': forms.ClearableFileInput,
}
labels = {
'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"),
'avatar_pict': _("Avatar: used on the forum"),
'scrub_pict': _("Scrub: let other know how your scrub looks like!"),