|
5 | 5 | from pathlib import Path |
6 | 6 |
|
7 | 7 | from django.conf import settings |
| 8 | +from django.contrib.contenttypes.models import ContentType |
8 | 9 | from django.db import transaction |
9 | 10 | from django.http import JsonResponse |
| 11 | +from django.urls import path, reverse |
10 | 12 | from django.utils.translation import gettext_lazy as _ |
| 13 | +from django.views.generic.base import RedirectView |
11 | 14 |
|
12 | 15 | import structlog |
13 | 16 | from django_q.models import OrmQ |
|
22 | 25 | import InvenTree.permissions |
23 | 26 | import InvenTree.version |
24 | 27 | from common.settings import get_global_setting |
25 | | -from InvenTree import helpers |
| 28 | +from InvenTree import helpers, ready |
26 | 29 | from InvenTree.auth_overrides import registration_enabled |
27 | 30 | from InvenTree.mixins import ListCreateAPI |
28 | 31 | from InvenTree.sso import sso_registration_enabled |
@@ -809,35 +812,122 @@ def post(self, request, *args, **kwargs): |
809 | 812 | return Response(results) |
810 | 813 |
|
811 | 814 |
|
812 | | -class MetadataView(RetrieveUpdateAPI): |
813 | | - """Generic API endpoint for reading and editing metadata for a model.""" |
| 815 | +class GenericMetadataView(RetrieveUpdateAPI): |
| 816 | + """Metadata for specific instance; see https://docs.inventree.org/en/stable/plugins/metadata/ for more detail on how metadata works. Most core models support metadata.""" |
814 | 817 |
|
815 | 818 | model = None # Placeholder for the model class |
| 819 | + serializer_class = MetadataSerializer |
| 820 | + permission_classes = [InvenTree.permissions.ContentTypePermission] |
816 | 821 |
|
817 | | - @classmethod |
818 | | - def as_view(cls, model, lookup_field=None, **initkwargs): |
819 | | - """Override to ensure model specific rendering.""" |
820 | | - if model is None: |
| 822 | + def get_permission_model(self): |
| 823 | + """Return the 'permission' model associated with this view.""" |
| 824 | + model_name = self.kwargs.get('model', None) |
| 825 | + |
| 826 | + if model_name is None: |
821 | 827 | raise ValidationError( |
822 | | - "MetadataView defined without 'model' arg" |
| 828 | + "GenericMetadataView called without 'model' URL parameter" |
823 | 829 | ) # pragma: no cover |
824 | | - initkwargs['model'] = model |
825 | 830 |
|
826 | | - # Set custom lookup field (instead of default 'pk' value) if supplied |
827 | | - if lookup_field: |
828 | | - initkwargs['lookup_field'] = lookup_field |
| 831 | + model = ContentType.objects.filter(model=model_name).first() |
829 | 832 |
|
830 | | - return super().as_view(**initkwargs) |
| 833 | + if model is None: |
| 834 | + raise ValidationError( |
| 835 | + f"GenericMetadataView called with invalid model '{model_name}'" |
| 836 | + ) # pragma: no cover |
831 | 837 |
|
832 | | - def get_permission_model(self): |
833 | | - """Return the 'permission' model associated with this view.""" |
834 | | - return self.model |
| 838 | + return model.model_class() |
835 | 839 |
|
836 | 840 | def get_queryset(self): |
837 | 841 | """Return the queryset for this endpoint.""" |
838 | | - return self.model.objects.all() |
| 842 | + model = self.get_permission_model() |
| 843 | + return model.objects.all() |
839 | 844 |
|
840 | 845 | def get_serializer(self, *args, **kwargs): |
841 | 846 | """Return MetadataSerializer instance.""" |
| 847 | + is_gen = ready.isGeneratingSchema() |
842 | 848 | # Detect if we are currently generating the OpenAPI schema |
| 849 | + if self.model is None and not is_gen: |
| 850 | + self.model = self.get_permission_model() |
| 851 | + if self.model is None and is_gen: |
| 852 | + # Provide a default model for schema generation |
| 853 | + import users.models |
| 854 | + |
| 855 | + self.model = users.models.User |
843 | 856 | return MetadataSerializer(self.model, *args, **kwargs) |
| 857 | + |
| 858 | + def dispatch(self, request, *args, **kwargs): |
| 859 | + """Override dispatch to set lookup field dynamically.""" |
| 860 | + self.lookup_field = self.kwargs.get('lookup_field', 'pk') |
| 861 | + self.lookup_url_kwarg = ( |
| 862 | + 'lookup_value' if 'lookup_field' in self.kwargs else 'pk' |
| 863 | + ) |
| 864 | + return super().dispatch(request, *args, **kwargs) |
| 865 | + |
| 866 | + |
| 867 | +class SimpleGenericMetadataView(GenericMetadataView): |
| 868 | + """Simplified version of GenericMetadataView which always uses 'pk' as the lookup field.""" |
| 869 | + |
| 870 | + def dispatch(self, request, *args, **kwargs): |
| 871 | + """Override dispatch to set lookup field to 'pk'.""" |
| 872 | + self.lookup_field = 'pk' |
| 873 | + self.lookup_url_kwarg = None |
| 874 | + return super().dispatch(request, *args, **kwargs) |
| 875 | + |
| 876 | + @extend_schema(operation_id='metadata_pk_retrieve') |
| 877 | + def get(self, request, *args, **kwargs): |
| 878 | + """Perform a GET request to retrieve metadata for the given object.""" |
| 879 | + return super().get(request, *args, **kwargs) |
| 880 | + |
| 881 | + @extend_schema(operation_id='metadata_pk_update') |
| 882 | + def put(self, request, *args, **kwargs): |
| 883 | + """Perform a PUT request to update metadata for the given object.""" |
| 884 | + return super().put(request, *args, **kwargs) |
| 885 | + |
| 886 | + @extend_schema(operation_id='metadata_pk_partial_update') |
| 887 | + def patch(self, request, *args, **kwargs): |
| 888 | + """Perform a PATCH request to partially update metadata for the given object.""" |
| 889 | + return super().patch(request, *args, **kwargs) |
| 890 | + |
| 891 | + |
| 892 | +class MetadataRedirectView(RedirectView): |
| 893 | + """Redirect to the generic metadata view for a given model.""" |
| 894 | + |
| 895 | + model_name = None # Placeholder for the model class |
| 896 | + lookup_field = 'pk' |
| 897 | + lookup_field_ref = 'pk' |
| 898 | + permanent = True |
| 899 | + |
| 900 | + def get_redirect_url(self, *args, **kwargs) -> str | None: |
| 901 | + """Return the redirect URL for this view.""" |
| 902 | + _kwargs = { |
| 903 | + 'model': self.model_name, |
| 904 | + 'lookup_value': self.kwargs.get(self.lookup_field_ref, None), |
| 905 | + 'lookup_field': self.lookup_field, |
| 906 | + } |
| 907 | + return reverse('api-generic-metadata', args=args, kwargs=_kwargs) |
| 908 | + |
| 909 | + |
| 910 | +def meta_path(model, lookup_field: str = 'pk', lookup_field_ref: str = 'pk'): |
| 911 | + """Helper function for constructing metadata path for a given model. |
| 912 | +
|
| 913 | + Arguments: |
| 914 | + model: The model class to use |
| 915 | + lookup_field: The lookup field to use (if not 'pk') |
| 916 | + lookup_field_ref: The reference name for the lookup field in the request(if not 'pk') |
| 917 | +
|
| 918 | + Returns: |
| 919 | + A path to the generic metadata view for the given model |
| 920 | + """ |
| 921 | + if model is None: |
| 922 | + raise ValidationError( |
| 923 | + "redirect_metadata_view called without 'model' arg" |
| 924 | + ) # pragma: no cover |
| 925 | + |
| 926 | + return path( |
| 927 | + 'metadata/', |
| 928 | + MetadataRedirectView.as_view( |
| 929 | + model_name=model._meta.model_name, |
| 930 | + lookup_field=lookup_field, |
| 931 | + lookup_field_ref=lookup_field_ref, |
| 932 | + ), |
| 933 | + ) |
0 commit comments