Skip to content
Snippets Groups Projects
  1. Dec 01, 2024
    • Jonathan Weth's avatar
      Finish widget for planned absences · f778196c
      Jonathan Weth authored
      Verified
      f778196c
    • Jonathan Weth's avatar
      Add first try of a planned absences widget · da41fc7e
      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()
      Verified
      da41fc7e
Loading