diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py
index 312744c6..caebd498 100644
--- a/core/management/commands/populate.py
+++ b/core/management/commands/populate.py
@@ -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()
diff --git a/core/migrations/0030_auto_20190704_1500.py b/core/migrations/0030_auto_20190704_1500.py
new file mode 100644
index 00000000..72121e9e
--- /dev/null
+++ b/core/migrations/0030_auto_20190704_1500.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-07-04 13:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [("core", "0029_auto_20180426_2013")]
+
+ operations = [
+ migrations.AlterField(
+ model_name="notification",
+ name="type",
+ field=models.CharField(
+ choices=[
+ ("POSTER_MODERATION", "A new poster needs to be moderated"),
+ ("MAILING_MODERATION", "A new mailing list needs to be moderated"),
+ (
+ "PEDAGOGY_MODERATION",
+ "A new pedagogy comment has been signaled for moderation",
+ ),
+ ("NEWS_MODERATION", "There are %s fresh news to be moderated"),
+ ("FILE_MODERATION", "New files to be moderated"),
+ (
+ "SAS_MODERATION",
+ "There are %s pictures to be moderated in the SAS",
+ ),
+ ("NEW_PICTURES", "You've been identified on some pictures"),
+ ("REFILLING", "You just refilled of %s €"),
+ ("SELLING", "You just bought %s"),
+ ("GENERIC", "You have a notification"),
+ ],
+ default="GENERIC",
+ max_length=32,
+ verbose_name="type",
+ ),
+ )
+ ]
diff --git a/core/models.py b/core/models.py
index f96b7881..b1def45c 100644
--- a/core/models.py
+++ b/core/models.py
@@ -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
diff --git a/core/static/core/style.scss b/core/static/core/style.scss
index 548c3b97..e919eb81 100644
--- a/core/static/core/style.scss
+++ b/core/static/core/style.scss
@@ -30,6 +30,12 @@ $shadow-color: rgb(223, 223, 223);
$background-bouton-color: hsl(0, 0%, 90%);
+/*--------------------------MEDIA QUERY HELPERS------------------------*/
+$small-devices: 576px;
+$medium-devices: 768px;
+$large-devices: 992px;
+$extra-large-devices: 1200px;
+
/*--------------------------------GENERAL------------------------------*/
body {
@@ -1564,7 +1570,6 @@ footer {
form {
margin: 0px auto;
margin-bottom: 10px;
- width: 60%;
}
label {
@@ -1668,3 +1673,445 @@ label {
}
}
+
+/* --------------------------------------pedagogy-----------------------------------*/
+
+$pedagogy-blue: #1bb9ea;
+$pedagogy-orange: #ea7900;
+$pedagogy-hover-blue: #0e97ce;
+$pedagogy-light-blue: #caf0ff;
+$pedagogy-white-text: #f0f0f0;
+
+.pedagogy {
+ &.star-not-checked {
+ color : #f7f7f7;
+ margin-bottom: 0px;
+ margin-top: 0px;
+ }
+ &.star-checked {
+ color: $pedagogy-orange;
+ margin-bottom: 0px;
+ margin-top: 0px;
+ }
+
+ @media screen and (max-width: $large-devices){
+ &.star-not-checked {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+ &.star-checked {
+ margin-left: 5px;
+ margin-right: 5px;
+ }
+ }
+
+ #dynamic_view {
+ font-size: 1.1em;
+
+ table {
+ }
+
+ td {
+ text-align: center;
+ border: none;
+ }
+
+ }
+
+ #search_form {
+ margin: 0px;
+ width: 100%;
+
+ .input-search {
+ background: $pedagogy-light-blue;
+ width: 300px;
+ height: 21px;
+ }
+ .button-search {
+ background: $pedagogy-orange;
+ color: white;
+ font-weight: bold;
+ margin-left: 20px;
+ }
+ .radio-guide input[type="radio"],input[type="checkbox"] {
+ display:none;
+ }
+ .radio-guide {
+ margin-top: 10px;
+ color: white;
+ }
+ .radio-guide label {
+ display:inline-block;
+ background-color: $pedagogy-blue;
+ padding: 10px 20px;
+ font-family:Arial;
+ font-size:16px;
+ border-radius: 4px;
+ }
+ .radio-guide input[type="radio"]:checked + label {
+ background-color:$pedagogy-orange;
+ }
+ .radio-guide input[type="checkbox"]:checked + label {
+ background-color:$pedagogy-orange;
+ }
+ .radio-guide label:hover {
+ background-color: $pedagogy-hover-blue;
+ }
+
+ #radioAUTUMN + label {
+ margin-left: 50px;
+ }
+ }
+
+ #uv_detail {
+ color: #062f38;
+
+ .uv-quick-info-container {
+ display: grid;
+ grid-template-columns: 20% 20% 20% 20% auto;
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ "hours-cm hours-td hours-tp hours-te hours-the"
+ "department credit-type semester . ." ;
+ }
+
+ .department {
+ grid-area: department;
+ }
+
+ .credit-type {
+ grid-area: credit-type;
+ }
+
+ .semester {
+ grid-area: semester;
+ }
+
+ .hours-cm {
+ grid-area: hours-cm;
+ }
+
+ .hours-td {
+ grid-area: hours-td;
+ }
+
+ .hours-tp {
+ grid-area: hours-tp;
+ }
+
+ .hours-te {
+ grid-area: hours-te;
+ }
+
+ .hours-the {
+ grid-area: hours-the;
+ }
+
+ #leave_comment {
+ .leave-comment-grid-container {
+ display: grid;
+ grid-template-columns: 270px auto;
+ grid-template-rows: 100%;
+ grid-template-areas: "stars comment";
+
+ @media screen and (max-width: $large-devices){
+ grid-template-columns: 100%;
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ "stars"
+ "comment";
+ }
+ }
+
+ .ui-accordion-content {
+ background-color: $white-color;
+ border-color: $pedagogy-orange;
+ border-right: none;
+ }
+
+ .form-stars {
+ grid-area: stars;
+ }
+
+ .form-comment {
+ grid-area: comment;
+ }
+
+ .ui-accordion-header {
+ background-color: $pedagogy-orange;
+ color: $pedagogy-white-text;
+ clip-path: polygon(0 0%, 0 100%, 30% 100%, 33% 0);
+
+ @media screen and (max-width: $large-devices){
+ clip-path: none;
+ }
+ }
+
+ .ui-accordion-header-icon {
+ color: $pedagogy-white-text;
+ margin-right: 10px;
+ }
+
+ .input-stars {
+ margin-top: 20px;
+ }
+
+ input[type="submit"] {
+ float: right;
+ }
+ }
+
+ .uv-details-container {
+ display: grid;
+ grid-template-columns: 150px 100px auto;
+ grid-template-rows: 156px 1fr;
+ grid-template-areas:
+ "grade grade-stars uv-infos"
+ ". . uv-infos";
+
+ @media screen and (max-width: $large-devices){
+ grid-template-columns: 50% 50%;
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ "grade grade-stars"
+ "uv-infos uv-infos";
+ }
+ }
+
+ .grade {
+ grid-area: grade;
+ color: $pedagogy-white-text;
+ background-color: $pedagogy-blue;
+ padding-right: 10px;
+
+ > p {
+ text-align: right;
+ font-weight: bold;
+ }
+ }
+
+ .grade-stars {
+ grid-area: grade-stars;
+ color: $pedagogy-white-text;
+ background-color: $pedagogy-blue;
+ font-weight: bold;
+ }
+
+ .uv-infos {
+ grid-area: uv-infos;
+ padding-left: 10px;
+ }
+
+ .comment-container {
+ display: grid;
+ grid-template-columns: 300px auto;
+ grid-template-rows: auto auto auto;
+ grid-template-areas:
+ "grade-block comment"
+ "grade-block info"
+ "comment-end-bar comment-end-bar";
+ margin-bottom: 30px;
+ margin-top: 10px;
+
+ @media screen and (max-width: $large-devices){
+ grid-template-columns: auto;
+ grid-template-rows: auto auto auto auto;
+ grid-template-areas:
+ "grade-block"
+ "comment"
+ "info"
+ "comment-end-bar"
+ }
+
+ .grade-block {
+ grid-area: grade-block;
+ width: 300px;
+
+ display: grid;
+ grid-template-columns: 150px 150px;
+ grid-template-rows: 156px auto;
+ grid-template-areas:
+ "grade-type grade-stars"
+ "grade-extension grade-extension";
+ grid-gap: 15px;
+
+ clip-path: polygon(0 0, 0 100%, 100% 100%, 100% 30px, 270px 0);
+ align-items: start;
+
+ background-color: $pedagogy-blue;
+
+ @media screen and (max-width: $large-devices){
+ grid-template-columns: 50% auto;
+ grid-template-rows: auto;
+ grid-template-areas:"grade-type grade-stars";
+ width: auto;
+ clip-path: none;
+ align-content: space-evenly;
+ align-items: end;
+ }
+
+ .grade-extension {
+ grid-area: grade-extension;
+ background-color: $pedagogy-blue;
+ }
+
+ .grade-type {
+ grid-area: grade-type;
+
+ > p {
+ color: $pedagogy-white-text;
+ font-weight: bold;
+ text-align: right;
+ }
+ }
+
+ .grade-stars {
+ grid-area: grade-stars;
+ }
+ }
+
+ .comment {
+ grid-area: comment;
+
+ display: grid;
+ grid-template-columns: auto;
+ grid-template-rows: auto auto;
+ grid-template-areas:
+ "anchor"
+ "markdown";
+
+ @media screen and (max-width: $large-devices){
+ border-left: solid;
+ border-right: solid;
+ border-color: $pedagogy-blue;
+ }
+
+ .anchor {
+ grid-area: anchor;
+ text-align: right;
+ margin-right: 15px;
+ }
+
+ .markdown {
+ grid-area: markdown;
+
+ min-height: 139px;
+ margin-top: 0px;
+ margin-right: 0px;
+ padding: 10px;
+ text-align: justify;
+ overflow: auto;
+ }
+ }
+
+ .info {
+ grid-area: info;
+ padding-bottom: 10px;
+
+ @media screen and (max-width: $large-devices){
+ border-left: solid;
+ border-right: solid;
+ border-color: $pedagogy-blue;
+ }
+
+ .status-reported {
+ color: red;
+ float: left;
+ padding-left: 10px;
+ }
+
+ .actions {
+ float: right;
+ }
+ }
+
+ .comment-end-bar {
+ grid-area: comment-end-bar;
+
+ display: grid;
+ grid-template-columns: 33% auto auto;
+ grid-template-rows: 2.5em;
+ grid-template-areas: "author date report";
+
+ background-color: $pedagogy-blue;
+ margin-top: -1px;
+
+ @media screen and (max-width: $large-devices){
+ grid-template-columns: auto;
+ grid-template-rows: auto auto auto;
+ grid-template-areas:
+ "report"
+ "date"
+ "author";
+ margin-top: 0px;
+ text-align: center;
+ }
+
+ .author {
+ grid-area: author;
+
+ padding-top: 6px;
+ padding-left: 20px;
+
+ background-color: $pedagogy-orange;
+ clip-path: polygon(0 10px, 0 100%, 350px 200%, 300px 10px);
+
+ @media screen and (max-width: $large-devices){
+ clip-path: none;
+ padding: 0px;
+ padding-bottom: 7px;
+ }
+
+ a {
+ color: $pedagogy-white-text;
+ font-weight: bold;
+ }
+
+ a:hover {
+ color: $pedagogy-hover-blue;
+ }
+
+ }
+
+ .date {
+ grid-area: date;
+ color: $pedagogy-white-text;
+
+ @media screen and (max-width: $large-devices){
+ padding-bottom: 7px;
+ }
+ }
+
+ .report {
+ grid-area: report;
+ justify-self: right;
+ padding-right: 30px;
+ padding-left: 30px;
+
+ a {
+ color: $pedagogy-white-text;
+ }
+
+ a:hover {
+ color: $pedagogy-hover-blue;
+ }
+
+ @media screen and (max-width: $large-devices){
+ text-align: center;
+ justify-self: inherit;
+ padding-bottom: 7px;
+ background-color: $white-color;
+
+ border-left: solid;
+ border-right: solid;
+ border-color: $pedagogy-blue;
+
+ a {
+ color: $black-color;
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/templates/core/base.jinja b/core/templates/core/base.jinja
index 8ff8b2e4..9d0e53cc 100644
--- a/core/templates/core/base.jinja
+++ b/core/templates/core/base.jinja
@@ -185,7 +185,7 @@
{% trans %}Matmatronch{% endtrans %}
{% trans %}Launderette{% endtrans %}
{% trans %}Files{% endtrans %}
- {# {% trans %}Pedagogy{% endtrans %} #}
+ {% trans %}Pedagogy{% endtrans %}
diff --git a/core/templates/core/user_tools.jinja b/core/templates/core/user_tools.jinja
index 6aa80f82..e5c8081b 100644
--- a/core/templates/core/user_tools.jinja
+++ b/core/templates/core/user_tools.jinja
@@ -104,6 +104,16 @@
{{ m.club }}
{% endfor %}
+
+
+
{% trans %}Pedagogy{% endtrans %}
+
+
{% trans %}Elections{% endtrans %}
+
+
{% trans %}Other tools{% endtrans %}
- {% trans %}Convert dokuwiki/BBcode syntax to Markdown{% endtrans %}
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 4041b770..1278e461 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-05-20 17:55+0200\n"
+"POT-Creation-Date: 2019-07-08 22:46+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia \n"
"Language-Team: AE info \n"
@@ -127,8 +127,8 @@ msgstr "numéro"
msgid "journal"
msgstr "classeur"
-#: accounting/models.py:269 core/models.py:809 core/models.py:1345
-#: core/models.py:1391 core/models.py:1420 counter/models.py:353
+#: accounting/models.py:269 core/models.py:813 core/models.py:1349
+#: core/models.py:1395 core/models.py:1424 counter/models.py:353
#: counter/models.py:446 counter/models.py:615 eboutic/models.py:42
#: eboutic/models.py:85 forum/models.py:298 forum/models.py:391
#: stock/models.py:99
@@ -136,7 +136,7 @@ msgid "date"
msgstr "date"
#: accounting/models.py:270 counter/models.py:123 counter/models.py:616
-#: stock/models.py:102
+#: pedagogy/models.py:220 stock/models.py:102
msgid "comment"
msgstr "commentaire"
@@ -166,7 +166,7 @@ msgid "accounting type"
msgstr "type comptable"
#: accounting/models.py:304 accounting/models.py:450 accounting/models.py:485
-#: accounting/models.py:519 core/models.py:1419 counter/models.py:412
+#: accounting/models.py:519 core/models.py:1423 counter/models.py:412
msgid "label"
msgstr "étiquette"
@@ -218,7 +218,7 @@ msgstr "Compte"
msgid "Company"
msgstr "Entreprise"
-#: accounting/models.py:317 sith/settings.py:375
+#: accounting/models.py:317 sith/settings.py:377
#: stock/templates/stock/shopping_list_items.jinja:37
msgid "Other"
msgstr "Autre"
@@ -265,7 +265,7 @@ msgstr ""
"Vous devez fournir soit un type comptable simplifié ou un type comptable "
"standard"
-#: accounting/models.py:442 counter/models.py:159
+#: accounting/models.py:442 counter/models.py:159 pedagogy/models.py:91
msgid "code"
msgstr "code"
@@ -377,9 +377,12 @@ msgstr "Compte en banque : "
#: election/templates/election/election_detail.jinja:401
#: forum/templates/forum/macros.jinja:21 forum/templates/forum/macros.jinja:134
#: launderette/templates/launderette/launderette_admin.jinja:16
-#: launderette/views.py:226 sas/templates/sas/album.jinja:26
-#: sas/templates/sas/moderation.jinja:18 sas/templates/sas/picture.jinja:74
-#: sas/templates/sas/picture.jinja:124
+#: launderette/views.py:226 pedagogy/templates/pedagogy/guide.jinja:51
+#: pedagogy/templates/pedagogy/guide.jinja:74
+#: pedagogy/templates/pedagogy/guide.jinja:110
+#: pedagogy/templates/pedagogy/uv_detail.jinja:178
+#: sas/templates/sas/album.jinja:26 sas/templates/sas/moderation.jinja:18
+#: sas/templates/sas/picture.jinja:74 sas/templates/sas/picture.jinja:124
#: stock/templates/stock/stock_shopping_list.jinja:43
#: stock/templates/stock/stock_shopping_list.jinja:69
#: trombi/templates/trombi/detail.jinja:35
@@ -433,6 +436,10 @@ msgstr "Nouveau compte club"
#: forum/templates/forum/macros.jinja:20 forum/templates/forum/macros.jinja:62
#: forum/templates/forum/macros.jinja:128
#: launderette/templates/launderette/launderette_list.jinja:16
+#: pedagogy/templates/pedagogy/guide.jinja:50
+#: pedagogy/templates/pedagogy/guide.jinja:73
+#: pedagogy/templates/pedagogy/guide.jinja:109
+#: pedagogy/templates/pedagogy/uv_detail.jinja:177
#: sas/templates/sas/album.jinja:18 sas/templates/sas/picture.jinja:100
#: trombi/templates/trombi/detail.jinja:9
#: trombi/templates/trombi/edit_profile.jinja:34
@@ -524,6 +531,7 @@ msgid "Effective amount"
msgstr "Montant effectif"
#: accounting/templates/accounting/club_account_details.jinja:36
+#: sith/settings.py:421
msgid "Closed"
msgstr "Fermé"
@@ -661,6 +669,8 @@ msgstr "Effectuées"
#: accounting/templates/accounting/journal_details.jinja:41
#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1162
+#: pedagogy/templates/pedagogy/moderation.jinja:13
+#: pedagogy/templates/pedagogy/uv_detail.jinja:133
#: trombi/templates/trombi/comment.jinja:4
#: trombi/templates/trombi/comment.jinja:8
#: trombi/templates/trombi/user_tools.jinja:50
@@ -924,6 +934,7 @@ msgid "Remove"
msgstr "Retirer"
#: club/forms.py:75 launderette/views.py:228
+#: pedagogy/templates/pedagogy/moderation.jinja:15
msgid "Action"
msgstr "Action"
@@ -1066,7 +1077,7 @@ msgid "Enter a valid address. Only the root of the address is needed."
msgstr ""
"Entrez une adresse valide. Seule la racine de l'adresse est nécessaire."
-#: club/models.py:335 com/models.py:79 com/models.py:260 core/models.py:810
+#: club/models.py:335 com/models.py:79 com/models.py:260 core/models.py:814
msgid "is moderated"
msgstr "est modéré"
@@ -1414,7 +1425,7 @@ msgstr "Appel"
#: com/models.py:69 com/models.py:155 com/models.py:213 election/models.py:14
#: election/models.py:116 election/models.py:156 forum/models.py:242
-#: forum/models.py:296
+#: forum/models.py:296 pedagogy/models.py:145
msgid "title"
msgstr "titre"
@@ -1426,13 +1437,14 @@ msgstr "résumé"
msgid "content"
msgstr "contenu"
-#: com/models.py:73 core/models.py:1389 launderette/models.py:95
+#: com/models.py:73 core/models.py:1393 launderette/models.py:95
#: launderette/models.py:130 launderette/models.py:182 stock/models.py:77
#: stock/models.py:131
msgid "type"
msgstr "type"
-#: com/models.py:77 com/models.py:216 trombi/models.py:183
+#: com/models.py:77 com/models.py:216 pedagogy/models.py:106
+#: pedagogy/models.py:215 trombi/models.py:183
msgid "author"
msgstr "auteur"
@@ -1476,7 +1488,7 @@ msgstr "weekmail"
msgid "rank"
msgstr "rang"
-#: com/models.py:250 core/models.py:780 core/models.py:824
+#: com/models.py:250 core/models.py:784 core/models.py:828
msgid "file"
msgstr "fichier"
@@ -1573,6 +1585,7 @@ msgstr "Type"
#: com/templates/com/weekmail.jinja:19 com/templates/com/weekmail.jinja:48
#: forum/templates/forum/forum.jinja:24 forum/templates/forum/forum.jinja:43
#: forum/templates/forum/main.jinja:27 forum/views.py:243
+#: pedagogy/templates/pedagogy/guide.jinja:44
msgid "Title"
msgstr "Titre"
@@ -1839,6 +1852,8 @@ msgstr "Supprimer du Weekmail"
#: com/templates/com/weekmail_preview.jinja:9
#: core/templates/core/user_account_detail.jinja:11
#: core/templates/core/user_account_detail.jinja:104 launderette/views.py:226
+#: pedagogy/templates/pedagogy/uv_detail.jinja:12
+#: pedagogy/templates/pedagogy/uv_detail.jinja:21
#: stock/templates/stock/shopping_list_items.jinja:9
#: trombi/templates/trombi/comment_moderation.jinja:10
#: trombi/templates/trombi/export.jinja:9
@@ -2129,7 +2144,7 @@ msgstr "département"
msgid "dpt option"
msgstr "Filière"
-#: core/models.py:248
+#: core/models.py:248 pedagogy/models.py:118 pedagogy/models.py:284
msgid "semester"
msgstr "semestre"
@@ -2187,109 +2202,109 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
msgid "Profile"
msgstr "Profil"
-#: core/models.py:738
+#: core/models.py:742
msgid "Visitor"
msgstr "Visiteur"
-#: core/models.py:744
+#: core/models.py:748
msgid "do you want to receive the weekmail"
msgstr "voulez-vous recevoir le Weekmail"
-#: core/models.py:746
+#: core/models.py:750
msgid "show your stats to others"
msgstr "montrez vos statistiques aux autres"
-#: core/models.py:748
+#: core/models.py:752
msgid "get a notification for every click"
msgstr "recevez une notification pour chaque click"
-#: core/models.py:751
+#: core/models.py:755
msgid "get a notification for every refilling"
msgstr "recevez une notification pour chaque rechargement"
-#: core/models.py:774
+#: core/models.py:778
msgid "file name"
msgstr "nom du fichier"
-#: core/models.py:776 core/models.py:1115
+#: core/models.py:780 core/models.py:1119
msgid "parent"
msgstr "parent"
-#: core/models.py:787
+#: core/models.py:791
msgid "compressed file"
msgstr "version allégée"
-#: core/models.py:794
+#: core/models.py:798
msgid "thumbnail"
msgstr "miniature"
-#: core/models.py:799 core/models.py:814
+#: core/models.py:803 core/models.py:818
msgid "owner"
msgstr "propriétaire"
-#: core/models.py:801 core/models.py:1134 core/views/files.py:193
+#: core/models.py:805 core/models.py:1138 core/views/files.py:193
msgid "edit group"
msgstr "groupe d'édition"
-#: core/models.py:804 core/models.py:1137 core/views/files.py:196
+#: core/models.py:808 core/models.py:1141 core/views/files.py:196
msgid "view group"
msgstr "groupe de vue"
-#: core/models.py:806
+#: core/models.py:810
msgid "is folder"
msgstr "est un dossier"
-#: core/models.py:807
+#: core/models.py:811
msgid "mime type"
msgstr "type mime"
-#: core/models.py:808
+#: core/models.py:812
msgid "size"
msgstr "taille"
-#: core/models.py:818
+#: core/models.py:822
msgid "asked for removal"
msgstr "retrait demandé"
-#: core/models.py:820
+#: core/models.py:824
msgid "is in the SAS"
msgstr "est dans le SAS"
-#: core/models.py:862
+#: core/models.py:866
msgid "Character '/' not authorized in name"
msgstr "Le caractère '/' n'est pas autorisé dans les noms de fichier"
-#: core/models.py:864 core/models.py:868
+#: core/models.py:868 core/models.py:872
msgid "Loop in folder tree"
msgstr "Boucle dans l'arborescence des dossiers"
-#: core/models.py:871
+#: core/models.py:875
msgid "You can not make a file be a children of a non folder file"
msgstr ""
"Vous ne pouvez pas mettre un fichier enfant de quelque chose qui n'est pas "
"un dossier"
-#: core/models.py:882
+#: core/models.py:886
msgid "Duplicate file"
msgstr "Un fichier de ce nom existe déjà"
-#: core/models.py:899
+#: core/models.py:903
msgid "You must provide a file"
msgstr "Vous devez fournir un fichier"
-#: core/models.py:1039
+#: core/models.py:1043
msgid "Folder: "
msgstr "Dossier : "
-#: core/models.py:1041
+#: core/models.py:1045
msgid "File: "
msgstr "Fichier : "
-#: core/models.py:1098
+#: core/models.py:1102
msgid "page unix name"
msgstr "nom unix de la page"
-#: core/models.py:1104
+#: core/models.py:1108
msgid ""
"Enter a valid page name. This value may contain only unaccented letters, "
"numbers and ./+/-/_ characters."
@@ -2297,51 +2312,51 @@ msgstr ""
"Entrez un nom de page correct. Uniquement des lettres non accentuées, "
"numéros, et ./+/-/_"
-#: core/models.py:1122
+#: core/models.py:1126
msgid "page name"
msgstr "nom de la page"
-#: core/models.py:1130
+#: core/models.py:1134
msgid "owner group"
msgstr "groupe propriétaire"
-#: core/models.py:1142
+#: core/models.py:1146
msgid "lock user"
msgstr "utilisateur bloquant"
-#: core/models.py:1148
+#: core/models.py:1152
msgid "lock_timeout"
msgstr "décompte du déblocage"
-#: core/models.py:1179
+#: core/models.py:1183
msgid "Duplicate page"
msgstr "Une page de ce nom existe déjà"
-#: core/models.py:1182
+#: core/models.py:1186
msgid "Loop in page tree"
msgstr "Boucle dans l'arborescence des pages"
-#: core/models.py:1342
+#: core/models.py:1346
msgid "revision"
msgstr "révision"
-#: core/models.py:1343
+#: core/models.py:1347
msgid "page title"
msgstr "titre de la page"
-#: core/models.py:1344
+#: core/models.py:1348
msgid "page content"
msgstr "contenu de la page"
-#: core/models.py:1386
+#: core/models.py:1390
msgid "url"
msgstr "url"
-#: core/models.py:1387
+#: core/models.py:1391
msgid "param"
msgstr "param"
-#: core/models.py:1392
+#: core/models.py:1396
msgid "viewed"
msgstr "vue"
@@ -2384,6 +2399,7 @@ msgstr "S'enregister"
#: matmat/templates/matmat/search_form.jinja:37
#: matmat/templates/matmat/search_form.jinja:47
#: matmat/templates/matmat/search_form.jinja:58
+#: pedagogy/templates/pedagogy/guide.jinja:21
msgid "Search"
msgstr "Recherche"
@@ -2463,7 +2479,7 @@ msgstr "Photos"
#: eboutic/templates/eboutic/eboutic_main.jinja:24
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:8
#: eboutic/templates/eboutic/eboutic_payment_result.jinja:4
-#: sith/settings.py:374 sith/settings.py:382
+#: sith/settings.py:376 sith/settings.py:384
msgid "Eboutic"
msgstr "Eboutic"
@@ -2487,6 +2503,10 @@ msgstr "Laverie"
msgid "Files"
msgstr "Fichiers"
+#: core/templates/core/base.jinja:188 core/templates/core/user_tools.jinja:109
+msgid "Pedagogy"
+msgstr "Pédagogie"
+
#: core/templates/core/base.jinja:192
msgid "My Benefits"
msgstr "Mes Avantages"
@@ -3444,31 +3464,42 @@ msgstr "Modérer les fichiers"
msgid "Moderate pictures"
msgstr "Modérer les photos"
-#: core/templates/core/user_tools.jinja:108
+#: core/templates/core/user_tools.jinja:112
+#: pedagogy/templates/pedagogy/guide.jinja:13
+msgid "Create UV"
+msgstr "Créer UV"
+
+#: core/templates/core/user_tools.jinja:113
+#: pedagogy/templates/pedagogy/guide.jinja:16
+#: trombi/templates/trombi/detail.jinja:10
+msgid "Moderate comments"
+msgstr "Modérer les commentaires"
+
+#: core/templates/core/user_tools.jinja:118
msgid "Elections"
msgstr "Élections"
-#: core/templates/core/user_tools.jinja:110
+#: core/templates/core/user_tools.jinja:120
msgid "See available elections"
msgstr "Voir les élections disponibles"
-#: core/templates/core/user_tools.jinja:111
+#: core/templates/core/user_tools.jinja:121
msgid "See archived elections"
msgstr "Voir les élections archivées"
-#: core/templates/core/user_tools.jinja:113
+#: core/templates/core/user_tools.jinja:123
msgid "Create a new election"
msgstr "Créer une nouvelle élection"
-#: core/templates/core/user_tools.jinja:116
+#: core/templates/core/user_tools.jinja:128
msgid "Other tools"
msgstr "Autres outils"
-#: core/templates/core/user_tools.jinja:118
+#: core/templates/core/user_tools.jinja:130
msgid "Convert dokuwiki/BBcode syntax to Markdown"
msgstr "Convertir de la syntaxe dokuwiki/BBcode vers Markdown"
-#: core/templates/core/user_tools.jinja:119
+#: core/templates/core/user_tools.jinja:131
msgid "Trombi tools"
msgstr "Outils Trombi"
@@ -3760,8 +3791,8 @@ msgstr "quantité"
msgid "Sith account"
msgstr "Compte utilisateur"
-#: counter/models.py:450 sith/settings.py:367 sith/settings.py:372
-#: sith/settings.py:390
+#: counter/models.py:450 sith/settings.py:369 sith/settings.py:374
+#: sith/settings.py:392
msgid "Credit card"
msgstr "Carte bancaire"
@@ -3835,11 +3866,11 @@ msgstr "titre de l'événement"
msgid "secret"
msgstr "secret"
-#: counter/models.py:766
+#: counter/models.py:767
msgid "uid"
msgstr "uid"
-#: counter/models.py:771
+#: counter/models.py:772
msgid "student cards"
msgstr "cartes étudiante"
@@ -4683,12 +4714,12 @@ msgid "Washing and drying"
msgstr "Lavage et séchage"
#: launderette/templates/launderette/launderette_book.jinja:27
-#: sith/settings.py:518
+#: sith/settings.py:557
msgid "Washing"
msgstr "Lavage"
#: launderette/templates/launderette/launderette_book.jinja:31
-#: sith/settings.py:518
+#: sith/settings.py:557
msgid "Drying"
msgstr "Séchage"
@@ -4768,6 +4799,266 @@ msgstr "Sexe"
msgid "Last/First name or nickname"
msgstr "Nom de famille, prénom ou surnom"
+#: pedagogy/forms.py:87
+msgid "Do not vote"
+msgstr "Ne pas voter"
+
+#: pedagogy/forms.py:156
+msgid "Accepted reports"
+msgstr "Signalements acceptés"
+
+#: pedagogy/forms.py:163
+msgid "Denied reports"
+msgstr "Signalements refusés"
+
+#: pedagogy/models.py:98
+msgid ""
+"The code of an UV must only contains uppercase characters without accent and "
+"numbers"
+msgstr ""
+"Le code d'une UV doit seulement contenire des caractères majuscule sans "
+"accents et nombres"
+
+#: pedagogy/models.py:111
+msgid "credit type"
+msgstr "type de crédit"
+
+#: pedagogy/models.py:116 pedagogy/models.py:146
+msgid "uv manager"
+msgstr "gestionnaire d'uv"
+
+#: pedagogy/models.py:124
+msgid "language"
+msgstr "langue"
+
+#: pedagogy/models.py:130
+msgid "credits"
+msgstr "crédits"
+
+#: pedagogy/models.py:138
+msgid "departmenmt"
+msgstr "département"
+
+#: pedagogy/models.py:147
+msgid "objectives"
+msgstr "objecifs"
+
+#: pedagogy/models.py:148
+msgid "program"
+msgstr "programme"
+
+#: pedagogy/models.py:149
+msgid "skills"
+msgstr "compétences"
+
+#: pedagogy/models.py:150
+msgid "key concepts"
+msgstr "concepts clefs"
+
+#: pedagogy/models.py:155
+msgid "hours CM"
+msgstr "heures CM"
+
+#: pedagogy/models.py:162
+msgid "hours TD"
+msgstr "heures TD"
+
+#: pedagogy/models.py:169
+msgid "hours TP"
+msgstr "heures TP"
+
+#: pedagogy/models.py:176
+msgid "hours THE"
+msgstr "heures THE"
+
+#: pedagogy/models.py:183
+msgid "hours TE"
+msgstr "heures TE"
+
+#: pedagogy/models.py:219 pedagogy/models.py:275
+msgid "uv"
+msgstr "uv"
+
+#: pedagogy/models.py:222
+msgid "global grade"
+msgstr "note globale"
+
+#: pedagogy/models.py:229
+msgid "utility grade"
+msgstr "note d'utilité"
+
+#: pedagogy/models.py:236
+msgid "interest grade"
+msgstr "note d'intérêt"
+
+#: pedagogy/models.py:243
+msgid "teaching grade"
+msgstr "note d'enseignement"
+
+#: pedagogy/models.py:250
+msgid "work load grade"
+msgstr "note de charge de travail"
+
+#: pedagogy/models.py:256
+msgid "publish date"
+msgstr "date de publication"
+
+#: pedagogy/models.py:278
+msgid "grade"
+msgstr "note"
+
+#: pedagogy/models.py:304
+msgid "report"
+msgstr "signaler"
+
+#: pedagogy/models.py:308
+msgid "reporter"
+msgstr "signalant"
+
+#: pedagogy/models.py:310
+msgid "reason"
+msgstr "raison"
+
+#: pedagogy/templates/pedagogy/guide.jinja:5
+msgid "UV Guide"
+msgstr "Guide des UVs"
+
+#: pedagogy/templates/pedagogy/guide.jinja:25
+#, fuzzy, python-format
+#| msgid "display time"
+msgid "%(display_name)s"
+msgstr "temps d'affichage"
+
+#: pedagogy/templates/pedagogy/guide.jinja:30
+#, fuzzy, python-format
+#| msgid "credit type"
+msgid "%(credit_type)s"
+msgstr "type de crédit"
+
+#: pedagogy/templates/pedagogy/guide.jinja:43
+#: pedagogy/templates/pedagogy/moderation.jinja:12
+msgid "UV"
+msgstr "UV"
+
+#: pedagogy/templates/pedagogy/guide.jinja:45
+msgid "Department"
+msgstr "Département"
+
+#: pedagogy/templates/pedagogy/guide.jinja:46
+msgid "Credit type"
+msgstr "Type de crédit"
+
+#: pedagogy/templates/pedagogy/guide.jinja:213
+msgid "Error connecting to the server"
+msgstr "Erreur lors de la connection au serveur"
+
+#: pedagogy/templates/pedagogy/macros.jinja:12
+msgid " not rated "
+msgstr "non noté"
+
+#: pedagogy/templates/pedagogy/moderation.jinja:4
+msgid "UV comment moderation"
+msgstr "Modération des commentaires d'UV"
+
+#: pedagogy/templates/pedagogy/moderation.jinja:14
+msgid "Reason"
+msgstr "Raison"
+
+#: pedagogy/templates/pedagogy/moderation.jinja:29
+msgid "Delete comment"
+msgstr "Supprimer commentaire"
+
+#: pedagogy/templates/pedagogy/moderation.jinja:30
+msgid "Delete report"
+msgstr "Supprimer signalement"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:6
+msgid "UV Details"
+msgstr "Détails d'UV"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:27
+msgid "CM: "
+msgstr "CM : "
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:30
+msgid "TD: "
+msgstr "TD : "
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:33
+msgid "TP: "
+msgstr "TP : "
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:36
+msgid "TE: "
+msgstr "TE : "
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:39
+msgid "THE: "
+msgstr "THE : "
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:57
+#: pedagogy/templates/pedagogy/uv_detail.jinja:146
+msgid "Global grade"
+msgstr "Note globale"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:58
+#: pedagogy/templates/pedagogy/uv_detail.jinja:147
+msgid "Utility"
+msgstr "Utilité"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:59
+#: pedagogy/templates/pedagogy/uv_detail.jinja:148
+msgid "Interest"
+msgstr "Intérêt"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:60
+#: pedagogy/templates/pedagogy/uv_detail.jinja:149
+msgid "Teaching"
+msgstr "Enseignement"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:61
+#: pedagogy/templates/pedagogy/uv_detail.jinja:150
+msgid "Work load"
+msgstr "Charge de travail"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:71
+msgid "Objectives"
+msgstr "Objectifs"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:73
+msgid "Program"
+msgstr "Programme"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:75
+msgid "Earned skills"
+msgstr "Compétences acquises"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:77
+msgid "Key concepts"
+msgstr "Concepts clefs"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:79
+msgid "UV manager: "
+msgstr "Gestionnaire d'UV : "
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:85
+msgid "Leave comment"
+msgstr "Laisser un commentaire"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:140
+#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:278
+#: trombi/templates/trombi/export.jinja:70
+msgid "Comments"
+msgstr "Commentaires"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:171
+msgid "This comment has been reported"
+msgstr "Ce commentaire a été signalé"
+
+#: pedagogy/templates/pedagogy/uv_detail.jinja:184
+msgid "Report this comment"
+msgstr "Signaler ce commentaire"
+
#: rootplace/templates/rootplace/delete_user_messages.jinja:8
msgid "Delete all forum messages from an user"
msgstr "Supprimer tous les messages forum d'un utilisateur"
@@ -4885,251 +5176,340 @@ msgstr "Erreur de création de l'album %(album)s : %(msg)s"
msgid "Add user"
msgstr "Ajouter une personne"
-#: sith/settings.py:216
+#: sith/settings.py:217 sith/settings.py:429
msgid "English"
msgstr "Anglais"
-#: sith/settings.py:216
+#: sith/settings.py:217 sith/settings.py:428
msgid "French"
msgstr "Français"
-#: sith/settings.py:348
+#: sith/settings.py:350
msgid "TC"
msgstr "TC"
-#: sith/settings.py:349
+#: sith/settings.py:351
msgid "IMSI"
msgstr "IMSI"
-#: sith/settings.py:350
+#: sith/settings.py:352
msgid "IMAP"
msgstr "IMAP"
-#: sith/settings.py:351
+#: sith/settings.py:353
msgid "INFO"
msgstr "INFO"
-#: sith/settings.py:352
+#: sith/settings.py:354
msgid "GI"
msgstr "GI"
-#: sith/settings.py:353
+#: sith/settings.py:355 sith/settings.py:439
msgid "E"
msgstr "E"
-#: sith/settings.py:354
+#: sith/settings.py:356
msgid "EE"
msgstr "EE"
-#: sith/settings.py:355
+#: sith/settings.py:357
msgid "GESC"
msgstr "GESC"
-#: sith/settings.py:356
+#: sith/settings.py:358
msgid "GMC"
msgstr "GMC"
-#: sith/settings.py:357
+#: sith/settings.py:359
msgid "MC"
msgstr "MC"
-#: sith/settings.py:358
+#: sith/settings.py:360
msgid "EDIM"
msgstr "EDIM"
-#: sith/settings.py:359
+#: sith/settings.py:361
msgid "Humanities"
msgstr "Humanités"
-#: sith/settings.py:360
+#: sith/settings.py:362
msgid "N/A"
msgstr "N/A"
-#: sith/settings.py:364 sith/settings.py:371 sith/settings.py:388
+#: sith/settings.py:366 sith/settings.py:373 sith/settings.py:390
msgid "Check"
msgstr "Chèque"
-#: sith/settings.py:365 sith/settings.py:373 sith/settings.py:389
+#: sith/settings.py:367 sith/settings.py:375 sith/settings.py:391
msgid "Cash"
msgstr "Espèces"
-#: sith/settings.py:366
+#: sith/settings.py:368
msgid "Transfert"
msgstr "Virement"
-#: sith/settings.py:379
+#: sith/settings.py:381
msgid "Belfort"
msgstr "Belfort"
-#: sith/settings.py:380
+#: sith/settings.py:382
msgid "Sevenans"
msgstr "Sevenans"
-#: sith/settings.py:381
+#: sith/settings.py:383
msgid "Montbéliard"
msgstr "Montbéliard"
-#: sith/settings.py:432
+#: sith/settings.py:409
+msgid "Free"
+msgstr "Libre"
+
+#: sith/settings.py:410
+msgid "CS"
+msgstr "CS"
+
+#: sith/settings.py:411
+msgid "TM"
+msgstr "TM"
+
+#: sith/settings.py:412
+msgid "OM"
+msgstr "OM"
+
+#: sith/settings.py:413
+msgid "QC"
+msgstr "QC"
+
+#: sith/settings.py:414
+msgid "EC"
+msgstr "EC"
+
+#: sith/settings.py:415
+msgid "RN"
+msgstr "RN"
+
+#: sith/settings.py:416
+msgid "ST"
+msgstr "ST"
+
+#: sith/settings.py:417
+msgid "EXT"
+msgstr "EXT"
+
+#: sith/settings.py:422
+msgid "Autumn"
+msgstr "Automne"
+
+#: sith/settings.py:423
+msgid "Spring"
+msgstr "Printemps"
+
+#: sith/settings.py:424
+msgid "Autumn and spring"
+msgstr "Automne et printemps"
+
+#: sith/settings.py:430
+msgid "German"
+msgstr "Allemant"
+
+#: sith/settings.py:431
+msgid "Spanich"
+msgstr "Espagnol"
+
+#: sith/settings.py:435
+msgid "A"
+msgstr "A"
+
+#: sith/settings.py:436
+msgid "B"
+msgstr "B"
+
+#: sith/settings.py:437
+msgid "C"
+msgstr "C"
+
+#: sith/settings.py:438
+msgid "D"
+msgstr "D"
+
+#: sith/settings.py:440
+msgid "FX"
+msgstr "FX"
+
+#: sith/settings.py:441
+msgid "F"
+msgstr "F"
+
+#: sith/settings.py:442
+msgid "Abs"
+msgstr "Abs"
+
+#: sith/settings.py:471
msgid "One semester"
msgstr "Un semestre, 15 €"
-#: sith/settings.py:433
+#: sith/settings.py:472
msgid "Two semesters"
msgstr "Deux semestres, 28 €"
-#: sith/settings.py:435
+#: sith/settings.py:474
msgid "Common core cursus"
msgstr "Cursus tronc commun, 45 €"
-#: sith/settings.py:439
+#: sith/settings.py:478
msgid "Branch cursus"
msgstr "Cursus branche, 45 €"
-#: sith/settings.py:440
+#: sith/settings.py:479
msgid "Alternating cursus"
msgstr "Cursus alternant, 30 €"
-#: sith/settings.py:441
+#: sith/settings.py:480
msgid "Honorary member"
msgstr "Membre honoraire, 0 €"
-#: sith/settings.py:442
+#: sith/settings.py:481
msgid "Assidu member"
msgstr "Membre d'Assidu, 0 €"
-#: sith/settings.py:443
+#: sith/settings.py:482
msgid "Amicale/DOCEO member"
msgstr "Membre de l'Amicale/DOCEO, 0 €"
-#: sith/settings.py:444
+#: sith/settings.py:483
msgid "UT network member"
msgstr "Cotisant du réseau UT, 0 €"
-#: sith/settings.py:445
+#: sith/settings.py:484
msgid "CROUS member"
msgstr "Membres du CROUS, 0 €"
-#: sith/settings.py:446
+#: sith/settings.py:485
msgid "Sbarro/ESTA member"
msgstr "Membre de Sbarro ou de l'ESTA, 15 €"
-#: sith/settings.py:448
+#: sith/settings.py:487
msgid "One semester Welcome Week"
msgstr "Un semestre Welcome Week"
-#: sith/settings.py:452
+#: sith/settings.py:491
msgid "Two months for free"
msgstr "Deux mois gratuits"
-#: sith/settings.py:453
+#: sith/settings.py:492
msgid "Eurok's volunteer"
msgstr "Bénévole Eurockéennes"
-#: sith/settings.py:455
+#: sith/settings.py:494
msgid "Six weeks for free"
msgstr "6 semaines gratuites"
-#: sith/settings.py:459
+#: sith/settings.py:498
msgid "One day"
msgstr "Un jour"
-#: sith/settings.py:478
+#: sith/settings.py:517
msgid "President"
msgstr "Président"
-#: sith/settings.py:479
+#: sith/settings.py:518
msgid "Vice-President"
msgstr "Vice-Président"
-#: sith/settings.py:480
+#: sith/settings.py:519
msgid "Treasurer"
msgstr "Trésorier"
-#: sith/settings.py:481
+#: sith/settings.py:520
msgid "Communication supervisor"
msgstr "Responsable communication"
-#: sith/settings.py:482
+#: sith/settings.py:521
msgid "Secretary"
msgstr "Secrétaire"
-#: sith/settings.py:483
+#: sith/settings.py:522
msgid "IT supervisor"
msgstr "Responsable info"
-#: sith/settings.py:484
+#: sith/settings.py:523
msgid "Board member"
msgstr "Membre du bureau"
-#: sith/settings.py:485
+#: sith/settings.py:524
msgid "Active member"
msgstr "Membre actif"
-#: sith/settings.py:486
+#: sith/settings.py:525
msgid "Curious"
msgstr "Curieux"
-#: sith/settings.py:522
+#: sith/settings.py:561
msgid "A new poster needs to be moderated"
msgstr "Une nouvelle affiche a besoin d'être modérée"
-#: sith/settings.py:523
+#: sith/settings.py:562
msgid "A new mailing list needs to be moderated"
msgstr "Une nouvelle mailing list a besoin d'être modérée"
-#: sith/settings.py:524
+#: sith/settings.py:565
+msgid "A new pedagogy comment has been signaled for moderation"
+msgstr ""
+"Un nouveau commentaire de la pédagogie a été signalé pour la modération"
+
+#: sith/settings.py:567
#, python-format
msgid "There are %s fresh news to be moderated"
msgstr "Il y a %s nouvelles toutes fraîches à modérer"
-#: sith/settings.py:525
+#: sith/settings.py:568
msgid "New files to be moderated"
msgstr "Nouveaux fichiers à modérer"
-#: sith/settings.py:526
+#: sith/settings.py:569
#, python-format
msgid "There are %s pictures to be moderated in the SAS"
msgstr "Il y a %s photos à modérer dans le SAS"
-#: sith/settings.py:527
+#: sith/settings.py:570
msgid "You've been identified on some pictures"
msgstr "Vous avez été identifié sur des photos"
-#: sith/settings.py:528
+#: sith/settings.py:571
#, python-format
msgid "You just refilled of %s €"
msgstr "Vous avez rechargé votre compte de %s€"
-#: sith/settings.py:529
+#: sith/settings.py:572
#, python-format
msgid "You just bought %s"
msgstr "Vous avez acheté %s"
-#: sith/settings.py:530
+#: sith/settings.py:573
msgid "You have a notification"
msgstr "Vous avez une notification"
-#: sith/settings.py:542
+#: sith/settings.py:585
msgid "Success!"
msgstr "Succès !"
-#: sith/settings.py:543
+#: sith/settings.py:586
msgid "Fail!"
msgstr "Échec !"
-#: sith/settings.py:544
+#: sith/settings.py:587
msgid "You successfully posted an article in the Weekmail"
msgstr "Article posté avec succès dans le Weekmail"
-#: sith/settings.py:545
+#: sith/settings.py:588
msgid "You successfully edited an article in the Weekmail"
msgstr "Article édité avec succès dans le Weekmail"
-#: sith/settings.py:546
+#: sith/settings.py:589
msgid "You successfully sent the Weekmail"
msgstr "Weekmail envoyé avec succès"
-#: sith/settings.py:554
+#: sith/settings.py:597
msgid "AE tee-shirt"
msgstr "Tee-shirt AE"
@@ -5198,11 +5578,6 @@ msgstr "Quantité demandée"
msgid "Quantity bought"
msgstr "Quantité achetée"
-#: stock/templates/stock/shopping_list_items.jinja:42 stock/views.py:278
-#: trombi/templates/trombi/export.jinja:70
-msgid "Comments"
-msgstr "Commentaires"
-
#: stock/templates/stock/shopping_list_quantity.jinja:4
#: stock/templates/stock/shopping_list_quantity.jinja:8
#, python-format
@@ -5495,10 +5870,6 @@ msgstr "Refuser"
msgid "%(club)s's Trombi"
msgstr "Trombi de %(club)s"
-#: trombi/templates/trombi/detail.jinja:10
-msgid "Moderate comments"
-msgstr "Modérer les commentaires"
-
#: trombi/templates/trombi/detail.jinja:11
msgid "Subscription deadline: "
msgstr "Fin des inscriptions : "
@@ -5686,8 +6057,11 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
msgid "Maximum characters: %(max_length)s"
msgstr "Nombre de caractères max: %(max_length)s"
-#~ msgid "Pedagogy"
-#~ msgstr "Pédagogie"
+#~ msgid "ENERGIE"
+#~ msgstr "ENERGIE"
+
+#~ msgid "HUMA"
+#~ msgstr "HUMA"
#~ msgid "Former website"
#~ msgstr "Ancien site"
diff --git a/migrate.py b/migrate.py
index 8abe62e8..bead1a51 100644
--- a/migrate.py
+++ b/migrate.py
@@ -77,6 +77,7 @@ from forum.models import (
ForumMessageMeta,
ForumUserInfo,
)
+from pedagogy.models import UV, UVComment, UVResult
db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS)
start = datetime.datetime.now()
@@ -1568,6 +1569,103 @@ def migrate_club_again():
pass
+def migrate_pedagogy():
+ cur = db.cursor(MySQLdb.cursors.SSDictCursor)
+
+ print("Migrating UVs")
+ root = User.objects.get(id=0)
+ semester_conversion = {
+ "closed": "CLOSED",
+ "A": "AUTUMN",
+ "P": "SPRING",
+ "AP": "AUTUMN_AND_SPRING",
+ }
+
+ def department_conversion(department):
+ # Default of this enum is HUMA
+ if not department or department == "Humas":
+ return "HUMA"
+ return department
+
+ def convert_number(num, default=0):
+ if not num:
+ return default
+ return num
+
+ def convert_text(text):
+ if not text:
+ return ""
+ return doku_to_markdown(to_unicode(text))
+
+ cur.execute(
+ """
+ SELECT * FROM pedag_uv
+ LEFT JOIN pedag_uv_dept dept
+ ON dept.id_uv = pedag_uv.id_uv
+ """
+ )
+ for uv in cur:
+ UV(
+ id=uv["id_uv"],
+ code=uv["code"],
+ author=root,
+ credit_type=uv["type"],
+ semester=semester_conversion[uv["semestre"]],
+ language="FR", # No infos in previous guide about that
+ credits=convert_number(uv["guide_credits"]),
+ department=department_conversion(uv["departement"]),
+ title=convert_text(uv["intitule"]),
+ manager=convert_text(uv["responsable"]),
+ objectives=convert_text(uv["guide_objectifs"]),
+ program=convert_text(uv["guide_programme"]),
+ skills="", # No info in previous guide about that
+ key_concepts="", # No info either
+ hours_CM=convert_number(uv["guide_c"]),
+ hours_TD=convert_number(uv["guide_td"]),
+ hours_TP=convert_number(uv["guide_tp"]),
+ hours_THE=convert_number(uv["guide_the"]),
+ hours_TE=0, # No info either
+ ).save()
+
+ print("Migrating UV Comments")
+
+ cur.execute("SELECT * FROM pedag_uv_commentaire")
+
+ for comment in cur:
+ author = User.objects.filter(id=comment["id_utilisateur"]).first()
+ uv = UV.objects.filter(id=comment["id_uv"]).first()
+ if not author or not uv:
+ continue
+ UVComment(
+ id=comment["id_commentaire"],
+ author=author,
+ uv=uv,
+ comment=convert_text(comment["content"]),
+ grade_global=convert_number(comment["note_generale"], -1),
+ grade_utility=convert_number(comment["note_utilite"], -1),
+ grade_interest=convert_number(comment["note_interet"], -1),
+ grade_teaching=convert_number(comment["note_enseignement"], -1),
+ grade_work_load=convert_number(comment["note_travail"], -1),
+ publish_date=comment["date"].replace(tzinfo=timezone("Europe/Paris")),
+ ).save()
+
+ print("Migrating UV Results")
+ cur.execute("SELECT * FROM pedag_resultat")
+
+ for result in cur:
+ author = User.objects.filter(id=comment["id_utilisateur"]).first()
+ uv = UV.objects.filter(id=comment["id_uv"]).first()
+ if not author or not uv:
+ continue
+ UVResult(
+ id=result["id_resultat"],
+ uv=uv,
+ user=author,
+ grade=result["note"],
+ semester=result["semestre"],
+ ).save()
+
+
def main():
print("Start at %s" % start)
# Core
@@ -1590,7 +1688,9 @@ def main():
# migrate_forum()
# reset_index('forum')
# migrate_mailings()
- migrate_club_again()
+ # migrate_club_again()
+ migrate_pedagogy()
+ reset_index("pedagogy")
end = datetime.datetime.now()
print("End at %s" % end)
print("Running time: %s" % (end - start))
diff --git a/pedagogy/__init__.py b/pedagogy/__init__.py
new file mode 100644
index 00000000..7ea16950
--- /dev/null
+++ b/pedagogy/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2019
+# - Sli
+#
+# 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.
+#
+#
diff --git a/pedagogy/admin.py b/pedagogy/admin.py
new file mode 100644
index 00000000..24eb52b8
--- /dev/null
+++ b/pedagogy/admin.py
@@ -0,0 +1,27 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2019
+# - Sli
+#
+# 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.contrib import admin
+
+# Register your models here.
diff --git a/pedagogy/forms.py b/pedagogy/forms.py
new file mode 100644
index 00000000..98a90438
--- /dev/null
+++ b/pedagogy/forms.py
@@ -0,0 +1,166 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2016,2017
+# - Skia
+#
+# 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 django.utils.translation import ugettext_lazy as _
+from django.forms.widgets import Widget
+from django.templatetags.static import static
+
+from core.views.forms import MarkdownInput
+from core.models import User
+
+from pedagogy.models import UV, UVComment, UVCommentReport
+
+
+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 StarList(forms.NumberInput):
+ template_name = "pedagogy/starlist.jinja"
+
+ def __init__(self, nubmer_of_stars=0):
+ super(StarList, self).__init__(None)
+ self.number_of_stars = nubmer_of_stars
+
+ def get_context(self, name, value, attrs):
+ context = super(StarList, self).get_context(name, value, attrs)
+ context["number_of_stars"] = range(0, self.number_of_stars)
+ context["translations"] = {"do_not_vote": _("Do not vote")}
+ return context
+
+
+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,
+ "grade_global": StarList(5),
+ "grade_utility": StarList(5),
+ "grade_interest": StarList(5),
+ "grade_teaching": StarList(5),
+ "grade_work_load": StarList(5),
+ }
+
+ 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
+
+
+class UVCommentReportForm(forms.ModelForm):
+ """
+ Form handeling creation and edit of an UVReport
+ """
+
+ class Meta:
+ model = UVCommentReport
+ fields = ("comment", "reporter", "reason")
+ widgets = {
+ "comment": forms.HiddenInput,
+ "reporter": forms.HiddenInput,
+ "reason": MarkdownInput,
+ }
+
+ def __init__(self, reporter_id, comment_id, *args, **kwargs):
+ super(UVCommentReportForm, self).__init__(*args, **kwargs)
+ self.fields["reporter"].queryset = User.objects.filter(id=reporter_id).all()
+ self.fields["reporter"].initial = reporter_id
+ self.fields["comment"].queryset = UVComment.objects.filter(id=comment_id).all()
+ self.fields["comment"].initial = comment_id
+
+
+class UVCommentModerationForm(forms.Form):
+ """
+ Form handeling bulk comment deletion
+ """
+
+ accepted_reports = forms.ModelMultipleChoiceField(
+ UVCommentReport.objects.all(),
+ label=_("Accepted reports"),
+ widget=forms.CheckboxSelectMultiple,
+ required=False,
+ )
+
+ denied_reports = forms.ModelMultipleChoiceField(
+ UVCommentReport.objects.all(),
+ label=_("Denied reports"),
+ widget=forms.CheckboxSelectMultiple,
+ required=False,
+ )
diff --git a/pedagogy/migrations/0001_initial.py b/pedagogy/migrations/0001_initial.py
new file mode 100644
index 00000000..b6ab939e
--- /dev/null
+++ b/pedagogy/migrations/0001_initial.py
@@ -0,0 +1,366 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-07-05 14:32
+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"),
+ ("AUTUMN_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(blank=True, 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.DateTimeField(blank=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",
+ ),
+ ),
+ ("reason", models.TextField(verbose_name="reason")),
+ (
+ "comment",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="reports",
+ to="pedagogy.UVComment",
+ verbose_name="report",
+ ),
+ ),
+ (
+ "reporter",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="reported_uv_comment",
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="reporter",
+ ),
+ ),
+ ],
+ ),
+ 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",
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/pedagogy/migrations/__init__.py b/pedagogy/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/pedagogy/models.py b/pedagogy/models.py
new file mode 100644
index 00000000..34f5fb17
--- /dev/null
+++ b/pedagogy/models.py
@@ -0,0 +1,337 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2019
+# - Sli
+#
+# 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.db import models
+from django.utils.translation import ugettext_lazy as _
+from django.utils import timezone
+from django.core import validators
+from django.conf import settings
+from django.utils.functional import cached_property
+from django.core.urlresolvers import reverse
+
+from rest_framework import serializers
+
+from core.models import User
+
+# Create your models here.
+
+
+class UV(models.Model):
+ """
+ Contains infos about an UV (course)
+ """
+
+ 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,
+ )
+
+ 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 __grade_average_generic(self, field):
+ comments = self.comments.filter(**{field + "__gte": 0})
+ if not comments.exists():
+ return -1
+
+ return int(sum(comments.values_list(field, flat=True)) / comments.count())
+
+ def get_absolute_url(self):
+ return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.id})
+
+ @cached_property
+ def grade_global_average(self):
+ return self.__grade_average_generic("grade_global")
+
+ @cached_property
+ def grade_utility_average(self):
+ return self.__grade_average_generic("grade_utility")
+
+ @cached_property
+ def grade_interest_average(self):
+ return self.__grade_average_generic("grade_interest")
+
+ @cached_property
+ def grade_teaching_average(self):
+ return self.__grade_average_generic("grade_teaching")
+
+ @cached_property
+ def grade_work_load_average(self):
+ return self.__grade_average_generic("grade_work_load")
+
+ def __str__(self):
+ return self.code
+
+
+class UVComment(models.Model):
+ """
+ A comment about an UV
+ """
+
+ 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"), blank=True)
+ 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.DateTimeField(_("publish date"), blank=True)
+
+ 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)
+
+ @cached_property
+ def is_reported(self):
+ """
+ Return True if someone reported this UV
+ """
+ return self.reports.exists()
+
+ def __str__(self):
+ return "%s - %s" % (self.uv, self.author)
+
+ def save(self, *args, **kwargs):
+ if self.publish_date is None:
+ self.publish_date = timezone.now()
+ super(UVComment, self).save(*args, **kwargs)
+
+
+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):
+ """
+ Report an inapropriate comment
+ """
+
+ comment = models.ForeignKey(
+ UVComment,
+ related_name="reports",
+ verbose_name=_("report"),
+ on_delete=models.CASCADE,
+ )
+ reporter = models.ForeignKey(
+ User, related_name="reported_uv_comment", verbose_name=_("reporter")
+ )
+ reason = models.TextField(_("reason"))
+
+ def is_owned_by(self, user):
+ """
+ Can be created by a pedagogy admin, a superuser or a subscriber
+ """
+ return user.is_subscribed or user.is_owner(self.comment.uv)
+
+
+# Custom serializers
+
+
+class UVSerializer(serializers.ModelSerializer):
+ """
+ Custom seralizer for UVs
+ Allow adding more informations like absolute_url
+ """
+
+ class Meta:
+ model = UV
+ fields = "__all__"
+
+ absolute_url = serializers.SerializerMethodField()
+ update_url = serializers.SerializerMethodField()
+ delete_url = serializers.SerializerMethodField()
+
+ def get_absolute_url(self, obj):
+ return obj.get_absolute_url()
+
+ def get_update_url(self, obj):
+ return reverse("pedagogy:uv_update", kwargs={"uv_id": obj.id})
+
+ def get_delete_url(self, obj):
+ return reverse("pedagogy:uv_delete", kwargs={"uv_id": obj.id})
diff --git a/pedagogy/search_indexes.py b/pedagogy/search_indexes.py
new file mode 100644
index 00000000..3ea75343
--- /dev/null
+++ b/pedagogy/search_indexes.py
@@ -0,0 +1,58 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2019
+# - Sli
+#
+# 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.db import models
+
+from haystack import indexes, signals
+
+from core.search_indexes import BigCharFieldIndex
+from pedagogy.models import UV
+
+
+class IndexSignalProcessor(signals.BaseSignalProcessor):
+ """
+ Auto update index on CRUD operations
+ """
+
+ def setup(self):
+ # Listen only to the ``UV`` model.
+ models.signals.post_save.connect(self.handle_save, sender=UV)
+ models.signals.post_delete.connect(self.handle_delete, sender=UV)
+
+ def teardown(self):
+ # Disconnect only to the ``UV`` model.
+ models.signals.post_save.disconnect(self.handle_save, sender=UV)
+ models.signals.post_delete.disconnect(self.handle_delete, sender=UV)
+
+
+class UVIndex(indexes.SearchIndex, indexes.Indexable):
+ """
+ Indexer class for UVs
+ """
+
+ text = BigCharFieldIndex(document=True, use_template=True)
+ auto = indexes.EdgeNgramField(use_template=True)
+
+ def get_model(self):
+ return UV
diff --git a/pedagogy/templates/pedagogy/guide.jinja b/pedagogy/templates/pedagogy/guide.jinja
new file mode 100644
index 00000000..4046d0a1
--- /dev/null
+++ b/pedagogy/templates/pedagogy/guide.jinja
@@ -0,0 +1,230 @@
+
+{% extends "core/base.jinja" %}
+
+{% block title %}
+{% trans %}UV Guide{% endtrans %}
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ {% trans %}UV{% endtrans %} |
+ {% trans %}Title{% endtrans %} |
+ {% trans %}Department{% endtrans %} |
+ {% trans %}Credit type{% endtrans %} |
+ |
+ |
+ {% if can_create_uv(user) %}
+ {% trans %}Edit{% endtrans %} |
+ {% trans %}Delete{% endtrans %} |
+ {% endif %}
+
+
+
+ {% for uv in object_list %}
+
+ {{ uv.code }} |
+ {{ uv.title }} |
+ {{ uv.department }} |
+ {{ uv.credit_type }} |
+
+ {% if uv.semester in ["AUTUMN", "AUTUMN_AND_SPRING"] %}
+
+ {% endif %}
+ |
+
+ {% if uv.semester in ["SPRING", "AUTUMN_AND_SPRING"] %}
+
+ {% endif %}
+ |
+ {% if user.is_owner(uv) -%}
+ {% trans %}Edit{% endtrans %} |
+ {% trans %}Delete{% endtrans %} |
+ {%- endif -%}
+
+ {% endfor %}
+
+
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/pedagogy/templates/pedagogy/macros.jinja b/pedagogy/templates/pedagogy/macros.jinja
new file mode 100644
index 00000000..20cb23c3
--- /dev/null
+++ b/pedagogy/templates/pedagogy/macros.jinja
@@ -0,0 +1,15 @@
+{% macro display_star(grade) -%}
+
+ {% if grade >= 0 %}
+ {% for i in range(5) %}
+ {% if i <= grade %}
+
+ {% else %}
+
+ {% endif %}
+ {% endfor %}
+ {% else %}
+ {% trans %} not rated {% endtrans %}
+ {% endif %}
+
+{%- endmacro %}
\ No newline at end of file
diff --git a/pedagogy/templates/pedagogy/moderation.jinja b/pedagogy/templates/pedagogy/moderation.jinja
new file mode 100644
index 00000000..61a2a617
--- /dev/null
+++ b/pedagogy/templates/pedagogy/moderation.jinja
@@ -0,0 +1,37 @@
+{% extends "core/base.jinja" %}
+
+{% block title %}
+{% trans %}UV comment moderation{% endtrans %}
+{% endblock title %}
+
+{% block content %}
+{{ form.errors }}
+
+
+
+ {% trans %}UV{% endtrans %} |
+ {% trans %}Comment{% endtrans %} |
+ {% trans %}Reason{% endtrans %} |
+ {% trans %}Action{% endtrans %} |
+
+
+
+ {% set queryset = form.accepted_reports.field.queryset %}
+ {% for widget in form.accepted_reports.subwidgets %}
+ {% set report = queryset.get(id=widget.data.value) %}
+
+ {% endfor %}
+
+
+{% endblock content %}
diff --git a/pedagogy/templates/pedagogy/starlist.jinja b/pedagogy/templates/pedagogy/starlist.jinja
new file mode 100644
index 00000000..e8f7db47
--- /dev/null
+++ b/pedagogy/templates/pedagogy/starlist.jinja
@@ -0,0 +1,50 @@
+
+
+
+
+ {% for i in number_of_stars %}
+
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/pedagogy/templates/pedagogy/uv_detail.jinja b/pedagogy/templates/pedagogy/uv_detail.jinja
new file mode 100644
index 00000000..b9933133
--- /dev/null
+++ b/pedagogy/templates/pedagogy/uv_detail.jinja
@@ -0,0 +1,219 @@
+{% extends "core/base.jinja" %}
+{% from "core/macros.jinja" import user_profile_link %}
+{% from "pedagogy/macros.jinja" import display_star %}
+
+{% block title %}
+{% trans %}UV Details{% endtrans %}
+{% endblock %}
+
+{% block content %}
+
+
+
{% trans %}Back{% endtrans %}
+
+
+
{{ object.code }} - {{ object.title }}
+
+
+
+ {% trans %}CM: {% endtrans %}{{ object.hours_CM }}
+
+
+ {% trans %}TD: {% endtrans %}{{ object.hours_TD }}
+
+
+ {% trans %}TP: {% endtrans %}{{ object.hours_TP }}
+
+
+ {% trans %}TE: {% endtrans %}{{ object.hours_TE }}
+
+
+ {% trans %}THE: {% endtrans %}{{ object.hours_THE }}
+
+
+
+ {{ object.department }}
+
+
+ {{ object.credit_type }}
+
+
+ {{ object.get_semester_display() }}
+
+
+
+
+
+
+
+
{% trans %}Global grade{% endtrans %}
+
{% trans %}Utility{% endtrans %}
+
{% trans %}Interest{% endtrans %}
+
{% trans %}Teaching{% endtrans %}
+
{% trans %}Work load{% endtrans %}
+
+
+
{{ display_star(object.grade_global_average) }}
+
{{ display_star(object.grade_utility_average) }}
+
{{ display_star(object.grade_interest_average) }}
+
{{ display_star(object.grade_teaching_average) }}
+
{{ display_star(object.grade_work_load_average) }}
+
+
+
{% trans %}Objectives{% endtrans %}
+
{{ object.objectives|markdown }}
+
{% trans %}Program{% endtrans %}
+
{{ object.program|markdown }}
+
{% trans %}Earned skills{% endtrans %}
+
{{ object.skills|markdown }}
+
{% trans %}Key concepts{% endtrans %}
+
{{ object.key_concepts|markdown }}
+
{% trans %}UV manager: {% endtrans %}{{ object.manager }}
+
+
+
+
+
+
+
+ {% if object.comments.exists() %}
+
{% trans %}Comments{% endtrans %}
+ {% for comment in object.comments.order_by("-publish_date").all() %}
+
+ {% endfor %}
+ {% endif %}
+
+
+
+
+
+{% endblock %}
diff --git a/pedagogy/templates/search/indexes/pedagogy/uv_auto.txt b/pedagogy/templates/search/indexes/pedagogy/uv_auto.txt
new file mode 100644
index 00000000..5154a4ce
--- /dev/null
+++ b/pedagogy/templates/search/indexes/pedagogy/uv_auto.txt
@@ -0,0 +1,3 @@
+{{ object.code }}
+{{ object.manager }}
+{{ object.title }}
\ No newline at end of file
diff --git a/pedagogy/templates/search/indexes/pedagogy/uv_text.txt b/pedagogy/templates/search/indexes/pedagogy/uv_text.txt
new file mode 100644
index 00000000..d47ae34d
--- /dev/null
+++ b/pedagogy/templates/search/indexes/pedagogy/uv_text.txt
@@ -0,0 +1,3 @@
+{{ object.code }}
+{{ object.manager }}
+{{ object.title }}
diff --git a/pedagogy/tests.py b/pedagogy/tests.py
new file mode 100644
index 00000000..e61d3475
--- /dev/null
+++ b/pedagogy/tests.py
@@ -0,0 +1,1060 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2019
+# - Sli
+#
+# 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.conf import settings
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+from django.core.management import call_command
+
+from core.models import User, Notification
+
+from pedagogy.models import UV, UVComment, UVCommentReport
+
+
+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)
+
+
+class UVSearchTest(TestCase):
+ """
+ Test UV guide rights for view and API
+ Test that the API is working well
+ """
+
+ def setUp(self):
+ call_command("populate")
+ call_command("update_index", "pedagogy")
+
+ def test_get_page_authorized_success(self):
+ # Test with root user
+ self.client.login(username="root", password="plop")
+ response = self.client.get(reverse("pedagogy:guide"))
+ self.assertEquals(response.status_code, 200)
+
+ # Test with pedagogy admin
+ self.client.login(username="tutu", password="plop")
+ response = self.client.get(reverse("pedagogy:guide"))
+ self.assertEquals(response.status_code, 200)
+
+ # Test with subscribed user
+ self.client.login(username="sli", password="plop")
+ response = self.client.get(reverse("pedagogy:guide"))
+ self.assertEquals(response.status_code, 200)
+
+ def test_get_page_unauthorized_fail(self):
+ # Test with anonymous user
+ response = self.client.get(reverse("pedagogy:guide"))
+ self.assertEquals(response.status_code, 403)
+
+ # Test with not subscribed user
+ self.client.login(username="guy", password="plop")
+ response = self.client.get(reverse("pedagogy:guide"))
+ self.assertEquals(response.status_code, 403)
+
+ def test_search_pa00_success(self):
+ self.client.login(username="sli", password="plop")
+
+ # Search with UV code
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "PA00"})
+ self.assertContains(response, text="PA00")
+
+ # Search with first letter of UV code
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "P"})
+ self.assertContains(response, text="PA00")
+
+ # Search with first letter of UV code in lowercase
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "p"})
+ self.assertContains(response, text="PA00")
+
+ # Search with UV title
+ response = self.client.get(
+ reverse("pedagogy:guide"), {"search": "participation"}
+ )
+ self.assertContains(response, text="PA00")
+
+ # Search with UV manager
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "HEYBERGER"})
+ self.assertContains(response, text="PA00")
+
+ # Search with department
+ response = self.client.get(reverse("pedagogy:guide"), {"department": "HUMA"})
+ self.assertContains(response, text="PA00")
+
+ # Search with semester
+ response = self.client.get(reverse("pedagogy:guide"), {"semester": "AUTUMN"})
+ self.assertContains(response, text="PA00")
+
+ response = self.client.get(reverse("pedagogy:guide"), {"semester": "SPRING"})
+ self.assertContains(response, text="PA00")
+
+ response = self.client.get(
+ reverse("pedagogy:guide"), {"semester": "AUTUMN_AND_SPRING"}
+ )
+ self.assertContains(response, text="PA00")
+
+ # Search with language
+ response = self.client.get(reverse("pedagogy:guide"), {"language": "FR"})
+ self.assertContains(response, text="PA00")
+
+ # Search with credit type
+ response = self.client.get(reverse("pedagogy:guide"), {"credit_type": "OM"})
+ self.assertContains(response, text="PA00")
+
+ # Search with combinaison of all
+ response = self.client.get(
+ reverse("pedagogy:guide"),
+ {
+ "search": "P",
+ "department": "HUMA",
+ "semester": "AUTUMN",
+ "language": "FR",
+ "credit_type": "OM",
+ },
+ )
+ self.assertContains(response, text="PA00")
+
+ # Test json briefly
+ response = self.client.get(
+ reverse("pedagogy:guide"),
+ {
+ "json": "t",
+ "search": "P",
+ "department": "HUMA",
+ "semester": "AUTUMN",
+ "language": "FR",
+ "credit_type": "OM",
+ },
+ )
+ self.assertJSONEqual(
+ response.content,
+ [
+ {
+ "id": 1,
+ "absolute_url": "/pedagogy/uv/1",
+ "update_url": "/pedagogy/uv/1/edit",
+ "delete_url": "/pedagogy/uv/1/delete",
+ "code": "PA00",
+ "author": 0,
+ "credit_type": "OM",
+ "semester": "AUTUMN_AND_SPRING",
+ "language": "FR",
+ "credits": 5,
+ "department": "HUMA",
+ "title": "Participation dans une association \u00e9tudiante",
+ "manager": "Laurent HEYBERGER",
+ "objectives": "* Permettre aux \u00e9tudiants de r\u00e9aliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.",
+ "program": "* Semestre pr\u00e9c\u00e9dent proposition d'un projet et d'un cahier des charges\n* Evaluation par un jury de six membres\n* Si accord r\u00e9alisation dans le cadre de l'UV\n* Compte-rendu de l'exp\u00e9rience\n* Pr\u00e9sentation",
+ "skills": "* G\u00e9rer un projet associatif ou une action \u00e9ducative en autonomie:\n* en produisant un cahier des charges qui -d\u00e9finit clairement le contexte du projet personnel -pose les jalons de ce projet -estime de mani\u00e8re r\u00e9aliste les moyens et objectifs du projet -d\u00e9finit exactement les livrables attendus\n * en \u00e9tant capable de respecter ce cahier des charges ou, le cas \u00e9ch\u00e9ant, de r\u00e9viser le cahier des charges de mani\u00e8re argument\u00e9e.\n* Relater son exp\u00e9rience dans un rapport:\n* qui permettra \u00e0 d'autres \u00e9tudiants de poursuivre les actions engag\u00e9es\n* qui montre la capacit\u00e9 \u00e0 s'auto-\u00e9valuer et \u00e0 adopter une distance critique sur son action.",
+ "key_concepts": "* Autonomie\n* Responsabilit\u00e9\n* Cahier des charges\n* Gestion de projet",
+ "hours_CM": 0,
+ "hours_TD": 0,
+ "hours_TP": 0,
+ "hours_THE": 121,
+ "hours_TE": 4,
+ }
+ ],
+ )
+
+ def test_search_pa00_fail(self):
+
+ # Search with UV code
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "IFC"})
+ self.assertNotContains(response, text="PA00")
+
+ # Search with first letter of UV code
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "I"})
+ self.assertNotContains(response, text="PA00")
+
+ # Search with UV manager
+ response = self.client.get(reverse("pedagogy:guide"), {"search": "GILLES"})
+ self.assertNotContains(response, text="PA00")
+
+ # Search with department
+ response = self.client.get(reverse("pedagogy:guide"), {"department": "TC"})
+ self.assertNotContains(response, text="PA00")
+
+ # Search with semester
+ response = self.client.get(reverse("pedagogy:guide"), {"semester": "CLOSED"})
+ self.assertNotContains(response, text="PA00")
+
+ # Search with language
+ response = self.client.get(reverse("pedagogy:guide"), {"language": "EN"})
+ self.assertNotContains(response, text="PA00")
+
+ # Search with credit type
+ response = self.client.get(reverse("pedagogy:guide"), {"credit_type": "TM"})
+ self.assertNotContains(response, text="PA00")
+
+
+class UVModerationFormTest(TestCase):
+ """
+ Test moderation view
+ Assert access rights and if the form works well
+ """
+
+ 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_1 = UVComment(**comment_kwargs)
+ self.comment_1.save()
+
+ # Prepare another 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_2 = UVComment(**comment_kwargs)
+ self.comment_2.save()
+
+ # Prepare a comment report for comment 1
+ self.report_1 = UVCommentReport(
+ comment=self.comment_1, reporter=self.krophil, reason="C'est moche"
+ )
+ self.report_1.save()
+ self.report_1_bis = UVCommentReport(
+ comment=self.comment_1, reporter=self.krophil, reason="C'est moche 2"
+ )
+ self.report_1_bis.save()
+
+ # Prepare a comment report for comment 2
+ self.report_2 = UVCommentReport(
+ comment=self.comment_2, reporter=self.krophil, reason="C'est moche"
+ )
+ self.report_2.save()
+
+ def test_access_authorized_success(self):
+ # Test with root
+ self.client.login(username="root", password="plop")
+ response = self.client.get(reverse("pedagogy:moderation"))
+ self.assertEquals(response.status_code, 200)
+
+ # Test with pedagogy admin
+ self.client.login(username="tutu", password="plop")
+ response = self.client.get(reverse("pedagogy:moderation"))
+ self.assertEquals(response.status_code, 200)
+
+ def test_access_unauthorized_fail(self):
+ # Test with anonymous user
+ response = self.client.get(reverse("pedagogy:moderation"))
+ self.assertEquals(response.status_code, 403)
+
+ # Test with unsubscribed user
+ self.client.login(username="guy", password="plop")
+ response = self.client.get(reverse("pedagogy:moderation"))
+ self.assertEquals(response.status_code, 403)
+
+ # Test with subscribed user
+ self.client.login(username="sli", password="plop")
+ response = self.client.get(reverse("pedagogy:moderation"))
+ self.assertEquals(response.status_code, 403)
+
+ def test_do_nothing(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(reverse("pedagogy:moderation"))
+ self.assertEquals(response.status_code, 302)
+
+ # Test that nothing has changed
+ self.assertTrue(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists())
+ self.assertTrue(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+ self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists())
+
+ def test_delete_comment(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"), {"accepted_reports": [self.report_1.id]}
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that the comment and it's associated report has been deleted
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists())
+ # Test that the bis report has been deleted
+ self.assertFalse(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+
+ # Test that the other comment and report still exists
+ self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists())
+
+ def test_delete_comment_bulk(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"),
+ {"accepted_reports": [self.report_1.id, self.report_2.id]},
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that comments and their associated reports has been deleted
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists())
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+ self.assertFalse(UVComment.objects.filter(id=self.comment_2.id).exists())
+ # Test that the bis report has been deleted
+ self.assertFalse(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+
+ def test_delete_comment_with_bis(self):
+ # Test case if two reports targets the same comment and are both deleted
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"),
+ {"accepted_reports": [self.report_1.id, self.report_1_bis.id]},
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that the comment and it's associated report has been deleted
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists())
+ # Test that the bis report has been deleted
+ self.assertFalse(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+
+ def test_delete_report(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"), {"denied_reports": [self.report_1.id]}
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that the report has been deleted and that the comment still exists
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists())
+ # Test that the bis report is still there
+ self.assertTrue(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+
+ # Test that the other comment and report still exists
+ self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists())
+
+ def test_delete_report_bulk(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"),
+ {
+ "denied_reports": [
+ self.report_1.id,
+ self.report_1_bis.id,
+ self.report_2.id,
+ ]
+ },
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that every reports has been deleted
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertFalse(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+
+ # Test that comments still exists
+ self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists())
+
+ def test_delete_mixed(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"),
+ {
+ "accepted_reports": [self.report_2.id],
+ "denied_reports": [self.report_1.id],
+ },
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that report 2 and his comment has been deleted
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+ self.assertFalse(UVComment.objects.filter(id=self.comment_2.id).exists())
+
+ # Test that report 1 has been deleted and it's comment still exists
+ self.assertFalse(UVCommentReport.objects.filter(id=self.report_1.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_1.id).exists())
+
+ # Test that report 1 bis is still there
+ self.assertTrue(
+ UVCommentReport.objects.filter(id=self.report_1_bis.id).exists()
+ )
+
+ def test_delete_mixed_with_bis(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("pedagogy:moderation"),
+ {
+ "accepted_reports": [self.report_1.id],
+ "denied_reports": [self.report_1_bis.id],
+ },
+ )
+ self.assertEquals(response.status_code, 302)
+
+ # Test that report 1 and 1 bis has been deleted
+ self.assertFalse(
+ UVCommentReport.objects.filter(
+ id__in=[self.report_1.id, self.report_1_bis.id]
+ ).exists()
+ )
+
+ # Test that comment 1 has been deleted
+ self.assertFalse(UVComment.objects.filter(id=self.comment_1.id).exists())
+
+ # Test that report and comment 2 still exists
+ self.assertTrue(UVCommentReport.objects.filter(id=self.report_2.id).exists())
+ self.assertTrue(UVComment.objects.filter(id=self.comment_2.id).exists())
+
+
+class UVCommentReportCreateTest(TestCase):
+ """
+ Test report creation view view
+ Assert access rights and if you can create with it
+ """
+
+ def setUp(self):
+ call_command("populate")
+
+ self.krophil = User.objects.get(username="krophil")
+ self.tutu = User.objects.get(username="tutu")
+
+ # 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()
+
+ def create_report_test(self, username, success):
+ self.client.login(username=username, password="plop")
+ response = self.client.post(
+ reverse("pedagogy:comment_report", kwargs={"comment_id": self.comment.id}),
+ {
+ "comment": self.comment.id,
+ "reporter": User.objects.get(username=username).id,
+ "reason": "C'est moche",
+ },
+ )
+ if success:
+ self.assertEquals(response.status_code, 302)
+ else:
+ self.assertEquals(response.status_code, 403)
+ self.assertEquals(UVCommentReport.objects.all().exists(), success)
+
+ def test_create_report_root_success(self):
+ self.create_report_test("root", True)
+
+ def test_create_report_pedagogy_admin_success(self):
+ self.create_report_test("tutu", True)
+
+ def test_create_report_subscriber_success(self):
+ self.create_report_test("sli", True)
+
+ def test_create_report_unsubscribed_fail(self):
+ self.create_report_test("guy", False)
+
+ def test_create_report_anonymous_fail(self):
+ response = self.client.post(
+ reverse("pedagogy:comment_report", kwargs={"comment_id": self.comment.id}),
+ {"comment": self.comment.id, "reporter": 0, "reason": "C'est moche"},
+ )
+ self.assertEquals(response.status_code, 403)
+ self.assertFalse(UVCommentReport.objects.all().exists())
+
+ def test_notifications(self):
+ self.assertFalse(
+ self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists()
+ )
+ # Create a comment report
+ self.create_report_test("tutu", True)
+
+ # Check that a notification has been created for pedagogy admins
+ self.assertTrue(
+ self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").exists()
+ )
+
+ # Check that only pedagogy admins recieves this notification
+ for notif in Notification.objects.filter(type="PEDAGOGY_MODERATION").all():
+ self.assertTrue(
+ notif.user.is_in_group(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
+ )
+
+ # Check that notifications are not duplicated if not viewed
+ self.create_report_test("tutu", True)
+ self.assertEquals(
+ self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count(), 1
+ )
+
+ # Check that a new notification is created when the old one has been viewed
+ notif = self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").first()
+ notif.viewed = True
+ notif.save()
+
+ self.create_report_test("tutu", True)
+
+ self.assertEquals(
+ self.tutu.notifications.filter(type="PEDAGOGY_MODERATION").count(), 2
+ )
diff --git a/pedagogy/urls.py b/pedagogy/urls.py
new file mode 100644
index 00000000..5527bf3b
--- /dev/null
+++ b/pedagogy/urls.py
@@ -0,0 +1,54 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2019
+# - Sli
+#
+# 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.conf.urls import url
+
+from pedagogy.views import *
+
+urlpatterns = [
+ # Urls displaying the actual application for visitors
+ url(r"^$", UVListView.as_view(), name="guide"),
+ url(r"^uv/(?P[0-9]+)$", UVDetailFormView.as_view(), name="uv_detail"),
+ url(
+ r"^comment/(?P[0-9]+)/edit$",
+ UVCommentUpdateView.as_view(),
+ name="comment_update",
+ ),
+ url(
+ r"^comment/(?P[0-9]+)/delete$",
+ UVCommentDeleteView.as_view(),
+ name="comment_delete",
+ ),
+ url(
+ r"^comment/(?P[0-9]+)/report$",
+ UVCommentReportCreateView.as_view(),
+ name="comment_report",
+ ),
+ # Moderation
+ url(r"^moderation$", UVModerationFormView.as_view(), name="moderation"),
+ # Administration : Create Update Delete Edit
+ url(r"^uv/create$", UVCreateView.as_view(), name="uv_create"),
+ url(r"^uv/(?P[0-9]+)/delete$", UVDeleteView.as_view(), name="uv_delete"),
+ url(r"^uv/(?P[0-9]+)/edit$", UVUpdateView.as_view(), name="uv_update"),
+]
diff --git a/pedagogy/views.py b/pedagogy/views.py
new file mode 100644
index 00000000..fccde187
--- /dev/null
+++ b/pedagogy/views.py
@@ -0,0 +1,335 @@
+# -*- coding:utf-8 -*
+#
+# Copyright 2017
+# - Sli
+#
+# 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.views.generic import (
+ CreateView,
+ DeleteView,
+ UpdateView,
+ ListView,
+ FormView,
+ View,
+)
+from django.utils import html
+from django.http import HttpResponse
+from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
+from django.core.urlresolvers import reverse_lazy, reverse
+from django.shortcuts import get_object_or_404
+from django.conf import settings
+
+from haystack.query import SearchQuerySet
+from rest_framework.renderers import JSONRenderer
+
+from core.views import (
+ DetailFormView,
+ CanCreateMixin,
+ CanEditMixin,
+ CanViewMixin,
+ CanEditPropMixin,
+)
+from core.models import RealGroup, Notification
+
+from pedagogy.forms import (
+ UVForm,
+ UVCommentForm,
+ UVCommentReportForm,
+ UVCommentModerationForm,
+)
+from pedagogy.models import UV, UVComment, UVCommentReport, UVSerializer
+
+# Some mixins
+
+
+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
+ """
+
+ 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 UVCommentUpdateView(CanEditPropMixin, UpdateView):
+ """
+ Allow edit of a given comment
+ """
+
+ 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 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
+ """
+
+ # This is very basic and is prone to changment
+
+ model = UV
+ ordering = ["code"]
+ template_name = "pedagogy/guide.jinja"
+
+ def get(self, *args, **kwargs):
+ if not self.request.GET.get("json", None):
+ # Return normal full template response
+ return super(UVListView, self).get(*args, **kwargs)
+
+ # Return serialized response
+ return HttpResponse(
+ JSONRenderer().render(UVSerializer(self.get_queryset(), many=True).data),
+ content_type="application/json",
+ )
+
+ def get_queryset(self):
+ queryset = super(UVListView, self).get_queryset()
+ search = self.request.GET.get("search", None)
+
+ additional_filters = {}
+
+ for filter_type in ["credit_type", "language", "department"]:
+ arg = self.request.GET.get(filter_type, None)
+ if arg:
+ additional_filters[filter_type] = arg
+
+ semester = self.request.GET.get("semester", None)
+ if semester:
+ if semester in ["AUTUMN", "SPRING"]:
+ additional_filters["semester__in"] = [semester, "AUTUMN_AND_SPRING"]
+ else:
+ additional_filters["semester"] = semester
+
+ queryset = queryset.filter(**additional_filters)
+ if not search:
+ return queryset
+
+ if len(search) == 1:
+ # It's a search with only one letter
+ # Haystack doesn't work well with only one letter
+ return queryset.filter(code__istartswith=search)
+
+ try:
+ qs = (
+ SearchQuerySet()
+ .models(self.model)
+ .autocomplete(auto=html.escape(search))
+ )
+ except TypeError:
+ return self.model.objects.none()
+
+ return queryset.filter(id__in=([o.object.id for o in qs]))
+
+
+class UVCommentReportCreateView(CanCreateMixin, CreateView):
+ """
+ Create a new report for an inapropriate comment
+ """
+
+ model = UVCommentReport
+ form_class = UVCommentReportForm
+ template_name = "core/edit.jinja"
+
+ def dispatch(self, request, *args, **kwargs):
+ self.uv_comment = get_object_or_404(UVComment, pk=kwargs["comment_id"])
+ return super(UVCommentReportCreateView, self).dispatch(request, *args, **kwargs)
+
+ def get_form_kwargs(self):
+ kwargs = super(UVCommentReportCreateView, self).get_form_kwargs()
+ kwargs["reporter_id"] = self.request.user.id
+ kwargs["comment_id"] = self.uv_comment.id
+ return kwargs
+
+ def form_valid(self, form):
+ resp = super(UVCommentReportCreateView, self).form_valid(form)
+
+ # Send a message to moderation admins
+ for user in (
+ RealGroup.objects.filter(id=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
+ .first()
+ .users.all()
+ ):
+ if not user.notifications.filter(
+ type="PEDAGOGY_MODERATION", viewed=False
+ ).exists():
+ Notification(
+ user=user,
+ url=reverse("pedagogy:moderation"),
+ type="PEDAGOGY_MODERATION",
+ ).save()
+
+ return resp
+
+ def get_success_url(self):
+ return reverse_lazy(
+ "pedagogy:uv_detail", kwargs={"uv_id": self.uv_comment.uv.id}
+ )
+
+
+class UVModerationFormView(FormView):
+ """
+ Moderation interface (Privileged)
+ """
+
+ form_class = UVCommentModerationForm
+ template_name = "pedagogy/moderation.jinja"
+
+ def dispatch(self, request, *args, **kwargs):
+ if not request.user.is_owner(UV()):
+ raise PermissionDenied
+ return super(UVModerationFormView, self).dispatch(request, *args, **kwargs)
+
+ def form_valid(self, form):
+ form_clean = form.clean()
+ for report in form_clean.get("accepted_reports", []):
+ try:
+ report.comment.delete() # Delete the related comment
+ except ObjectDoesNotExist:
+ # To avoid errors when two reports points the same comment
+ pass
+ for report in form_clean.get("denied_reports", []):
+ try:
+ report.delete() # Delete the report itself
+ except ObjectDoesNotExist:
+ # To avoid errors when two reports points the same comment
+ pass
+ return super(UVModerationFormView, self).form_valid(form)
+
+ def get_success_url(self):
+ return reverse_lazy("pedagogy:moderation")
+
+
+class UVCreateView(CanCreateMixin, CreateView):
+ """
+ Add a new UV (Privileged)
+ """
+
+ 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(CanEditPropMixin, DeleteView):
+ """
+ Allow to delete an UV (Privileged)
+ """
+
+ model = UV
+ pk_url_kwarg = "uv_id"
+ template_name = "core/delete_confirm.jinja"
+
+ def get_success_url(self):
+ return reverse_lazy("pedagogy:guide")
+
+
+class UVUpdateView(CanEditPropMixin, UpdateView):
+ """
+ Allow to edit an UV (Privilegied)
+ """
+
+ 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
+
+ def get_success_url(self):
+ return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})
diff --git a/sith/settings.py b/sith/settings.py
index e0b395e3..6470c52b 100644
--- a/sith/settings.py
+++ b/sith/settings.py
@@ -93,6 +93,7 @@ INSTALLED_APPS = (
"stock",
"trombi",
"matmat",
+ "pedagogy",
)
MIDDLEWARE = (
@@ -321,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
@@ -403,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")),
+ ("AUTUMN_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
@@ -521,6 +560,10 @@ SITH_LAUNDERETTE_PRICES = {"WASHING": 1.0, "DRYING": 0.75}
SITH_NOTIFICATIONS = [
("POSTER_MODERATION", _("A new poster needs to be moderated")),
("MAILING_MODERATION", _("A new mailing list needs to be moderated")),
+ (
+ "PEDAGOGY_MODERATION",
+ _("A new pedagogy comment has been signaled for moderation"),
+ ),
("NEWS_MODERATION", _("There are %s fresh news to be moderated")),
("FILE_MODERATION", _("New files to be moderated")),
("SAS_MODERATION", _("There are %s pictures to be moderated in the SAS")),
diff --git a/sith/urls.py b/sith/urls.py
index 179724b8..23f82469 100644
--- a/sith/urls.py
+++ b/sith/urls.py
@@ -84,6 +84,10 @@ urlpatterns = [
url(
r"^matmatronch/", include("matmat.urls", namespace="matmat", app_name="matmat")
),
+ url(
+ r"^pedagogy/",
+ include("pedagogy.urls", namespace="pedagogy", app_name="pedagogy"),
+ ),
url(r"^admin/", include(admin.site.urls)),
url(r"^ajax_select/", include(ajax_select_urls)),
url(r"^i18n/", include("django.conf.urls.i18n")),
{% trans %}Report this comment{% endtrans %}
{{ comment.publish_date.strftime('%d/%m/%Y') }}
{{ user_profile_link(comment.author) }}