Compare commits

...

15 Commits

Author SHA1 Message Date
Antoine Bartuccio 358a625cc4
pedagogy: simplify and implement department system according to old database model 2019-06-18 10:56:05 +02:00
Antoine Bartuccio d44fa73b2a
pedagogy: Fix grade range on UVCOmment 2019-06-17 18:42:33 +02:00
Antoine Bartuccio 5ccb499665
pedagogy: full test suite for UVComment 2019-06-16 20:05:53 +02:00
Antoine Bartuccio c467165bf3
pedagogy: fix error with author change on comment when edited by an admin 2019-06-16 18:34:11 +02:00
Antoine Bartuccio 8512f3c5d0
pedagogy: fix some previous tests never launched and test for display/create/delete of UVComment 2019-06-16 18:26:30 +02:00
Antoine Bartuccio 5003e57338
pedagogy: functional but basic uv comment system 2019-06-16 17:02:45 +02:00
Antoine Bartuccio b7c2da53fe
pedagogy: models for UVComment and UVResult 2019-06-16 16:02:27 +02:00
Antoine Bartuccio 598cdc0284
pedagogy: tests for uv deletion and update 2019-06-16 13:10:27 +02:00
Antoine Bartuccio 692d9a25e3
pedagogy: tests for uv display 2019-06-16 12:44:55 +02:00
Antoine Bartuccio 38f6c27983
pedagogy: tests for uv creation 2019-06-16 12:19:04 +02:00
Antoine Bartuccio 1172402166
pedagogy: basic uv detail view 2019-06-16 02:19:56 +02:00
Antoine Bartuccio ab344ba02f
pedagogy: complete CRUD for UV model 2019-06-16 00:29:46 +02:00
Antoine Bartuccio ec33311715
pedagogy: basic display list of UV 2019-06-15 23:31:31 +02:00
Antoine Bartuccio 5bf5d0277c
pedagogy: create view and form for UV
WARNING: A new group has been created, to be set by the infra team at deployment !!!
2019-06-15 17:01:25 +02:00
Antoine Bartuccio 31f6ee9ca4
pedagogy: create first iteration of UV model 2019-06-15 14:17:49 +02:00
12 changed files with 1496 additions and 77 deletions

View File

@ -52,6 +52,7 @@ from counter.models import Customer, ProductType, Product, Counter, Selling, Stu
from com.models import Sith, Weekmail, News, NewsDate
from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumTopic
from pedagogy.models import UV
class Command(BaseCommand):
@ -84,6 +85,7 @@ class Command(BaseCommand):
Group(name="Banned to subscribe").save()
Group(name="SAS admin").save()
Group(name="Forum admin").save()
Group(name="Pedagogy admin").save()
self.reset_index("core", "auth")
root = User(
id=0,
@ -857,6 +859,18 @@ Welcome to the wiki page!
start_date=timezone.now(),
role=settings.SITH_CLUB_ROLES_ID["Board member"],
).save()
# Adding user tutu
tutu = User(
username="tutu",
last_name="Tu",
first_name="Tu",
email="tutu@git.an",
date_of_birth="1942-06-12",
)
tutu.set_password("plop")
tutu.save()
tutu.groups = [settings.SITH_GROUP_PEDAGOGY_ADMIN_ID]
tutu.save()
# Adding subscription for sli
s = Subscription(
@ -895,6 +909,18 @@ Welcome to the wiki page!
start=s.subscription_start,
)
s.save()
# Tutu
s = Subscription(
member=tutu,
subscription_type=default_subscription,
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
)
s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"],
start=s.subscription_start,
)
s.save()
Selling(
label=dcons.name,
@ -1077,3 +1103,35 @@ Welcome to the wiki page!
start_date=friday + timedelta(hours=24 * 7 * i),
end_date=friday + timedelta(hours=24 * 7 * i + 8),
).save()
# Create som data for pedagogy
UV(
code="PA00",
author=User.objects.get(id=0),
credit_type=settings.SITH_PEDAGOGY_UV_TYPE[3][0],
manager="Laurent HEYBERGER",
semester=settings.SITH_PEDAGOGY_UV_SEMESTER[3][0],
language=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0],
department=settings.SITH_PROFILE_DEPARTMENTS[-2][0],
credits=5,
title="Participation dans une association étudiante",
objectives="* Permettre aux étudiants de réaliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.",
program="""* Semestre précédent proposition d'un projet et d'un cahier des charges
* Evaluation par un jury de six membres
* Si accord réalisation dans le cadre de l'UV
* Compte-rendu de l'expérience
* Présentation""",
skills="""* Gérer un projet associatif ou une action éducative en autonomie:
* en produisant un cahier des charges qui -définit clairement le contexte du projet personnel -pose les jalons de ce projet -estime de manière réaliste les moyens et objectifs du projet -définit exactement les livrables attendus
* en étant capable de respecter ce cahier des charges ou, le cas échéant, de réviser le cahier des charges de manière argumentée.
* Relater son expérience dans un rapport:
* qui permettra à d'autres étudiants de poursuivre les actions engagées
* qui montre la capacité à s'auto-évaluer et à adopter une distance critique sur son action.""",
key_concepts="""* Autonomie
* Responsabilité
* Cahier des charges
* Gestion de projet""",
hours_THE=121,
hours_TE=4,
).save()

View File

@ -670,6 +670,10 @@ class AnonymousUser(AuthAnonymousUser):
def was_subscribed(self):
return False
@property
def is_subscribed(self):
return False
@property
def subscribed(self):
return False

View File

@ -185,7 +185,7 @@
<a href="{{ url('matmat:search_clear') }}">{% trans %}Matmatronch{% endtrans %}</a>
<a href="/launderette">{% trans %}Launderette{% endtrans %}</a>
<a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a>
{# <a href="https://ae2.utbm.fr/uvs/">{% trans %}Pedagogy{% endtrans %}</a> #}
<a href="{{ url('pedagogy:guide') }}">{% trans %}Pedagogy{% endtrans %}</a>
</div>
</div>
<div class="dropdown">

102
pedagogy/forms.py Normal file
View File

@ -0,0 +1,102 @@
# -*- coding:utf-8 -*
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django import forms
from core.views.forms import MarkdownInput
from core.models import User
from pedagogy.models import UV, UVComment
class UVForm(forms.ModelForm):
"""
Form handeling creation and edit of an UV
"""
class Meta:
model = UV
fields = (
"code",
"author",
"credit_type",
"semester",
"language",
"department",
"credits",
"hours_CM",
"hours_TD",
"hours_TP",
"hours_THE",
"hours_TE",
"manager",
"title",
"objectives",
"program",
"skills",
"key_concepts",
)
widgets = {
"objectives": MarkdownInput,
"program": MarkdownInput,
"skills": MarkdownInput,
"key_concepts": MarkdownInput,
"author": forms.HiddenInput,
}
def __init__(self, author_id, *args, **kwargs):
super(UVForm, self).__init__(*args, **kwargs)
self.fields["author"].queryset = User.objects.filter(id=author_id).all()
self.fields["author"].initial = author_id
class UVCommentForm(forms.ModelForm):
"""
Form handeling creation and edit of an UVComment
"""
class Meta:
model = UVComment
fields = (
"author",
"uv",
"grade_global",
"grade_utility",
"grade_interest",
"grade_teaching",
"grade_work_load",
"comment",
)
widgets = {
"comment": MarkdownInput,
"author": forms.HiddenInput,
"uv": forms.HiddenInput,
}
def __init__(self, author_id, uv_id, *args, **kwargs):
super(UVCommentForm, self).__init__(*args, **kwargs)
self.fields["author"].queryset = User.objects.filter(id=author_id).all()
self.fields["author"].initial = author_id
self.fields["uv"].queryset = UV.objects.filter(id=uv_id).all()
self.fields["uv"].initial = uv_id

View File

@ -0,0 +1,347 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-06-18 08:52
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="UV",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"code",
models.CharField(
max_length=10,
unique=True,
validators=[
django.core.validators.RegexValidator(
message="The code of an UV must only contains uppercase characters without accent and numbers",
regex="([A-Z0-9]+)",
)
],
verbose_name="code",
),
),
(
"credit_type",
models.CharField(
choices=[
("FREE", "Free"),
("CS", "CS"),
("TM", "TM"),
("OM", "OM"),
("QC", "QC"),
("EC", "EC"),
("RN", "RN"),
("ST", "ST"),
("EXT", "EXT"),
],
default="FREE",
max_length=10,
verbose_name="credit type",
),
),
(
"semester",
models.CharField(
choices=[
("CLOSED", "Closed"),
("AUTUMN", "Autumn"),
("SPRING", "Spring"),
("AUTOMN_AND_SPRING", "Autumn and spring"),
],
default="CLOSED",
max_length=20,
verbose_name="semester",
),
),
(
"language",
models.CharField(
choices=[
("FR", "French"),
("EN", "English"),
("DE", "German"),
("SP", "Spanich"),
],
default="FR",
max_length=10,
verbose_name="language",
),
),
(
"credits",
models.IntegerField(
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="credits",
),
),
(
"department",
models.CharField(
choices=[
("TC", "TC"),
("IMSI", "IMSI"),
("IMAP", "IMAP"),
("INFO", "INFO"),
("GI", "GI"),
("E", "E"),
("EE", "EE"),
("GESC", "GESC"),
("GMC", "GMC"),
("MC", "MC"),
("EDIM", "EDIM"),
("HUMA", "Humanities"),
("NA", "N/A"),
],
default="NA",
max_length=10,
verbose_name="departmenmt",
),
),
("title", models.CharField(max_length=300, verbose_name="title")),
(
"manager",
models.CharField(max_length=300, verbose_name="uv manager"),
),
("objectives", models.TextField(verbose_name="objectives")),
("program", models.TextField(verbose_name="program")),
("skills", models.TextField(verbose_name="skills")),
("key_concepts", models.TextField(verbose_name="key concepts")),
(
"hours_CM",
models.IntegerField(
default=0,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="hours CM",
),
),
(
"hours_TD",
models.IntegerField(
default=0,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="hours TD",
),
),
(
"hours_TP",
models.IntegerField(
default=0,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="hours TP",
),
),
(
"hours_THE",
models.IntegerField(
default=0,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="hours THE",
),
),
(
"hours_TE",
models.IntegerField(
default=0,
validators=[django.core.validators.MinValueValidator(0)],
verbose_name="hours TE",
),
),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="uv_created",
to=settings.AUTH_USER_MODEL,
verbose_name="author",
),
),
],
),
migrations.CreateModel(
name="UVComment",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("comment", models.TextField(verbose_name="comment")),
(
"grade_global",
models.IntegerField(
default=-1,
validators=[
django.core.validators.MinValueValidator(-1),
django.core.validators.MaxValueValidator(4),
],
verbose_name="global grade",
),
),
(
"grade_utility",
models.IntegerField(
default=-1,
validators=[
django.core.validators.MinValueValidator(-1),
django.core.validators.MaxValueValidator(4),
],
verbose_name="utility grade",
),
),
(
"grade_interest",
models.IntegerField(
default=-1,
validators=[
django.core.validators.MinValueValidator(-1),
django.core.validators.MaxValueValidator(4),
],
verbose_name="interest grade",
),
),
(
"grade_teaching",
models.IntegerField(
default=-1,
validators=[
django.core.validators.MinValueValidator(-1),
django.core.validators.MaxValueValidator(4),
],
verbose_name="teaching grade",
),
),
(
"grade_work_load",
models.IntegerField(
default=-1,
validators=[
django.core.validators.MinValueValidator(-1),
django.core.validators.MaxValueValidator(4),
],
verbose_name="work load grade",
),
),
(
"publish_date",
models.DateField(auto_now=True, verbose_name="publish date"),
),
(
"author",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="uv_comments",
to=settings.AUTH_USER_MODEL,
verbose_name="author",
),
),
(
"uv",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="comments",
to="pedagogy.UV",
verbose_name="uv",
),
),
],
),
migrations.CreateModel(
name="UVCommentReport",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
)
],
),
migrations.CreateModel(
name="UVResult",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"grade",
models.CharField(
choices=[
("A", "A"),
("B", "B"),
("C", "C"),
("D", "D"),
("E", "E"),
("FX", "FX"),
("F", "F"),
("ABS", "Abs"),
],
default="A",
max_length=10,
verbose_name="grade",
),
),
(
"semester",
models.CharField(
max_length=5,
validators=[
django.core.validators.RegexValidator("[AP][0-9]{3}")
],
verbose_name="semester",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="uv_results",
to=settings.AUTH_USER_MODEL,
verbose_name="user",
),
),
(
"uv",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="results",
to="pedagogy.UV",
verbose_name="uv",
),
),
],
),
]

View File

@ -23,6 +23,11 @@
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core import validators
from django.conf import settings
from core.models import User
# Create your models here.
@ -32,7 +37,120 @@ class UV(models.Model):
Contains infos about an UV (course)
"""
pass
def is_owned_by(self, user):
"""
Can be created by superuser, root or pedagogy admin user
"""
return user.is_in_group(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
def can_be_viewed_by(self, user):
"""
Only visible by subscribers
"""
return user.is_subscribed
def __str__(self):
return self.code
code = models.CharField(
_("code"),
max_length=10,
unique=True,
validators=[
validators.RegexValidator(
regex="([A-Z0-9]+)",
message=_(
"The code of an UV must only contains uppercase characters without accent and numbers"
),
)
],
)
author = models.ForeignKey(
User,
related_name="uv_created",
verbose_name=_("author"),
null=False,
blank=False,
)
credit_type = models.CharField(
_("credit type"),
max_length=10,
choices=settings.SITH_PEDAGOGY_UV_TYPE,
default=settings.SITH_PEDAGOGY_UV_TYPE[0][0],
)
manager = models.CharField(_("uv manager"), max_length=300)
semester = models.CharField(
_("semester"),
max_length=20,
choices=settings.SITH_PEDAGOGY_UV_SEMESTER,
default=settings.SITH_PEDAGOGY_UV_SEMESTER[0][0],
)
language = models.CharField(
_("language"),
max_length=10,
choices=settings.SITH_PEDAGOGY_UV_LANGUAGE,
default=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0],
)
credits = models.IntegerField(
_("credits"),
validators=[validators.MinValueValidator(0)],
blank=False,
null=False,
)
# Double star type not implemented yet
department = models.CharField(
_("departmenmt"),
max_length=10,
choices=settings.SITH_PROFILE_DEPARTMENTS,
default=settings.SITH_PROFILE_DEPARTMENTS[-1][0],
)
# All texts about the UV
title = models.CharField(_("title"), max_length=300)
manager = models.CharField(_("uv manager"), max_length=300)
objectives = models.TextField(_("objectives"))
program = models.TextField(_("program"))
skills = models.TextField(_("skills"))
key_concepts = models.TextField(_("key concepts"))
# Hours types CM, TD, TP, THE and TE
# Kind of dirty but I have nothing else in mind for now
hours_CM = models.IntegerField(
_("hours CM"),
validators=[validators.MinValueValidator(0)],
blank=False,
null=False,
default=0,
)
hours_TD = models.IntegerField(
_("hours TD"),
validators=[validators.MinValueValidator(0)],
blank=False,
null=False,
default=0,
)
hours_TP = models.IntegerField(
_("hours TP"),
validators=[validators.MinValueValidator(0)],
blank=False,
null=False,
default=0,
)
hours_THE = models.IntegerField(
_("hours THE"),
validators=[validators.MinValueValidator(0)],
blank=False,
null=False,
default=0,
)
hours_TE = models.IntegerField(
_("hours TE"),
validators=[validators.MinValueValidator(0)],
blank=False,
null=False,
default=0,
)
class UVComment(models.Model):
@ -40,7 +158,86 @@ class UVComment(models.Model):
A comment about an UV
"""
pass
def is_owned_by(self, user):
"""
Is owned by a pedagogy admin, a superuser or the author himself
"""
return self.author == user or user.is_owner(self.uv)
def __str__(self):
return "%s - %s" % (self.uv, self.author)
author = models.ForeignKey(
User,
related_name="uv_comments",
verbose_name=_("author"),
null=False,
blank=False,
)
uv = models.ForeignKey(UV, related_name="comments", verbose_name=_("uv"))
comment = models.TextField(_("comment"))
grade_global = models.IntegerField(
_("global grade"),
validators=[validators.MinValueValidator(-1), validators.MaxValueValidator(4)],
blank=False,
null=False,
default=-1,
)
grade_utility = models.IntegerField(
_("utility grade"),
validators=[validators.MinValueValidator(-1), validators.MaxValueValidator(4)],
blank=False,
null=False,
default=-1,
)
grade_interest = models.IntegerField(
_("interest grade"),
validators=[validators.MinValueValidator(-1), validators.MaxValueValidator(4)],
blank=False,
null=False,
default=-1,
)
grade_teaching = models.IntegerField(
_("teaching grade"),
validators=[validators.MinValueValidator(-1), validators.MaxValueValidator(4)],
blank=False,
null=False,
default=-1,
)
grade_work_load = models.IntegerField(
_("work load grade"),
validators=[validators.MinValueValidator(-1), validators.MaxValueValidator(4)],
blank=False,
null=False,
default=-1,
)
publish_date = models.DateField(_("publish date"), auto_now=True)
class UVResult(models.Model):
"""
Results got to an UV
Views will be implemented after the first release
Will list every UV done by an user
Linked to user
uv
Contains a grade settings.SITH_PEDAGOGY_UV_RESULT_GRADE
a semester (P/A)20xx
"""
uv = models.ForeignKey(UV, related_name="results", verbose_name=_("uv"))
user = models.ForeignKey(User, related_name="uv_results", verbose_name=("user"))
grade = models.CharField(
_("grade"),
max_length=10,
choices=settings.SITH_PEDAGOGY_UV_RESULT_GRADE,
default=settings.SITH_PEDAGOGY_UV_RESULT_GRADE[0][0],
)
semester = models.CharField(
_("semester"),
max_length=5,
validators=[validators.RegexValidator("[AP][0-9]{3}")],
)
class UVCommentReport(models.Model):
@ -49,19 +246,3 @@ class UVCommentReport(models.Model):
"""
pass
class EducationDepartment(models.Model):
"""
Education department of the school
"""
pass
class StudyField(models.Model):
"""
Speciality inside an Education Department
"""
pass

View File

@ -0,0 +1,23 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}UV Guide{% endtrans %}
{% endblock %}
{% block content %}
{% if can_create_uv(user) %}
<p>
<a href="{{ url('pedagogy:uv_create') }}">{% trans %}Create UV{% endtrans %}</a>
</p>
{% endif %}
{% for uv in object_list %}
<p>
<a href="{{ url('pedagogy:uv_detail', uv_id=uv.id) }}">{{ uv.code }}</a>
{% if user.is_owner(uv) -%}
<a href="{{ url('pedagogy:uv_update', uv_id=uv.id) }}">{% trans %}Edit{% endtrans %}</a>
<a href="{{ url('pedagogy:uv_delete', uv_id=uv.id) }}">{% trans %}Delete{% endtrans %}</a>
{%- endif -%}
</p>
{% endfor %}
{% endblock content %}

View File

@ -0,0 +1,40 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}UV Details{% endtrans %}
{% endblock %}
{% block content %}
<p><a href="{{ url('pedagogy:guide') }}">{% trans %}Back{% endtrans %}</a></p>
<h1>{{ object.code }} - {{ object.title }}</h1>
<p>{% trans %}Department: {% endtrans %}{{ object.department }}</p>
<p>{{ object.objectives|markdown }}</p>
<p>{{ object.program|markdown }}</p>
<p>{{ object.skills|markdown }}</p>
<p>{{ object.key_concepts|markdown }}</p>
{% if object.comments.exists() %}
<h2>{% trans %}Comments{% endtrans %}</h2>
{% for comment in object.comments.all() %}
<p>{{ comment.grade_global }}</p>
<p>{{ comment.grade_utility }}</p>
<p>{{ comment.grade_interest }}</p>
<p>{{ comment.grade_teaching }}</p>
<p>{{ comment.grade_work_load }}</p>
<p>{{ comment.comment }}</p>
<p>{% trans %}Published: {% endtrans %}{{ comment.publish_date }}</p>
<p>{% trans %}Author: {% endtrans %}{{ comment.author }}</p>
{% if user.is_owner(comment) %}
<p><a href="{{ url('pedagogy:comment_update', comment_id=comment.id) }}">{% trans %}Edit{% endtrans %}</a></p>
<p><a href="{{ url('pedagogy:comment_delete', comment_id=comment.id) }}">{% trans %}Delete{% endtrans %}</a></p>
{% endif %}
{% endfor %}
{% endif %}
<h2>{% trans %}Leave comment{% endtrans %}</h2>
<form action="{{ url('pedagogy:uv_detail', uv_id=object.id) }}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Comment{% endtrans %}" /></p>
</form>
{% endblock %}

View File

@ -23,5 +23,546 @@
#
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.core.management import call_command
# Create your tests here.
from core.models import User
from pedagogy.models import UV, UVComment
def create_uv_template(user_id, code="IFC1", exclude_list=[]):
"""
Factory to help UV creation/update in post requests
"""
uv = {
"code": code,
"author": user_id,
"credit_type": "TM",
"semester": "SPRING",
"language": "FR",
"department": "TC",
"credits": 3,
"hours_CM": 10,
"hours_TD": 28,
"hours_TP": 0,
"hours_THE": 37,
"hours_TE": 0,
"manager": "Gilles BERTRAND",
"title": "Algorithmique et programmation : niveau I, initiés - partie I",
"objectives": """* Introduction à l'algorithmique et à la programmation pour initiés.
* Pratiques et développement en langage C.""",
"program": """* Découverte des outils élémentaires utilisés pour écrire, compiler et exécuter un programme écrit en langage C
* Règles de programmation : normes en cours, règles de présentation du code, commentaires
* Initiation à l'algorithmique et découverte des bases du langage C :
* les conditions
* les boucles
* les types de données
* les tableaux à une dimension
* manipulations des chaînes de caractères
* les fonctions et procédures""",
"skills": "* D'écrire un algorithme et de l'implémenter en C",
"key_concepts": """* Algorithme
* Variables scalaires et vectorielles
* Structures alternatives, répétitives
* Fonctions, procédures
* Chaînes de caractères""",
}
for excluded in exclude_list:
uv.pop(excluded)
return uv
# UV class tests
class UVCreation(TestCase):
"""
Test uv creation
"""
def setUp(self):
call_command("populate")
self.bibou = User.objects.filter(username="root").first()
self.tutu = User.objects.filter(username="tutu").first()
self.sli = User.objects.filter(username="sli").first()
self.guy = User.objects.filter(username="guy").first()
def test_create_uv_admin_success(self):
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("pedagogy:uv_create"), create_uv_template(self.bibou.id)
)
self.assertEquals(response.status_code, 302)
self.assertTrue(UV.objects.filter(code="IFC1").exists())
def test_create_uv_pedagogy_admin_success(self):
self.client.login(username="tutu", password="plop")
response = self.client.post(
reverse("pedagogy:uv_create"), create_uv_template(self.tutu.id)
)
self.assertEquals(response.status_code, 302)
self.assertTrue(UV.objects.filter(code="IFC1").exists())
def test_create_uv_unauthorized_fail(self):
# Test with anonymous user
response = self.client.post(
reverse("pedagogy:uv_create"), create_uv_template(0)
)
self.assertEquals(response.status_code, 403)
# Test with subscribed user
self.client.login(username="sli", password="plop")
response = self.client.post(
reverse("pedagogy:uv_create"), create_uv_template(self.sli.id)
)
self.assertEquals(response.status_code, 403)
# Test with non subscribed user
self.client.login(username="guy", password="plop")
response = self.client.post(
reverse("pedagogy:uv_create"), create_uv_template(self.guy.id)
)
self.assertEquals(response.status_code, 403)
# Check that the UV has never been created
self.assertFalse(UV.objects.filter(code="IFC1").exists())
def test_create_uv_bad_request_fail(self):
self.client.login(username="tutu", password="plop")
# Test with wrong user id (if someone cheats on the hidden input)
response = self.client.post(
reverse("pedagogy:uv_create"), create_uv_template(self.bibou.id)
)
self.assertNotEquals(response.status_code, 302)
self.assertEquals(response.status_code, 200)
# Remove a required field
response = self.client.post(
reverse("pedagogy:uv_create"),
create_uv_template(self.tutu.id, exclude_list=["title"]),
)
self.assertNotEquals(response.status_code, 302)
self.assertEquals(response.status_code, 200)
# Check that the UV hase never been created
self.assertFalse(UV.objects.filter(code="IFC1").exists())
class UVListTest(TestCase):
"""
Test guide display rights
"""
def setUp(self):
call_command("populate")
def test_uv_list_display_success(self):
# Display for root
self.client.login(username="root", password="plop")
response = self.client.get(reverse("pedagogy:guide"))
self.assertContains(response, text="PA00")
# Display for pedagogy admin
self.client.login(username="tutu", password="plop")
response = self.client.get(reverse("pedagogy:guide"))
self.assertContains(response, text="PA00")
# Display for simple subscriber
self.client.login(username="sli", password="plop")
response = self.client.get(reverse("pedagogy:guide"))
self.assertContains(response, text="PA00")
def test_uv_list_display_fail(self):
# Don't display for anonymous user
response = self.client.get(reverse("pedagogy:guide"))
self.assertEquals(response.status_code, 403)
# Don't display for none subscribed users
self.client.login(username="guy", password="plop")
response = self.client.get(reverse("pedagogy:guide"))
self.assertEquals(response.status_code, 403)
class UVDeleteTest(TestCase):
"""
Test UV deletion rights
"""
def setUp(self):
call_command("populate")
def test_uv_delete_root_success(self):
self.client.login(username="root", password="plop")
self.client.post(
reverse(
"pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id}
)
)
self.assertFalse(UV.objects.filter(code="PA00").exists())
def test_uv_delete_pedagogy_admin_success(self):
self.client.login(username="tutu", password="plop")
self.client.post(
reverse(
"pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id}
)
)
self.assertFalse(UV.objects.filter(code="PA00").exists())
def test_uv_delete_pedagogy_unauthorized_fail(self):
# Anonymous user
response = self.client.post(
reverse(
"pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id}
)
)
self.assertEquals(response.status_code, 403)
# Not subscribed user
self.client.login(username="guy", password="plop")
response = self.client.post(
reverse(
"pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id}
)
)
self.assertEquals(response.status_code, 403)
# Simply subscribed user
self.client.login(username="sli", password="plop")
response = self.client.post(
reverse(
"pedagogy:uv_delete", kwargs={"uv_id": UV.objects.get(code="PA00").id}
)
)
self.assertEquals(response.status_code, 403)
# Check that the UV still exists
self.assertTrue(UV.objects.filter(code="PA00").exists())
class UVUpdateTest(TestCase):
"""
Test UV update rights
"""
def setUp(self):
call_command("populate")
self.bibou = User.objects.filter(username="root").first()
self.tutu = User.objects.filter(username="tutu").first()
self.sli = User.objects.filter(username="sli").first()
self.guy = User.objects.filter(username="guy").first()
def test_uv_update_root_success(self):
self.client.login(username="root", password="plop")
self.client.post(
reverse(
"pedagogy:uv_update", kwargs={"uv_id": UV.objects.get(code="PA00").id}
),
create_uv_template(self.bibou.id, code="PA00"),
)
self.assertEquals(UV.objects.get(code="PA00").credit_type, "TM")
def test_uv_update_pedagogy_admin_success(self):
self.client.login(username="tutu", password="plop")
self.client.post(
reverse(
"pedagogy:uv_update", kwargs={"uv_id": UV.objects.get(code="PA00").id}
),
create_uv_template(self.tutu.id, code="PA00"),
)
self.assertEquals(UV.objects.get(code="PA00").credit_type, "TM")
def test_uv_update_pedagogy_unauthorized_fail(self):
# Anonymous user
response = self.client.post(
reverse(
"pedagogy:uv_update", kwargs={"uv_id": UV.objects.get(code="PA00").id}
),
create_uv_template(0, code="PA00"),
)
self.assertEquals(response.status_code, 403)
# Not subscribed user
self.client.login(username="guy", password="plop")
response = self.client.post(
reverse(
"pedagogy:uv_update", kwargs={"uv_id": UV.objects.get(code="PA00").id}
),
create_uv_template(self.guy.id, code="PA00"),
)
self.assertEquals(response.status_code, 403)
# Simply subscribed user
self.client.login(username="sli", password="plop")
response = self.client.post(
reverse(
"pedagogy:uv_update", kwargs={"uv_id": UV.objects.get(code="PA00").id}
),
create_uv_template(self.sli.id, code="PA00"),
)
self.assertEquals(response.status_code, 403)
# Check that the UV has not changed
self.assertEquals(UV.objects.get(code="PA00").credit_type, "OM")
# UVComment class tests
def create_uv_comment_template(user_id, uv_code="PA00", exclude_list=[]):
"""
Factory to help UVComment creation/update in post requests
"""
comment = {
"author": user_id,
"uv": UV.objects.get(code=uv_code).id,
"grade_global": 4,
"grade_utility": 4,
"grade_interest": 4,
"grade_teaching": -1,
"grade_work_load": 2,
"comment": "Superbe UV qui fait vivre la vie associative de l'école",
}
for excluded in exclude_list:
comment.pop(excluded)
return comment
class UVCommentCreationAndDisplay(TestCase):
"""
Test UVComment creation and it's display
Display and creation are the same view
"""
def setUp(self):
call_command("populate")
self.bibou = User.objects.filter(username="root").first()
self.tutu = User.objects.filter(username="tutu").first()
self.sli = User.objects.filter(username="sli").first()
self.guy = User.objects.filter(username="guy").first()
self.uv = UV.objects.get(code="PA00")
def test_create_uv_comment_admin_success(self):
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}),
create_uv_comment_template(self.bibou.id),
)
self.assertEquals(response.status_code, 302)
response = self.client.get(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id})
)
self.assertContains(response, text="Superbe UV")
def test_create_uv_comment_pedagogy_admin_success(self):
self.client.login(username="tutu", password="plop")
response = self.client.post(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}),
create_uv_comment_template(self.tutu.id),
)
self.assertEquals(response.status_code, 302)
response = self.client.get(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id})
)
self.assertContains(response, text="Superbe UV")
def test_create_uv_comment_subscriber_success(self):
self.client.login(username="sli", password="plop")
response = self.client.post(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}),
create_uv_comment_template(self.sli.id),
)
self.assertEquals(response.status_code, 302)
response = self.client.get(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id})
)
self.assertContains(response, text="Superbe UV")
def test_create_uv_comment_unauthorized_fail(self):
# Test with anonymous user
response = self.client.post(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}),
create_uv_comment_template(0),
)
self.assertEquals(response.status_code, 403)
# Test with non subscribed user
self.client.login(username="guy", password="plop")
response = self.client.post(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}),
create_uv_comment_template(self.guy.id),
)
self.assertEquals(response.status_code, 403)
# Check that the comment has never been created
self.client.login(username="root", password="plop")
response = self.client.get(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id})
)
self.assertNotContains(response, text="Superbe UV")
def test_create_uv_comment_bad_form_fail(self):
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id}),
create_uv_comment_template(self.bibou.id, exclude_list=["grade_global"]),
)
self.assertEquals(response.status_code, 200)
response = self.client.get(
reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv.id})
)
self.assertNotContains(response, text="Superbe UV")
class UVCommentDeleteTest(TestCase):
"""
Test UVComment deletion rights
"""
def setUp(self):
call_command("populate")
comment_kwargs = create_uv_comment_template(
User.objects.get(username="krophil").id
)
comment_kwargs["author"] = User.objects.get(id=comment_kwargs["author"])
comment_kwargs["uv"] = UV.objects.get(id=comment_kwargs["uv"])
self.comment = UVComment(**comment_kwargs)
self.comment.save()
def test_uv_comment_delete_root_success(self):
self.client.login(username="root", password="plop")
self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
self.assertFalse(UVComment.objects.filter(id=self.comment.id).exists())
def test_uv_comment_delete_pedagogy_admin_success(self):
self.client.login(username="tutu", password="plop")
self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
self.assertFalse(UVComment.objects.filter(id=self.comment.id).exists())
def test_uv_comment_delete_author_success(self):
self.client.login(username="krophil", password="plop")
self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
self.assertFalse(UVComment.objects.filter(id=self.comment.id).exists())
def test_uv_comment_delete_unauthorized_fail(self):
# Anonymous user
response = self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
self.assertEquals(response.status_code, 403)
# Unsbscribed user
self.client.login(username="guy", password="plop")
response = self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
self.assertEquals(response.status_code, 403)
# Subscribed user (not author of the comment)
self.client.login(username="sli", password="plop")
response = self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
self.assertEquals(response.status_code, 403)
# Check that the comment still exists
self.assertTrue(UVComment.objects.filter(id=self.comment.id).exists())
class UVCommentUpdateTest(TestCase):
"""
Test UVComment update rights
"""
def setUp(self):
call_command("populate")
self.krophil = User.objects.get(username="krophil")
# Prepare a comment
comment_kwargs = create_uv_comment_template(self.krophil.id)
comment_kwargs["author"] = self.krophil
comment_kwargs["uv"] = UV.objects.get(id=comment_kwargs["uv"])
self.comment = UVComment(**comment_kwargs)
self.comment.save()
# Prepare edit of this comment for post requests
self.comment_edit = create_uv_comment_template(self.krophil.id)
self.comment_edit["comment"] = "Edited"
def test_uv_comment_update_root_success(self):
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 302)
self.comment.refresh_from_db()
self.assertEquals(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_pedagogy_admin_success(self):
self.client.login(username="tutu", password="plop")
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 302)
self.comment.refresh_from_db()
self.assertEquals(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_author_success(self):
self.client.login(username="krophil", password="plop")
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 302)
self.comment.refresh_from_db()
self.assertEquals(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_unauthorized_fail(self):
# Anonymous user
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 403)
# Unsbscribed user
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 403)
# Subscribed user (not author of the comment)
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 403)
# Check that the comment hasn't change
self.comment.refresh_from_db()
self.assertNotEquals(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_original_author_does_not_change(self):
self.client.login(username="root", password="plop")
self.comment_edit["author"] = User.objects.get(username="root").id
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
self.assertEquals(response.status_code, 200)
self.assertEquals(self.comment.author, self.krophil)

View File

@ -31,9 +31,14 @@ urlpatterns = [
url(r"^$", UVListView.as_view(), name="guide"),
url(r"^uv/(?P<uv_id>[0-9]+)$", UVDetailFormView.as_view(), name="uv_detail"),
url(
r"^comment/(?P<comment_id>[0-9]+)$",
UVCommentDetailView.as_view(),
name="comment_detail",
r"^comment/(?P<comment_id>[0-9]+)/edit$",
UVCommentUpdateView.as_view(),
name="comment_update",
),
url(
r"^comment/(?P<comment_id>[0-9]+)/delete$",
UVCommentDeleteView.as_view(),
name="comment_delete",
),
url(
r"^comment/(?P<comment_id>[0-9]+)/report$",
@ -46,20 +51,5 @@ urlpatterns = [
# Administration : Create Update Delete Edit
url(r"^uv/create$", UVCreateView.as_view(), name="uv_create"),
url(r"^uv/(?P<uv_id>[0-9]+)/delete$", UVDeleteView.as_view(), name="uv_delete"),
url(
r"^department/create$",
EducationDepartmentCreateView.as_view(),
name="department_create",
),
url(
r"^department/(?P<department_id>[0-9]+)/delete$",
EducationDepartmentDeleteView.as_view(),
name="department_delete",
),
url(r"^study/create$", StudyFieldCreateView.as_view(), name="study_create"),
url(
r"^study/(?P<study_id>[0-9]+)/delete$",
StudyFieldDeleteView.as_view(),
name="study_delete",
),
url(r"^uv/(?P<uv_id>[0-9]+)/edit$", UVUpdateView.as_view(), name="uv_update"),
]

View File

@ -22,34 +22,127 @@
#
#
from django.views.generic import CreateView, DeleteView, DetailView, ListView, FormView
from django.views.generic import (
CreateView,
DeleteView,
DetailView,
UpdateView,
ListView,
FormView,
View,
)
from django.core.urlresolvers import reverse_lazy
from core.views import DetailFormView
from core.views import (
DetailFormView,
CanCreateMixin,
CanEditMixin,
CanViewMixin,
CanEditPropMixin,
)
from pedagogy.forms import UVForm, UVCommentForm
from pedagogy.models import UV, UVComment
# Some mixins
class UVDetailFormView(DetailFormView):
class CanCreateUVFunctionMixin(View):
"""
Add the function can_create_uv(user) into the template
"""
@staticmethod
def can_create_uv(user):
"""
Creates a dummy instance of UV and test is_owner
"""
return user.is_owner(UV())
def get_context_data(self, **kwargs):
"""
Pass the function to the template
"""
kwargs = super(CanCreateUVFunctionMixin, self).get_context_data(**kwargs)
kwargs["can_create_uv"] = self.can_create_uv
return kwargs
# Acutal views
class UVDetailFormView(CanViewMixin, CanCreateUVFunctionMixin, DetailFormView):
"""
Dispaly every comment of an UV and detailed infos about it
Allow to comment the UV
"""
pass
model = UV
pk_url_kwarg = "uv_id"
template_name = "pedagogy/uv_detail.jinja"
form_class = UVCommentForm
def get_form_kwargs(self):
kwargs = super(UVDetailFormView, self).get_form_kwargs()
kwargs["author_id"] = self.request.user.id
kwargs["uv_id"] = self.get_object().id
return kwargs
def form_valid(self, form):
form.save()
return super(UVDetailFormView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy(
"pedagogy:uv_detail", kwargs={"uv_id": self.get_object().id}
)
class UVCommentDetailView(DetailView):
class UVCommentUpdateView(CanEditPropMixin, UpdateView):
"""
Display a specified UVComment (for easy sharing of the comment)
Allow edit of a given comment
"""
pass
model = UVComment
form_class = UVCommentForm
pk_url_kwarg = "comment_id"
template_name = "core/edit.jinja"
def get_form_kwargs(self):
kwargs = super(UVCommentUpdateView, self).get_form_kwargs()
obj = self.get_object()
kwargs["author_id"] = obj.author.id
kwargs["uv_id"] = obj.uv.id
return kwargs
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.uv.id})
class UVListView(ListView):
class UVCommentDeleteView(CanEditPropMixin, DeleteView):
"""
Allow delete of a given comment
"""
model = UVComment
pk_url_kwarg = "comment_id"
template_name = "core/delete_confirm.jinja"
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.uv.id})
class UVListView(CanViewMixin, CanCreateUVFunctionMixin, ListView):
"""
UV guide main page
"""
pass
# This is very basic and is prone to changment
model = UV
ordering = ["code"]
template_name = "pedagogy/guide.jinja"
class UVCommentReportCreateView(CreateView):
@ -76,49 +169,51 @@ class UVModerationFormView(FormView):
pass
class UVCreateView(CreateView):
class UVCreateView(CanCreateMixin, CreateView):
"""
Add a new UV (Privileged)
"""
pass
model = UV
form_class = UVForm
template_name = "core/edit.jinja"
def get_form_kwargs(self):
kwargs = super(UVCreateView, self).get_form_kwargs()
kwargs["author_id"] = self.request.user.id
return kwargs
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})
class UVDeleteView(DeleteView):
class UVDeleteView(CanEditPropMixin, DeleteView):
"""
Allow to delete an UV (Privileged)
"""
pass
model = UV
pk_url_kwarg = "uv_id"
template_name = "core/delete_confirm.jinja"
def get_success_url(self):
return reverse_lazy("pedagogy:guide")
class EducationDepartmentCreateView(CreateView):
class UVUpdateView(CanEditPropMixin, UpdateView):
"""
Add a new Education Department (Privileged)
Allow to edit an UV (Privilegied)
"""
pass
model = UV
form_class = UVForm
pk_url_kwarg = "uv_id"
template_name = "core/edit.jinja"
def get_form_kwargs(self):
kwargs = super(UVUpdateView, self).get_form_kwargs()
kwargs["author_id"] = self.request.user.id
return kwargs
class EducationDepartmentDeleteView(DeleteView):
"""
Allow to delete an Education Department (Privileged)
"""
pass
class StudyFieldCreateView(CreateView):
"""
Add a new Study Field (Privileged)
"""
pass
class StudyFieldDeleteView(DeleteView):
"""
Allow to delete an Study Field (Privileged)
"""
pass
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})

View File

@ -322,6 +322,7 @@ SITH_GROUP_BANNED_COUNTER_ID = 9
SITH_GROUP_BANNED_SUBSCRIPTION_ID = 10
SITH_GROUP_SAS_ADMIN_ID = 11
SITH_GROUP_FORUM_ADMIN_ID = 12
SITH_GROUP_PEDAGOGY_ADMIN_ID = 13
SITH_CLUB_REFOUND_ID = 89
@ -404,6 +405,43 @@ SITH_COUNTER_BANK = [
("LA-POSTE", "La Poste"),
]
SITH_PEDAGOGY_UV_TYPE = [
("FREE", _("Free")),
("CS", _("CS")),
("TM", _("TM")),
("OM", _("OM")),
("QC", _("QC")),
("EC", _("EC")),
("RN", _("RN")),
("ST", _("ST")),
("EXT", _("EXT")),
]
SITH_PEDAGOGY_UV_SEMESTER = [
("CLOSED", _("Closed")),
("AUTUMN", _("Autumn")),
("SPRING", _("Spring")),
("AUTOMN_AND_SPRING", _("Autumn and spring")),
]
SITH_PEDAGOGY_UV_LANGUAGE = [
("FR", _("French")),
("EN", _("English")),
("DE", _("German")),
("SP", _("Spanich")),
]
SITH_PEDAGOGY_UV_RESULT_GRADE = [
("A", _("A")),
("B", _("B")),
("C", _("C")),
("D", _("D")),
("E", _("E")),
("FX", _("FX")),
("F", _("F")),
("ABS", _("Abs")),
]
SITH_ECOCUP_CONS = 1152
SITH_ECOCUP_DECO = 1151