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