Merge branch 'auto-uv-pedagogy' into 'master'

Auto fill UVs in pedagogy

See merge request ae/Sith!253
This commit is contained in:
Antoine Bartuccio 2019-10-25 12:09:39 +02:00
commit 42055b9001
11 changed files with 465 additions and 208 deletions

View File

@ -53,4 +53,5 @@ urlpatterns = [
re_path(r"^login/", include("rest_framework.urls", namespace="rest_framework")), re_path(r"^login/", include("rest_framework.urls", namespace="rest_framework")),
re_path(r"^markdown$", RenderMarkdown, name="api_markdown"), re_path(r"^markdown$", RenderMarkdown, name="api_markdown"),
re_path(r"^mailings$", FetchMailingLists, name="mailings_fetch"), re_path(r"^mailings$", FetchMailingLists, name="mailings_fetch"),
re_path(r"^uv$", uv_endpoint, name="uv_endpoint"),
] ]

View File

@ -77,3 +77,4 @@ from .user import *
from .club import * from .club import *
from .group import * from .group import *
from .launderette import * from .launderette import *
from .uv import *

View File

@ -25,7 +25,6 @@
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import api_view, renderer_classes from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import StaticHTMLRenderer from rest_framework.renderers import StaticHTMLRenderer
from rest_framework.views import APIView
from core.templatetags.renderer import markdown from core.templatetags.renderer import markdown

127
api/views/uv.py Normal file
View File

@ -0,0 +1,127 @@
from rest_framework.response import Response
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import JSONRenderer
from django.core.exceptions import PermissionDenied
from django.conf import settings
from rest_framework import serializers
import urllib.request
import json
from pedagogy.views import CanCreateUVFunctionMixin
@api_view(["GET"])
@renderer_classes((JSONRenderer,))
def uv_endpoint(request):
if not CanCreateUVFunctionMixin.can_create_uv(request.user):
raise PermissionDenied
params = request.query_params
if "year" not in params or "code" not in params:
raise serializers.ValidationError("Missing query parameter")
short_uv, full_uv = find_uv("fr", params["year"], params["code"])
if short_uv is None or full_uv is None:
return Response(status=204)
return Response(make_clean_uv(short_uv, full_uv))
def find_uv(lang, year, code):
"""
Uses the UTBM API to find an UV.
short_uv is the UV entry in the UV list. It is returned as it contains
information which are not in full_uv.
full_uv is the detailed representation of an UV.
"""
# query the UV list
uvs_url = settings.SITH_PEDAGOGY_UTBM_API + "/uvs/{}/{}".format(lang, year)
response = urllib.request.urlopen(uvs_url)
uvs = json.loads(response.read().decode("utf-8"))
try:
# find the first UV which matches the code
short_uv = next(uv for uv in uvs if uv["code"] == code)
except StopIteration:
return (None, None)
# get detailed information about the UV
uv_url = settings.SITH_PEDAGOGY_UTBM_API + "/uv/{}/{}/{}/{}".format(
lang, year, code, short_uv["codeFormation"]
)
response = urllib.request.urlopen(uv_url)
full_uv = json.loads(response.read().decode("utf-8"))
return (short_uv, full_uv)
def make_clean_uv(short_uv, full_uv):
"""
Cleans the data up so that it corresponds to our data representation.
"""
res = {}
res["credit_type"] = short_uv["codeCategorie"]
# probably wrong on a few UVs as we pick the first UV we find but
# availability depends on the formation
semesters = {
(True, True): "AUTUMN_AND_SPRING",
(True, False): "AUTUMN",
(False, True): "SPRING",
}
res["semester"] = semesters.get(
(short_uv["ouvertAutomne"], short_uv["ouvertPrintemps"]), "CLOSED"
)
langs = {"es": "SP", "en": "EN", "de": "DE"}
res["language"] = langs.get(full_uv["codeLangue"], "FR")
if full_uv["departement"] == "Pôle Humanités":
res["department"] = "HUMA"
else:
departments = {
"AL": "IMSI",
"AE": "EE",
"GI": "GI",
"GC": "EE",
"GM": "MC",
"TC": "TC",
"GP": "IMSI",
"ED": "EDIM",
"AI": "GI",
"AM": "MC",
}
res["department"] = departments.get(full_uv["codeFormation"], "NA")
res["credits"] = full_uv["creditsEcts"]
activities = ("CM", "TD", "TP", "THE", "TE")
for activity in activities:
res["hours_{}".format(activity)] = 0
for activity in full_uv["activites"]:
if activity["code"] in activities:
res["hours_{}".format(activity["code"])] += activity["nbh"] // 60
# wrong if the manager changes depending on the semester
semester = full_uv.get("automne", None)
if not semester:
semester = full_uv.get("printemps", {})
res["manager"] = semester.get("responsable", "")
res["title"] = full_uv["libelle"]
descriptions = {
"objectives": "objectifs",
"program": "programme",
"skills": "acquisitionCompetences",
"key_concepts": "acquisitionNotions",
}
for res_key, full_uv_key in descriptions.items():
res[res_key] = full_uv[full_uv_key]
# if not found or the API did not return a string
if type(res[res_key]) != str:
res[res_key] = ""
return res

View File

@ -38,7 +38,7 @@ def get_cached_user(request):
if not hasattr(request, "_cached_user"): if not hasattr(request, "_cached_user"):
user = get_user(request) user = get_user(request)
if user.is_anonymous: if user.is_anonymous:
user = AnonymousUser(request) user = AnonymousUser()
request._cached_user = user request._cached_user = user

View File

@ -659,7 +659,7 @@ class User(AbstractBaseUser):
class AnonymousUser(AuthAnonymousUser): class AnonymousUser(AuthAnonymousUser):
def __init__(self, request): def __init__(self):
super(AnonymousUser, self).__init__() super(AnonymousUser, self).__init__()
@property @property

View File

@ -38,7 +38,21 @@ $( function() {
$("#quick_notif li").click(function () { $("#quick_notif li").click(function () {
$(this).hide(); $(this).hide();
}) })
} ); });
function createQuickNotif(msg) {
const el = document.createElement('li')
el.textContent = msg
el.addEventListener('click', () => el.parentNode.removeChild(el))
document.getElementById('quick_notif').appendChild(el)
}
function deleteQuickNotifs() {
const el = document.getElementById('quick_notif')
while (el.firstChild) {
el.removeChild(el.firstChild)
}
}
function display_notif() { function display_notif() {
$('#header_notif').toggle().parent().toggleClass("white"); $('#header_notif').toggle().parent().toggleClass("white");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
{% extends "core/base.jinja" %}
{% block title %}
{% trans %}Edit UV{% endtrans %}
{% endblock %}
{% block content %}
<h2>{% trans %}Edit UV{% endtrans %}</h2>
<form action="" method="post" enctype="multipart/form-data" id="uv_edit">
{% csrf_token %}
{{ form.non_field_errors() }}
{% for field in form %}
{% if field.is_hidden %}
{{ field }}
{% else %}
<p>
{{ field.errors }}
<label for="{{ field.name }}">{{ field.label }}</label>
{{ field }}
{% if field.name == 'code' %}
<button type="button" id="autofill">{% trans %}Import from UTBM{% endtrans %}</button>
{% endif %}
</p>
{% endif %}
{% endfor %}
<p><input type="submit" value="{% trans %}Update{% endtrans %}" /></p>
</form>
{% endblock %}
{% block script %}
{{ super() }}
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
const autofillBtn = document.getElementById('autofill')
const codeInput = document.querySelector('input[name="code"]')
autofillBtn.addEventListener('click', () => {
const today = new Date()
let year = today.getFullYear()
if (today.getMonth() < 7) { // student year starts in september
year--
}
const url = "{{ url('api:uv_endpoint') }}?year=" + year + "&code=" + codeInput.value
deleteQuickNotifs()
$.ajax({
dataType: "json",
url: url,
success: function(data, _, xhr) {
if (xhr.status != 200) {
createQuickNotif("{% trans %}Unknown UV code{% endtrans %}")
return
}
for (let key in data) {
if (data.hasOwnProperty(key)) {
const el = document.querySelector('[name="' + key + '"]')
if (el.tagName == 'TEXTAREA') {
el.parentNode.querySelector('.CodeMirror').CodeMirror.setValue(data[key])
} else {
el.value = data[key]
}
}
}
createQuickNotif('{% trans %}Successful autocomplete{% endtrans %}')
},
error: function(_, _, statusMessage) {
createQuickNotif('{% trans %}An error occured: {% endtrans %}' + statusMessage)
},
})
})
})
</script>
{% endblock %}

View File

@ -294,7 +294,7 @@ class UVCreateView(CanCreateMixin, CreateView):
model = UV model = UV
form_class = UVForm form_class = UVForm
template_name = "core/edit.jinja" template_name = "pedagogy/uv_edit.jinja"
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(UVCreateView, self).get_form_kwargs() kwargs = super(UVCreateView, self).get_form_kwargs()
@ -326,7 +326,7 @@ class UVUpdateView(CanEditPropMixin, UpdateView):
model = UV model = UV
form_class = UVForm form_class = UVForm
pk_url_kwarg = "uv_id" pk_url_kwarg = "uv_id"
template_name = "core/edit.jinja" template_name = "pedagogy/uv_edit.jinja"
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(UVUpdateView, self).get_form_kwargs() kwargs = super(UVUpdateView, self).get_form_kwargs()

View File

@ -196,6 +196,8 @@ SASS_PRECISION = 8
WSGI_APPLICATION = "sith.wsgi.application" WSGI_APPLICATION = "sith.wsgi.application"
REST_FRAMEWORK = {}
# Database # Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases # https://docs.djangoproject.com/en/1.8/ref/settings/#databases
@ -252,6 +254,7 @@ LOGOUT_URL = "/logout"
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"
DEFAULT_FROM_EMAIL = "bibou@git.an" DEFAULT_FROM_EMAIL = "bibou@git.an"
SITH_COM_EMAIL = "bibou_com@git.an" SITH_COM_EMAIL = "bibou_com@git.an"
REST_FRAMEWORK["UNAUTHENTICATED_USER"] = "core.models.AnonymousUser"
# Email # Email
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
@ -442,6 +445,8 @@ SITH_PEDAGOGY_UV_RESULT_GRADE = [
("ABS", _("Abs")), ("ABS", _("Abs")),
] ]
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
SITH_ECOCUP_CONS = 1152 SITH_ECOCUP_CONS = 1152
SITH_ECOCUP_DECO = 1151 SITH_ECOCUP_DECO = 1151