Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Alsijil
  • sunweaver/AlekSIS-App-Alsijil
  • 8tincsoVluke/AlekSIS-App-Alsijil
  • perfreicpo/AlekSIS-App-Alsijil
  • noifobarep/AlekSIS-App-Alsijil
  • 7ingannisdo/AlekSIS-App-Alsijil
  • unmruntartpa/AlekSIS-App-Alsijil
  • balrorebta/AlekSIS-App-Alsijil
  • comliFdifwa/AlekSIS-App-Alsijil
  • 3ranaadza/AlekSIS-App-Alsijil
10 results
Show changes
{% load i18n material_form_internal material_form %}
{% load i18n material_form_internal material_form data_helpers %}
{% include "alsijil/partials/lesson/heading.html" %}
{% include "alsijil/partials/lesson/prev_next.html" with with_save=0 %}
......@@ -19,6 +19,29 @@
{% if can_edit_lesson_documentation %}
{% form form=lesson_documentation_form %}{% endform %}
{% if instructions %}
<h6>{% trans "Instructions" %}</h6>
<div class="collection">
{% for instruction in instructions %}
<div class="collection-item">
<i class="material-icons primary-color-text left iconify" data-icon="mdi:{{ instruction.icon|default:"clipboard-check-outline" }}"></i>
<a href="{{ instruction.pdf_file.url }}" target="_blank">{{ instruction.name }}</a>
<div class="right grey-text darken-4">
{% trans "Instruction done" %}&nbsp;&nbsp;
<label class="secondary-content right">
<input type="checkbox"
class="filled-in"
name="lesson_documentation-done_instructions"
value="{{ instruction.pk }}"
{% if instruction.pk|slugify in lesson_documentation_form.done_instructions.value %}checked{% endif %}>
<span></span>
</label>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% elif can_view_lesson_documentation %}
<table>
<tr>
......@@ -46,6 +69,21 @@
</td>
</tr>
</table>
{% if lesson_documentation.done_instructions.all %}
<h6>{% trans "Done instructions" %}</h6>
<div class="collection">
{% for instruction in lesson_documentation.done_instructions.all %}
<div class="collection-item">
<i class="material-icons primary-color-text left iconify" data-icon="mdi:{{ instruction.icon|default:"clipboard-check-outline" }}"></i>
<a href="{{ instruction.pdf_file.url }}" target="_blank">{{ instruction.name }}</a>
<div class="right green-text">
<i class="material-icons left">check</i>
{% trans "Instruction done in this lesson" %}
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
</div>
<div class="card-action-light hide-on-small-only">
......
......@@ -128,4 +128,14 @@ urlpatterns = [
name="assign_group_role_multiple",
),
path("all/", views.AllRegisterObjectsView.as_view(), name="all_register_objects"),
path("instructions/", views.InstructionsListView.as_view(), name="instructions"),
path("instructions/create/", views.InstructionCreateView.as_view(), name="create_instruction"),
path(
"instructions/<int:pk>/edit/", views.InstructionEditView.as_view(), name="edit_instruction"
),
path(
"instructions/<int:pk>/delete/",
views.InstructionDeleteView.as_view(),
name="delete_instruction",
),
]
from typing import Any, Union
from django.contrib.auth.models import User
from django.db.models import Q
from rules import predicate
......@@ -8,7 +9,7 @@ from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
from aleksis.core.models import Group, Person
from aleksis.core.util.predicates import check_object_permission
from ..models import PersonalNote
from ..models import Instruction, PersonalNote
@predicate
......@@ -295,3 +296,24 @@ def is_group_role_assignment_group_owner(user: User, obj: Union[Group, Person])
def is_owner_of_any_group(user: User, obj):
"""Predicate which checks if the person is group owner of any group."""
return Group.objects.filter(owners=user.person).exists()
@predicate
def has_any_instruction(user: User, obj):
"""Predicate which checks if the user has any instruction."""
return Instruction.objects.filter(
Q(groups__members=user.person) | Q(groups__owners=user.person) | Q(groups__isnull=False)
).exists()
@predicate
def is_instruction_for_person(user: User, obj: Instruction):
"""Predicate which checks if the instruction is for the person."""
if not obj.groups.all():
return True
return (
user
in Person.objects.filter(
Q(member_of__instructions=obj) | Q(owner_of__instructions=obj)
).exists()
)
from collections import OrderedDict
from contextlib import nullcontext
from copy import deepcopy
from datetime import date, datetime, timedelta
......@@ -21,6 +22,7 @@ from django.views.generic import DetailView
import reversion
from calendarweek import CalendarWeek
from django_filters.views import FilterView
from django_tables2 import RequestConfig, SingleTableView
from guardian.core import ObjectPermissionChecker
from guardian.shortcuts import get_objects_for_user
......@@ -43,7 +45,7 @@ from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_op
from aleksis.core.util.pdf import render_pdf
from aleksis.core.util.predicates import check_global_permission
from .filters import PersonalNoteFilter
from .filters import InstructionFilter, PersonalNoteFilter
from .forms import (
AssignGroupRoleForm,
ExcuseTypeForm,
......@@ -51,6 +53,7 @@ from .forms import (
FilterRegisterObjectForm,
GroupRoleAssignmentEditForm,
GroupRoleForm,
InstructionForm,
LessonDocumentationForm,
PersonalNoteFormSet,
PersonOverviewForm,
......@@ -63,6 +66,7 @@ from .models import (
ExtraMark,
GroupRole,
GroupRoleAssignment,
Instruction,
LessonDocumentation,
PersonalNote,
)
......@@ -218,11 +222,17 @@ def register_object(
lesson_documentation = register_object.get_or_create_lesson_documentation(wanted_week)
context["has_documentation"] = bool(lesson_documentation.topic)
instructions = Instruction.objects.on_day(date_of_lesson).filter(
Q(groups__in=groups) | Q(groups__isnull=True)
)
context["instructions"] = instructions
lesson_documentation_form = LessonDocumentationForm(
request.POST or None,
instance=lesson_documentation,
prefix="lesson_documentation",
)
lesson_documentation_form.fields["done_instructions"].queryset = instructions
# Prefetch object permissions for all related groups of the register object
# because the object permissions are checked for all groups of the register object
......@@ -240,8 +250,10 @@ def register_object(
else:
persons = Person.objects.all()
persons_qs = register_object.get_personal_notes(persons, wanted_week).filter(
person__member_of__in=request.user.person.owner_of.all()
persons_qs = (
register_object.get_personal_notes(persons, wanted_week)
.filter(person__member_of__in=request.user.person.owner_of.all())
.distinct()
)
# Annotate group roles
......@@ -476,12 +488,16 @@ def week_view(
if not request.user.has_perm("alsijil.view_week_personalnote_rule", instance):
persons_qs = persons_qs.filter(pk=request.user.person.pk)
elif group:
persons_qs = persons_qs.filter(member_of=group).filter(
member_of__in=request.user.person.owner_of.all()
persons_qs = (
persons_qs.filter(member_of=group)
.filter(member_of__in=request.user.person.owner_of.all())
.distinct()
)
else:
persons_qs = persons_qs.filter(member_of__in=groups).filter(
member_of__in=request.user.person.owner_of.all()
persons_qs = (
persons_qs.filter(member_of__in=groups)
.filter(member_of__in=request.user.person.owner_of.all())
.distinct()
)
# Prefetch object permissions for persons and groups the persons are members of
......@@ -792,12 +808,16 @@ def my_students(request: HttpRequest) -> HttpResponse:
new_groups = []
for group in relevant_groups:
persons = group.generate_person_list_with_class_register_statistics(
group.members.prefetch_related(
"primary_group__owners",
Prefetch("member_of", queryset=relevant_groups, to_attr="member_of_prefetched"),
persons = (
group.generate_person_list_with_class_register_statistics(
group.members.prefetch_related(
"primary_group__owners",
Prefetch("member_of", queryset=relevant_groups, to_attr="member_of_prefetched"),
)
)
).filter(member_of__in=request.user.person.owner_of.all())
.filter(member_of__in=request.user.person.owner_of.all())
.distinct()
)
persons_for_group = []
for person in persons:
person.set_object_permission_checker(checker)
......@@ -830,10 +850,10 @@ class StudentsList(PermissionRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["group"] = self.object
context[
"persons"
] = self.object.generate_person_list_with_class_register_statistics().filter(
member_of__in=self.request.user.person.owner_of.all()
context["persons"] = (
self.object.generate_person_list_with_class_register_statistics()
.filter(member_of__in=self.request.user.person.owner_of.all())
.distinct()
)
context["extra_marks"] = ExtraMark.objects.all()
context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
......@@ -1441,3 +1461,116 @@ class AllRegisterObjectsView(PermissionRequiredMixin, View):
if self.action_form.is_valid():
self.action_form.execute()
return render(request, "alsijil/class_register/all_objects.html", context)
class InstructionsListView(PermissionRequiredMixin, FilterView):
"""Table of all instructions."""
model = Instruction
permission_required = "alsijil.view_instructions_rule"
template_name = "alsijil/instruction/list.html"
filterset_class = InstructionFilter
def get_queryset(self):
if self.request.user.has_perm("alsijil.view_instruction"):
return super().get_queryset()
return (
super()
.get_queryset()
.filter(
Q(groups__members=self.request.user.person)
| Q(groups__owners=self.request.user.person)
)
.distinct()
)
def get_context_data(self, **kwargs) -> dict:
context = super().get_context_data(**kwargs)
if not self.request.user.has_perm("alsijil.view_done_instructions_rule"):
return context
qs = self.object_list
docs = LessonDocumentation.objects.filter(done_instructions__in=qs)
done_instructions_by_group = {}
for doc in docs:
groups = doc.register_object.get_groups().all()
for group in groups:
parent_groups = group.parent_groups_recursive
group_members = set(group.members.all())
selected_groups = list(parent_groups) + [group]
match_found = False
for selected_group in selected_groups:
if selected_group == group and match_found:
# Use original group only if no matching parent group has been found
continue
# Use parent groups if the members are the same
if set(selected_group.members.all()) == group_members:
match_found = True
done_instructions_by_group.setdefault(
selected_group, dict(docs={}, instructions=set())
)
instructions = list(doc.done_instructions.all())
for instruction in instructions:
done_instructions_by_group[selected_group]["docs"].setdefault(
instruction, set()
)
done_instructions_by_group[selected_group]["docs"][instruction].add(doc)
done_instructions_by_group[selected_group]["instructions"].update(
instructions
)
# Sort done instructions by group name
done_instructions_by_group = OrderedDict(
sorted(done_instructions_by_group.items(), key=lambda x: x[0].short_name or x[0].name)
)
# Build table with instructions on x-axis and groups on y-axis
table_by_groups = OrderedDict()
for group, el in done_instructions_by_group.items():
done_instructions = el["instructions"]
table_by_groups.setdefault(group, OrderedDict())
for instruction in qs:
docs = el["docs"].get(instruction, set())
table_by_groups[group][instruction] = (instruction in done_instructions, docs)
context["done_instructions"] = table_by_groups
return context
@method_decorator(never_cache, name="dispatch")
class InstructionCreateView(PermissionRequiredMixin, AdvancedCreateView):
"""Create view for instructions."""
model = Instruction
form_class = InstructionForm
permission_required = "alsijil.add_instruction_rule"
template_name = "alsijil/instruction/create.html"
success_url = reverse_lazy("instructions")
success_message = _("The instruction has been created.")
@method_decorator(never_cache, name="dispatch")
class InstructionEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for instructions."""
model = Instruction
form_class = InstructionForm
permission_required = "alsijil.edit_instruction_rule"
template_name = "alsijil/instruction/edit.html"
success_url = reverse_lazy("instructions")
success_message = _("The instruction has been saved.")
@method_decorator(never_cache, "dispatch")
class InstructionDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView):
"""Delete view for instructions."""
model = Instruction
permission_required = "alsijil.delete_instruction_rule"
template_name = "core/pages/delete.html"
success_url = reverse_lazy("instructions")
success_message = _("The instruction has been deleted.")