diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue index dbb504c393b0dc03f43b647ca305f614fd7df1da..d203ad680dcae02fda4fc736e2099845c8f5ce3b 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/AbsenceCreationDialog.vue @@ -1,5 +1,5 @@ <template> - <mobile-fullscreen-dialog v-model="popup" persistent> + <mobile-fullscreen-dialog v-model="popup" persistent :close-button="false"> <template #activator="activator"> <fab-button color="secondary" @@ -167,7 +167,7 @@ export default { }, handleSave() { this.cancel(); - this.$toastSuccess("alsijil.coursebook.absences.success"); + this.$toastSuccess(this.$t("alsijil.coursebook.absences.success")); }, }, }; diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue index 02dda22073e775b30324db22ea358ff7e9924ba4..aa9176a8bbf4e3bb61c6641580e1eb5c5b96f980 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/absences/ManageStudentsDialog.vue @@ -2,7 +2,7 @@ import AbsenceReasonButtons from "aleksis.apps.kolego/components/AbsenceReasonButtons.vue"; import AbsenceReasonChip from "aleksis.apps.kolego/components/AbsenceReasonChip.vue"; import AbsenceReasonGroupSelect from "aleksis.apps.kolego/components/AbsenceReasonGroupSelect.vue"; -import CancelButton from "aleksis.core/components/generic/buttons/CancelButton.vue"; +import DialogCloseButton from "aleksis.core/components/generic/buttons/DialogCloseButton.vue"; import MobileFullscreenDialog from "aleksis.core/components/generic/dialogs/MobileFullscreenDialog.vue"; import updateParticipationMixin from "./updateParticipationMixin.js"; import deepSearchMixin from "aleksis.core/mixins/deepSearchMixin.js"; @@ -23,11 +23,11 @@ export default { AbsenceReasonGroupSelect, AbsenceReasonButtons, PersonalNotes, - CancelButton, LessonInformation, MobileFullscreenDialog, SlideIterator, TardinessField, + DialogCloseButton, }, mixins: [updateParticipationMixin, deepSearchMixin], data() { @@ -77,13 +77,17 @@ export default { v-bind="$attrs" v-on="$listeners" v-model="dialog" + :close-button="false" > <template #activator="activator"> <slot name="activator" v-bind="activator" /> </template> <template #title> - <lesson-information v-bind="documentationPartProps" :compact="false" /> + <div class="d-flex full-width"> + <lesson-information v-bind="documentationPartProps" :compact="false" /> + <dialog-close-button @click="dialog = false" class="ml-4" /> + </div> <v-scroll-x-transition leave-absolute> <v-text-field v-show="!isExpanded" @@ -100,15 +104,6 @@ export default { class="pt-4 full-width" /> </v-scroll-x-transition> - <v-scroll-x-transition> - <div v-show="selected.length > 0" class="full-width mt-4"> - <absence-reason-buttons - allow-empty - empty-value="present" - @input="handleMultipleAction" - /> - </div> - </v-scroll-x-transition> </template> <template #content> <slide-iterator @@ -212,11 +207,15 @@ export default { </template> <template #actions> - <cancel-button - @click="dialog = false" - i18n-key="actions.close" - v-show="$vuetify.breakpoint.mobile" - /> + <v-scroll-y-reverse-transition> + <div v-show="selected.length > 0" class="full-width"> + <absence-reason-buttons + allow-empty + empty-value="present" + @input="handleMultipleAction" + /> + </div> + </v-scroll-y-reverse-transition> </template> </mobile-fullscreen-dialog> </template> diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index b00d9277e507e284130340b84cbbca8c07597d1f..c47546e235ce26da6fef88fee0a2e0324d9df75f 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -2,8 +2,14 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from dynamic_preferences.preferences import Section -from dynamic_preferences.types import BooleanPreference, ChoicePreference, IntegerPreference - +from dynamic_preferences.types import ( + BooleanPreference, + ChoicePreference, + IntegerPreference, + ModelMultipleChoicePreference, +) + +from aleksis.core.models import GroupType from aleksis.core.registries import person_preferences_registry, site_preferences_registry alsijil = Section("alsijil", verbose_name=_("Class register")) @@ -181,3 +187,19 @@ class AllowEditFutureDocumentations(ChoicePreference): ), ) verbose_name = _("Set time range for which documentations may be edited") + + +@site_preferences_registry.register +class GroupTypesRegisterAbsence(ModelMultipleChoicePreference): + section = alsijil + name = "group_types_register_absence" + required = False + default = [] + model = GroupType + verbose_name = _( + "User is allowed to register absences for members " + "of groups the user is an owner of with these group types" + ) + help_text = _( + "If you leave it empty, all member of groups the user is an owner of will be shown." + ) diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index c1ffafbe856ffa64c91a17e3eaf062942debbe40..8b8e15e7b0250c76651144190ea4153b4e5717df 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -14,6 +14,8 @@ from .util.predicates import ( can_edit_documentation, can_edit_participation_status, can_edit_personal_note, + can_register_absence_for_at_least_one_group, + can_register_absence_for_person, can_view_any_documentation, can_view_documentation, can_view_participation_status, @@ -171,12 +173,12 @@ add_perm("alsijil.view_week_personalnote_rule", view_week_personal_notes_predica # Register absence view_register_absence_predicate = has_person & ( - is_person_group_owner | has_global_perm("alsijil.register_absence") + can_register_absence_for_at_least_one_group | has_global_perm("alsijil.register_absence") ) add_perm("alsijil.view_register_absence_rule", view_register_absence_predicate) register_absence_predicate = has_person & ( - is_group_owner + can_register_absence_for_person | has_global_perm("alsijil.register_absence") | has_object_perm("core.register_absence_person") | has_person_group_object_perm("core.register_absence_group") diff --git a/aleksis/apps/alsijil/schema/__init__.py b/aleksis/apps/alsijil/schema/__init__.py index 35c0201f92bcf66b46f8e25583ea36522c7775af..49bfbe7abb2b04515c30ccd931c4d9d847578caf 100644 --- a/aleksis/apps/alsijil/schema/__init__.py +++ b/aleksis/apps/alsijil/schema/__init__.py @@ -12,7 +12,7 @@ from aleksis.core.models import Group, Person from aleksis.core.schema.base import FilterOrderList from aleksis.core.schema.group import GroupType from aleksis.core.schema.person import PersonType -from aleksis.core.util.core_helpers import has_person +from aleksis.core.util.core_helpers import get_site_preferences, has_person from ..models import Documentation from .absences import ( @@ -177,7 +177,15 @@ class Query(graphene.ObjectType): @staticmethod def resolve_absence_creation_persons(root, info, **kwargs): if not info.context.user.has_perm("alsijil.register_absence"): - return Person.objects.filter(member_of__owners=info.context.user.person) + group_types = get_site_preferences()["alsijil__group_types_register_absence"] + if group_types: + return Person.objects.filter( + member_of__in=Group.objects.filter( + owners=info.context.user.person, group_type__in=group_types + ) + ) + else: + return Person.objects.filter(member_of__owners=info.context.user.person) return Person.objects.all() @staticmethod diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index e1d8f8a02760b846d35672ffddeeb73d161ece72..5966a7459bc8a32f7e7468f20de2e7da0044441e 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -103,6 +103,26 @@ def is_person_group_owner(user: User, obj) -> bool: return Group.objects.filter(owners=user.person).exists() +@predicate +def can_register_absence_for_at_least_one_group(user: User, obj) -> bool: + """Predicate for registering absence for at least one group.""" + group_types = get_site_preferences()["alsijil__group_types_register_absence"] + qs = Group.objects.filter(owners=user.person) + if not group_types: + return qs.exists() + return qs.filter(group_type__in=group_types).exists() + + +@predicate +def can_register_absence_for_person(user: User, obj: Person) -> bool: + """Predicate for registering absence for person.""" + group_types = get_site_preferences()["alsijil__group_types_register_absence"] + qs = obj.member_of.filter(owners=user.person) + if not group_types: + return qs.exists() + return qs.filter(group_type__in=group_types).exists() + + def use_prefetched(obj, attr): prefetched_attr = f"{attr}_prefetched" if hasattr(obj, prefetched_attr):