Models #8

Merged
narmanli merged 21 commits from models into master 2017-03-05 13:26:42 +01:00
35 changed files with 995 additions and 16 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
__pycache__
venv
evenementiel/settings.py
.*.swp

5
equipment/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class EquipmentConfig(AppConfig):
name = 'equipment'

View file

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-21 20:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('event', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='AbstractEquipment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Nom du matériel')),
('stock', models.PositiveSmallIntegerField(verbose_name='Quantité disponible')),
('description', models.TextField(verbose_name='Description')),
],
options={
'verbose_name': 'matériel abstrait',
'verbose_name_plural': 'matériels abstraits',
},
),
migrations.CreateModel(
name='EquipmentAttribution',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.PositiveSmallIntegerField(verbose_name='Quantité attribuée')),
('remarks', models.TextField(verbose_name="Remarques concernant l'attribution")),
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')),
],
options={
'verbose_name': 'attribution de matériel',
'verbose_name_plural': 'attributions de matériel',
},
),
migrations.CreateModel(
name='EquipmentRemark',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remark', models.TextField(verbose_name='Remarque sur le matériel')),
('is_broken', models.BooleanField()),
('is_lost', models.BooleanField()),
],
options={
'verbose_name': 'remarque sur matériel',
'verbose_name_plural': 'remarques sur le matériel',
},
),
migrations.CreateModel(
name='Equipment',
fields=[
('abstractequipment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='equipment.AbstractEquipment')),
],
options={
'verbose_name': 'matériel permanent',
'verbose_name_plural': 'matériels permanents',
},
bases=('equipment.abstractequipment',),
),
migrations.CreateModel(
name='TemporaryEquipment',
fields=[
('abstractequipment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='equipment.AbstractEquipment')),
('event', models.ForeignKey(help_text='Évènement pour lequel le matériel a été loué ou emprunté ou apporté', on_delete=django.db.models.deletion.CASCADE, related_name='specific_equipment', to='event.Event')),
],
options={
'verbose_name': 'matériel temporaire',
'verbose_name_plural': 'matériels temporaires',
},
bases=('equipment.abstractequipment',),
),
migrations.AddField(
model_name='equipmentremark',
name='equipment',
field=models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.AbstractEquipment'),
),
migrations.AddField(
model_name='equipmentattribution',
name='equipment',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.AbstractEquipment'),
),
migrations.AddField(
model_name='abstractequipment',
name='activities',
field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'),
),
]

View file

78
equipment/models.py Normal file
View file

@ -0,0 +1,78 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from event.models import Event, Activity
class AbstractEquipment(models.Model):
name = models.CharField(
_("Nom du matériel"),
max_length=200,
)
stock = models.PositiveSmallIntegerField(_("Quantité disponible"))
description = models.TextField(_("Description"))
activities = models.ManyToManyField(
Activity,
related_name="equipment",
through="EquipmentAttribution",
)
class Meta:
verbose_name = _("matériel abstrait")
verbose_name_plural = _("matériels abstraits")
def __str__(self):
return self.name
class Equipment(AbstractEquipment):
class Meta:
verbose_name = _("matériel permanent")
verbose_name_plural = _("matériels permanents")
class TemporaryEquipment(AbstractEquipment):
event = models.ForeignKey(
Event,
related_name="specific_equipment",
help_text=_("Évènement pour lequel le matériel "
"a été loué ou emprunté ou apporté"),
)
class Meta:
verbose_name = _("matériel temporaire")
verbose_name_plural = _("matériels temporaires")
class EquipmentAttribution(models.Model):
equipment = models.ForeignKey(AbstractEquipment)
activity = models.ForeignKey(Activity)
amount = models.PositiveSmallIntegerField(_("Quantité attribuée"))
remarks = models.TextField("Remarques concernant l'attribution")
class Meta:
verbose_name = _("attribution de matériel")
verbose_name_plural = _("attributions de matériel")
def __str__(self):
return "%s (%d) -> %s" % (self.equipment.name,
self.amout,
self.activity.get_herited('title'))
class EquipmentRemark(models.Model):
remark = models.TextField("Remarque sur le matériel")
equipment = models.ForeignKey(
AbstractEquipment,
related_name="remarks",
help_text=_("Matériel concerné par la remarque"),
)
is_broken = models.BooleanField()
is_lost = models.BooleanField()
class Meta:
verbose_name = _("remarque sur matériel")
verbose_name_plural = _("remarques sur le matériel")
def __str__(self):
return "%s : %s" % (self.equipment.name,
self.remark)

View file

@ -31,6 +31,9 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'equipment.apps.EquipmentConfig',
'event.apps.EventConfig',
'user.apps.UserConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@ -131,6 +134,7 @@ USE_TZ = True
STATIC_URL = '/static/'
def show_toolbar(request):
"""
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar

View file

@ -1,21 +1,7 @@
"""evenementiel URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.9/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^user/', include('user.urls')),
]

0
event/__init__.py Normal file
View file

3
event/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
event/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class EventConfig(AppConfig):
name = 'event'

View file

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-21 20:14
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='ActivityTag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Nom du tag')),
('is_public', models.BooleanField(help_text="Sert à faire une distinction dans l'affichage selon que cela soit destiné au public ou à l'équipe organisatrice")),
('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pasune couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='Couleur')),
],
options={
'verbose_name': 'tag',
'verbose_name_plural': 'tags',
},
),
migrations.CreateModel(
name='ActivityTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(blank=True, max_length=200, null=True, verbose_name="Nom de l'activité")),
('is_public', models.NullBooleanField()),
('has_perm', models.NullBooleanField()),
('min_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Nombre minimum de permanents')),
('max_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Nombre maximum de permanents')),
('description', models.TextField(blank=True, help_text='Public, Visible par tout le monde.', null=True, verbose_name='Description')),
('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='Remarques')),
],
options={
'verbose_name': 'template activité',
'verbose_name_plural': 'templates activité',
},
),
migrations.CreateModel(
name='Event',
fields=[
('title', models.CharField(max_length=200, verbose_name="Nom de l'évènement")),
('slug', models.SlugField(help_text="Seulement des lettres, des chiffres oules caractères '_' ou '-'.", primary_key=True, serialize=False, unique=True, verbose_name='Identificateur')),
('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='Date de création')),
('description', models.TextField(verbose_name='Description')),
('beginning_date', models.DateTimeField(verbose_name='Date de début')),
('ending_date', models.DateTimeField(verbose_name='Date de fin')),
('created_by', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'évènement',
'verbose_name_plural': 'évènements',
},
),
migrations.CreateModel(
name='Place',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Nom du lieu')),
('description', models.TextField(blank=True)),
],
options={
'verbose_name': 'lieu',
'verbose_name_plural': 'lieux',
},
),
migrations.CreateModel(
name='Activity',
fields=[
('activitytemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='event.ActivityTemplate')),
],
options={
'verbose_name': 'activité',
'verbose_name_plural': 'activités',
},
bases=('event.activitytemplate',),
),
migrations.AddField(
model_name='activitytemplate',
name='event',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='event.Event'),
),
migrations.AddField(
model_name='activitytemplate',
name='place',
field=models.ManyToManyField(blank=True, related_name='activities', to='event.Place'),
),
migrations.AddField(
model_name='activitytemplate',
name='tags',
field=models.ManyToManyField(blank=True, related_name='activities', to='event.ActivityTag'),
),
migrations.AddField(
model_name='activity',
name='parent',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='event.ActivityTemplate'),
),
migrations.AddField(
model_name='activity',
name='staff',
field=models.ManyToManyField(blank=True, related_name='in_perm_activities', to=settings.AUTH_USER_MODEL),
),
]

View file

181
event/models.py Normal file
View file

@ -0,0 +1,181 @@
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from django.core.validators import RegexValidator
from django.core.exceptions import FieldError
from django.db import models
class Event(models.Model):
title = models.CharField(
_("Nom de l'évènement"),
max_length=200,
)
slug = models.SlugField(
_('Identificateur'),
unique=True,
primary_key=True,
help_text=_("Seulement des lettres, des chiffres ou"
"les caractères '_' ou '-'."),
)
created_by = models.ForeignKey(
User,
related_name="created_events",
editable=False,
)
creation_date = models.DateTimeField(
_('Date de création'),
auto_now_add=True,
)
description = models.TextField(_('Description'))
beginning_date = models.DateTimeField(_('Date de début'))
ending_date = models.DateTimeField(_('Date de fin'))
class Meta:
verbose_name = _("évènement")
verbose_name_plural = _("évènements")
def __str__(self):
return self.title
class Place(models.Model):
name = models.CharField(
_("Nom du lieu"),
max_length=200,
)
description = models.TextField(blank=True)
class Meta:
verbose_name = _("lieu")
verbose_name_plural = _("lieux")
def __str__(self):
return self.name
class ActivityTag(models.Model):
name = models.CharField(
_("Nom du tag"),
max_length=200,
)
is_public = models.BooleanField(
help_text=_("Sert à faire une distinction dans"
" l'affichage selon que cela soit"
" destiné au public ou à l'équipe"
" organisatrice"),
)
color_regex = RegexValidator(
regex=r'^#(?:[0-9a-fA-F]{3}){1,2}$',
message="La chaîne de caractère rentrée n'est pas"
"une couleur en hexadécimal.",
)
color = models.CharField(
_('Couleur'),
max_length=7,
validators=[color_regex],
help_text="Rentrer une couleur en hexadécimal",
)
class Meta:
verbose_name = _("tag")
verbose_name_plural = _("tags")
def __str__(self):
return self.name
class ActivityTemplate(models.Model):
title = models.CharField(
_("Nom de l'activité"),
max_length=200,
blank=True,
null=True,
)
# FIXME: voir comment on traite l'héritage de `event`
event = models.ForeignKey(
Event,
related_name="activities",
blank=True,
null=True,
)
is_public = models.NullBooleanField(
blank=True,
)
has_perm = models.NullBooleanField(
blank=True,
)
min_perm = models.PositiveSmallIntegerField(
_('Nombre minimum de permanents'),
blank=True,
null=True,
)
max_perm = models.PositiveSmallIntegerField(
_('Nombre maximum de permanents'),
blank=True,
null=True,
)
description = models.TextField(
_('Description'),
help_text=_("Public, Visible par tout le monde."),
blank=True,
null=True,
)
remarks = models.TextField(
_('Remarques'),
help_text=_("Visible uniquement par les organisateurs"),
blank=True,
null=True,
)
tags = models.ManyToManyField(
ActivityTag,
related_name="activities",
blank=True,
)
place = models.ManyToManyField(
Place,
related_name="activities",
blank=True,
)
class Meta:
verbose_name = _("template activité")
verbose_name_plural = _("templates activité")
def __str__(self):
return self.title
class Activity(ActivityTemplate):
parent = models.ForeignKey(
ActivityTemplate,
related_name="children",
)
staff = models.ManyToManyField(
User,
related_name="in_perm_activities",
blank=True,
)
def get_herited(self, attrname):
attr = super(Activity, self).__getattribute__(attrname)
if attrname in {"parent", "staff", "equipment"}:
raise FieldError(
_("%(attrname)s n'est pas un champ héritable"),
params={'attrname': attrname},
)
elif (attrname == 'place' or attrname == 'tags'):
if attr.exists():
return attr
else:
return self.parent.__getattribute__(attrname)
elif attr is None:
return self.parent.__getattribute__(attrname)
else:
return attr
class Meta:
verbose_name = _("activité")
verbose_name_plural = _("activités")
def __str__(self):
return self.get_herited('title')

149
event/tests.py Normal file
View file

@ -0,0 +1,149 @@
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.test import TestCase
from datetime import timedelta
from django.utils import timezone
from .models import Event, ActivityTemplate, Activity, Place, \
ActivityTag
class ActivityInheritanceTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.erkan = User.objects.create(
username='enarmanli',
email='erkan.narmanli@ens.fr',
first_name='Erkan',
last_name='Narmanli',
)
cls.event = Event.objects.create(
title='La Nuit 2042',
slug='nuit42',
created_by=cls.erkan,
creation_date=timezone.now(),
description="La nuit c'est lol",
beginning_date=timezone.now()
+ timedelta(days=30),
ending_date=timezone.now()
+ timedelta(days=31),
)
cls.loge = Place.objects.create(name="Loge 45")
cls.aqua = Place.objects.create(name="Aquarium")
cls.tag_foo = ActivityTag.objects.create(
name="foo",
is_public=True,
color="#0F0F0F",
)
cls.tag_bar = ActivityTag.objects.create(
name="bar",
is_public=True,
color="#0F0F0F",
)
def setUp(self):
self.template_act = ActivityTemplate.objects.create()
self.real_act = Activity.objects.create(
parent=self.template_act,
)
def test_inherites_title(self):
self.template_act.title = "parent"
self.assertEqual(self.real_act.get_herited('title'), "parent")
self.real_act.title = "enfant"
self.assertEqual(self.real_act.get_herited('title'), "enfant")
def test_inherites_description(self):
self.template_act.description = "parent"
self.assertEqual(self.real_act.get_herited('description'), "parent")
self.real_act.description = "enfant"
self.assertEqual(self.real_act.get_herited('description'), "enfant")
def test_inherites_remarks(self):
self.template_act.remarks = "parent"
self.assertEqual(self.real_act.get_herited('remarks'), "parent")
self.real_act.remarks = "enfant"
self.assertEqual(self.real_act.get_herited('remarks'), "enfant")
def test_inherites_is_public(self):
self.template_act.is_public = True
self.assertEqual(self.real_act.get_herited('is_public'), True)
self.real_act.is_public = False
self.assertEqual(self.real_act.get_herited('is_public'), False)
def test_inherites_has_perm(self):
self.template_act.has_perm = True
self.assertEqual(self.real_act.get_herited('has_perm'), True)
self.real_act.has_perm = False
self.assertEqual(self.real_act.get_herited('has_perm'), False)
def test_inherites_min_perm(self):
self.template_act.min_perm = 42
self.assertEqual(self.real_act.get_herited('min_perm'), 42)
self.real_act.min_perm = 1
self.assertEqual(self.real_act.get_herited('min_perm'), 1)
def test_inherites_max_perm(self):
self.template_act.max_perm = 42
self.assertEqual(self.real_act.get_herited('max_perm'), 42)
self.real_act.max_perm = 1
self.assertEqual(self.real_act.get_herited('max_perm'), 1)
def test_inherites_place(self):
self.template_act.place.add(self.loge)
self.assertEqual(
self.real_act.get_herited('place').get(),
self.loge
)
self.real_act.place.add(self.aqua)
self.assertEqual(
self.real_act.get_herited('place').get(),
self.aqua
)
def test_inherites_tags(self):
self.template_act.tags.add(self.tag_foo)
self.assertEqual(
self.real_act.get_herited('tags').get(),
self.tag_foo
)
self.real_act.tags.add(self.tag_bar)
self.assertEqual(
self.real_act.get_herited('tags').get(),
self.tag_bar
)
class ActivityTagColorTest(TestCase):
def test_positive_color_long(self):
self.tag = ActivityTag.objects.create(
name="bar",
is_public=True,
color="#0F0F0F",
)
self.tag.full_clean()
def test_positive_color_small(self):
self.tag = ActivityTag.objects.create(
name="bar",
is_public=True,
color="#0F0",
)
self.tag.full_clean()
def test_negative_color_1(self):
self.tag = ActivityTag.objects.create(
name="bar",
is_public=True,
color="#ABCDEG",
)
with self.assertRaises(ValidationError):
self.tag.full_clean()
def test_negative_color_2(self):
self.tag = ActivityTag.objects.create(
name="bar",
is_public=True,
color="#ACDE-1",
)
with self.assertRaises(ValidationError):
self.tag.full_clean()

6
event/views.py Normal file
View file

@ -0,0 +1,6 @@
from django.shortcuts import render
from django.views.generic import TemplateView
class Index(TemplateView):
template_name="event/index.html"

0
user/__init__.py Normal file
View file

3
user/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
user/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class UserConfig(AppConfig):
name = 'user'

36
user/forms.py Normal file
View file

@ -0,0 +1,36 @@
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django import forms
from django.conf import settings
class CreateUserForm(UserCreationForm):
key = forms.CharField(
label="Clef de sécurité",
widget=forms.PasswordInput,
help_text="Cette clef est fournie par l'administrat·rice·eur "
"du site. Pour en obtenir une veuillez la-le contacter."
)
error_m = {'wrong_key': "La clef fournie est erronée."}
class Meta:
model = User
fields = ('username', 'first_name', 'last_name',
'email', 'password1', 'password2',)
def save(self, commit=True):
user = super(CreateUserForm, self).save(commit=False)
user.email = self.cleaned_data["email"]
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
if commit:
user.save()
return user
def clean_key(self):
key = self.cleaned_data.get("key")
if key != settings.CREATE_USER_KEY:
raise forms.ValidationError(
self.error_m['wrong_key'],
code='wrong_key')
return key

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-14 22:17
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profil',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-16 16:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='profil',
name='modif_pad',
field=models.BooleanField(default=False, verbose_name='Modifier tous les pads'),
),
migrations.AddField(
model_name='profil',
name='read_kholle',
field=models.BooleanField(default=False, verbose_name='Lecture de khôlles'),
),
migrations.AddField(
model_name='profil',
name='write_kholle',
field=models.BooleanField(default=False, verbose_name='Écriture de khôlles'),
),
migrations.AddField(
model_name='profil',
name='write_pad',
field=models.BooleanField(default=False, verbose_name='Écrire des pads'),
),
]

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-23 14:03
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0002_auto_20160616_1803'),
]
operations = [
migrations.RemoveField(
model_name='profil',
name='modif_pad',
),
migrations.RemoveField(
model_name='profil',
name='read_kholle',
),
migrations.RemoveField(
model_name='profil',
name='write_kholle',
),
migrations.RemoveField(
model_name='profil',
name='write_pad',
),
]

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-23 16:08
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('user', '0003_auto_20160623_1603'),
]
operations = [
migrations.RemoveField(
model_name='profil',
name='user',
),
migrations.DeleteModel(
name='Profil',
),
]

View file

3
user/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
from django.contrib.auth.models import User

View file

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block aside %}
{% block user_aside_before %}{% endblock %}
<p>Les comptes utilisateurs vous donnent accès à la gestion et d'évènements. Vous pouvez vous connecter par le CAS ENS ou via votre compte si vous n'avez pas d'identifiants CAS. Pour créer un tel compte il est nécessaire d'avoir une clée ; merci de contacter un-e administrat-rice-eur pour en obtenir une.</p>
{% block user_aside_after %}{% endblock %}
{% endblock %}

View file

@ -0,0 +1,4 @@
{% extends "user/user_form.html" %}
{% block action_name %}{% url 'user:password_change' %}{% endblock %}
{% block user_error %}Une erreur s'est produite, veuillez réessayer.{% endblock %}

View file

@ -0,0 +1,6 @@
Bonjour,
Quelqu'un a demandé à réinitialiser le mot de passe pour le compte utilisateur de qwann.fr utilisant l'adresse mail : {{ email }}. Pour réinitialiser le mot de passe, veuillez suivre le lien suivant :
{{ protocol}}://qwann.fr{% url 'user:password_reset_confirm' uidb64=uid token=token %}
Merci de ne pas répondre à ce mail.

View file

@ -0,0 +1,28 @@
{% extends "user/user_form.html" %}
{% block action_name %}{% url 'user:login' %}{% endblock %}
{% block user_error %}L'identitfiant et le mot de passe ne correspondent pas !{% endblock %}
{% block extra_form_input %}
<input type="hidden" name="next" value="{{ next }}" />
{% endblock %}
{% block user_aside_after %}
<hr/>
<p>Vous n'avez pas de compte utilisateur et vous souhaiteriez en créer un ?</p>
<form action="{% url 'user:create_user' %}" class="form-horizontal">
<input type="submit" value="Créer un compte" class="btn btn-default btn-block btn-lg">
</form>
<hr/>
<p>Vous avez déjà un compte utilisateur et vous avez oublié votre mot de passe ?</p>
<form action="{% url 'user:password_reset' %}" class="form-horizontal">
<input type="submit" value="Réinitialiser le mot de passe" class="btn btn-default btn-block btn-lg">
</form>
<script type="text/javascript">
jQuery(document).ready(function() {
/* Met le focus sur le champ user
* du formulaire de connexion */
$('#id_username').focus();
});
</script>
{% endblock %}

View file

@ -0,0 +1,4 @@
{% extends "user/user_form.html" %}
{% block action_name %}{% url 'user:password_reset' %}{% endblock %}
{% block user_error %}L'identitfiant et le mot de passe ne correspondent pas !{% endblock %}

View file

@ -0,0 +1 @@
[Qwann.fr] Réinitialisation du mot de passe

View file

@ -0,0 +1,35 @@
{% extends "user/base_user.html" %}
{% load bootstrap %}
{% block section_title %}{{ sec_title }}{% endblock %}
{% block content %}
{% if form.errors %}
<p class="text-danger">{% block user_error %}{% endblock %}</p>
{% endif %}
<form class="form-horizontal" method="post" action="{%block action_name%}{%endblock%}">
{% csrf_token %}
<fieldset>
{% for field in form %}
{{ field | bootstrap }}
{% endfor %}
</fieldset>
<div class="form-action">
<button type="submit" class="btn btn-primary pull-right">
{{ button }}
</button>
{% block extra_form_input %}{% endblock %}
</div>
</form>
<script type="text/javascript">
jQuery(document).ready(function() {
/* Met le focus sur le premier champ input
* du formulaire chargé */
$('form').eq(0).find('input').eq(1).focus();
/* NB : c'est eq(1) parce qu'il y a le csrf_token ;) */
/* NB : ATTENTION, le premier champ input n'est
* pas toujours le premier champs, ce code n'est
* pas toujours pertinent */
});
</script>
{% endblock %}

3
user/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

88
user/urls.py Normal file
View file

@ -0,0 +1,88 @@
from django.conf.urls import url, include
from django.contrib.auth import views as auth_views
from django.core.urlresolvers import reverse_lazy
from event.views import Index #TODO : mettre le vrai home
from user.views import CreateUser
app_name = 'user'
urlpatterns = [
# CREATE USER
url('^create/$', CreateUser.as_view(), name='create_user'),
# LOGIN
url('^login/$',
auth_views.login,
{ 'template_name': 'user/login.html',
'extra_context': {
'sec_title' : 'Connexion',
'button' : 'Se connecter',
},
},
name='login',
),
# LOGOUT
url('^logout/$',
auth_views.logout,
#TODO : mettre le vrai home
{ 'next_page': reverse_lazy('event:index'),
},
name='logout',),
# PASSWORD_CHANGE
url('^password_change/$',
auth_views.password_change,
{ 'template_name': 'user/change_pass.html',
#TODO : mettre le vrai home
'post_change_redirect': reverse_lazy('event:index'),
'extra_context': {
'sec_title' : 'Changement de mot de passe',
'button' : 'Modifier',
},
},
name='password_change'),
# url('^password_change/done/$', name='password_change_done'),
# RESET PASSWORD
url('^password_reset/$',
auth_views.password_reset,
{ 'template_name' : 'user/password_reset.html',
'email_template_name': 'email_password_reset.html',
'subject_template_name': 'subject_password_reset.txt',
'post_reset_redirect': reverse_lazy('user:password_reset_done'),
'extra_context': {
'sec_title' : 'Demande de nouveau mot de passe',
'button' : 'Envoyer'
},
},
name='password_reset'),
# PASS RESET DONE
url('^password_reset/done/$',
#TODO : mettre le vrai home
Index.as_view(),
name='password_reset_done'),
# PASS RESET CONFIRM
url('^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
auth_views.password_reset_confirm,
{
'template_name': 'user/user_form.html',
'post_reset_redirect' : reverse_lazy('user:password_reset_complete'),
'extra_context': {
'sec_title' : 'Changer de mot de passe',
'button' : 'Changer'
},
},
name='password_reset_confirm'),
# PASS RESET COMPLETE
url('^reset/done/$',
#TODO : mettre le vrai home
Index.as_view(),
name='password_reset_complete'),
]
# Inclu les vues suivantes :
# ^login/$ [name='login']
# ^logout/$ [name='logout']
# ^password_change/$ [name='password_change']
# ^password_change/done/$ [name='password_change_done']
# ^password_reset/$ [name='password_reset']
# ^password_reset/done/$ [name='password_reset_done']
# ^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ [name='password_reset_confirm']
# ^reset/done/$ [name='password_reset_complete']

15
user/views.py Normal file
View file

@ -0,0 +1,15 @@
from user.forms import CreateUserForm
from django.views.generic.edit import CreateView
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
class CreateUser(SuccessMessageMixin, CreateView):
template_name = 'user/user_form.html'
form_class = CreateUserForm
success_url = reverse_lazy('erkan:index')
success_message = "Votre compte utilisateur a été correctement créé !"
def get_context_data(self, **kwargs):
ctx = super(CreateUser, self).get_context_data(**kwargs)
ctx['button'] = 'Créer'
ctx['sec_title'] = "Création d'utilisateur"
return ctx