mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
fix(backend): repair remaining schema generation errors (#9453)
* Remove hardcoded currency enum from schema * Convert schema custom key enums to int to allow customized keys to validate * Convert stock status key enums to int to allow customizations to validate in schema * api version bump * fix remaining operationId errors * fix errors * fix another error * fix missing model * ensure we do not ignore warnings anymore * Restore enumerated help text for currencies * Remove commented block of old code * Restore custom key enumerated values to schema documentation * Restore status key enumeration to schema documentation * fix more enums * Add debug definitions for schema generation * fix schema generation for PluginRelationSerializer * add migrations * fix enum names for allauth schema duplications * bump api version --------- Co-authored-by: Joe Rogers <1337joe@gmail.com>
This commit is contained in:
parent
f87f4387ed
commit
0d6c47fcd5
1
.github/workflows/qc_checks.yaml
vendored
1
.github/workflows/qc_checks.yaml
vendored
@ -193,7 +193,6 @@ jobs:
|
|||||||
diff -u src/backend/InvenTree/schema.yml api.yaml && echo "no difference in API schema " || exit 2
|
diff -u src/backend/InvenTree/schema.yml api.yaml && echo "no difference in API schema " || exit 2
|
||||||
- name: Check schema - including warnings
|
- name: Check schema - including warnings
|
||||||
run: invoke dev.schema
|
run: invoke dev.schema
|
||||||
continue-on-error: true
|
|
||||||
- name: Extract version for publishing
|
- name: Extract version for publishing
|
||||||
id: version
|
id: version
|
||||||
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
|
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
|
||||||
|
22
.vscode/launch.json
vendored
22
.vscode/launch.json
vendored
@ -40,6 +40,28 @@
|
|||||||
"django": true,
|
"django": true,
|
||||||
"justMyCode": false
|
"justMyCode": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "InvenTree invoke schema",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/.venv/lib/python3.9/site-packages/invoke/__main__.py",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"args": [
|
||||||
|
"dev.schema","--ignore-warnings"
|
||||||
|
],
|
||||||
|
"justMyCode": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "schema generation",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/src/backend/InvenTree/manage.py",
|
||||||
|
"args": [
|
||||||
|
"schema",
|
||||||
|
"--file","src/frontend/schema.yml"
|
||||||
|
],
|
||||||
|
"justMyCode": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "InvenTree Frontend - Vite",
|
"name": "InvenTree Frontend - Vite",
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 333
|
INVENTREE_API_VERSION = 334
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v334 - 2025-04-08 : https://github.com/inventree/InvenTree/pull/9453
|
||||||
|
- Fixes various operationId and enum collisions and help texts
|
||||||
|
|
||||||
v333 - 2025-04-03 : https://github.com/inventree/InvenTree/pull/9452
|
v333 - 2025-04-03 : https://github.com/inventree/InvenTree/pull/9452
|
||||||
- Currency string is no longer restricted to a hardcoded enum
|
- Currency string is no longer restricted to a hardcoded enum
|
||||||
- Customizable status keys are no longer hardcoded enum values
|
- Customizable status keys are no longer hardcoded enum values
|
||||||
|
@ -10,16 +10,19 @@ from drf_spectacular.utils import _SchemaType
|
|||||||
class ExtendedAutoSchema(AutoSchema):
|
class ExtendedAutoSchema(AutoSchema):
|
||||||
"""Extend drf-spectacular to allow customizing the schema to match the actual API behavior."""
|
"""Extend drf-spectacular to allow customizing the schema to match the actual API behavior."""
|
||||||
|
|
||||||
def is_bulk_delete(self) -> bool:
|
def is_bulk_action(self, ref: str) -> bool:
|
||||||
"""Check the class of the current view for the BulkDeleteMixin."""
|
"""Check the class of the current view for the bulk mixins."""
|
||||||
return 'BulkDeleteMixin' in [c.__name__ for c in type(self.view).__mro__]
|
return ref in [c.__name__ for c in type(self.view).__mro__]
|
||||||
|
|
||||||
def get_operation_id(self) -> str:
|
def get_operation_id(self) -> str:
|
||||||
"""Custom path handling overrides, falling back to default behavior."""
|
"""Custom path handling overrides, falling back to default behavior."""
|
||||||
result_id = super().get_operation_id()
|
result_id = super().get_operation_id()
|
||||||
|
|
||||||
# rename bulk deletes to deconflict with single delete operation_id
|
# rename bulk actions to deconflict with single action operation_id
|
||||||
if self.method == 'DELETE' and self.is_bulk_delete():
|
if (self.method == 'DELETE' and self.is_bulk_action('BulkDeleteMixin')) or (
|
||||||
|
(self.method == 'PUT' or self.method == 'PATCH')
|
||||||
|
and self.is_bulk_action('BulkUpdateMixin')
|
||||||
|
):
|
||||||
action = self.method_mapping[self.method.lower()]
|
action = self.method_mapping[self.method.lower()]
|
||||||
result_id = result_id.replace(action, 'bulk_' + action)
|
result_id = result_id.replace(action, 'bulk_' + action)
|
||||||
|
|
||||||
@ -42,7 +45,7 @@ class ExtendedAutoSchema(AutoSchema):
|
|||||||
|
|
||||||
# drf-spectacular doesn't support a body on DELETE endpoints because the semantics are not well-defined and
|
# drf-spectacular doesn't support a body on DELETE endpoints because the semantics are not well-defined and
|
||||||
# OpenAPI recommends against it. This allows us to generate a schema that follows existing behavior.
|
# OpenAPI recommends against it. This allows us to generate a schema that follows existing behavior.
|
||||||
if self.method == 'DELETE' and self.is_bulk_delete():
|
if self.method == 'DELETE' and self.is_bulk_action('BulkDeleteMixin'):
|
||||||
original_method = self.method
|
original_method = self.method
|
||||||
self.method = 'PUT'
|
self.method = 'PUT'
|
||||||
request_body = self._get_request_body()
|
request_body = self._get_request_body()
|
||||||
|
@ -1435,6 +1435,15 @@ SPECTACULAR_SETTINGS = {
|
|||||||
'drf_spectacular.hooks.postprocess_schema_enums',
|
'drf_spectacular.hooks.postprocess_schema_enums',
|
||||||
'InvenTree.schema.postprocess_required_nullable',
|
'InvenTree.schema.postprocess_required_nullable',
|
||||||
],
|
],
|
||||||
|
'ENUM_NAME_OVERRIDES': {
|
||||||
|
'UserTypeEnum': 'users.models.UserProfile.UserType',
|
||||||
|
'TemplateModelTypeEnum': 'report.models.ReportTemplateBase.ModelChoices',
|
||||||
|
'AttachmentModelTypeEnum': 'common.models.Attachment.ModelChoices',
|
||||||
|
'DataImportSessionModelTypeEnum': 'importer.models.DataImportSession.ModelChoices',
|
||||||
|
# Allauth
|
||||||
|
'UnauthorizedStatus': [[401, 401]],
|
||||||
|
'IsTrueEnum': [[True, True]],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if SITE_URL and not TESTING:
|
if SITE_URL and not TESTING:
|
||||||
|
@ -699,7 +699,6 @@ class ContentTypeDetail(RetrieveAPI):
|
|||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(operation_id='contenttype_retrieve_model')
|
|
||||||
class ContentTypeModelDetail(ContentTypeDetail):
|
class ContentTypeModelDetail(ContentTypeDetail):
|
||||||
"""Detail view for a ContentType model."""
|
"""Detail view for a ContentType model."""
|
||||||
|
|
||||||
@ -714,6 +713,11 @@ class ContentTypeModelDetail(ContentTypeDetail):
|
|||||||
raise NotFound()
|
raise NotFound()
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
|
|
||||||
|
@extend_schema(operation_id='contenttype_retrieve_model')
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Detail view for a ContentType model."""
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AttachmentFilter(rest_filters.FilterSet):
|
class AttachmentFilter(rest_filters.FilterSet):
|
||||||
"""Filterset for the AttachmentList API endpoint."""
|
"""Filterset for the AttachmentList API endpoint."""
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.2.20 on 2025-04-07 20:53
|
||||||
|
|
||||||
|
import common.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("common", "0037_dataoutput"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="attachment",
|
||||||
|
name="model_type",
|
||||||
|
field=models.CharField(
|
||||||
|
help_text="Target model type for image",
|
||||||
|
max_length=100,
|
||||||
|
validators=[common.validators.validate_attachment_model_type],
|
||||||
|
verbose_name="Model type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -40,15 +40,11 @@ from djmoney.contrib.exchange.models import convert_money
|
|||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
import common.currency
|
|
||||||
import common.validators
|
import common.validators
|
||||||
import InvenTree.exceptions
|
|
||||||
import InvenTree.fields
|
import InvenTree.fields
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.models
|
import InvenTree.models
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
import InvenTree.tasks
|
|
||||||
import InvenTree.validators
|
|
||||||
import users.models
|
import users.models
|
||||||
from common.setting.type import InvenTreeSettingsKeyType, SettingsKeyType
|
from common.setting.type import InvenTreeSettingsKeyType, SettingsKeyType
|
||||||
from generic.states import ColorEnum
|
from generic.states import ColorEnum
|
||||||
@ -59,6 +55,24 @@ from InvenTree.sanitizer import sanitize_svg
|
|||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
|
|
||||||
|
class RenderMeta(models.enums.ChoicesMeta):
|
||||||
|
"""Metaclass for rendering choices."""
|
||||||
|
|
||||||
|
choice_fnc = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def choices(self):
|
||||||
|
"""Return a list of choices for the enum class."""
|
||||||
|
fnc = getattr(self, 'choice_fnc', None)
|
||||||
|
if fnc:
|
||||||
|
return fnc()
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class RenderChoices(models.TextChoices, metaclass=RenderMeta):
|
||||||
|
"""Class for creating enumerated string choices for schema rendering."""
|
||||||
|
|
||||||
|
|
||||||
class MetaMixin(models.Model):
|
class MetaMixin(models.Model):
|
||||||
"""A base class for InvenTree models to include shared meta fields.
|
"""A base class for InvenTree models to include shared meta fields.
|
||||||
|
|
||||||
@ -1811,6 +1825,11 @@ class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
|
|||||||
|
|
||||||
verbose_name = _('Attachment')
|
verbose_name = _('Attachment')
|
||||||
|
|
||||||
|
class ModelChoices(RenderChoices):
|
||||||
|
"""Model choices for attachments."""
|
||||||
|
|
||||||
|
choice_fnc = common.validators.attachment_model_options
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""Custom 'save' method for the Attachment model.
|
"""Custom 'save' method for the Attachment model.
|
||||||
|
|
||||||
@ -1859,7 +1878,8 @@ class Attachment(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
|
|||||||
model_type = models.CharField(
|
model_type = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
validators=[common.validators.validate_attachment_model_type],
|
validators=[common.validators.validate_attachment_model_type],
|
||||||
help_text=_('Target model type for this image'),
|
verbose_name=_('Model type'),
|
||||||
|
help_text=_('Target model type for image'),
|
||||||
)
|
)
|
||||||
|
|
||||||
model_id = models.PositiveIntegerField()
|
model_id = models.PositiveIntegerField()
|
||||||
|
@ -99,6 +99,7 @@ class AllStatusViews(StatusView):
|
|||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
serializer_class = EmptySerializer
|
serializer_class = EmptySerializer
|
||||||
|
|
||||||
|
@extend_schema(operation_id='generic_status_retrieve_all')
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Perform a GET request to learn information about status codes."""
|
"""Perform a GET request to learn information about status codes."""
|
||||||
from InvenTree.helpers import inheritors
|
from InvenTree.helpers import inheritors
|
||||||
|
@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions, serializers
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
@ -56,10 +56,19 @@ class DataImporterPermissionMixin:
|
|||||||
permission_classes = [permissions.IsAuthenticated, DataImporterPermission]
|
permission_classes = [permissions.IsAuthenticated, DataImporterPermission]
|
||||||
|
|
||||||
|
|
||||||
|
class DataImporterModelSerializer(serializers.Serializer):
|
||||||
|
"""Model references to map info that might get imported."""
|
||||||
|
|
||||||
|
serializer = serializers.CharField(read_only=True)
|
||||||
|
model_type = serializers.CharField(read_only=True)
|
||||||
|
api_url = serializers.URLField(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class DataImporterModelList(APIView):
|
class DataImporterModelList(APIView):
|
||||||
"""API endpoint for displaying a list of models available for import."""
|
"""API endpoint for displaying a list of models available for import."""
|
||||||
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
serializer_class = DataImporterModelSerializer(many=True)
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""Return a list of models available for import."""
|
"""Return a list of models available for import."""
|
||||||
@ -102,6 +111,7 @@ class DataImportSessionAcceptFields(APIView):
|
|||||||
"""API endpoint to accept the field mapping for a DataImportSession."""
|
"""API endpoint to accept the field mapping for a DataImportSession."""
|
||||||
|
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.IsAuthenticated]
|
||||||
|
serializer_class = None
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={200: importer.serializers.DataImportSessionSerializer(many=False)}
|
responses={200: importer.serializers.DataImportSessionSerializer(many=False)}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.2.20 on 2025-04-07 20:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import importer.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("importer", "0003_dataimportsession_field_filters"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dataimportsession",
|
||||||
|
name="model_type",
|
||||||
|
field=models.CharField(
|
||||||
|
help_text="Target model type for this import session",
|
||||||
|
max_length=100,
|
||||||
|
validators=[importer.validators.validate_importer_model_type],
|
||||||
|
verbose_name="Model Type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -18,6 +18,7 @@ import importer.registry
|
|||||||
import importer.tasks
|
import importer.tasks
|
||||||
import importer.validators
|
import importer.validators
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
|
from common.models import RenderChoices
|
||||||
from importer.status_codes import DataImportStatusCode
|
from importer.status_codes import DataImportStatusCode
|
||||||
|
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
@ -38,6 +39,11 @@ class DataImportSession(models.Model):
|
|||||||
field_filters: JSONField for field filter values - optional field API filters
|
field_filters: JSONField for field filter values - optional field API filters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class ModelChoices(RenderChoices):
|
||||||
|
"""Model choices for data import sessions."""
|
||||||
|
|
||||||
|
choice_fnc = importer.registry.supported_models
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
"""Return the API URL associated with the DataImportSession model."""
|
"""Return the API URL associated with the DataImportSession model."""
|
||||||
@ -77,6 +83,8 @@ class DataImportSession(models.Model):
|
|||||||
blank=False,
|
blank=False,
|
||||||
max_length=100,
|
max_length=100,
|
||||||
validators=[importer.validators.validate_importer_model_type],
|
validators=[importer.validators.validate_importer_model_type],
|
||||||
|
verbose_name=_('Model Type'),
|
||||||
|
help_text=_('Target model type for this import session'),
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.PositiveIntegerField(
|
status = models.PositiveIntegerField(
|
||||||
|
@ -292,6 +292,15 @@ class PluginSettingList(ListAPI):
|
|||||||
|
|
||||||
filterset_fields = ['plugin__active', 'plugin__key']
|
filterset_fields = ['plugin__active', 'plugin__key']
|
||||||
|
|
||||||
|
@extend_schema(operation_id='plugins_settings_list_all')
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""List endpoint for all plugin related settings.
|
||||||
|
|
||||||
|
- read only
|
||||||
|
- only accessible by staff users
|
||||||
|
"""
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def check_plugin(
|
def check_plugin(
|
||||||
plugin_slug: Optional[str], plugin_pk: Optional[int]
|
plugin_slug: Optional[str], plugin_pk: Optional[int]
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from common.serializers import GenericReferencedSettingSerializer
|
from common.serializers import GenericReferencedSettingSerializer
|
||||||
@ -307,6 +309,7 @@ class PluginRegistryStatusSerializer(serializers.Serializer):
|
|||||||
registry_errors = serializers.ListField(child=PluginRegistryErrorSerializer())
|
registry_errors = serializers.ListField(child=PluginRegistryErrorSerializer())
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
class PluginRelationSerializer(serializers.PrimaryKeyRelatedField):
|
class PluginRelationSerializer(serializers.PrimaryKeyRelatedField):
|
||||||
"""Serializer for a plugin field. Uses the 'slug' of the plugin as the lookup."""
|
"""Serializer for a plugin field. Uses the 'slug' of the plugin as the lookup."""
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 4.2.20 on 2025-04-07 20:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import report.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("report", "0029_remove_reportoutput_template_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="labeltemplate",
|
||||||
|
name="model_type",
|
||||||
|
field=models.CharField(
|
||||||
|
help_text="Target model type for template",
|
||||||
|
max_length=100,
|
||||||
|
validators=[report.validators.validate_report_model_type],
|
||||||
|
verbose_name="Model Type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reporttemplate",
|
||||||
|
name="model_type",
|
||||||
|
field=models.CharField(
|
||||||
|
help_text="Target model type for template",
|
||||||
|
max_length=100,
|
||||||
|
validators=[report.validators.validate_report_model_type],
|
||||||
|
verbose_name="Model Type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -27,7 +27,7 @@ import InvenTree.helpers
|
|||||||
import InvenTree.models
|
import InvenTree.models
|
||||||
import report.helpers
|
import report.helpers
|
||||||
import report.validators
|
import report.validators
|
||||||
from common.models import DataOutput
|
from common.models import DataOutput, RenderChoices
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
from InvenTree.helpers_model import get_base_url
|
from InvenTree.helpers_model import get_base_url
|
||||||
from InvenTree.models import MetadataMixin
|
from InvenTree.models import MetadataMixin
|
||||||
@ -179,6 +179,11 @@ class ReportContextExtension(TypedDict):
|
|||||||
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||||
"""Base class for reports, labels."""
|
"""Base class for reports, labels."""
|
||||||
|
|
||||||
|
class ModelChoices(RenderChoices):
|
||||||
|
"""Model choices for report templates."""
|
||||||
|
|
||||||
|
choice_fnc = report.helpers.report_model_options
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Metaclass options."""
|
"""Metaclass options."""
|
||||||
|
|
||||||
@ -269,6 +274,7 @@ class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
|||||||
model_type = models.CharField(
|
model_type = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
validators=[report.validators.validate_report_model_type],
|
validators=[report.validators.validate_report_model_type],
|
||||||
|
verbose_name=_('Model Type'),
|
||||||
help_text=_('Target model type for template'),
|
help_text=_('Target model type for template'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ import InvenTree.models
|
|||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
import InvenTree.tasks
|
import InvenTree.tasks
|
||||||
import report.mixins
|
import report.mixins
|
||||||
import report.models
|
|
||||||
import stock.tasks
|
import stock.tasks
|
||||||
from common.icons import validate_icon
|
from common.icons import validate_icon
|
||||||
from common.settings import get_global_setting
|
from common.settings import get_global_setting
|
||||||
|
@ -328,8 +328,8 @@ class TokenListView(TokenMixin, ListCreateAPI):
|
|||||||
'revoked',
|
'revoked',
|
||||||
'revoked',
|
'revoked',
|
||||||
]
|
]
|
||||||
|
|
||||||
filterset_fields = ['revoked', 'user']
|
filterset_fields = ['revoked', 'user']
|
||||||
|
queryset = ApiToken.objects.none()
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
"""Create token and show key to user."""
|
"""Create token and show key to user."""
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 4.2.20 on 2025-04-07 20:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0014_userprofile"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userprofile",
|
||||||
|
name="type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("bot", "Bot"),
|
||||||
|
("internal", "Internal"),
|
||||||
|
("external", "External"),
|
||||||
|
("guest", "Guest"),
|
||||||
|
],
|
||||||
|
default="internal",
|
||||||
|
help_text="Which type of user is this?",
|
||||||
|
max_length=10,
|
||||||
|
verbose_name="User Type",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1025,7 +1025,7 @@ class UserProfile(InvenTree.models.MetadataMixin):
|
|||||||
max_length=10,
|
max_length=10,
|
||||||
choices=UserType.choices,
|
choices=UserType.choices,
|
||||||
default=UserType.INTERNAL,
|
default=UserType.INTERNAL,
|
||||||
verbose_name=_('Type'),
|
verbose_name=_('User Type'),
|
||||||
help_text=_('Which type of user is this?'),
|
help_text=_('Which type of user is this?'),
|
||||||
)
|
)
|
||||||
organisation = models.CharField(
|
organisation = models.CharField(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user