|
| 1 | +import django_filters |
| 2 | +from django_filters import rest_framework as filters |
| 3 | + |
| 4 | +from teamvault.apps.secrets.api.serializers import ( |
| 5 | + REPR_ACCESS_POLICY, |
| 6 | + REPR_CONTENT_TYPE, |
| 7 | + SECRET_REPR_STATUS, |
| 8 | +) |
| 9 | +from teamvault.apps.secrets.enums import SecretStatus |
| 10 | +from teamvault.apps.secrets.models import Secret |
| 11 | + |
| 12 | +# Deleted secrets are never returned by the list queryset, so they are not an |
| 13 | +# allowed filter value. |
| 14 | +SELECTABLE_STATUS = {repr_: value for repr_, value in SECRET_REPR_STATUS.items() if value != SecretStatus.DELETED} |
| 15 | + |
| 16 | + |
| 17 | +def _string_choice_filter(repr_to_value): |
| 18 | + """ChoiceFilter accepting the API's string reprs (e.g. ``needs_changing``) |
| 19 | + and coercing them to the underlying integer field value. |
| 20 | +
|
| 21 | + Invariant: ``choices`` and the coercion are both derived from |
| 22 | + ``repr_to_value``, so every value that passes choice-validation is |
| 23 | + guaranteed to be a key here. Callers MUST pass a single dict whose keys |
| 24 | + are exactly the valid API reprs — do not supply choices and coercion |
| 25 | + from different sources, or coercion can KeyError → HTTP 500. |
| 26 | + """ |
| 27 | + return django_filters.TypedChoiceFilter( |
| 28 | + choices=[(repr_, repr_) for repr_ in repr_to_value], |
| 29 | + coerce=lambda value: repr_to_value[value], |
| 30 | + ) |
| 31 | + |
| 32 | + |
| 33 | +class SecretListFilter(filters.FilterSet): |
| 34 | + access_policy = _string_choice_filter(REPR_ACCESS_POLICY) |
| 35 | + content_type = _string_choice_filter(REPR_CONTENT_TYPE) |
| 36 | + status = _string_choice_filter(SELECTABLE_STATUS) |
| 37 | + |
| 38 | + name = django_filters.CharFilter(lookup_expr='icontains') |
| 39 | + url = django_filters.CharFilter(lookup_expr='icontains') |
| 40 | + username = django_filters.CharFilter(lookup_expr='icontains') |
| 41 | + created_by = django_filters.CharFilter( |
| 42 | + field_name='created_by__username', |
| 43 | + lookup_expr='icontains', |
| 44 | + ) |
| 45 | + |
| 46 | + ordering = django_filters.OrderingFilter( |
| 47 | + fields=( |
| 48 | + ('last_read', 'last_read'), |
| 49 | + ('last_changed', 'last_changed'), |
| 50 | + ('created', 'created'), |
| 51 | + ('name', 'name'), |
| 52 | + ), |
| 53 | + ) |
| 54 | + |
| 55 | + @property |
| 56 | + def qs(self): |
| 57 | + parent = super().qs |
| 58 | + # Guarantee a deterministic total order so LIMIT/OFFSET pagination |
| 59 | + # is stable across requests even when the sort key has ties. |
| 60 | + return parent.order_by(*parent.query.order_by, 'pk') |
| 61 | + |
| 62 | + class Meta: |
| 63 | + model = Secret |
| 64 | + fields = ('needs_changing_on_leave',) |
0 commit comments