authentik.enterprise.lifecycle.api.iterations

  1from datetime import datetime
  2
  3from django.db.models import BooleanField as ModelBooleanField
  4from django.db.models import Case, Q, Value, When
  5from django_filters.rest_framework import BooleanFilter, FilterSet
  6from drf_spectacular.utils import extend_schema
  7from rest_framework.decorators import action
  8from rest_framework.fields import IntegerField, SerializerMethodField
  9from rest_framework.mixins import CreateModelMixin
 10from rest_framework.request import Request
 11from rest_framework.response import Response
 12from rest_framework.viewsets import GenericViewSet
 13
 14from authentik.core.api.utils import ModelSerializer
 15from authentik.enterprise.api import EnterpriseRequiredMixin
 16from authentik.enterprise.lifecycle.api.reviews import ReviewSerializer
 17from authentik.enterprise.lifecycle.models import LifecycleIteration, ReviewState
 18from authentik.enterprise.lifecycle.utils import (
 19    ContentTypeField,
 20    ReviewerGroupSerializer,
 21    ReviewerUserSerializer,
 22    admin_link_for_model,
 23    parse_content_type,
 24    start_of_day,
 25)
 26from authentik.lib.utils.time import timedelta_from_string
 27
 28
 29class LifecycleIterationSerializer(EnterpriseRequiredMixin, ModelSerializer):
 30    content_type = ContentTypeField()
 31    object_verbose = SerializerMethodField()
 32    object_admin_url = SerializerMethodField(read_only=True)
 33    grace_period_end = SerializerMethodField(read_only=True)
 34    reviews = ReviewSerializer(many=True, read_only=True, source="review_set.all")
 35    user_can_review = SerializerMethodField(read_only=True)
 36
 37    reviewer_groups = ReviewerGroupSerializer(
 38        many=True, read_only=True, source="rule.reviewer_groups"
 39    )
 40    min_reviewers = IntegerField(read_only=True, source="rule.min_reviewers")
 41    reviewers = ReviewerUserSerializer(many=True, read_only=True, source="rule.reviewers")
 42
 43    next_review_date = SerializerMethodField(read_only=True)
 44
 45    class Meta:
 46        model = LifecycleIteration
 47        fields = [
 48            "id",
 49            "content_type",
 50            "object_id",
 51            "object_verbose",
 52            "object_admin_url",
 53            "state",
 54            "opened_on",
 55            "grace_period_end",
 56            "next_review_date",
 57            "reviews",
 58            "user_can_review",
 59            "reviewer_groups",
 60            "min_reviewers",
 61            "reviewers",
 62        ]
 63        read_only_fields = fields
 64
 65    def get_object_verbose(self, iteration: LifecycleIteration) -> str:
 66        return str(iteration.object)
 67
 68    def get_object_admin_url(self, iteration: LifecycleIteration) -> str:
 69        return admin_link_for_model(iteration.object)
 70
 71    def get_grace_period_end(self, iteration: LifecycleIteration) -> datetime:
 72        return start_of_day(
 73            iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
 74        )
 75
 76    def get_next_review_date(self, iteration: LifecycleIteration) -> datetime:
 77        return start_of_day(iteration.opened_on + timedelta_from_string(iteration.rule.interval))
 78
 79    def get_user_can_review(self, iteration: LifecycleIteration) -> bool:
 80        return iteration.user_can_review(self.context["request"].user)
 81
 82
 83class LifecycleIterationFilterSet(FilterSet):
 84    user_is_reviewer = BooleanFilter(field_name="user_is_reviewer", lookup_expr="exact")
 85
 86
 87class IterationViewSet(EnterpriseRequiredMixin, CreateModelMixin, GenericViewSet):
 88    queryset = LifecycleIteration.objects.all()
 89    serializer_class = LifecycleIterationSerializer
 90    ordering = ["-opened_on"]
 91    ordering_fields = ["state", "content_type__model", "opened_on", "grace_period_end"]
 92    filterset_class = LifecycleIterationFilterSet
 93
 94    def get_queryset(self):
 95        user = self.request.user
 96        return self.queryset.annotate(
 97            user_is_reviewer=Case(
 98                When(
 99                    Q(rule__reviewers=user)
100                    | Q(rule__reviewer_groups__in=user.groups.all().with_ancestors()),
101                    then=Value(True),
102                ),
103                default=Value(False),
104                output_field=ModelBooleanField(),
105            )
106        ).distinct()
107
108    @action(
109        detail=False,
110        methods=["get"],
111        url_path=r"latest/(?P<content_type>[^/]+)/(?P<object_id>[^/]+)",
112    )
113    def latest_iteration(self, request: Request, content_type: str, object_id: str) -> Response:
114        ct = parse_content_type(content_type)
115        try:
116            obj = (
117                self.get_queryset()
118                .filter(
119                    content_type__app_label=ct["app_label"],
120                    content_type__model=ct["model"],
121                    object_id=object_id,
122                )
123                .latest("opened_on")
124            )
125        except LifecycleIteration.DoesNotExist:
126            return Response(status=404)
127        serializer = self.get_serializer(obj)
128        return Response(serializer.data)
129
130    @extend_schema(
131        operation_id="lifecycle_iterations_list_open",
132        responses={200: LifecycleIterationSerializer(many=True)},
133    )
134    @action(
135        detail=False,
136        methods=["get"],
137        url_path=r"open",
138    )
139    def open_iterations(self, request: Request):
140        iterations = self.get_queryset().filter(
141            Q(state=ReviewState.PENDING) | Q(state=ReviewState.OVERDUE)
142        )
143        iterations = self.filter_queryset(iterations)
144        page = self.paginate_queryset(iterations)
145        if page is not None:
146            serializer = self.get_serializer(page, many=True)
147            return self.get_paginated_response(serializer.data)
148
149        serializer = self.get_serializer(iterations, many=True)
150        return Response(serializer.data)
30class LifecycleIterationSerializer(EnterpriseRequiredMixin, ModelSerializer):
31    content_type = ContentTypeField()
32    object_verbose = SerializerMethodField()
33    object_admin_url = SerializerMethodField(read_only=True)
34    grace_period_end = SerializerMethodField(read_only=True)
35    reviews = ReviewSerializer(many=True, read_only=True, source="review_set.all")
36    user_can_review = SerializerMethodField(read_only=True)
37
38    reviewer_groups = ReviewerGroupSerializer(
39        many=True, read_only=True, source="rule.reviewer_groups"
40    )
41    min_reviewers = IntegerField(read_only=True, source="rule.min_reviewers")
42    reviewers = ReviewerUserSerializer(many=True, read_only=True, source="rule.reviewers")
43
44    next_review_date = SerializerMethodField(read_only=True)
45
46    class Meta:
47        model = LifecycleIteration
48        fields = [
49            "id",
50            "content_type",
51            "object_id",
52            "object_verbose",
53            "object_admin_url",
54            "state",
55            "opened_on",
56            "grace_period_end",
57            "next_review_date",
58            "reviews",
59            "user_can_review",
60            "reviewer_groups",
61            "min_reviewers",
62            "reviewers",
63        ]
64        read_only_fields = fields
65
66    def get_object_verbose(self, iteration: LifecycleIteration) -> str:
67        return str(iteration.object)
68
69    def get_object_admin_url(self, iteration: LifecycleIteration) -> str:
70        return admin_link_for_model(iteration.object)
71
72    def get_grace_period_end(self, iteration: LifecycleIteration) -> datetime:
73        return start_of_day(
74            iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
75        )
76
77    def get_next_review_date(self, iteration: LifecycleIteration) -> datetime:
78        return start_of_day(iteration.opened_on + timedelta_from_string(iteration.rule.interval))
79
80    def get_user_can_review(self, iteration: LifecycleIteration) -> bool:
81        return iteration.user_can_review(self.context["request"].user)

Mixin to validate that a valid enterprise license exists before allowing to save the object

content_type
object_verbose
object_admin_url
grace_period_end
reviews
user_can_review
reviewer_groups
min_reviewers
reviewers
next_review_date
def get_object_verbose( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> str:
66    def get_object_verbose(self, iteration: LifecycleIteration) -> str:
67        return str(iteration.object)
def get_object_admin_url( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> str:
69    def get_object_admin_url(self, iteration: LifecycleIteration) -> str:
70        return admin_link_for_model(iteration.object)
def get_grace_period_end( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> datetime.datetime:
72    def get_grace_period_end(self, iteration: LifecycleIteration) -> datetime:
73        return start_of_day(
74            iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
75        )
def get_next_review_date( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> datetime.datetime:
77    def get_next_review_date(self, iteration: LifecycleIteration) -> datetime:
78        return start_of_day(iteration.opened_on + timedelta_from_string(iteration.rule.interval))
def get_user_can_review( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> bool:
80    def get_user_can_review(self, iteration: LifecycleIteration) -> bool:
81        return iteration.user_can_review(self.context["request"].user)
class LifecycleIterationSerializer.Meta:
46    class Meta:
47        model = LifecycleIteration
48        fields = [
49            "id",
50            "content_type",
51            "object_id",
52            "object_verbose",
53            "object_admin_url",
54            "state",
55            "opened_on",
56            "grace_period_end",
57            "next_review_date",
58            "reviews",
59            "user_can_review",
60            "reviewer_groups",
61            "min_reviewers",
62            "reviewers",
63        ]
64        read_only_fields = fields
fields = ['id', 'content_type', 'object_id', 'object_verbose', 'object_admin_url', 'state', 'opened_on', 'grace_period_end', 'next_review_date', 'reviews', 'user_can_review', 'reviewer_groups', 'min_reviewers', 'reviewers']
read_only_fields = ['id', 'content_type', 'object_id', 'object_verbose', 'object_admin_url', 'state', 'opened_on', 'grace_period_end', 'next_review_date', 'reviews', 'user_can_review', 'reviewer_groups', 'min_reviewers', 'reviewers']
class LifecycleIterationFilterSet(django_filters.rest_framework.filterset.FilterSet):
84class LifecycleIterationFilterSet(FilterSet):
85    user_is_reviewer = BooleanFilter(field_name="user_is_reviewer", lookup_expr="exact")
user_is_reviewer
declared_filters = OrderedDict({'user_is_reviewer': <django_filters.rest_framework.filters.BooleanFilter object>})
base_filters = OrderedDict({'user_is_reviewer': <django_filters.rest_framework.filters.BooleanFilter object>})
class IterationViewSet(authentik.enterprise.api.EnterpriseRequiredMixin, rest_framework.mixins.CreateModelMixin, rest_framework.viewsets.GenericViewSet):
 88class IterationViewSet(EnterpriseRequiredMixin, CreateModelMixin, GenericViewSet):
 89    queryset = LifecycleIteration.objects.all()
 90    serializer_class = LifecycleIterationSerializer
 91    ordering = ["-opened_on"]
 92    ordering_fields = ["state", "content_type__model", "opened_on", "grace_period_end"]
 93    filterset_class = LifecycleIterationFilterSet
 94
 95    def get_queryset(self):
 96        user = self.request.user
 97        return self.queryset.annotate(
 98            user_is_reviewer=Case(
 99                When(
100                    Q(rule__reviewers=user)
101                    | Q(rule__reviewer_groups__in=user.groups.all().with_ancestors()),
102                    then=Value(True),
103                ),
104                default=Value(False),
105                output_field=ModelBooleanField(),
106            )
107        ).distinct()
108
109    @action(
110        detail=False,
111        methods=["get"],
112        url_path=r"latest/(?P<content_type>[^/]+)/(?P<object_id>[^/]+)",
113    )
114    def latest_iteration(self, request: Request, content_type: str, object_id: str) -> Response:
115        ct = parse_content_type(content_type)
116        try:
117            obj = (
118                self.get_queryset()
119                .filter(
120                    content_type__app_label=ct["app_label"],
121                    content_type__model=ct["model"],
122                    object_id=object_id,
123                )
124                .latest("opened_on")
125            )
126        except LifecycleIteration.DoesNotExist:
127            return Response(status=404)
128        serializer = self.get_serializer(obj)
129        return Response(serializer.data)
130
131    @extend_schema(
132        operation_id="lifecycle_iterations_list_open",
133        responses={200: LifecycleIterationSerializer(many=True)},
134    )
135    @action(
136        detail=False,
137        methods=["get"],
138        url_path=r"open",
139    )
140    def open_iterations(self, request: Request):
141        iterations = self.get_queryset().filter(
142            Q(state=ReviewState.PENDING) | Q(state=ReviewState.OVERDUE)
143        )
144        iterations = self.filter_queryset(iterations)
145        page = self.paginate_queryset(iterations)
146        if page is not None:
147            serializer = self.get_serializer(page, many=True)
148            return self.get_paginated_response(serializer.data)
149
150        serializer = self.get_serializer(iterations, many=True)
151        return Response(serializer.data)

Mixin to validate that a valid enterprise license exists before allowing to save the object

queryset = <QuerySet []>
serializer_class = <class 'LifecycleIterationSerializer'>
ordering = ['-opened_on']
ordering_fields = ['state', 'content_type__model', 'opened_on', 'grace_period_end']
filterset_class = <class 'LifecycleIterationFilterSet'>
def get_queryset(self):
 95    def get_queryset(self):
 96        user = self.request.user
 97        return self.queryset.annotate(
 98            user_is_reviewer=Case(
 99                When(
100                    Q(rule__reviewers=user)
101                    | Q(rule__reviewer_groups__in=user.groups.all().with_ancestors()),
102                    then=Value(True),
103                ),
104                default=Value(False),
105                output_field=ModelBooleanField(),
106            )
107        ).distinct()

Get the list of items for this view. This must be an iterable, and may be a queryset. Defaults to using self.queryset.

This method should always be used rather than accessing self.queryset directly, as self.queryset gets evaluated only once, and those results are cached for all subsequent requests.

You may want to override this if you need to provide different querysets depending on the incoming request.

(Eg. return a list of items that is specific to the user)

@action(detail=False, methods=['get'], url_path='latest/(?P<content_type>[^/]+)/(?P<object_id>[^/]+)')
def latest_iteration( self, request: rest_framework.request.Request, content_type: str, object_id: str) -> rest_framework.response.Response:
109    @action(
110        detail=False,
111        methods=["get"],
112        url_path=r"latest/(?P<content_type>[^/]+)/(?P<object_id>[^/]+)",
113    )
114    def latest_iteration(self, request: Request, content_type: str, object_id: str) -> Response:
115        ct = parse_content_type(content_type)
116        try:
117            obj = (
118                self.get_queryset()
119                .filter(
120                    content_type__app_label=ct["app_label"],
121                    content_type__model=ct["model"],
122                    object_id=object_id,
123                )
124                .latest("opened_on")
125            )
126        except LifecycleIteration.DoesNotExist:
127            return Response(status=404)
128        serializer = self.get_serializer(obj)
129        return Response(serializer.data)
@extend_schema(operation_id='lifecycle_iterations_list_open', responses={200: LifecycleIterationSerializer(many=True)})
@action(detail=False, methods=['get'], url_path='open')
def open_iterations(self, request: rest_framework.request.Request):
131    @extend_schema(
132        operation_id="lifecycle_iterations_list_open",
133        responses={200: LifecycleIterationSerializer(many=True)},
134    )
135    @action(
136        detail=False,
137        methods=["get"],
138        url_path=r"open",
139    )
140    def open_iterations(self, request: Request):
141        iterations = self.get_queryset().filter(
142            Q(state=ReviewState.PENDING) | Q(state=ReviewState.OVERDUE)
143        )
144        iterations = self.filter_queryset(iterations)
145        page = self.paginate_queryset(iterations)
146        if page is not None:
147            serializer = self.get_serializer(page, many=True)
148            return self.get_paginated_response(serializer.data)
149
150        serializer = self.get_serializer(iterations, many=True)
151        return Response(serializer.data)
name = None
description = None
suffix = None
detail = None
basename = None