"""Views for Certificate Profile management."""
from __future__ import annotations
import contextlib
import json
from typing import TYPE_CHECKING, Any, ClassVar, cast
from django.contrib import messages
from django.db.models import ProtectedError, QuerySet
from django.forms import ValidationError
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic.edit import FormView, UpdateView
from django.views.generic.list import ListView
from django_filters.rest_framework import DjangoFilterBackend
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import filters, viewsets
from rest_framework.permissions import IsAuthenticated
from pki.forms import CertificateIssuanceForm, CertProfileConfigForm
from pki.models import CertificateProfileModel
from pki.serializer.cert_profile import CertProfileSerializer
from trustpoint.logger import LoggerMixin
from trustpoint.settings import UIConfig
from trustpoint.views.base import (
BulkDeleteView,
ContextDataMixin,
SortableTableMixin,
)
if TYPE_CHECKING:
from django.forms import Form
[docs]
class CertProfileContextMixin(ContextDataMixin):
"""Mixin which adds context_data for the PKI -> Cert Profiles pages."""
[docs]
context_page_category = 'pki'
[docs]
context_page_name = 'cert_profiles'
[docs]
class CertProfileTableView(CertProfileContextMixin,
SortableTableMixin[CertificateProfileModel],
ListView[CertificateProfileModel]):
"""Certificate Profile Table View."""
[docs]
model = CertificateProfileModel
[docs]
template_name = 'pki/cert_profiles/cert_profiles.html' # Template file
[docs]
context_object_name = 'cert_profiles'
[docs]
paginate_by = UIConfig.paginate_by # Number of items per page
[docs]
default_sort_param = 'unique_name'
[docs]
class CertProfileConfigView(LoggerMixin, CertProfileContextMixin,
UpdateView[CertificateProfileModel, CertProfileConfigForm]):
"""View to display the details of and edit a Certificate Profile."""
[docs]
http_method_names = ('get', 'post')
[docs]
model = CertificateProfileModel
[docs]
success_url = reverse_lazy('pki:cert_profiles')
[docs]
template_name = 'pki/cert_profiles/config.html'
[docs]
context_object_name = 'profile'
# this is an LSP violation as superclass cannot return None, but makes sense to not add a duplicate "add" view
[docs]
def get_object(self, _queryset: QuerySet[Any, Any] | None = None) -> CertificateProfileModel | None: # type: ignore[override]
"""Retrieve the CertificateProfileModel object based on the primary key in the URL."""
pk = self.kwargs.get('pk')
if pk:
return get_object_or_404(CertificateProfileModel, pk=pk)
return None # Add view case
[docs]
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
"""Add additional context data."""
context = super().get_context_data(**kwargs)
form = context['form']
raw_json = form['profile_json'].value()
if not form['unique_name'].value():
context['is_new'] = True
default_json = { # new profile default
'type': 'cert_profile',
'subj': {},
'ext': {},
}
context['json_valid'] = True
if not raw_json or raw_json == 'null':
context['profile_json'] = default_json
return context
cleaned_raw = raw_json.encode('utf-8').decode('unicode_escape')
if cleaned_raw.startswith('"') and cleaned_raw.endswith('"'):
cleaned_raw = cleaned_raw[1:-1]
with contextlib.suppress(json.JSONDecodeError):
context['profile_json'] = json.loads(cleaned_raw)
return context
with contextlib.suppress(json.JSONDecodeError):
context['profile_json'] = json.loads(raw_json)
return context
# Invalid JSON typed by the user - render as-is to revise
context['json_valid'] = False
context['profile_json'] = cleaned_raw
return context
[docs]
def get_initial(self) -> dict[str, Any]:
"""Initialize the form with default values."""
initial = super().get_initial()
initial['unique_name'] = self.object.unique_name if self.object else ''
return initial
[docs]
class CertProfileIssuanceView(LoggerMixin, CertProfileContextMixin,
FormView[CertificateIssuanceForm]):
"""View to display the issuance form for a Certificate Profile."""
[docs]
http_method_names = ('get', 'post')
[docs]
template_name = 'pki/cert_profiles/issuance.html'
[docs]
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
"""Dispatch the request, ensuring the profile exists."""
self.profile = get_object_or_404(CertificateProfileModel, pk=kwargs['pk'])
return cast('HttpResponse', super().dispatch(request, *args, **kwargs))
[docs]
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
"""Add additional context data."""
context = super().get_context_data(**kwargs)
context['profile'] = self.profile
context['profile_dict'] = self.get_form_kwargs()['profile']
return context
[docs]
class CertProfileBulkDeleteConfirmView(CertProfileContextMixin, BulkDeleteView):
"""View to confirm the deletion of multiple certificate profiles."""
[docs]
model = CertificateProfileModel
[docs]
success_url = reverse_lazy('pki:cert_profiles')
[docs]
ignore_url = reverse_lazy('pki:cert_profiles')
[docs]
template_name = 'pki/cert_profiles/confirm_delete.html'
[docs]
context_object_name = 'cert_profiles'
[docs]
queryset: QuerySet[CertificateProfileModel]
[docs]
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
"""Handle GET requests."""
queryset = self.get_queryset()
if not queryset.exists():
messages.error(request, _('No certificate profiles selected for deletion.'))
return HttpResponseRedirect(self.success_url)
return super().get(request, *args, **kwargs)
@extend_schema(tags=['Certificate Profile'])
@extend_schema_view(
retrieve=extend_schema(description='Retrieve a single certificate profile by id.'),
create=extend_schema(description='Create a certificate profile.'),
update=extend_schema(description='Update an existing certificate profile.'),
partial_update=extend_schema(description='Partially update an existing certificate profile.'),
destroy=extend_schema(description='Delete a certificate profile.')
)
[docs]
class CertProfileViewSet(viewsets.ModelViewSet[CertificateProfileModel]):
"""ViewSet for managing Certificate Profile instances.
Supports standard CRUD operations such as list, retrieve,
create, update, and delete.
"""
[docs]
queryset = CertificateProfileModel.objects.all().order_by('-created_at')
[docs]
serializer_class = CertProfileSerializer
[docs]
permission_classes = (IsAuthenticated,)
[docs]
filter_backends = (
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter
)
[docs]
filterset_fields: ClassVar = ['unique_name', 'created_at']
[docs]
search_fields: ClassVar = ['unique_name', 'display_name']
[docs]
ordering_fields: ClassVar = ['unique_name', 'created_at']