Skip to content
Snippets Groups Projects
Verified Commit e3d26b7d authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Add permissions for students view and list

parent f3206ef4
No related branches found
No related tags found
1 merge request!49Resolve "Add rules and permissions"
Pipeline #3518 passed
......@@ -38,13 +38,23 @@ MENUS = {
"name": _("My overview"),
"url": "overview_me",
"icon": "insert_chart",
"validators": ["menu_generator.validators.is_authenticated"],
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"alsijil.view_person_overview_menu",
),
],
},
{
"name": _("My students"),
"url": "my_students",
"icon": "people",
"validators": ["menu_generator.validators.is_authenticated"],
"validators": [
(
"aleksis.core.util.predicates.permission_validator",
"alsijil.view_my_students",
),
],
},
{
"name": _("Register absence"),
......
......@@ -19,6 +19,7 @@ from .util.predicates import (
is_lesson_teacher,
is_own_personal_note,
is_person_group_owner,
is_person_primary_group_owner,
is_personal_note_lesson_parent_group_owner,
is_personal_note_lesson_teacher,
is_teacher,
......@@ -46,6 +47,16 @@ view_lesson_personal_notes_predicate = has_person & (
)
add_perm("alsijil.view_lesson_personalnote", view_lesson_personal_notes_predicate)
# Edit personal note
edit_lesson_personal_note_predicate = has_person & (
has_global_perm("alsijil.change_personalnote")
| has_lesson_group_object_perm("core.edit_personalnote_group")
| is_lesson_teacher
| is_lesson_parent_group_owner
)
add_perm("alsijil.edit_lesson_personalnote", edit_lesson_personal_note_predicate)
# View personal note
view_personal_note_predicate = has_person & (
has_global_perm("alsijil.view_personalnote")
......@@ -58,9 +69,10 @@ add_perm("alsijil.view_personalnote", view_personal_note_predicate)
# Edit personal note
edit_personal_note_predicate = has_person & (
has_global_perm("alsijil.change_personalnote")
| has_lesson_group_object_perm("core.edit_personalnote_group")
| is_lesson_teacher
has_global_perm("alsijil.view_personalnote")
| has_personal_note_group_perm("core.edit_personalnote_group")
| is_personal_note_lesson_teacher
| is_personal_note_lesson_parent_group_owner
)
add_perm("alsijil.edit_personalnote", edit_personal_note_predicate)
......@@ -127,6 +139,53 @@ view_full_register_predicate = has_person & (
)
add_perm("alsijil.view_full_register", view_full_register_predicate)
# View students list
view_my_students_predicate = has_person & is_teacher
add_perm("alsijil.view_my_students", view_my_students_predicate)
# View person overview
view_person_overview_predicate = has_person & (
is_current_person | is_person_group_owner
)
add_perm("alsijil.view_person_overview", view_person_overview_predicate)
# View person overview
view_person_overview_menu_predicate = has_person
add_perm("alsijil.view_person_overview_menu", view_person_overview_menu_predicate)
# View person overview personal notes
view_person_overview_personal_notes_predicate = has_person & (
has_global_perm("alsijil.view_personalnote")
| has_person_group_object_perm("core.view_personalnote_group")
| is_person_primary_group_owner
)
add_perm(
"alsijil.view_person_overview_personalnote",
view_person_overview_personal_notes_predicate,
)
# Edit person overview personal notes
edit_person_overview_personal_notes_predicate = has_person & (
has_global_perm("alsijil.edit_personalnote")
| has_person_group_object_perm("core.edit_personalnote_group")
| is_person_primary_group_owner
)
add_perm(
"alsijil.edit_person_overview_personalnote",
edit_person_overview_personal_notes_predicate,
)
# View person statistics on personal notes
view_person_statistics_personal_notes_predicate = has_person & (
has_global_perm("alsijil.view_personalnote")
| has_person_group_object_perm("core.view_personalnote_group")
| is_person_primary_group_owner
)
add_perm(
"alsijil.view_person_statistics_personalnote",
view_person_statistics_personal_notes_predicate,
)
# View excuse type list
view_excusetypes_predicate = has_person & has_global_perm("alsijil.view_excusetype")
add_perm("alsijil.view_excusetypes", view_excusetypes_predicate)
......
......@@ -30,7 +30,7 @@
{% block content %}
{% has_perm "alsijil.view_lessondocumentation" user lesson_period as can_view_lesson_documentation %}
{% has_perm "alsijil.edit_lessondocumentation" user lesson_period as can_edit_lesson_documentation %}
{% has_perm "alsijil.edit_personalnote" user lesson_period as can_edit_personalnote %}
{% has_perm "alsijil.edit_lesson_personalnote" user lesson_period as can_edit_lesson_personalnote %}
<div class="row">
<div class="col s12">
......@@ -53,7 +53,7 @@
</div>
<form method="post">
{% if can_edit_lesson_documentation or can_edit_personalnote %}
{% if can_edit_lesson_documentation or can_edit_lesson_personalnote %}
<p>{% include "core/partials/save_button.html" %}</p>
{% endif %}
......@@ -194,7 +194,7 @@
<span class="card-title">
{% blocktrans %}Personal notes{% endblocktrans %}
</span>
{% if can_edit_personalnote %}
{% if can_edit_lesson_personalnote %}
{% form form=personal_note_formset.management_form %}{% endform %}
{% endif %}
......@@ -212,7 +212,7 @@
</thead>
<tbody>
{% for form in personal_note_formset %}
{% if can_edit_personalnote %}
{% if can_edit_lesson_personalnote %}
<tr>
{{ form.id }}
<td>{{ form.person_name }}{{ form.person_name.value }}</td>
......@@ -303,7 +303,7 @@
{% endif %}
</div>
{% if can_edit_lesson_documentation or can_edit_personalnote %}
{% if can_edit_lesson_documentation or can_edit_lesson_personalnote %}
<p>{% include "core/partials/save_button.html" %}</p>
{% endif %}
</form>
......
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load rules %}
{% load data_helpers %}
{% load week_helpers %}
{% load i18n %}
......@@ -14,6 +15,8 @@
{% endblock %}
{% block content %}
{% has_perm "alsijil.edit_person_overview_personalnote" user person as can_mark_all_as_excused %}
<div class="row">
<div class="col s12 m12 l6">
<h5>{% trans "Unexcused absences" %}</h5>
......@@ -35,12 +38,15 @@
{% if note.remarks %}
<p class="no-margin"><em>{{ note.remarks }}</em></p>
{% endif %}
<form action="" method="post" class="hide-on-med-and-up">
{% csrf_token %}
{% trans "Mark as" %}
<input type="hidden" value="{{ note.pk }}" name="personal_note">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
{% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
{% if can_edit_personal_note %}
<form action="" method="post" class="hide-on-med-and-up">
{% csrf_token %}
{% trans "Mark as" %}
<input type="hidden" value="{{ note.pk }}" name="personal_note">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
{% endif %}
</li>
{% empty %}
<li class="collection-item flow-text">
......@@ -48,47 +54,49 @@
</li>
{% endfor %}
</ul>
<h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
<ul class="collapsible">
{% for school_term, stat in stats %}
<li {% if forloop.first %}class="active"{% endif %}>
<div class="collapsible-header">
<i class="material-icons">date_range</i>{{ school_term }}</div>
<div class="collapsible-body">
<table>
<tr>
<th colspan="2">{% trans 'Absences' %}</th>
<td>{{ stat.absences_count }}</td>
</tr>
<tr>
<td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
<td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
<th class="truncate">{% trans 'Excused' %}</th>
<td>{{ stat.excused }}</td>
</tr>
{% for excuse_type in excuse_types %}
<th>{{ excuse_type.name }}</th>
<td>{{ stat|get_dict:excuse_type.count_label }}</td>
{% endfor %}
<tr>
<th>{% trans 'Unexcused' %}</th>
<td>{{ stat.unexcused }}</td>
</tr>
<tr>
<th colspan="2">{% trans 'Tardiness' %}</th>
<td>{{ stat.tardiness }}'</td>
</tr>
{% for extra_mark in extra_marks %}
{% if stats %}
<h5>{% trans "Statistics on absences, tardiness and remarks" %}</h5>
<ul class="collapsible">
{% for school_term, stat in stats %}
<li {% if forloop.first %}class="active"{% endif %}>
<div class="collapsible-header">
<i class="material-icons">date_range</i>{{ school_term }}</div>
<div class="collapsible-body">
<table>
<tr>
<th colspan="2">{{ extra_mark.name }}</th>
<td>{{ stat|get_dict:extra_mark.count_label }}</td>
<th colspan="2">{% trans 'Absences' %}</th>
<td>{{ stat.absences_count }}</td>
</tr>
{% endfor %}
</table>
</div>
</li>
{% endfor %}
</ul>
<tr>
<td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-small-only">{% trans "thereof" %}</td>
<td rowspan="{{ excuse_types.count|add:2 }}" class="hide-on-med-and-up"></td>
<th class="truncate">{% trans 'Excused' %}</th>
<td>{{ stat.excused }}</td>
</tr>
{% for excuse_type in excuse_types %}
<th>{{ excuse_type.name }}</th>
<td>{{ stat|get_dict:excuse_type.count_label }}</td>
{% endfor %}
<tr>
<th>{% trans 'Unexcused' %}</th>
<td>{{ stat.unexcused }}</td>
</tr>
<tr>
<th colspan="2">{% trans 'Tardiness' %}</th>
<td>{{ stat.tardiness }}'</td>
</tr>
{% for extra_mark in extra_marks %}
<tr>
<th colspan="2">{{ extra_mark.name }}</th>
<td>{{ stat|get_dict:extra_mark.count_label }}</td>
</tr>
{% endfor %}
</table>
</div>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="col s12 m12 l6">
<h5>{% trans "Relevant personal notes" %}</h5>
......@@ -113,12 +121,14 @@
{% weekday_to_date note.calendar_week note.lesson_period.period.weekday as note_date %}
{% ifchanged note_date %}
<li class="collection-item">
<form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
{% csrf_token %}
{% trans "Mark all as" %}
<input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
{% if can_mark_all_as_excused %}
<form action="" method="post" class="right hide-on-small-only" style="margin-top: -7px;">
{% csrf_token %}
{% trans "Mark all as" %}
<input type="hidden" value="{{ note_date|date:"Y-m-d" }}" name="date">
{% include "alsijil/partials/mark_as_buttons.html" %}
</form>
{% endif %}
<i class="material-icons left">schedule</i>
{{ note_date }}
......@@ -184,7 +194,8 @@
</div>
<div class="col s12 hide-on-med-and-up">
{% if note.absent and not note.excused %}
{% has_perm "alsijil.edit_personalnote" user note as can_edit_personal_note %}
{% if note.absent and not note.excused and can_edit_personal_note %}
<form action="" method="post">
{% csrf_token %}
{% trans "Mark as" %}
......
......@@ -116,7 +116,12 @@
</span>
{% for person in persons %}
<h5 class="card-title">
<a href="{% url "overview_person" person.person.pk %}">{{ person.person.full_name }}</a>
{% has_perm "alsijil.view_person_overview" user person.person as can_view_person_overview %}
{% if can_view_person_overview %}
<a href="{% url "overview_person" person.person.pk %}">{{ person.person.full_name }}</a>
{% else %}
{{ person.person.full_name }}
{% endif %}
</h5>
<p class="card-text">
{% trans "Absent" %}: {{ person.person.absences_count }}
......
......@@ -75,7 +75,22 @@ def is_person_group_owner(user: User, obj: Person) -> bool:
Checks whether the person linked to the user is
the owner of any group of the given person.
"""
return obj.member_of.filter(owners=user.person).exists()
if obj:
return obj.member_of.filter(owners=user.person).exists()
return False
@predicate
def is_person_primary_group_owner(user: User, obj: Person) -> bool:
"""
Predicate for group owners of the person's primary group.
Checks whether the person linked to the user is
the owner of the primary group of the given person.
"""
if obj.primary_group:
return user.person in obj.primary_group.owners.all()
return False
def has_person_group_object_perm(perm: str):
......
......@@ -129,7 +129,7 @@ def lesson(
or not get_site_preferences()["alsijil__block_personal_notes_for_cancelled"]
):
if personal_note_formset.is_valid() and request.user.has_perm(
"alsijil.edit_personalnote", lesson_period
"alsijil.edit_lesson_personalnote", lesson_period
):
instances = personal_note_formset.save()
......@@ -470,6 +470,7 @@ def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
return render(request, "alsijil/print/full_register.html", context)
@permission_required("alsijil.view_my_students")
def my_students(request: HttpRequest) -> HttpResponse:
context = {}
relevant_groups = (
......@@ -482,6 +483,10 @@ def my_students(request: HttpRequest) -> HttpResponse:
return render(request, "alsijil/class_register/persons.html", context)
@permission_required(
"alsijil.view_person_overview",
fn=objectgetter_optional(Person, "request.user.person", True),
)
def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
context = {}
person = objectgetter_optional(
......@@ -512,6 +517,11 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
request.POST["date"], "%Y-%m-%d"
).date()
if not request.user.has_perm(
"alsijil.edit_person_overview_personalnote", person
):
raise PermissionDenied()
notes = person.personal_notes.filter(
week=date.isocalendar()[1],
lesson_period__period__weekday=date.weekday(),
......@@ -532,6 +542,8 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
note = PersonalNote.objects.get(
pk=int(request.POST["personal_note"])
)
if not request.user.has_perm("alsijil.edit_personalnote", note):
raise PermissionDenied()
if note.absent:
note.excused = True
note.excuse_type = excuse_type
......@@ -544,10 +556,20 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
person.refresh_from_db()
unexcused_absences = person.personal_notes.filter(absent=True, excused=False)
allowed_personal_notes = person.personal_notes.all()
if not request.user.has_perm("alsijil.view_person_overview_personalnote", person):
print("has")
allowed_personal_notes = allowed_personal_notes.filter(
lesson_period__lesson__groups__owners=request.user.person
)
print(allowed_personal_notes)
unexcused_absences = allowed_personal_notes.filter(absent=True, excused=False)
context["unexcused_absences"] = unexcused_absences
personal_notes = person.personal_notes.filter(
personal_notes = allowed_personal_notes.filter(
Q(absent=True) | Q(late__gt=0) | ~Q(remarks="") | Q(extra_marks__isnull=False)
).order_by(
"-lesson_period__lesson__validity__date_start",
......@@ -558,48 +580,51 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
context["personal_notes"] = personal_notes
context["excuse_types"] = ExcuseType.objects.all()
school_terms = SchoolTerm.objects.all().order_by("-date_start")
stats = []
for school_term in school_terms:
stat = {}
personal_notes = PersonalNote.objects.filter(
person=person, lesson_period__lesson__validity__school_term=school_term
)
if not personal_notes.exists():
continue
stat.update(
personal_notes.filter(absent=True).aggregate(absences_count=Count("absent"))
)
stat.update(
personal_notes.filter(
absent=True, excused=True, excuse_type__isnull=True
).aggregate(excused=Count("absent"))
)
stat.update(
personal_notes.filter(absent=True, excused=False).aggregate(
unexcused=Count("absent")
if request.user.has_perm("alsijil.view_person_statistics_personalnote", person):
school_terms = SchoolTerm.objects.all().order_by("-date_start")
stats = []
for school_term in school_terms:
stat = {}
personal_notes = PersonalNote.objects.filter(
person=person, lesson_period__lesson__validity__school_term=school_term
)
)
stat.update(personal_notes.aggregate(tardiness=Sum("late")))
for extra_mark in ExtraMark.objects.all():
if not personal_notes.exists():
continue
stat.update(
personal_notes.filter(extra_marks=extra_mark).aggregate(
**{extra_mark.count_label: Count("pk")}
personal_notes.filter(absent=True).aggregate(
absences_count=Count("absent")
)
)
for excuse_type in ExcuseType.objects.all():
stat.update(
personal_notes.filter(absent=True, excuse_type=excuse_type).aggregate(
**{excuse_type.count_label: Count("absent")}
personal_notes.filter(
absent=True, excused=True, excuse_type__isnull=True
).aggregate(excused=Count("absent"))
)
stat.update(
personal_notes.filter(absent=True, excused=False).aggregate(
unexcused=Count("absent")
)
)
stat.update(personal_notes.aggregate(tardiness=Sum("late")))
for extra_mark in ExtraMark.objects.all():
stat.update(
personal_notes.filter(extra_marks=extra_mark).aggregate(
**{extra_mark.count_label: Count("pk")}
)
)
for excuse_type in ExcuseType.objects.all():
stat.update(
personal_notes.filter(
absent=True, excuse_type=excuse_type
).aggregate(**{excuse_type.count_label: Count("absent")})
)
stats.append((school_term, stat))
context["stats"] = stats
stats.append((school_term, stat))
context["stats"] = stats
context["excuse_types"] = ExcuseType.objects.all()
context["extra_marks"] = ExtraMark.objects.all()
return render(request, "alsijil/class_register/person.html", context)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment