- Feb 12, 2025
-
-
Jonathan Weth authored
Resolve "Adapt to core changes" Closes #42 See merge request !73
-
Jonathan Weth authored
Resolve Unprotected Absence Reasons queries See merge request !74
-
permcu authored
-
permcu authored
This broke permission-checking.
-
- Feb 05, 2025
-
-
magicfelix authored
-
- Jan 13, 2025
-
-
Jonathan Weth authored
Resolve "Ensure that operation names are set for all queries and mutations" Closes #40 See merge request !67
-
- Jan 11, 2025
-
-
magicfelix authored
-
- Jan 08, 2025
-
-
Jonathan Weth authored
Resolve "Add mechanism to clear all absences of person in given time span" Closes #39 See merge request !66
-
- Jan 07, 2025
-
-
Hangzhi Yu authored
-
- Dec 04, 2024
-
-
Jonathan Weth authored
Update copyright and meta files See merge request !58
-
Jonathan Weth authored
-
- Dec 01, 2024
-
-
Jonathan Weth authored
Fix permissions See merge request !57
-
Jonathan Weth authored
-
Jonathan Weth authored
Fix predicates See merge request !56
-
Jonathan Weth authored
-
Jonathan Weth authored
Resolve "Add widget to persons page with (future) absences" Closes #33 See merge request !54
-
Jonathan Weth authored
-
Jonathan Weth authored
diff --git a/aleksis/apps/kolego/frontend/components/widgets/PlannedAbsencesForPersonWidget.vue b/aleksis/apps/kolego/frontend/components/widgets/PlannedAbsencesForPersonWidget.vue new file mode 100644 index 0000000..14fd1d9 --- /dev/null +++ b/aleksis/apps/kolego/frontend/components/widgets/PlannedAbsencesForPersonWidget.vue @@ -0,0 +1,97 @@ +<template> + <v-card> + <v-card-title> + {{ $t("kolego.widgets.planned_absences.title") }} + </v-card-title> + <c-r-u-d-iterator + i18n-key="alsijil.coursebook.statistics" + :gql-query="gqlQuery" + :gql-additional-query-args="gqlQueryArgs" + :enable-create="false" + :enable-edit="false" + :enable-search="false" + :items-per-page="-1" + :elevated="false" + > + <template #default="{ items }"> + <v-list> + <v-list-item v-for="item in items" :key="item.id"> + <v-list-item-content> + <v-list-item-title> + <template + v-if=" + $parseISODate(item.datetimeStart).hasSame( + $parseISODate(item.datetimeEnd), + 'day', + ) + " + > + <time :datetime="item.datetimeStart" class="text-no-wrap"> + {{ $d($parseISODate(item.datetimeStart), "short") }}, + </time> + + <time :datetime="item.datetimeStart" class="text-no-wrap"> + {{ $d($parseISODate(item.datetimeStart), "shortTime") }} + </time> + <span>-</span> + <time :datetime="item.datetimeEnd" class="text-no-wrap"> + {{ $d($parseISODate(item.datetimeEnd), "shortTime") }} + </time> + </template> + <template v-else> + <time :datetime="item.datetimeStart" class="text-no-wrap"> + {{ $d($parseISODate(item.datetimeStart), "shortDateTime") }} + </time> + <span>-</span> + <time :datetime="item.datetimeEnd" class="text-no-wrap"> + {{ $d($parseISODate(item.datetimeEnd), "shortDateTime") }} + </time> + </template> + + <absence-reason-chip + :absence-reason="item.reason" + class="float-right" + small + /> + </v-list-item-title> + <v-list-item-subtitle> + {{ item.comment }} + </v-list-item-subtitle> + </v-list-item-content> + <v-list-item-icon> + <v-btn icon color="red" + ><v-icon>mdi-delete-outline</v-icon></v-btn + > + </v-list-item-icon> + </v-list-item> + </v-list> + </template> + </c-r-u-d-iterator> + </v-card> +</template> + +<script> +import personOverviewCardMixin from "aleksis.core/mixins/personOverviewCardMixin.js"; +import { absences } from "./absences.graphql"; +import CRUDIterator from "aleksis.core/components/generic/CRUDIterator.vue"; +import AbsenceReasonChip from "../AbsenceReasonChip.vue"; +export default { + name: "PlannedAbsencesForPersonWidget", + mixins: [personOverviewCardMixin], + components: { CRUDIterator, AbsenceReasonChip }, + data() { + return { + gqlQuery: absences, + }; + }, + computed: { + gqlQueryArgs() { + return { + person: this.person.id, + }; + }, + }, +}; +</script> + +<style scoped></style> diff --git a/aleksis/apps/kolego/frontend/components/widgets/absences.graphql b/aleksis/apps/kolego/frontend/components/widgets/absences.graphql new file mode 100644 index 0000000..652648d --- /dev/null +++ b/aleksis/apps/kolego/frontend/components/widgets/absences.graphql @@ -0,0 +1,27 @@ +query absences($orderBy: [String], $filters: JSONString, $person: ID!) { + items: plannedAbsencesForPerson( + orderBy: $orderBy + filters: $filters + person: $person + ) { + id + person { + id + fullName + } + reason { + id + shortName + name + colour + default + } + comment + datetimeStart + datetimeEnd + dateStart + dateEnd + canEdit + canDelete + } +} diff --git a/aleksis/apps/kolego/frontend/index.js b/aleksis/apps/kolego/frontend/index.js index 260b3d9..aae17e7 100644 --- a/aleksis/apps/kolego/frontend/index.js +++ b/aleksis/apps/kolego/frontend/index.js @@ -1,3 +1,19 @@ +export const collectionItems = { + corePersonWidgets: [ + { + key: "core-person-widgets", + component: () => + import("./components/widgets/PlannedAbsencesForPersonWidget.vue"), + shouldDisplay: () => true, + colProps: { + cols: 12, + md: 6, + lg: 4, + }, + }, + ], +}; + export default { meta: { inMenu: true, diff --git a/aleksis/apps/kolego/preferences.py b/aleksis/apps/kolego/preferences.py new file mode 100644 index 0000000..b8748c0 --- /dev/null +++ b/aleksis/apps/kolego/preferences.py @@ -0,0 +1,24 @@ +from django.utils.translation import gettext_lazy as _ + +from dynamic_preferences.preferences import Section +from dynamic_preferences.types import ( + ModelMultipleChoicePreference, +) + +from aleksis.core.models import GroupType +from aleksis.core.registries import site_preferences_registry + +kolego = Section("kolego", verbose_name=_("Absences")) + + +@site_preferences_registry.register +class GroupTypesViewPersonAbsences(ModelMultipleChoicePreference): + section = kolego + name = "group_types_view_person_absences" + required = False + default = [] + model = GroupType + verbose_name = _( + "User is allowed to view (planned) absences for members " + "of groups the user is an owner of with these group types" + ) diff --git a/aleksis/apps/kolego/rules.py b/aleksis/apps/kolego/rules.py index 535b0f3..07cfd49 100644 --- a/aleksis/apps/kolego/rules.py +++ b/aleksis/apps/kolego/rules.py @@ -8,6 +8,8 @@ from aleksis.core.util.predicates import ( has_person, ) +from .util.predicates import can_view_absences_for_person + view_absences_predicate = has_person & ( has_global_perm("kolego.view_absence") | has_any_object("kolego.view_absence", Absence) ) @@ -89,3 +91,8 @@ view_menu_predicate = has_person & ( view_absences_predicate | view_absencereasons_predicate | view_absencereasontags_predicate ) rules.add_perm("kolego.view_menu_rule", view_menu_predicate) + +view_person_absences_predicate = has_person & ( + has_global_perm("kolego.view_absence") | can_view_absences_for_person +) +rules.add_perm("kolego.view_person_absences_rule", view_person_absences_predicate) diff --git a/aleksis/apps/kolego/schema/__init__.py b/aleksis/apps/kolego/schema/__init__.py index bf93f42..961c4a6 100644 --- a/aleksis/apps/kolego/schema/__init__.py +++ b/aleksis/apps/kolego/schema/__init__.py @@ -1,4 +1,5 @@ from django.apps import apps +from django.utils import timezone from django.db.models import QuerySet import graphene @@ -6,6 +7,7 @@ import graphene_django_optimizer from guardian.shortcuts import get_objects_for_user from aleksis.apps.kolego.models.absence import Absence, AbsenceReason, AbsenceReasonTag +from aleksis.core.models import Person from aleksis.core.schema.base import FilterOrderList from aleksis.core.util.core_helpers import filter_active_school_term_by_date @@ -28,6 +30,7 @@ from .absence import ( class Query(graphene.ObjectType): app_name = graphene.String() absences = FilterOrderList(AbsenceType) + planned_absences_for_person = FilterOrderList(AbsenceType, person=graphene.ID(required=True)) absence_reasons = FilterOrderList(AbsenceReasonType) absence_reason_tags = FilterOrderList(AbsenceReasonTagType) all_absence_reason_tags = FilterOrderList(AbsenceReasonTagType) @@ -47,6 +50,21 @@ class Query(graphene.ObjectType): info, ) + @staticmethod + def resolve_planned_absences_for_person(root, info, person: str, **kwargs): + person = Person.objects.get(pk=person) + if not info.context.user.has_perm("kolego.view_person_absences_rule", person): + return [] + return graphene_django_optimizer.query( + filter_active_school_term_by_date( + info.context, + Absence.objects.filter(person=person, datetime_end__gte=timezone.now()).order_by( + "datetime_start" + ), + ), + info, + ) + @staticmethod def resolve_absencereasons(root, info, **kwargs) -> QuerySet: if not info.context.user.has_perm("kolego.fetch_absencereasons_rule"): diff --git a/aleksis/apps/kolego/util/predicates.py b/aleksis/apps/kolego/util/predicates.py new file mode 100644 index 0000000..7db1702 --- /dev/null +++ b/aleksis/apps/kolego/util/predicates.py @@ -0,0 +1,16 @@ +from django.contrib.auth.models import User + +from rules import predicate + +from aleksis.core.models import Person +from aleksis.core.util.core_helpers import get_site_preferences + + +@predicate +def can_view_absences_for_person(user: User, obj: Person) -> bool: + """Predicate for viewing absences of a person.""" + group_types = get_site_preferences()["alsijil__group_types_view_person_absences"] + qs = obj.member_of.filter(owners=user.person) + if not group_types.exists(): + return False + return qs.filter(group_type__in=group_types).exists()
-
Jonathan Weth authored
Resolve "Only allow one absence at any point of time" Closes #32 See merge request !52
-
Jonathan Weth authored
-
Jonathan Weth authored
-
Hangzhi Yu authored
-
Hangzhi Yu authored
-
Hangzhi Yu authored
-
- Nov 30, 2024
-
-
Hangzhi Yu authored
-
Jonathan Weth authored
Fix return annotations and use staticmethod See merge request !55
-
Hangzhi Yu authored
-
Hangzhi Yu authored
-
Hangzhi Yu authored
-
magicfelix authored
-
- Nov 29, 2024
-
-
Hangzhi Yu authored
-
- Nov 27, 2024
-
-
Hangzhi Yu authored
-
Hangzhi Yu authored
-
- Nov 22, 2024
-
-
Jonathan Weth authored
Resolve "AbsenceReasonButtons avoid loading if data is there" Closes #34 See merge request !53
-
Jonathan Weth authored
Resolve "Adapt to active school term" Closes #31 See merge request !50
-
Julian authored
-
- Nov 21, 2024
-
-
magicfelix authored
-
- Nov 20, 2024
-
-
Jonathan Weth authored
Resolve "Enable creation and editing of AbsenceReason's count-as-absent-prop" Closes #27 See merge request !51
-
permcu authored
-
permcu authored
-