This commit is contained in:
root 2018-03-11 23:28:44 +01:00
commit 7404de04e3
46 changed files with 1150 additions and 747 deletions

30
content/forms.py Normal file
View file

@ -0,0 +1,30 @@
from django import forms
from .models import Content, Category
class CreateContent(forms.ModelForm):
class Meta:
model = Content
fields = [
'name',
'category',
'file',
]
def __init__(self, school, *args, **kwargs):
super().__init__(*args, **kwargs)
self.instance.school = school
already_created = map(lambda x:x.category.pk, school.content_set.select_related('category'))
self.fields['category'].queryset = Category.objects.exclude(pk__in=already_created)
class ContentEdit(forms.ModelForm):
class Meta:
model = Content
fields = [
'name',
'file'
]

View file

@ -1,5 +1,6 @@
# Generated by Django 2.0.1 on 2018-02-28 18:43
# Generated by Django 2.0.1 on 2018-03-08 22:03
import content.models
from django.db import migrations, models
import django.db.models.deletion
@ -9,6 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('users', '0001_initial'),
]
operations = [
@ -26,8 +28,9 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Nom du contenu')),
('file', models.FileField(upload_to='', verbose_name='Fichier')),
('file', models.FileField(upload_to=content.models.get_upload_to, validators=[content.models.validate_file_extension], verbose_name='Fichier')),
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='content.Category', verbose_name='Catégorie')),
('school_owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.School')),
],
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 2.0.1 on 2018-03-09 10:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='category',
name='description_short',
field=models.TextField(null=True, verbose_name='Description courte'),
),
migrations.AlterField(
model_name='category',
name='description',
field=models.TextField(default='', verbose_name='Description de la catégorie'),
),
]

View file

@ -1,22 +0,0 @@
# Generated by Django 2.0.1 on 2018-02-28 18:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('content', '0001_initial'),
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='content',
name='school_owner',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SchoolProfile'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.0.1 on 2018-03-09 11:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0002_auto_20180309_1116'),
]
operations = [
migrations.AlterField(
model_name='content',
name='school_owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='users.School'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.0.1 on 2018-03-09 11:35
import content.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0003_auto_20180309_1233'),
]
operations = [
migrations.AlterField(
model_name='content',
name='file',
field=models.FileField(upload_to=content.models.get_upload_to, verbose_name='Fichier'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.0.1 on 2018-03-09 11:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0004_auto_20180309_1235'),
]
operations = [
migrations.AlterField(
model_name='content',
name='file',
field=models.FileField(upload_to='', verbose_name='Fichier'),
),
]

View file

@ -0,0 +1,19 @@
# Generated by Django 2.0.1 on 2018-03-09 11:57
import content.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0005_auto_20180309_1255'),
]
operations = [
migrations.AlterField(
model_name='content',
name='file',
field=models.FileField(upload_to=content.models.get_upload_to, verbose_name='Fichier'),
),
]

View file

@ -0,0 +1,25 @@
# Generated by Django 2.0.1 on 2018-03-09 12:15
import content.models
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0006_auto_20180309_1257'),
]
operations = [
migrations.AlterField(
model_name='content',
name='file',
field=models.FileField(upload_to=content.models.get_upload_to, validators=[content.models.validate_file_extension], verbose_name='Fichier'),
),
migrations.AlterField(
model_name='content',
name='school_owner',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.School'),
),
]

View file

@ -1,9 +1,11 @@
from django.db import models
from django.urls import reverse
from django.contrib.auth.models import Group
from django.conf import settings
import os
from django.db import models
from django.core.exceptions import ValidationError
from django.urls import reverse
from users.models import School
from users.models import SchoolProfile
class Category(models.Model):
"""Une catégorie de contenu."""
@ -11,21 +13,40 @@ class Category(models.Model):
max_length=255,
verbose_name="Nom de la catégorie"
)
description_short = models.TextField(
verbose_name="Description courte",
null=True,
)
description = models.TextField(
verbose_name="Descriton de la catégorie",
verbose_name="Description de la catégorie",
default=""
)
image = models.ImageField(
verbose_name="Illustration de la catégorie",
null=True,
)
def get_absolute_url(self):
return reverse('content:category-list', kwargs={'pk':self.pk})
return reverse('content:category', kwargs={'pk': self.pk})
def __str__(self):
return self.name
def get_upload_to(instance, filename):
extension = filename.split('.')[-1]
proper_school = ''.join(e for e in instance.school_owner.name if e.isalnum() and ord(e)<128)
proper_name = ''.join(e for e in instance.category.name if e.isalnum() and ord(e)<128)
return "static/media/"+proper_school+"/"+proper_name+'.'+extension
def validate_file_extension(value):
ext = os.path.splitext(value.name)[1] # [0] returns path+filename
valid_extensions = ['.mp4', '.avi', '.mov']
if not ext.lower() in valid_extensions:
raise ValidationError(u'Format non supporté : {}'.format(ext))
class Content(models.Model):
"""Un contenu du site (vidéo)."""
name = models.CharField(
@ -33,7 +54,7 @@ class Content(models.Model):
verbose_name="Nom du contenu"
)
school_owner = models.ForeignKey(
SchoolProfile,
School,
on_delete=models.CASCADE,
)
category = models.ForeignKey(
@ -43,11 +64,10 @@ class Content(models.Model):
null=True
)
file = models.FileField(
verbose_name="Fichier"
verbose_name="Fichier",
validators=[validate_file_extension],
upload_to=get_upload_to
)
def __str__(self):
return self.name
def manager_right(self):
return 'users.manage_' + str(self.school_owner.group.pk)

View file

@ -5,15 +5,13 @@
</video>
<div class="card-body">
<h2 class="display-5">{{content.name}}</h2>
<p class="lead">Contenu proposé par {{content.school_owner.group.name}}</p>
{% if content.manager_right in perms %}
<p class="lead">Catégorie : {{content.category.name}}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<a class="btn btn-sm btn-outline-primary"i href="{% url "content:content-edit" content.pk %}"><i class="fa fa-edit"></i> Éditer</a>
<a class="btn btn-sm btn-outline-danger" href="{% url "content:content-delete" content.pk %}" ><i class="fa fa-trash"></i> Supprimer</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>

View file

@ -22,15 +22,13 @@ $('html, body').animate({scrollTop: $('#category-content').offset().top}, 800);
<div class="position-relative overflow-hidden p-3 p-md-5 text-center bg-light page-title">
<div class="col-md-5 p-lg-5 mx-auto my-5 title-block">
<h1 class="display-4 font-weight-normal">{{category.name}}</h1>
<p class="lead font-weight-normal">{{category.description}}</p>
<p class="lead font-weight-normal">{{category.description_short}}</p>
<a class="btn btn-outline-secondary smooth-scroll" href="#category-content">Aller voir !</a>
</div>
</div>
<br />
<div class="row text-center" id="category-content">
{% for content in contents %}
{% include "content/content.html" %}
{% endfor %}
<div id="category-content">
{{category.description|safe}}
</div>
<br />
<br />

View file

@ -1,22 +1,17 @@
from django.urls import path
from . import views
from .views import (
ContentCategoryList,
CreateCategory,
ViewCategory,
DeleteCategory,
EditCategory,
CreateContent,
DeleteContent,
EditContent,
)
app_name = 'content'
urlpatterns = [
path(
'category/<int:pk>/',
ContentCategoryList.as_view(),
name='category-list'
),
path(
'category/delete/<int:pk>',
DeleteCategory.as_view(),
@ -28,13 +23,18 @@ urlpatterns = [
name='category-new'
),
path(
'category/edit/<int:pk>',
'category/<int:pk>',
ViewCategory.as_view(),
name='category',
),
path(
'category/<int:pk>/edit',
EditCategory.as_view(),
name='category-edit',
),
path(
'new',
CreateContent.as_view(),
'new/<int:school_pk>',
views.create_content,
name='content-new',
),
path(
@ -44,7 +44,7 @@ urlpatterns = [
),
path(
'<int:pk>/edit',
EditContent.as_view(),
views.edit_content,
name="content-edit",
),

View file

@ -1,31 +1,21 @@
from django.views import generic
from django.urls import reverse, reverse_lazy
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib import messages
from .models import Content, Category
from users.models import School
from . import forms
from settings.models import SiteSettings
class ContentCategoryList(generic.ListView):
"""Affiche les contenus d'une catégorie."""
model = Content
context_object_name = "contents"
class ViewCategory(generic.DetailView):
"""Affiche une catégorie."""
model = Category
template_name = "content/content_list.html"
def get_queryset(self):
pk = self.kwargs['pk']
category = get_object_or_404(Category, pk=pk)
return Content.objects.filter(category=category)
def get_context_data(self, **kwargs):
context = super(generic.ListView, self).get_context_data(**kwargs)
pk = self.kwargs['pk']
category = get_object_or_404(Category, pk=pk)
context['category'] = category
return context
class CreateCategory(PermissionRequiredMixin, generic.CreateView):
"""Création de catégorie."""
@ -60,39 +50,6 @@ class EditCategory(PermissionRequiredMixin, generic.UpdateView):
return context
class CreateContent(PermissionRequiredMixin, generic.CreateView):
"""Création de contenu."""
model = Content
fields = [
'name',
'category',
'file'
]
template_name = "edit.html"
extra_context = {
'title' : 'Envoi de contenu',
'validate' : 'Envoyer'
}
def has_permission(self):
return self.request.user.has_perm('users.manage_'+str(self.request.user.userprofile.school.group.pk))
def get_success_url(self):
return self.object.school_owner.get_absolute_url()
def form_valid(self, form):
form.instance.school_owner = self.request.user.userprofile.school
r = super().form_valid(form)
return r
def dispatch(self, request, *args, **kwargs):
settings,_ = SiteSettings.objects.get_or_create()
if not settings.allow_upload :
messages.error(request, "Le téléversement de contenu n'est pas autorisé actuellement.")
return redirect(reverse("home"))
return super().dispatch(request, *args, **kwargs)
class DeleteContent(PermissionRequiredMixin, generic.DeleteView):
"""Suppression de contenu"""
model = Content
@ -103,34 +60,53 @@ class DeleteContent(PermissionRequiredMixin, generic.DeleteView):
def has_permission(self):
school = get_object_or_404(Content, pk=self.kwargs['pk']).school_owner
return self.request.user.has_perm('users.manage_'+str(school.group.pk))
return self.request.user.is_staff or self.request.user == school.admin
class EditContent(PermissionRequiredMixin, generic.UpdateView):
"""Édition d'un contenu"""
model = Content
template_name = "edit.html"
fields = [
'name',
'category',
'file'
]
template_name = "edit.html"
extra_context = {
'title' : 'Édition de contenu',
'validate' : 'Envoyer'
}
def create_content(request, school_pk):
settings,_ = SiteSettings.objects.get_or_create()
if not settings.allow_upload :
messages.error(request, "Le téléversement de contenu n'est pas autorisé actuellement.")
return redirect(reverse("home"))
school = get_object_or_404(School, pk=school_pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
def get_success_url(self):
return self.object.school_owner.get_absolute_url()
content_form = forms.CreateContent(school, request.POST or None, request.FILES or None)
content_form.instance.school_owner = school
def has_permission(self):
school = get_object_or_404(Content, pk=self.kwargs['pk']).school_owner
return self.request.user.has_perm('users.manage_'+str(school.group.pk))
if content_form.is_valid():
content_form.save()
messages.success(request, "Contenu ajouté.")
return redirect(school.get_absolute_url())
return render(request, 'edit.html', {
'form' : content_form,
'title' : 'Ajout de contenu',
'validate' : 'Ajouter'
})
def dispatch(self, request, *args, **kwargs):
settings,_ = SiteSettings.objects.get_or_create()
if not settings.allow_upload :
messages.error(request, "Le téléversement de contenu n'est pas autorisé actuellement.")
return redirect(reverse("home"))
return super().dispatch(request, *args, **kwargs)
def edit_content(request, pk):
settings,_ = SiteSettings.objects.get_or_create()
if not settings.allow_upload :
messages.error(request, "Le téléversement de contenu n'est pas autorisé actuellement.")
return redirect(reverse("home"))
content = get_object_or_404(Content, pk=pk)
school = content.school_owner
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
content_form = forms.ContentEdit(request.POST or None, request.FILES or None, instance=content)
if content_form.is_valid():
content_form.save()
messages.success(request, "Contenu modifié.")
return redirect(school.get_absolute_url())
return render(request, 'edit.html', {
'form' : content_form,
'title' : 'Modifier un contenu',
'validate' : 'Modifier'
})

View file

@ -15,5 +15,5 @@ class SelectUserForm(forms.Form):
def populate(self):
admins,_ = Group.objects.get_or_create(name='admins')
choices = [(u.pk, u.first_name + ' ' + u.last_name + '(' + u.username + ')') for u in User.objects.all()]
choices = [(u.pk, u.first_name + ' ' + u.last_name + ' (' + u.username + ')') for u in User.objects.all()]
self.fields['pk'].choices = choices

View file

@ -1,4 +1,4 @@
# Generated by Django 2.0.1 on 2018-02-28 18:43
# Generated by Django 2.0.1 on 2018-03-08 22:03
from django.db import migrations, models
@ -17,6 +17,16 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('allow_upload', models.BooleanField(default=False, verbose_name="Autoriser l'upload de vidéos.")),
('home_message', models.TextField(default='', verbose_name="Message de la page d'accueil")),
('site_logo', models.ImageField(blank=True, null=True, upload_to='', verbose_name='Logo du site')),
('event_poster', models.ImageField(blank=True, null=True, upload_to='', verbose_name="Affiche de l'événement")),
],
),
migrations.CreateModel(
name='StaticPage',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Titre de la catégorie')),
('text', models.TextField(verbose_name='Texte de la catégorie')),
],
),
]

View file

@ -1,28 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 10:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('settings', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='sitesettings',
name='event_poster',
field=models.ImageField(null=True, upload_to='', verbose_name="Affiche de l'événement"),
),
migrations.AddField(
model_name='sitesettings',
name='min_number_of_categories',
field=models.PositiveIntegerField(default=0, verbose_name='Nombre minimal de catégories dans laquelle participer'),
),
migrations.AddField(
model_name='sitesettings',
name='site_logo',
field=models.ImageField(null=True, upload_to='', verbose_name='Logo du site'),
),
]

View file

@ -1,23 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 11:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('settings', '0002_auto_20180301_1047'),
]
operations = [
migrations.AlterField(
model_name='sitesettings',
name='event_poster',
field=models.ImageField(blank=True, null=True, upload_to='', verbose_name="Affiche de l'événement"),
),
migrations.AlterField(
model_name='sitesettings',
name='site_logo',
field=models.ImageField(blank=True, null=True, upload_to='', verbose_name='Logo du site'),
),
]

View file

@ -1,7 +1,5 @@
from django.db import models
from .aes_field import AESEncryptedField
class SiteSettings(models.Model):
PRETTY_NAME = "Réglages du site"
@ -23,11 +21,17 @@ class SiteSettings(models.Model):
null=True,
blank=True
)
min_number_of_categories = models.PositiveIntegerField(
verbose_name="Nombre minimal de catégories dans laquelle participer",
default=0,
)
@classmethod
def get_settings(cls):
return cls.objects.get_or_create()[0]
class StaticPage(models.Model):
name = models.CharField(
max_length=255,
verbose_name="Titre de la catégorie",
)
text = models.TextField(
verbose_name="Texte de la catégorie"
)

View file

@ -24,7 +24,7 @@
<td>{{admin.last_name}}</td>
<td>{{admin.username}}</td>
<td>
<a class="btn btn-outline-danger btn-sm" href="{% url 'settings:degrade-user' admin.pk %}">
<a class="btn btn-outline-danger btn-sm" href="{% url 'settings:degrade-user' admin.pk %}">
<i class="fa fa-trash"></i>
Enlever le privilège Administrateur
</a>
@ -65,6 +65,33 @@
</tr>
{% endfor %}
</table>
<h2>Pages statiques</h2>
<a class="btn btn-success btn-sm" role="button" href="{% url 'settings:staticpage-new' %}">
<i class="fas fa-plus"></i>
Créer une nouvelle page statique
</a>
<br/>
<br/>
<table class="table table-striped">
<tr>
<th>Nom</th>
<th></th>
</tr>
{% for p in static_pages %}
<tr>
<td><a href="{% url 'settings:staticpage' p.pk %}">{{p.name}}</a></td>
<td><a class="btn btn-outline-primary btn-sm" href="{% url 'settings:staticpage-edit' p.pk %}">
<i class="fas fa-edit"></i>
Éditer
</a>
<a class="btn btn-outline-danger btn-sm" title="Supprimer" href="{% url 'settings:staticpage-delete' p.pk %}">
<i class="fas fa-trash-alt"></i>
Supprimer
</a>
</td>
</tr>
{% endfor %}
</table>
<h2>Écoles</h2>
<a class="btn btn-success btn-sm" role="button" href="{% url 'users:new-school' %}">
@ -76,30 +103,52 @@
<table class="table table-striped">
<tr>
<th>Nom</th>
<th>Nombre de membres</th>
<th>Nombre de contenus</th>
<th>Nombre de catégories</th>
<th>Numéro de téléphone</th>
<th>Inscription</th>
<th>Administrateur</th>
<th></th>
{% for school in schools %}
<tr>
<th><a href="{{school.get_absolute_url}}">{{school.group.name}}</a></th>
<td>{{school.group.user_set.count}}</td>
<th><a href="{{school.get_absolute_url}}">{{school.name}}</a></th>
<td>{{school.content_set.count}}</td>
<td>{{school.number_of_categories}}</td>
<td>{{school.phone}}</td>
<td><a class="btn btn-outline-primary btn-sm" href="{% url "users:edit-school-name" pk=school.group.pk%}">
<i class="fas fa-edit"></i>
Éditer
</a>
<a class="btn btn-outline-danger btn-sm" title="Supprimer" href="">
<i class="fas fa-trash-alt"></i>
Supprimer
</a>
<td>
{% if school.validated %}
<span class="badge badge-success">
<i class="fa fa-check"></i>
Inscription validée
</span>
{% else %}
<span class="badge badge-danger">
<i class="fa fa-exclamation"></i>
Inscription non validée
</span>
{% endif %}
</td>
<td>
{% if school.admin %}
{{school.admin.first_name}} {{school.admin.last_name}} ({{school.admin.username}})
{% else %}
Non défini
{% endif %}
</td>
<td>
<a href="{% url 'users:edit-admin' school.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fa fa-edit"></i>
Définir
</a>
</td>
</tr>
{% endfor %}
</table>
<h2>Utilisateurs</h2>
<a class="btn btn-success btn-lg" href="{% url 'users:new-user' %}">
<i class="fa fa-plus"></i>
Ajouter un utilisateur
</a>
<h2>Réglages</h2>
<a class="btn btn-primary btn-sm" href="{% url 'settings:site-settings' %}">
<i class="fas fa-edit"></i>
@ -142,9 +191,5 @@
{% endif %}
</td>
</tr>
<tr>
<th>Nombre minimal de catégories</th>
<td>{{ site_settings.min_number_of_categories }}</td>
</tr>
</table>
{% endblock %}

View file

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<h1>{{object.name}}</h1>
{{object.text|safe}}
{% endblock %}

View file

@ -1,5 +1,5 @@
from django import template
from settings.models import SiteSettings
from settings.models import SiteSettings, StaticPage
register = template.Library()
@ -9,7 +9,18 @@ def load_site_settings(parser, token):
return LoadSiteSettingsNode()
@register.tag('load_static_pages')
def load_static_pages(parser, token):
return LoadStaticPagesNode()
class LoadSiteSettingsNode(template.Node):
def render(self, context):
context['site_settings'] = SiteSettings.get_settings()
return ''
class LoadStaticPagesNode(template.Node):
def render(self, context):
context['static_pages'] = StaticPage.objects.all()
return ''

View file

@ -1,5 +1,5 @@
from django.urls import path
from .views import SettingsView, EditSiteSettingsView, degrade_user, promote_user
from .views import SettingsView, EditSiteSettingsView, degrade_user, promote_user, CreateStaticPageView, StaticPageView, DeleteStaticPageView, EditStaticPageView
app_name = 'settings'
urlpatterns = [
@ -22,6 +22,25 @@ urlpatterns = [
'promote_user',
promote_user,
name='promote-user',
),
path(
'static_page/new',
CreateStaticPageView.as_view(),
name='staticpage-new'
),
path(
'static_page/<int:pk>',
StaticPageView.as_view(),
name='staticpage'
),
path(
'static_page/<int:pk>/delete',
DeleteStaticPageView.as_view(),
name='staticpage-delete'
),
path(
'static_page/<int:pk>/edit',
EditStaticPageView.as_view(),
name='staticpage-edit'
)
]

View file

@ -1,14 +1,14 @@
from django.views.generic import TemplateView, UpdateView
from django.views.generic import TemplateView, UpdateView, CreateView, DetailView, DeleteView
from django.urls import reverse_lazy, reverse
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.contrib.auth.decorators import permission_required
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.models import Group, User
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib import messages
from content.models import Category
from users.models import SchoolProfile
from .models import SiteSettings
from users.models import School
from .models import SiteSettings, StaticPage
from .forms import SelectUserForm
@ -20,11 +20,13 @@ class SettingsView(LoginRequiredMixin, PermissionRequiredMixin, TemplateView):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['site_settings'], _ = SiteSettings.objects.get_or_create()
context['schools'] = SchoolProfile.objects.all()
context['schools'] = School.objects.all()
context['settings'] = True
context['administrators'] = Group.objects.get(name='admins').user_set.all()
context['administrators'] = User.objects.filter(is_staff=True)
context['static_pages'] = StaticPage.objects.all()
return context
class EditSiteSettingsView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
template_name = "edit.html"
model = SiteSettings
@ -42,24 +44,82 @@ class EditSiteSettingsView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVi
return context
@permission_required('auth.change_user')
class CreateStaticPageView(LoginRequiredMixin, CreateView):
template_name = "edit.html"
model = StaticPage
fields = '__all__'
success_url = reverse_lazy('settings:index')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Création de page statique"
context["validate"] = "Créer"
return context
@classmethod
def as_view(self, *args, **kwargs):
view = super().as_view(*args, **kwargs)
return staff_member_required(view)
class StaticPageView(DetailView):
template_name = "settings/static_page.html"
model = StaticPage
fields = '__all__'
@classmethod
def as_view(self, *args, **kwargs):
view = super().as_view(*args, **kwargs)
return staff_member_required(view)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = self.object
return context
class DeleteStaticPageView(DeleteView):
template_name = "confirm_delete.html"
model = StaticPage
success_url = reverse_lazy('settings:index')
@classmethod
def as_view(self, *args, **kwargs):
view = super().as_view(*args, **kwargs)
return staff_member_required(view)
class EditStaticPageView(UpdateView):
template_name = "edit.html"
model = StaticPage
success_url = reverse_lazy('settings:index')
fields = '__all__'
@classmethod
def as_view(self, *args, **kwargs):
view = super().as_view(*args, **kwargs)
return staff_member_required(view)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Édition de page statique"
context["validate"] = "Éditer"
return context
@staff_member_required
def degrade_user(request, pk):
user = get_object_or_404(User, pk=pk)
admins,_ = Group.objects.get_or_create(name='admins')
user.groups.remove(admins)
user.is_staff = False
user.save()
messages.success(request, user.username + ' a été enlevé des administrateurs du site')
return redirect(reverse('settings:index'))
@permission_required('auth.change_user')
@staff_member_required
def promote_user(request):
user_form = SelectUserForm(request.POST or None)
user_form.populate()
if user_form.is_valid():
user=user_form.get_user()
admins,_ = Group.objects.get_or_create(name='admins')
user.groups.add(admins)
user.is_staff = True
user.save()
messages.success(request, user.username + ' a été ajouté des administrateurs du site')
return redirect(reverse('settings:index'))

View file

@ -1,16 +1,18 @@
"""
WSGI config for site_tps project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""
import os
import sys
VIRTUALENV_LOC = '/var/www/site_tps/env_site'
# Activation de l'environnement virtuel
activate_env=os.path.join(VIRTUALENV_LOC, 'bin/activate_this.py')
exec(compile(open(activate_env, "rb").read(), activate_env, 'exec'), {'__file__':activate_env})
# Ajout du répertoire du site au PATH
sys.path.append('/var/www/site_tps')
sys.path.append('/var/www/site_tps/site_tps')
# Les trucs par défaut de Django
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "site_tps.settings")
application = get_wsgi_application()

Binary file not shown.

BIN
static/Western Dead.ttf Normal file

Binary file not shown.

View file

@ -28,11 +28,12 @@ body {
@font-face {
font-family: 'Valeria';
font-style: normal;
src: url("/static/ValeriaBoldGrunge.ttf");
src: url("/static/Western Dead.ttf");
}
h1.site-title
{
font-family: 'Valeria';
font-size: 5rem;
}
{% block style %}{% endblock %}
</style>

View file

@ -1,6 +1,6 @@
{% extends 'base.html'%}
{% block content %}
<h1 class="site-title">IL ETAIT UNE FOIS DANS L'EST</h1>
<h1 class="site-title text-center">IL ETAIT UNE FOIS DANS L'EST</h1>
<br/>
<br/>
<div class="row">

View file

@ -2,6 +2,7 @@
{% load load_settings %}
{% load_categories %}
{% load_site_settings %}
{% load_static_pages %}
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{% url "home"%}">
{% if site_settings.site_logo %}
@ -17,14 +18,20 @@
{% for c in categories %}
<li class="nav-item
{% if category.pk == c.pk %}active{%endif%}">
<a class="nav-link" href="{% url 'content:category-list' c.pk %}">{{c.name}}
<a class="nav-link" href="{% url 'content:category' c.pk %}">{{c.name}}
</a></li>
{% endfor %}
{% for p in static_pages %}
<li class="nav-item
{% if page.pk == p.pk %}active{%endif%}">
<a class="nav-link" href="{% url 'settings:staticpage' p.pk %}">{{p.name}}
</a></li>
{% endfor %}
</ul>
<ul class="navbar-nav ml-auto">
{% if request.user.userprofile.school %}
<li class="nav-item {% if school %}active{% endif %}"><a class="nav-link" href="{% url 'users:school' request.user.userprofile.school.group.pk %}"><i class="fas fa-graduation-cap"></i> Mon école</a></li>
{% if request.user.school %}
<li class="nav-item {% if school %}active{% endif %}"><a class="nav-link" href="{% url 'users:school' request.user.school.pk %}"><i class="fas fa-graduation-cap"></i> Mon école</a></li>
{% endif %}
<li class="nav-item {% if settings %}active{% endif %}"><a class="nav-link" href="{% url 'settings:index' %}"><i class="fas fa-cogs"></i> Administration</a></li>
{% if request.user.is_authenticated %}
@ -33,16 +40,15 @@
{{request.user}}
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{% url 'users:profile' request.user.pk%}"><i class="fa fa-user"></i> Accéder à mon profil</a>
<a class="dropdown-item" href="{% url 'users:logout' %}"><i class="fa fa-sign-out-alt"></i> Se déconnecter</a>
</div>
</li>
{% else %}
<li class="nav-item {% if active == 4 %}active{% endif %}">
<a class="nav-link" href="{% url 'users:login' %}">Connexion<span class="sr-only">(current)</span></a>
<a class="nav-link" href="{% url 'users:login' %}">Connexion<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'users:new-user' %}">Inscription<span class="sr-only">(current)</span></a>
<li>
<a class="nav-link" href="{% url "users:password-reset" %}">(Mot de passe oublié)</a>
</li>
{% endif %}

View file

@ -1,44 +1,6 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
from django.contrib.auth.models import User, Group
from .models import School
from .models import UserProfile, SchoolProfile
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class UserInline(admin.StackedInline):
model = UserProfile
can_delete = False
verbose_name_plural = 'user profiles'
# Define a new User admin
class UserAdmin(BaseUserAdmin):
inlines = (UserInline, )
# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class SchoolInline(admin.StackedInline):
model = SchoolProfile
can_delete = False
verbose_name_plural = 'schools'
fk_name = 'admins'
# Define a new User admin
class GroupAdmin(BaseGroupAdmin):
inlines = (SchoolInline, )
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
admin.site.unregister(Group)
admin.site.register(Group, GroupAdmin)
class SchoolAdmin(admin.ModelAdmin):
pass
admin.site.register(School, SchoolAdmin)

72
users/forms.py Normal file
View file

@ -0,0 +1,72 @@
from django import forms
from django.contrib.auth.models import User
from .models import School
class CreateSchool(forms.ModelForm):
class Meta:
model = School
fields = ['name', 'admin']
class EditName(forms.ModelForm):
class Meta:
model = School
fields = ['name']
class CreateUser(forms.ModelForm):
class Meta:
model = User
fields = [
'username',
'first_name',
'last_name',
'email',
'groups',
'password',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['password'].widget = forms.PasswordInput()
class EditPhone(forms.ModelForm):
class Meta:
model = School
fields = ['phone']
class EditLogo(forms.ModelForm):
class Meta:
model = School
fields = ['logo']
class EditMail(forms.ModelForm):
class Meta:
model = User
fields = ['email']
class EditJury1(forms.ModelForm):
class Meta:
model = School
fields = [
'first_name_j1',
'last_name_j1',
'phone_j1',
'mail_j1'
]
class EditJury2(forms.ModelForm):
class Meta:
model = School
fields = [
'first_name_j2',
'last_name_j2',
'phone_j2',
'mail_j2'
]

View file

@ -1,8 +1,10 @@
# Generated by Django 2.0.1 on 2018-02-28 18:43
# Generated by Django 2.0.1 on 2018-03-08 22:03
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import users.models
class Migration(migrations.Migration):
@ -10,24 +12,25 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='SchoolProfile',
name='School',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('group', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='students', to='auth.Group')),
],
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('school', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.SchoolProfile')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('phone', models.CharField(help_text='Visible uniquement des administrateurs', max_length=10, null=True, validators=[django.core.validators.RegexValidator('^[0-9]{10}$', 'Veuillez entrer un numéro à 10 chiffres.')], verbose_name='Numéro de téléphone pour contacter le responsable des productions')),
('logo', models.ImageField(null=True, upload_to=users.models.get_upload_to, verbose_name="Logo à utiliser pour représenter l'école")),
('first_name_j1', models.CharField(max_length=255, verbose_name='Prénom juré n°1')),
('last_name_j1', models.CharField(max_length=255, verbose_name='Nom juré n°1')),
('phone_j1', models.CharField(help_text='Visible uniquement des administrateurs', max_length=10, null=True, validators=[django.core.validators.RegexValidator('^[0-9]{10}$', 'Veuillez entrer un numéro à 10 chiffres.')], verbose_name='Numéro de téléphone juré n°1')),
('mail_j1', models.EmailField(max_length=254, verbose_name='Email juré n°1')),
('first_name_j2', models.CharField(max_length=255, verbose_name='Prénom juré n°2')),
('last_name_j2', models.CharField(max_length=255, verbose_name='Nom juré n°2')),
('phone_j2', models.CharField(help_text='Visible uniquement des administrateurs', max_length=10, null=True, validators=[django.core.validators.RegexValidator('^[0-9]{10}$', 'Veuillez entrer un numéro à 10 chiffres.')], verbose_name='Numéro de téléphone juré n°2')),
('mail_j2', models.EmailField(max_length=254, verbose_name='Email juré n°2')),
('admin', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name="Administrateur de l'école")),
],
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.0.1 on 2018-03-09 08:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='school',
name='validated',
field=models.BooleanField(default=False, verbose_name='Inscription validé.'),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 07:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='schoolprofile',
name='admins',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='admin_of', to='auth.Group'),
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 09:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0002_schoolprofile_admins'),
]
operations = [
migrations.AlterField(
model_name='schoolprofile',
name='group',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='school', to='auth.Group'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.0.1 on 2018-03-09 09:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0002_school_validated'),
]
operations = [
migrations.AddField(
model_name='school',
name='name',
field=models.CharField(default='', max_length=255, verbose_name="Nom de l'école"),
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 09:52
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0003_auto_20180301_0933'),
]
operations = [
migrations.AlterField(
model_name='schoolprofile',
name='group',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='school', to='auth.Group'),
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 10:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('users', '0004_auto_20180301_0952'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='school',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.SchoolProfile'),
),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.0.1 on 2018-03-01 23:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0005_auto_20180301_1029'),
]
operations = [
migrations.AddField(
model_name='schoolprofile',
name='phone',
field=models.CharField(help_text='Visible uniquement des administrateurs', max_length=10, null=True, verbose_name='Numéro de téléphone pour contacter le responsable des productions'),
),
]

View file

@ -7,18 +7,22 @@ from django.dispatch import receiver
from django.core import validators
class SchoolProfile(models.Model):
def get_upload_to(instance, filename):
return "static/media/"+instance.name+"/"+filename
class School(models.Model):
"""Ajoute un champ pour distinguer les groupes écoles des autres."""
group = models.OneToOneField(
Group,
on_delete=models.CASCADE,
related_name="school",
name = models.CharField(
verbose_name="Nom de l'école",
max_length=255,
default=""
)
admins = models.OneToOneField(
Group,
admin = models.OneToOneField(
User,
verbose_name="Administrateur de l'école",
null=True,
on_delete=models.SET_NULL,
related_name="admin_of",
null=True
)
phone = models.CharField(
max_length=10,
@ -31,65 +35,64 @@ class SchoolProfile(models.Model):
"Veuillez entrer un numéro à 10 chiffres."),
]
)
logo = models.ImageField(
upload_to=get_upload_to,
verbose_name="Logo à utiliser pour représenter l'école",
null=True,
blank=False,
)
validated = models.BooleanField(
verbose_name="Inscription validé.",
default=False
)
first_name_j1 = models.CharField(
max_length=255,
verbose_name="Prénom juré n°1"
)
last_name_j1 = models.CharField(
max_length=255,
verbose_name="Nom juré n°1"
)
phone_j1 = models.CharField(
max_length=10,
help_text="Visible uniquement des administrateurs",
verbose_name="Numéro de téléphone juré n°1",
blank=False,
null=True,
validators=[
validators.RegexValidator('^[0-9]{10}$',
"Veuillez entrer un numéro à 10 chiffres."),
]
)
mail_j1 = models.EmailField(verbose_name="Email juré n°1")
first_name_j2 = models.CharField(
max_length=255,
verbose_name="Prénom juré n°2"
)
last_name_j2 = models.CharField(
max_length=255,
verbose_name="Nom juré n°2"
)
phone_j2 = models.CharField(
max_length=10,
help_text="Visible uniquement des administrateurs",
verbose_name="Numéro de téléphone juré n°2",
blank=False,
null=True,
validators=[
validators.RegexValidator('^[0-9]{10}$',
"Veuillez entrer un numéro à 10 chiffres."),
]
)
mail_j2 = models.EmailField(verbose_name="Email juré n°2")
def __str__(self):
return self.group.name
return self.name
def get_absolute_url(self):
return reverse("users:school", kwargs={'pk':self.group.pk})
return reverse("users:school", kwargs={'pk':self.pk})
def number_of_categories(self):
return self.content_set.values('category').distinct().count()
def save(self, *args, **kwargs):
viewing_right, _ = Permission.objects.get_or_create(
codename='view_' + str(self.group.pk),
name='Peut voir ' + str(self.group.pk),
content_type=ContentType.objects.get_for_model(SchoolProfile)
)
if viewing_right not in self.group.permissions.all():
self.group.permissions.add(viewing_right)
self.group.save()
admins,_ = Group.objects.get_or_create(name='admins')
admins.permissions.add(viewing_right)
super().save(*args, **kwargs)
@receiver(post_save, sender=SchoolProfile)
def update_permissions_school(sender, instance, **kwargs):
instance.admins,admin_created = Group.objects.get_or_create(name=str(instance.group.pk)+'_admins')
admin_right,_ = Permission.objects.get_or_create(
codename='manage_' + str(instance.group.pk),
name="Administrateur de l'école " + str(instance.group.pk),
content_type=ContentType.objects.get_for_model(SchoolProfile)
)
admins,_ = Group.objects.get_or_create(name='admins')
admins.permissions.add(admin_right)
if admin_created:
instance.save(update_fields=['admins'])
instance.admins.permissions.add(admin_right)
instance.admins.save()
class UserProfile(models.Model):
"""Profil d'un utilisateur"""
school = models.ForeignKey(SchoolProfile, on_delete=models.SET_NULL, null=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
@receiver(post_save, sender=UserProfile)
def update_groups(sender, instance, **kwargs):
instance.user.groups.add(instance.school.group)
@receiver(post_save, sender=User)
def update_permission_user(sender, instance, **kwargs):
perm,_ = Permission.objects.get_or_create(
codename='manage_'+str(instance.pk),
name='Peut administrer ' + instance.username,
content_type=ContentType.objects.get_for_model(User)
)
instance.user_permissions.add(perm)
admins,_ = Group.objects.get_or_create(name='admins')
admins.permissions.add(perm)

View file

@ -0,0 +1,14 @@
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://festart.rezometz.org{% url 'users:password-reset-confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
{% endautoescape %}

View file

@ -2,61 +2,163 @@
{% load bootstrap4 %}
{% block content %}
<h1>{{object.name}}</h1>
{% if manager_right in perms %}
Numéro de téléphone :
{% if object.school.phone %}{{object.school.phone}}
<a class="btn btn-primary btn-sm" href="{% url 'users:edit-school-name' object.pk %}">
{% if school.validated %}
<span class="badge badge-success">
<i class="fa fa-check"></i>
Inscription validée
</span>
{% endif %}
<h1>
{{school.name}}
</h1>
<a class="btn btn-primary btn-sm" href="{% url 'users:password-change' %}">
<i class="fa fa-edit"></i>
Changer mon mot de passe
</a>
<a class="btn btn-primary btn-sm" href="{% url 'users:edit-school-name' school.pk %}">
<i class="fa fa-edit"></i>
Éditer
</a>
{%else%}
Non indiqué{%endif%}<br/>
{%endif%}
{% if manager_right in perms %}
<h2>Membres</h2>
<table class="table table-striped">
<thead>
<th>Nom</th>
<th>Prénom</th>
<th>Pseudo</th>
<th>Administrer</th>
</thead>
<tbody>
{% for member in members %}
<tr>
<td>{{member.last_name}}</td>
<td>{{member.first_name}}</td>
<td>{{member.username}}</td>
<tr>
<th>
Numéro de téléphone
</th>
<td>
{% if member in manager_group.user_set.all %}
<a class="btn btn-outline-danger btn-sm" href="{% url 'users:degrade-user' object.pk member.pk %}">
<i class="fa fa-trash"></i>
Enlever le privilège Administrateur
</a>
{% else %}
<a class="btn btn-outline-warning btn-sm" href="{% url 'users:promote-user' object.pk member.pk %}">
<i class="fa fa-star"></i>
Promouvoir administrateur
</a>
{% endif %}
{% if school.phone %}{{school.phone}}{%else%}
Non indiqué{%endif%}
</td>
</tr>
{% endfor %}
</tbody>
<td>
<a class="btn btn-primary btn-sm" href="{% url 'users:edit-school-phone' school.pk %}">
<i class="fa fa-edit"></i>
Éditer
</a>
</td>
</tr>
<tr>
<th>
Email
</th>
<td>
{{school.admin.email}}
</td>
<td>
<a class="btn btn-primary btn-sm" href="{% url 'users:edit-school-mail' school.pk %}">
<i class="fa fa-edit"></i>
Éditer
</a>
</td>
</tr>
<tr>
<th>
Juré n°1
</th>
<td>
<table class="table table-striped">
<tr>
<th>
Nom
</th>
<td>
{{school.last_name_j1}}
</td>
</tr>
<tr>
<th>
Prénom
</th>
<td>
{{school.first_name_j1}}
</td>
</tr>
<tr>
<th>
Email
</th>
<td>
{{school.mail_j1}}
</td>
</tr>
<tr>
<th>
N° de téléphone
</th>
<td>
{{school.phone_j1}}
</td>
</tr>
</table>
</td>
<td>
<a class="btn btn-primary btn-sm" href="{% url 'users:edit-jury-1' school.pk %}">
<i class="fa fa-edit"></i>
Éditer
</a>
</td>
</tr>
<tr>
<th>
Juré n°2
</th>
<td>
<table class="table table-striped">
<tr>
<th>
Nom
</th>
<td>
{{school.last_name_j2}}
</td>
</tr>
<tr>
<th>
Prénom
</th>
<td>
{{school.first_name_j2}}
</td>
</tr>
<tr>
<th>
Email
</th>
<td>
{{school.mail_j2}}
</td>
</tr>
<tr>
<th>
N° de téléphone
</th>
<td>
{{school.phone_j2}}
</td>
</tr>
</table>
</td>
<td>
<a class="btn btn-primary btn-sm" href="{% url 'users:edit-jury-2' school.pk %}">
<i class="fa fa-edit"></i>
Éditer
</a>
</td>
</tr>
</table>
{% endif %}
<a class="btn btn-success btn-lg" href="{% url 'users:validate' school.pk %}">
<i class="fa fa-check"></i>
Valider mon inscription
</a>
<h2>Contenus</h2>
{% if manager_right in perms %}
<a class="btn btn-success btn-sm" href="{% url 'content:content-new' %}">
<a class="btn btn-success btn-sm" href="{% url 'content:content-new' school.pk %}">
<i class="fa fa-plus"></i>
Ajouter un contenu
</a>
{% endif %}
<br />
<br />
<div class="row">
{% for content in contents %}
{% for content in school.content_set.all %}
{% include "content/content.html" %}
{%endfor%}
</div>

View file

@ -0,0 +1,14 @@
Bienvenue sur le site d'Il était une fois dans l'Est.
Vous recevez cet email car vous avez été désigné responsable des productions
vidéos pour votre école.
Votre identifiant est : {{id}}
Rendez-vous ici : http://festart.rezometz.org/users/reset pour réinitialiser
votre mot de passe (en utilisant cette adresse email).
Vous pouvez retrouver l'ensemble des informations sur l'évènement ici :
http://festart.rezometz.org
L'équipe de Il était une fois dans l'Est.

View file

@ -1,85 +1,93 @@
from django.urls import path
from .views import (
CreateUser,
CreateUserProfile,
CreateSchool,
EditSchoolName,
EditSchoolPhone,
DeleteSchool,
Login,
Logout,
PasswordChange,
Profile,
School,
promote_user,
degrade_user
)
from django.urls import reverse_lazy
from django.contrib.auth import views as auth_views
from . import views
app_name = 'users'
urlpatterns = [
path(
'user/new',
CreateUser.as_view(),
'new',
views.create_user,
name='new-user'
),
path(
'login',
Login.as_view(),
auth_views.LoginView.as_view(template_name="edit.html"),
name='login'
),
path(
'logout',
Logout.as_view(),
name='logout',
auth_views.LogoutView.as_view(),
name='logout'
),
path(
'change_password',
PasswordChange.as_view(),
name='change-password'
'password_change',
auth_views.PasswordChangeView.as_view(template_name="edit.html"),
name='password-change'
),
path(
'user/<int:pk>/set_school',
CreateUserProfile.as_view(),
name='create-userprofile'
'password_change/done',
views.PasswordChangeDoneView.as_view(),
name='password-change-done'
),
path(
'user/<int:pk>',
Profile.as_view(),
name='profile',
'reset',
views.PasswordResetView.as_view(),
name='password-reset'
),
path(
'reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(template_name="edit.html", success_url=reverse_lazy('users:password-reset-done')),
name='password-reset-confirm'
),
path(
'reset/done/',
views.PasswordResetCompleteView.as_view(),
name='password-reset-done'
),
path(
'school/new',
CreateSchool.as_view(),
views.create_school,
name='new-school'
),
path(
'school/<int:pk>',
School.as_view(),
name='school'
),
path(
'school/<int:school_pk>/degrade/<int:user_pk>',
degrade_user,
name='degrade-user'
),
path(
'school/<int:school_pk>/promote/<int:user_pk>',
promote_user,
name='promote-user'
),
path(
'school/<int:pk>/edit_name',
EditSchoolName.as_view(),
name='edit-school-name'
views.school,
name='school',
),
path(
'school/<int:pk>/edit_phone',
EditSchoolPhone.as_view(),
views.edit_phone,
name='edit-school-phone'
),
path(
'school/<int:pk>/delete',
DeleteSchool.as_view(),
name='delete-school'
'school/<int:pk>/edit_name',
views.edit_name,
name='edit-school-name'
),
path(
'school/<int:pk>/edit_mail',
views.edit_mail,
name='edit-school-mail'
),
path(
'school/<int:pk>/jury_1',
views.edit_jury_1,
name='edit-jury-1'
),
path(
'school/<int:pk>/jury_2',
views.edit_jury_2,
name='edit-jury-2'
),
path(
'school/<int:pk>/edit_admin',
views.edit_admin,
name='edit-admin'
),
path(
'school/<int:pk>/validate',
views.validate,
name='validate',
)
]

View file

@ -1,220 +1,266 @@
from django.contrib.auth.models import User, Group
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.views.generic import CreateView, UpdateView, DeleteView, DetailView
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView, login_required
from django.contrib.auth.hashers import make_password
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib import messages
from django.urls import reverse, reverse_lazy
from django.shortcuts import get_object_or_404, redirect
from django.core.mail import send_mail
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib.admin.views.decorators import staff_member_required
from django.template.loader import render_to_string
from django.contrib.auth import views as auth_views
from .models import UserProfile, SchoolProfile
from content.models import Content
from settings.forms import SelectUserForm
from content.models import Category
from .models import School
from . import forms
class CreateUser(CreateView):
model = User
fields = [
'first_name',
'last_name',
'email',
'username',
'password',
]
template_name = 'edit.html'
def get_success_url(self):
return reverse(
'users:create-userprofile',
kwargs={'pk': self.object.pk}
@staff_member_required
def create_user(request):
user_form = forms.CreateUser(request.POST or None)
if user_form.is_valid():
u = user_form.save()
send_mail(
"Bienvenue sur Il était une fois dans l'Est.",
render_to_string("users/welcome_user.txt", {'id':u.username}),
"noreply.festart@rezometz.org",
[u.email],
fail_silently=False
)
messages.success(request, "L'utilisateur {} {} a bien été créé un mail lui a été envoyé pour réinitialiser son mot de passe.".format(
u.first_name, u.last_name))
return redirect(reverse('settings:index'))
return render(request, 'edit.html', {
'form': user_form,
'title': "Création d'un utilisateur",
'validate': "Créer"
})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Inscription"
context['validate'] = "S'inscrire"
return context
def form_valid(self, form):
r = super().form_valid(form)
self.object.set_password(form.cleaned_data['password'])
self.object.save()
@staff_member_required
def create_school(request):
school_form = forms.CreateSchool(request.POST or None)
if school_form.is_valid():
s = school_form.save()
messages.success(
request, "L'école {} a bien été créée.".format(s.name))
return redirect(reverse('settings:index'))
return render(request, 'edit.html', {
'form': school_form,
'title': "Création d'une école",
'validate': "Créer"
})
def school(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
return render(request, 'users/school.html', {'school': school})
def edit_phone(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
school_form = forms.EditPhone(request.POST or None, instance=school)
if school_form.is_valid():
s = school_form.save()
messages.success(
request, "L'école {} a bien été modifiée.".format(s.name))
return redirect(s.get_absolute_url())
return render(request, 'edit.html', {
'form': school_form,
'title': "Édition du numéro de téléphone",
'validate': "Modifier"
})
def edit_logo(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
school_form = forms.EditLogo(request.POST or None, instance=school)
if school_form.is_valid():
s = school_form.save()
messages.success(
request, "L'école {} a bien été modifiée.".format(s.name))
return redirect(s.get_absolute_url())
return render(request, 'edit.html', {
'form': school_form,
'title': "Édition du logo",
'validate': "Modifier"
})
def edit_mail(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
user_form = forms.EditMail(request.POST or None, instance=school.admin)
if user_form.is_valid():
s = user_form.save()
messages.success(
request, "L'école {} a bien été modifiée.".format(s.school.name))
return redirect(s.school.get_absolute_url())
return render(request, 'edit.html', {
'form': user_form,
'title': "Édition du numéro du mail",
'validate': "Modifier"
})
def edit_jury_1(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
school_form = forms.EditJury1(request.POST or None, instance=school)
if school_form.is_valid():
s = school_form.save()
messages.success(
request, "L'école {} a bien été modifiée.".format(s.name))
return redirect(s.get_absolute_url())
return render(request, 'edit.html', {
'form': school_form,
'title': "Édition du jury 1",
'validate': "Modifier"
})
def edit_jury_2(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
school_form = forms.EditJury2(request.POST or None, instance=school)
if school_form.is_valid():
s = school_form.save()
messages.success(
request, "L'école {} a bien été modifiée.".format(s.name))
return redirect(s.get_absolute_url())
return render(request, 'edit.html', {
'form': school_form,
'title': "Édition du jury 2",
'validate': "Modifier"
})
def edit_name(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
school_form = forms.EditName(request.POST or None, instance=school)
if school_form.is_valid():
s = school_form.save()
messages.success(
request, "L'école {} a bien été modifiée.".format(s.name))
return redirect(s.get_absolute_url())
return render(request, 'edit.html', {
'form': school_form,
'title': "Édition du nom",
'validate': "Modifier"
})
@staff_member_required
def edit_admin(request, pk):
school = get_object_or_404(School, pk=pk)
user_form = SelectUserForm(request.POST or None)
user_form.populate()
if user_form.is_valid():
user = user_form.get_user()
school.admin = user
school.save()
user.save()
messages.success(request, user.username +
' a été nommé admin de ' + school.name)
return redirect(reverse('settings:index'))
return render(request, 'edit.html', {
'form': user_form,
'title': "Définir l'administrateur de {}".format(school.name),
'validate': 'Ajouter'
})
def validate(request, pk):
school = get_object_or_404(School, pk=pk)
can = request.user.is_staff or request.user == school.admin
if not can:
messages.error(request, 'Vous ne pouvez pas accéder à cette page')
return redirect(reverse('home'))
jury_1_ok = any([
school.first_name_j1,
school.last_name_j1,
school.phone_j1,
school.mail_j1
])
jury_2_ok = any([
school.first_name_j2,
school.last_name_j2,
school.phone_j2,
school.mail_j2
])
logo_ok = school.logo
phone_ok = school.phone
mail_ok = school.admin.email
if not jury_1_ok:
messages.warning(request, 'Pas de jury n°1 défini.')
if not jury_2_ok:
messages.warning(request, 'Pas de jury n°2 défini.')
if not logo_ok:
messages.warning(request, 'Pas de logo défini.')
if not phone_ok:
messages.error(request, 'Pas de téléphone défini.')
if not mail_ok:
messages.error(request, 'Pas de mail défini.')
for category in Category.objects.all():
if not category.content_set.filter(school_owner=school):
messages.warning(
request, 'Pas de contenu dans la catégorie {}.'.format(category.name))
if phone_ok and mail_ok:
school.validated = True
school.save()
messages.success(request, 'Inscription validée.')
return redirect(school.get_absolute_url())
class PasswordChangeDoneView(auth_views.PasswordChangeDoneView):
template_name = "home.html"
def dispatch(self, *args, **kwargs):
r = super().dispatch(*args, **kwargs)
messages.success(self.request, "Le mot de passe a été changé.")
return r
class Profile(LoginRequiredMixin, UpdateView):
model = User
template_name = 'users/profile.html'
fields = [
'username',
'first_name',
'last_name',
'email'
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.object.username
context['validate'] = "Modifier"
return context
def get_success_url(self):
return reverse(
'users:profile',
kwargs={'pk': self.object.pk}
)
class CreateUserProfile(CreateView):
model = UserProfile
fields = ['school']
template_name = 'edit.html'
class PasswordResetView(auth_views.PasswordResetView):
template_name = "edit.html"
success_url = reverse_lazy('home')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Choix de l'école"
context['validate'] = "Choisir"
return context
email_template_name = "users/password_reset_mail.html"
def form_valid(self, form):
form.instance.user = get_object_or_404(User, pk=self.kwargs['pk'])
return super(CreateUserProfile, self).form_valid(form)
messages.success(self.request, "Un mail pour le changement de mot de passe a été envoyé.")
return super().form_valid(form)
class CreateSchool(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
permission_required = 'users.add_schoolprofile'
model = Group
fields = ['name']
template_name = 'edit.html'
success_url = reverse_lazy('settings:index')
class PasswordResetCompleteView(auth_views.PasswordResetCompleteView):
template_name = "home.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Création de l'école"
context['validate'] = "Créer"
return context
def form_valid(self, form):
response = super(CreateSchool, self).form_valid(form)
profile = SchoolProfile()
profile.group = form.instance
profile.save()
return response
class EditSchoolName(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Group
fields = ['name']
template_name = 'edit.html'
queryset = Group.objects.filter(school__isnull=False)
def get_success_url(self):
return reverse('users:edit-school-phone', kwargs={'pk':self.object.school.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Édition de l'école"
context['validate'] = "Modifier"
return context
def has_permission(self):
return self.request.user.has_perm('users.manage_'+str(self.kwargs['pk']))
def form_valid(self, *args, **kwargs):
r = super().form_valid(*args, **kwargs)
self.object.school.save()
def dispatch(self, *args, **kwargs):
r = super().dispatch(*args, **kwargs)
messages.success(self.request, "Votre mot de passe a été réinitialisé.")
return r
class EditSchoolPhone(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = SchoolProfile
fields = ['phone']
template_name = 'edit.html'
def get_success_url(self):
return reverse('users:school', kwargs={'pk':self.object.group.pk})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Édition de l'école"
context['validate'] = "Modifier"
return context
def has_permission(self):
return self.request.user.has_perm('users.manage_'+str(self.kwargs['pk']))
class DeleteSchool(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
model = Group
permission_required = 'users.delete_schoolprofile'
queryset = Group.objects.filter(school__isnull=False)
class School(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
model = Group
template_name = "users/school.html"
queryset = Group.objects.filter(school__isnull=False)
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['contents'] = Content.objects.filter(school_owner=self.object.school)
context['school'] = True
context['members'] = User.objects.filter(userprofile__school=self.object.school)
context['manager_right'] = 'users.manage_' + str(self.object.pk)
context['manager_group'],_ = Group.objects.get_or_create(name=str(self.object.pk)+'_admins')
return context
def has_permission(self):
return self.request.user.has_perm('users.view_'+str(self.kwargs['pk']))
class Logout(SuccessMessageMixin, LogoutView):
success_message = "Vous vous êtes bien déconnecté."
class Login(SuccessMessageMixin, LoginView):
template_name = "edit.html"
success_message = "Bienvenue !"
extra_context = {
'title' : "Connexion",
'validate' : "Se connecter",
}
class PasswordChange(SuccessMessageMixin, PasswordChangeView):
template_name = "edit.html"
success_url = reverse_lazy("home")
success_message = "Le mot de passe a été changé."
extra_context = {
'title' : "Changer le mot de passe",
'validate' : "Changer",
}
@login_required
def promote_user(request, school_pk, user_pk):
school = get_object_or_404(Group, pk=school_pk)
user = get_object_or_404(User, pk=user_pk)
if request.user.has_perm('manage_'+str(school.pk)):
admins,_ = Group.objects.get_or_create(name=str(school.pk)+'_admins')
user.groups.add(admins)
user.save()
messages.success(request, user.username + ' a été ajouté aux administrateurs de ' + school.name)
return redirect(reverse('users:school', kwargs={'pk':school.pk}))
messages.error(request, "Vous n'aves pas ce droit.")
return redirect('home')
@login_required
def degrade_user(request, school_pk, user_pk):
school = get_object_or_404(Group, pk=school_pk)
user = get_object_or_404(User, pk=user_pk)
if request.user.has_perm('manage_'+str(school.pk)):
admins,_ = Group.objects.get_or_create(name=str(school.pk)+'_admins')
user.groups.remove(admins)
user.save()
messages.success(request, user.username + ' a été enlevé des administrateurs de ' + school.name)
return redirect(reverse('users:school', kwargs={'pk':school.pk}))
messages.error(request, "Vous n'aves pas ce droit.")
return redirect('home')