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)
class
LifecycleIterationSerializer(authentik.enterprise.api.EnterpriseRequiredMixin, authentik.core.api.utils.ModelSerializer):
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
def
get_object_verbose( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> str:
def
get_object_admin_url( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> str:
def
get_grace_period_end( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> datetime.datetime:
def
get_next_review_date( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> datetime.datetime:
def
get_user_can_review( self, iteration: authentik.enterprise.lifecycle.models.LifecycleIteration) -> bool:
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
model =
<class 'authentik.enterprise.lifecycle.models.LifecycleIteration'>
class
LifecycleIterationFilterSet(django_filters.rest_framework.filterset.FilterSet):
84class LifecycleIterationFilterSet(FilterSet): 85 user_is_reviewer = BooleanFilter(field_name="user_is_reviewer", lookup_expr="exact")
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
serializer_class =
<class 'LifecycleIterationSerializer'>
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)