From cc2e7ee8a5dfc3df70b11a87fb68a846ef36e459 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 18 Feb 2023 18:51:00 +1100 Subject: [PATCH] Move Meta class to top of class definition (#4363) --- InvenTree/InvenTree/forms.py | 20 +- InvenTree/InvenTree/models.py | 20 +- InvenTree/build/admin.py | 22 +- InvenTree/build/models.py | 20 +- InvenTree/build/serializers.py | 151 +++++------ InvenTree/common/models.py | 30 +-- InvenTree/common/serializers.py | 62 ++--- InvenTree/company/admin.py | 42 +-- InvenTree/company/api.py | 18 +- InvenTree/company/models.py | 54 ++-- InvenTree/company/serializers.py | 250 ++++++++--------- InvenTree/label/serializers.py | 12 +- InvenTree/order/admin.py | 52 ++-- InvenTree/order/api.py | 18 +- InvenTree/order/models.py | 11 +- InvenTree/order/serializers.py | 407 ++++++++++++++-------------- InvenTree/part/admin.py | 94 +++---- InvenTree/part/forms.py | 14 +- InvenTree/part/models.py | 64 ++--- InvenTree/part/serializers.py | 450 +++++++++++++++---------------- InvenTree/plugin/serializers.py | 32 +-- InvenTree/report/models.py | 9 +- InvenTree/report/serializers.py | 17 +- InvenTree/stock/admin.py | 54 ++-- InvenTree/stock/serializers.py | 222 +++++++-------- InvenTree/users/models.py | 16 +- InvenTree/users/serializers.py | 8 +- 27 files changed, 1085 insertions(+), 1084 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 7078448d56..6c0a28faa4 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -126,6 +126,16 @@ class EditUserForm(HelperForm): class SetPasswordForm(HelperForm): """Form for setting user password.""" + class Meta: + """Metaclass options.""" + + model = User + fields = [ + 'enter_password', + 'confirm_password', + 'old_password', + ] + enter_password = forms.CharField( max_length=100, min_length=8, @@ -152,16 +162,6 @@ class SetPasswordForm(HelperForm): widget=forms.PasswordInput(attrs={'autocomplete': 'current-password', 'autofocus': True}), ) - class Meta: - """Metaclass options.""" - - model = User - fields = [ - 'enter_password', - 'confirm_password', - 'old_password', - ] - # override allauth class CustomSignupForm(SignupForm): diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 1d44776e8d..61adfe8e4b 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -116,6 +116,11 @@ class ReferenceIndexingMixin(models.Model): # Name of the global setting which defines the required reference pattern for this model REFERENCE_PATTERN_SETTING = None + class Meta: + """Metaclass options. Abstract ensures no database table is created.""" + + abstract = True + @classmethod def get_reference_pattern(cls): """Returns the reference pattern associated with this model. @@ -272,11 +277,6 @@ class ReferenceIndexingMixin(models.Model): # Check that the reference field can be rebuild cls.rebuild_reference_field(value, validate=True) - class Meta: - """Metaclass options. Abstract ensures no database table is created.""" - - abstract = True - @classmethod def rebuild_reference_field(cls, reference, validate=False): """Extract integer out of reference for sorting. @@ -369,6 +369,10 @@ class InvenTreeAttachment(models.Model): upload_date: Date the file was uploaded """ + class Meta: + """Metaclass options. Abstract ensures no database table is created.""" + abstract = True + def getSubdir(self): """Return the subdirectory under which attachments should be stored. @@ -483,11 +487,6 @@ class InvenTreeAttachment(models.Model): except Exception: raise ValidationError(_("Error renaming file")) - class Meta: - """Metaclass options. Abstract ensures no database table is created.""" - - abstract = True - class InvenTreeTree(MPTTModel): """Provides an abstracted self-referencing tree model for data categories. @@ -503,7 +502,6 @@ class InvenTreeTree(MPTTModel): class Meta: """Metaclass defines extra model properties.""" - abstract = True class MPTTMeta: diff --git a/InvenTree/build/admin.py b/InvenTree/build/admin.py index 6f203d071b..d15b9a0154 100644 --- a/InvenTree/build/admin.py +++ b/InvenTree/build/admin.py @@ -17,6 +17,17 @@ class BuildResource(InvenTreeResource): # but we don't for other ones. # TODO: 2022-05-12 - Need to investigate why this is the case! + class Meta: + """Metaclass options""" + models = Build + skip_unchanged = True + report_skipped = False + clean_model_instances = True + exclude = [ + 'lft', 'rght', 'tree_id', 'level', + 'metadata', + ] + id = Field(attribute='pk') reference = Field(attribute='reference') @@ -39,17 +50,6 @@ class BuildResource(InvenTreeResource): notes = Field(attribute='notes') - class Meta: - """Metaclass options""" - models = Build - skip_unchanged = True - report_skipped = False - clean_model_instances = True - exclude = [ - 'lft', 'rght', 'tree_id', 'level', - 'metadata', - ] - class BuildAdmin(ImportExportModelAdmin): """Class for managing the Build model via the admin interface""" diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 13b09ce000..091d656d9e 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -64,6 +64,11 @@ class Build(MPTTModel, ReferenceIndexingMixin): priority: Priority of the build """ + class Meta: + """Metaclass options for the BuildOrder model""" + verbose_name = _("Build Order") + verbose_name_plural = _("Build Orders") + OVERDUE_FILTER = Q(status__in=BuildStatus.ACTIVE_CODES) & ~Q(target_date=None) & Q(target_date__lte=datetime.now().date()) # Global setting for specifying reference pattern @@ -106,11 +111,6 @@ class Build(MPTTModel, ReferenceIndexingMixin): 'parent': _('Invalid choice for parent build'), }) - class Meta: - """Metaclass options for the BuildOrder model""" - verbose_name = _("Build Order") - verbose_name_plural = _("Build Orders") - @staticmethod def filterByDate(queryset, min_date, max_date): """Filter by 'minimum and maximum date range'. @@ -1153,17 +1153,17 @@ class BuildItem(models.Model): install_into: Destination stock item (or None) """ - @staticmethod - def get_api_url(): - """Return the API URL used to access this model""" - return reverse('api-build-item-list') - class Meta: """Serializer metaclass""" unique_together = [ ('build', 'stock_item', 'install_into'), ] + @staticmethod + def get_api_url(): + """Return the API URL used to access this model""" + return reverse('api-build-item-list') + def save(self, *args, **kwargs): """Custom save method for the BuildItem model""" self.clean() diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 78d3dc98ae..5bacad8845 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -30,7 +30,48 @@ from .models import Build, BuildItem, BuildOrderAttachment class BuildSerializer(InvenTreeModelSerializer): """Serializes a Build object.""" + class Meta: + """Serializer metaclass""" + model = Build + fields = [ + 'pk', + 'url', + 'title', + 'batch', + 'creation_date', + 'completed', + 'completion_date', + 'destination', + 'parent', + 'part', + 'part_detail', + 'overdue', + 'reference', + 'sales_order', + 'quantity', + 'status', + 'status_text', + 'target_date', + 'take_from', + 'notes', + 'link', + 'issued_by', + 'issued_by_detail', + 'responsible', + 'responsible_detail', + 'priority', + ] + + read_only_fields = [ + 'completed', + 'creation_date', + 'completion_data', + 'status', + 'status_text', + ] + url = serializers.CharField(source='get_absolute_url', read_only=True) + status_text = serializers.CharField(source='get_status_display', read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) @@ -83,46 +124,6 @@ class BuildSerializer(InvenTreeModelSerializer): return reference - class Meta: - """Serializer metaclass""" - model = Build - fields = [ - 'pk', - 'url', - 'title', - 'batch', - 'creation_date', - 'completed', - 'completion_date', - 'destination', - 'parent', - 'part', - 'part_detail', - 'overdue', - 'reference', - 'sales_order', - 'quantity', - 'status', - 'status_text', - 'target_date', - 'take_from', - 'notes', - 'link', - 'issued_by', - 'issued_by_detail', - 'responsible', - 'responsible_detail', - 'priority', - ] - - read_only_fields = [ - 'completed', - 'creation_date', - 'completion_data', - 'status', - 'status_text', - ] - class BuildOutputSerializer(serializers.Serializer): """Serializer for a "BuildOutput". @@ -130,6 +131,12 @@ class BuildOutputSerializer(serializers.Serializer): Note that a "BuildOutput" is really just a StockItem which is "in production"! """ + class Meta: + """Serializer metaclass""" + fields = [ + 'output', + ] + output = serializers.PrimaryKeyRelatedField( queryset=StockItem.objects.all(), many=False, @@ -170,12 +177,6 @@ class BuildOutputSerializer(serializers.Serializer): return output - class Meta: - """Serializer metaclass""" - fields = [ - 'output', - ] - class BuildOutputCreateSerializer(serializers.Serializer): """Serializer for creating a new BuildOutput against a BuildOrder. @@ -633,6 +634,15 @@ class BuildUnallocationSerializer(serializers.Serializer): class BuildAllocationItemSerializer(serializers.Serializer): """A serializer for allocating a single stock item against a build order.""" + class Meta: + """Serializer metaclass""" + fields = [ + 'bom_item', + 'stock_item', + 'quantity', + 'output', + ] + bom_item = serializers.PrimaryKeyRelatedField( queryset=BomItem.objects.all(), many=False, @@ -693,15 +703,6 @@ class BuildAllocationItemSerializer(serializers.Serializer): label=_('Build Output'), ) - class Meta: - """Serializer metaclass""" - fields = [ - 'bom_item', - 'stock_item', - 'quantity', - 'output', - ] - def validate(self, data): """Perform data validation for this item""" super().validate(data) @@ -751,14 +752,14 @@ class BuildAllocationItemSerializer(serializers.Serializer): class BuildAllocationSerializer(serializers.Serializer): """DRF serializer for allocation stock items against a build order.""" - items = BuildAllocationItemSerializer(many=True) - class Meta: """Serializer metaclass""" fields = [ 'items', ] + items = BuildAllocationItemSerializer(many=True) + def validate(self, data): """Validation.""" data = super().validate(data) @@ -870,6 +871,24 @@ class BuildAutoAllocationSerializer(serializers.Serializer): class BuildItemSerializer(InvenTreeModelSerializer): """Serializes a BuildItem object.""" + class Meta: + """Serializer metaclass""" + model = BuildItem + fields = [ + 'pk', + 'bom_part', + 'build', + 'build_detail', + 'install_into', + 'location', + 'location_detail', + 'part', + 'part_detail', + 'stock_item', + 'stock_item_detail', + 'quantity' + ] + bom_part = serializers.IntegerField(source='bom_item.sub_part.pk', read_only=True) part = serializers.IntegerField(source='stock_item.part.pk', read_only=True) location = serializers.IntegerField(source='stock_item.location.pk', read_only=True) @@ -903,24 +922,6 @@ class BuildItemSerializer(InvenTreeModelSerializer): if not stock_detail: self.fields.pop('stock_item_detail') - class Meta: - """Serializer metaclass""" - model = BuildItem - fields = [ - 'pk', - 'bom_part', - 'build', - 'build_detail', - 'install_into', - 'location', - 'location_detail', - 'part', - 'part_detail', - 'stock_item', - 'stock_item_detail', - 'quantity' - ] - class BuildAttachmentSerializer(InvenTreeAttachmentSerializer): """Serializer for a BuildAttachment.""" diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index e906e81b70..3f251fd0b8 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -852,6 +852,12 @@ class InvenTreeSetting(BaseInvenTreeSetting): even if that key does not exist. """ + class Meta: + """Meta options for InvenTreeSetting.""" + + verbose_name = "InvenTree Setting" + verbose_name_plural = "InvenTree Settings" + def save(self, *args, **kwargs): """When saving a global setting, check to see if it requires a server restart. @@ -1601,12 +1607,6 @@ class InvenTreeSetting(BaseInvenTreeSetting): typ = 'inventree' - class Meta: - """Meta options for InvenTreeSetting.""" - - verbose_name = "InvenTree Setting" - verbose_name_plural = "InvenTree Settings" - key = models.CharField( max_length=50, blank=False, @@ -1631,6 +1631,15 @@ class InvenTreeSetting(BaseInvenTreeSetting): class InvenTreeUserSetting(BaseInvenTreeSetting): """An InvenTreeSetting object with a usercontext.""" + class Meta: + """Meta options for InvenTreeUserSetting.""" + + verbose_name = "InvenTree User Setting" + verbose_name_plural = "InvenTree User Settings" + constraints = [ + models.UniqueConstraint(fields=['key', 'user'], name='unique key and user') + ] + SETTINGS = { 'HOMEPAGE_PART_STARRED': { 'name': _('Show subscribed parts'), @@ -1947,15 +1956,6 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): typ = 'user' - class Meta: - """Meta options for InvenTreeUserSetting.""" - - verbose_name = "InvenTree User Setting" - verbose_name_plural = "InvenTree User Settings" - constraints = [ - models.UniqueConstraint(fields=['key', 'user'], name='unique key and user') - ] - key = models.CharField( max_length=50, blank=False, diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 8444e85085..7b6998da5a 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -77,8 +77,6 @@ class GlobalSettingsSerializer(SettingsSerializer): class UserSettingsSerializer(SettingsSerializer): """Serializer for the InvenTreeUserSetting model.""" - user = serializers.PrimaryKeyRelatedField(read_only=True) - class Meta: """Meta options for UserSettingsSerializer.""" @@ -97,6 +95,8 @@ class UserSettingsSerializer(SettingsSerializer): 'typ', ] + user = serializers.PrimaryKeyRelatedField(read_only=True) + class GenericReferencedSettingSerializer(SettingsSerializer): """Serializer for a GenericReferencedSetting model. @@ -140,6 +140,33 @@ class GenericReferencedSettingSerializer(SettingsSerializer): class NotificationMessageSerializer(InvenTreeModelSerializer): """Serializer for the InvenTreeUserSetting model.""" + class Meta: + """Meta options for NotificationMessageSerializer.""" + + model = NotificationMessage + fields = [ + 'pk', + 'target', + 'source', + 'user', + 'category', + 'name', + 'message', + 'creation', + 'age', + 'age_human', + 'read', + ] + + read_only_fields = [ + 'category', + 'name', + 'message', + 'creation', + 'age', + 'age_human', + ] + target = serializers.SerializerMethodField(read_only=True) source = serializers.SerializerMethodField(read_only=True) user = serializers.PrimaryKeyRelatedField(read_only=True) @@ -170,39 +197,10 @@ class NotificationMessageSerializer(InvenTreeModelSerializer): """Function to resolve generic object reference to source.""" return get_objectreference(obj, 'source_content_type', 'source_object_id') - class Meta: - """Meta options for NotificationMessageSerializer.""" - - model = NotificationMessage - fields = [ - 'pk', - 'target', - 'source', - 'user', - 'category', - 'name', - 'message', - 'creation', - 'age', - 'age_human', - 'read', - ] - - read_only_fields = [ - 'category', - 'name', - 'message', - 'creation', - 'age', - 'age_human', - ] - class NewsFeedEntrySerializer(InvenTreeModelSerializer): """Serializer for the NewsFeedEntry model.""" - read = serializers.BooleanField() - class Meta: """Meta options for NewsFeedEntrySerializer.""" @@ -218,6 +216,8 @@ class NewsFeedEntrySerializer(InvenTreeModelSerializer): 'read', ] + read = serializers.BooleanField() + class ConfigSerializer(serializers.Serializer): """Serializer for the InvenTree configuration. diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index 1c1c98e6a1..dc1aa2ba72 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -41,6 +41,13 @@ class CompanyAdmin(ImportExportModelAdmin): class SupplierPartResource(InvenTreeResource): """Class for managing SupplierPart data import/export.""" + class Meta: + """Metaclass defines extra admin options""" + model = SupplierPart + skip_unchanged = True + report_skipped = True + clean_model_instances = True + part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) part_name = Field(attribute='part__full_name', readonly=True) @@ -49,13 +56,6 @@ class SupplierPartResource(InvenTreeResource): supplier_name = Field(attribute='supplier__name', readonly=True) - class Meta: - """Metaclass defines extra admin options""" - model = SupplierPart - skip_unchanged = True - report_skipped = True - clean_model_instances = True - class SupplierPriceBreakInline(admin.TabularInline): """Inline for supplier-part pricing""" @@ -87,6 +87,13 @@ class SupplierPartAdmin(ImportExportModelAdmin): class ManufacturerPartResource(InvenTreeResource): """Class for managing ManufacturerPart data import/export.""" + class Meta: + """Metaclass defines extra admin options""" + model = ManufacturerPart + skip_unchanged = True + report_skipped = True + clean_model_instances = True + part = Field(attribute='part', widget=widgets.ForeignKeyWidget(Part)) part_name = Field(attribute='part__full_name', readonly=True) @@ -95,13 +102,6 @@ class ManufacturerPartResource(InvenTreeResource): manufacturer_name = Field(attribute='manufacturer__name', readonly=True) - class Meta: - """Metaclass defines extra admin options""" - model = ManufacturerPart - skip_unchanged = True - report_skipped = True - clean_model_instances = True - class ManufacturerPartAdmin(ImportExportModelAdmin): """Admin class for ManufacturerPart model.""" @@ -157,6 +157,13 @@ class ManufacturerPartParameterAdmin(ImportExportModelAdmin): class SupplierPriceBreakResource(InvenTreeResource): """Class for managing SupplierPriceBreak data import/export.""" + class Meta: + """Metaclass defines extra admin options""" + model = SupplierPriceBreak + skip_unchanged = True + report_skipped = False + clean_model_instances = True + part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart)) supplier_id = Field(attribute='part__supplier__pk', readonly=True) @@ -169,13 +176,6 @@ class SupplierPriceBreakResource(InvenTreeResource): MPN = Field(attribute='part__MPN', readonly=True) - class Meta: - """Metaclass defines extra admin options""" - model = SupplierPriceBreak - skip_unchanged = True - report_skipped = False - clean_model_instances = True - class SupplierPriceBreakAdmin(ImportExportModelAdmin): """Admin class for the SupplierPriceBreak model""" diff --git a/InvenTree/company/api.py b/InvenTree/company/api.py index 13c0901e43..8479348ce3 100644 --- a/InvenTree/company/api.py +++ b/InvenTree/company/api.py @@ -427,6 +427,15 @@ class SupplierPartDetail(RetrieveUpdateDestroyAPI): class SupplierPriceBreakFilter(rest_filters.FilterSet): """Custom API filters for the SupplierPriceBreak list endpoint""" + class Meta: + """Metaclass options""" + + model = SupplierPriceBreak + fields = [ + 'part', + 'quantity', + ] + base_part = rest_filters.ModelChoiceFilter( label='Base Part', queryset=part.models.Part.objects.all(), @@ -439,15 +448,6 @@ class SupplierPriceBreakFilter(rest_filters.FilterSet): field_name='part__supplier', ) - class Meta: - """Metaclass options""" - - model = SupplierPriceBreak - fields = [ - 'part', - 'quantity', - ] - class SupplierPriceBreakList(ListCreateAPI): """API endpoint for list view of SupplierPriceBreak object. diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 3227af4707..99010b8d72 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -81,11 +81,6 @@ class Company(MetadataMixin, models.Model): currency_code: Specifies the default currency for the company """ - @staticmethod - def get_api_url(): - """Return the API URL associated with the Company model""" - return reverse('api-company-list') - class Meta: """Metaclass defines extra model options""" ordering = ['name', ] @@ -94,6 +89,11 @@ class Company(MetadataMixin, models.Model): ] verbose_name_plural = "Companies" + @staticmethod + def get_api_url(): + """Return the API URL associated with the Company model""" + return reverse('api-company-list') + name = models.CharField(max_length=100, blank=False, help_text=_('Company name'), verbose_name=_('Company name')) @@ -258,15 +258,15 @@ class ManufacturerPart(models.Model): description: Descriptive notes field """ + class Meta: + """Metaclass defines extra model options""" + unique_together = ('part', 'manufacturer', 'MPN') + @staticmethod def get_api_url(): """Return the API URL associated with the ManufacturerPart instance""" return reverse('api-manufacturer-part-list') - class Meta: - """Metaclass defines extra model options""" - unique_together = ('part', 'manufacturer', 'MPN') - part = models.ForeignKey('part.Part', on_delete=models.CASCADE, related_name='manufacturer_parts', verbose_name=_('Base Part'), @@ -360,15 +360,15 @@ class ManufacturerPartParameter(models.Model): Each parameter is a simple string (text) value. """ + class Meta: + """Metaclass defines extra model options""" + unique_together = ('manufacturer_part', 'name') + @staticmethod def get_api_url(): """Return the API URL associated with the ManufacturerPartParameter model""" return reverse('api-manufacturer-part-parameter-list') - class Meta: - """Metaclass defines extra model options""" - unique_together = ('manufacturer_part', 'name') - manufacturer_part = models.ForeignKey( ManufacturerPart, on_delete=models.CASCADE, @@ -434,6 +434,13 @@ class SupplierPart(InvenTreeBarcodeMixin, common.models.MetaMixin): updated: Date that the SupplierPart was last updated """ + class Meta: + """Metaclass defines extra model options""" + unique_together = ('part', 'supplier', 'SKU') + + # This model was moved from the 'Part' app + db_table = 'part_supplierpart' + objects = SupplierPartManager() @staticmethod @@ -453,13 +460,6 @@ class SupplierPart(InvenTreeBarcodeMixin, common.models.MetaMixin): } } - class Meta: - """Metaclass defines extra model options""" - unique_together = ('part', 'supplier', 'SKU') - - # This model was moved from the 'Part' app - db_table = 'part_supplierpart' - def clean(self): """Custom clean action for the SupplierPart model: @@ -696,13 +696,6 @@ class SupplierPriceBreak(common.models.PriceBreak): currency: Reference to the currency of this pricebreak (leave empty for base currency) """ - @staticmethod - def get_api_url(): - """Return the API URL associated with the SupplierPriceBreak model""" - return reverse('api-part-supplier-price-list') - - part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks', verbose_name=_('Part'),) - class Meta: """Metaclass defines extra model options""" unique_together = ("part", "quantity") @@ -714,6 +707,13 @@ class SupplierPriceBreak(common.models.PriceBreak): """Format a string representation of a SupplierPriceBreak instance""" return f'{self.part.SKU} - {self.price} @ {self.quantity}' + @staticmethod + def get_api_url(): + """Return the API URL associated with the SupplierPriceBreak model""" + return reverse('api-part-supplier-price-list') + + part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks', verbose_name=_('Part'),) + @receiver(post_save, sender=SupplierPriceBreak, dispatch_uid='post_save_supplier_price_break') def after_save_supplier_price(sender, instance, created, **kwargs): diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 7c498456bf..faa281cbed 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -25,10 +25,6 @@ from .models import (Company, CompanyAttachment, ManufacturerPart, class CompanyBriefSerializer(InvenTreeModelSerializer): """Serializer for Company object (limited detail)""" - url = serializers.CharField(source='get_absolute_url', read_only=True) - - image = serializers.CharField(source='get_thumbnail_url', read_only=True) - class Meta: """Metaclass options.""" @@ -41,33 +37,14 @@ class CompanyBriefSerializer(InvenTreeModelSerializer): 'image', ] + url = serializers.CharField(source='get_absolute_url', read_only=True) + + image = serializers.CharField(source='get_thumbnail_url', read_only=True) + class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer): """Serializer for Company object (full detail)""" - @staticmethod - def annotate_queryset(queryset): - """Annoate the supplied queryset with aggregated information""" - # Add count of parts manufactured - queryset = queryset.annotate( - parts_manufactured=SubqueryCount('manufactured_parts') - ) - - queryset = queryset.annotate( - parts_supplied=SubqueryCount('supplied_parts') - ) - - return queryset - - url = serializers.CharField(source='get_absolute_url', read_only=True) - - image = InvenTreeImageSerializerField(required=False, allow_null=True) - - parts_supplied = serializers.IntegerField(read_only=True) - parts_manufactured = serializers.IntegerField(read_only=True) - - currency = InvenTreeCurrencySerializer(help_text=_('Default currency used for this supplier'), required=True) - class Meta: """Metaclass options.""" @@ -95,6 +72,29 @@ class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer): 'remote_image', ] + @staticmethod + def annotate_queryset(queryset): + """Annoate the supplied queryset with aggregated information""" + # Add count of parts manufactured + queryset = queryset.annotate( + parts_manufactured=SubqueryCount('manufactured_parts') + ) + + queryset = queryset.annotate( + parts_supplied=SubqueryCount('supplied_parts') + ) + + return queryset + + url = serializers.CharField(source='get_absolute_url', read_only=True) + + image = InvenTreeImageSerializerField(required=False, allow_null=True) + + parts_supplied = serializers.IntegerField(read_only=True) + parts_manufactured = serializers.IntegerField(read_only=True) + + currency = InvenTreeCurrencySerializer(help_text=_('Default currency used for this supplier'), required=True) + def save(self): """Save the Company instance""" super().save() @@ -135,11 +135,21 @@ class CompanyAttachmentSerializer(InvenTreeAttachmentSerializer): class ManufacturerPartSerializer(InvenTreeModelSerializer): """Serializer for ManufacturerPart object.""" - part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + class Meta: + """Metaclass options.""" - manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True) - - pretty_name = serializers.CharField(read_only=True) + model = ManufacturerPart + fields = [ + 'pk', + 'part', + 'part_detail', + 'pretty_name', + 'manufacturer', + 'manufacturer_detail', + 'description', + 'MPN', + 'link', + ] def __init__(self, *args, **kwargs): """Initialize this serializer with extra detail fields as required""" @@ -158,24 +168,14 @@ class ManufacturerPartSerializer(InvenTreeModelSerializer): if prettify is not True: self.fields.pop('pretty_name') + part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + + manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True) + + pretty_name = serializers.CharField(read_only=True) + manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True)) - class Meta: - """Metaclass options.""" - - model = ManufacturerPart - fields = [ - 'pk', - 'part', - 'part_detail', - 'pretty_name', - 'manufacturer', - 'manufacturer_detail', - 'description', - 'MPN', - 'link', - ] - class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer): """Serializer for the ManufacturerPartAttachment class.""" @@ -193,17 +193,6 @@ class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer): class ManufacturerPartParameterSerializer(InvenTreeModelSerializer): """Serializer for the ManufacturerPartParameter model.""" - manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', many=False, read_only=True) - - def __init__(self, *args, **kwargs): - """Initialize this serializer with extra detail fields as required""" - man_detail = kwargs.pop('manufacturer_part_detail', False) - - super().__init__(*args, **kwargs) - - if not man_detail: - self.fields.pop('manufacturer_part_detail') - class Meta: """Metaclass options.""" @@ -218,66 +207,20 @@ class ManufacturerPartParameterSerializer(InvenTreeModelSerializer): 'units', ] - -class SupplierPartSerializer(InvenTreeModelSerializer): - """Serializer for SupplierPart object.""" - - # Annotated field showing total in-stock quantity - in_stock = serializers.FloatField(read_only=True) - - part_detail = PartBriefSerializer(source='part', many=False, read_only=True) - - supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) - - manufacturer_detail = CompanyBriefSerializer(source='manufacturer_part.manufacturer', many=False, read_only=True) - - pretty_name = serializers.CharField(read_only=True) - - pack_size = serializers.FloatField(label=_('Pack Quantity')) - def __init__(self, *args, **kwargs): """Initialize this serializer with extra detail fields as required""" - - # Check if 'available' quantity was supplied - self.has_available_quantity = 'available' in kwargs.get('data', {}) - - brief = kwargs.pop('brief', False) - - detail_default = not brief - - part_detail = kwargs.pop('part_detail', detail_default) - supplier_detail = kwargs.pop('supplier_detail', detail_default) - manufacturer_detail = kwargs.pop('manufacturer_detail', detail_default) - - prettify = kwargs.pop('pretty', False) + man_detail = kwargs.pop('manufacturer_part_detail', False) super().__init__(*args, **kwargs) - if part_detail is not True: - self.fields.pop('part_detail') - - if supplier_detail is not True: - self.fields.pop('supplier_detail') - - if manufacturer_detail is not True: - self.fields.pop('manufacturer_detail') + if not man_detail: self.fields.pop('manufacturer_part_detail') - if prettify is not True: - self.fields.pop('pretty_name') + manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', many=False, read_only=True) - supplier = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_supplier=True)) - manufacturer = serializers.CharField(read_only=True) - - MPN = serializers.CharField(read_only=True) - - manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', read_only=True) - - url = serializers.CharField(source='get_absolute_url', read_only=True) - - # Date fields - updated = serializers.DateTimeField(allow_null=True, read_only=True) +class SupplierPartSerializer(InvenTreeModelSerializer): + """Serializer for SupplierPart object.""" class Meta: """Metaclass options.""" @@ -314,6 +257,63 @@ class SupplierPartSerializer(InvenTreeModelSerializer): 'barcode_hash', ] + def __init__(self, *args, **kwargs): + """Initialize this serializer with extra detail fields as required""" + + # Check if 'available' quantity was supplied + self.has_available_quantity = 'available' in kwargs.get('data', {}) + + brief = kwargs.pop('brief', False) + + detail_default = not brief + + part_detail = kwargs.pop('part_detail', detail_default) + supplier_detail = kwargs.pop('supplier_detail', detail_default) + manufacturer_detail = kwargs.pop('manufacturer_detail', detail_default) + + prettify = kwargs.pop('pretty', False) + + super().__init__(*args, **kwargs) + + if part_detail is not True: + self.fields.pop('part_detail') + + if supplier_detail is not True: + self.fields.pop('supplier_detail') + + if manufacturer_detail is not True: + self.fields.pop('manufacturer_detail') + self.fields.pop('manufacturer_part_detail') + + if prettify is not True: + self.fields.pop('pretty_name') + + # Annotated field showing total in-stock quantity + in_stock = serializers.FloatField(read_only=True) + + part_detail = PartBriefSerializer(source='part', many=False, read_only=True) + + supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True) + + manufacturer_detail = CompanyBriefSerializer(source='manufacturer_part.manufacturer', many=False, read_only=True) + + pretty_name = serializers.CharField(read_only=True) + + pack_size = serializers.FloatField(label=_('Pack Quantity')) + + supplier = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_supplier=True)) + + manufacturer = serializers.CharField(read_only=True) + + MPN = serializers.CharField(read_only=True) + + manufacturer_part_detail = ManufacturerPartSerializer(source='manufacturer_part', read_only=True) + + url = serializers.CharField(source='get_absolute_url', read_only=True) + + # Date fields + updated = serializers.DateTimeField(allow_null=True, read_only=True) + @staticmethod def annotate_queryset(queryset): """Annotate the SupplierPart queryset with extra fields: @@ -369,6 +369,22 @@ class SupplierPartSerializer(InvenTreeModelSerializer): class SupplierPriceBreakSerializer(InvenTreeModelSerializer): """Serializer for SupplierPriceBreak object.""" + class Meta: + """Metaclass options.""" + + model = SupplierPriceBreak + fields = [ + 'pk', + 'part', + 'part_detail', + 'quantity', + 'price', + 'price_currency', + 'supplier', + 'supplier_detail', + 'updated', + ] + def __init__(self, *args, **kwargs): """Initialize this serializer with extra fields as required""" @@ -399,19 +415,3 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer): # Detail serializer for SupplierPart part_detail = SupplierPartSerializer(source='part', brief=True, many=False, read_only=True) - - class Meta: - """Metaclass options.""" - - model = SupplierPriceBreak - fields = [ - 'pk', - 'part', - 'part_detail', - 'quantity', - 'price', - 'price_currency', - 'supplier', - 'supplier_detail', - 'updated', - ] diff --git a/InvenTree/label/serializers.py b/InvenTree/label/serializers.py index b250b6f441..da5e6ccedc 100644 --- a/InvenTree/label/serializers.py +++ b/InvenTree/label/serializers.py @@ -9,8 +9,6 @@ from .models import PartLabel, StockItemLabel, StockLocationLabel class StockItemLabelSerializer(InvenTreeModelSerializer): """Serializes a StockItemLabel object.""" - label = InvenTreeAttachmentSerializerField(required=True) - class Meta: """Metaclass options.""" @@ -24,12 +22,12 @@ class StockItemLabelSerializer(InvenTreeModelSerializer): 'enabled', ] + label = InvenTreeAttachmentSerializerField(required=True) + class StockLocationLabelSerializer(InvenTreeModelSerializer): """Serializes a StockLocationLabel object.""" - label = InvenTreeAttachmentSerializerField(required=True) - class Meta: """Metaclass options.""" @@ -43,12 +41,12 @@ class StockLocationLabelSerializer(InvenTreeModelSerializer): 'enabled', ] + label = InvenTreeAttachmentSerializerField(required=True) + class PartLabelSerializer(InvenTreeModelSerializer): """Serializes a PartLabel object.""" - label = InvenTreeAttachmentSerializerField(required=True) - class Meta: """Metaclass options.""" @@ -61,3 +59,5 @@ class PartLabelSerializer(InvenTreeModelSerializer): 'filters', 'enabled', ] + + label = InvenTreeAttachmentSerializerField(required=True) diff --git a/InvenTree/order/admin.py b/InvenTree/order/admin.py index aa24c095f6..2f0f5bd36d 100644 --- a/InvenTree/order/admin.py +++ b/InvenTree/order/admin.py @@ -101,12 +101,6 @@ class SalesOrderAdmin(ImportExportModelAdmin): class PurchaseOrderResource(InvenTreeResource): """Class for managing import / export of PurchaseOrder data.""" - # Add number of line items - line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True) - - # Is this order overdue? - overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True) - class Meta: """Metaclass""" model = PurchaseOrder @@ -116,10 +110,23 @@ class PurchaseOrderResource(InvenTreeResource): 'metadata', ] + # Add number of line items + line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True) + + # Is this order overdue? + overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True) + class PurchaseOrderLineItemResource(InvenTreeResource): """Class for managing import / export of PurchaseOrderLineItem data.""" + class Meta: + """Metaclass""" + model = PurchaseOrderLineItem + skip_unchanged = True + report_skipped = False + clean_model_instances = True + part_name = Field(attribute='part__part__name', readonly=True) manufacturer = Field(attribute='part__manufacturer', readonly=True) @@ -128,13 +135,6 @@ class PurchaseOrderLineItemResource(InvenTreeResource): SKU = Field(attribute='part__SKU', readonly=True) - class Meta: - """Metaclass""" - model = PurchaseOrderLineItem - skip_unchanged = True - report_skipped = False - clean_model_instances = True - class PurchaseOrderExtraLineResource(InvenTreeResource): """Class for managing import / export of PurchaseOrderExtraLine data.""" @@ -148,12 +148,6 @@ class PurchaseOrderExtraLineResource(InvenTreeResource): class SalesOrderResource(InvenTreeResource): """Class for managing import / export of SalesOrder data.""" - # Add number of line items - line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True) - - # Is this order overdue? - overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True) - class Meta: """Metaclass options""" model = SalesOrder @@ -163,10 +157,23 @@ class SalesOrderResource(InvenTreeResource): 'metadata', ] + # Add number of line items + line_items = Field(attribute='line_count', widget=widgets.IntegerWidget(), readonly=True) + + # Is this order overdue? + overdue = Field(attribute='is_overdue', widget=widgets.BooleanWidget(), readonly=True) + class SalesOrderLineItemResource(InvenTreeResource): """Class for managing import / export of SalesOrderLineItem data.""" + class Meta: + """Metaclass options""" + model = SalesOrderLineItem + skip_unchanged = True + report_skipped = False + clean_model_instances = True + part_name = Field(attribute='part__name', readonly=True) IPN = Field(attribute='part__IPN', readonly=True) @@ -185,13 +192,6 @@ class SalesOrderLineItemResource(InvenTreeResource): else: return '' - class Meta: - """Metaclass options""" - model = SalesOrderLineItem - skip_unchanged = True - report_skipped = False - clean_model_instances = True - class SalesOrderExtraLineResource(InvenTreeResource): """Class for managing import / export of SalesOrderExtraLine data.""" diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index f4c155e241..f45381525e 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -113,6 +113,7 @@ class OrderFilter(rest_filters.FilterSet): class PurchaseOrderFilter(OrderFilter): """Custom API filters for the PurchaseOrderList endpoint.""" + class Meta: """Metaclass options.""" @@ -124,6 +125,7 @@ class PurchaseOrderFilter(OrderFilter): class SalesOrderFilter(OrderFilter): """Custom API filters for the SalesOrderList endpoint.""" + class Meta: """Metaclass options.""" @@ -1093,6 +1095,14 @@ class SalesOrderAllocationList(ListAPI): class SalesOrderShipmentFilter(rest_filters.FilterSet): """Custom filterset for the SalesOrderShipmentList endpoint.""" + class Meta: + """Metaclass options.""" + + model = models.SalesOrderShipment + fields = [ + 'order', + ] + shipped = rest_filters.BooleanFilter(label='shipped', method='filter_shipped') def filter_shipped(self, queryset, name, value): @@ -1106,14 +1116,6 @@ class SalesOrderShipmentFilter(rest_filters.FilterSet): return queryset - class Meta: - """Metaclass options.""" - - model = models.SalesOrderShipment - fields = [ - 'order', - ] - class SalesOrderShipmentList(ListCreateAPI): """API list endpoint for SalesOrderShipment model.""" diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 430632f9b6..37b2d96b7b 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -63,6 +63,10 @@ class Order(MetadataMixin, ReferenceIndexingMixin): responsible: User (or group) responsible for managing the order """ + class Meta: + """Metaclass options. Abstract ensures no database table is created.""" + abstract = True + def save(self, *args, **kwargs): """Custom save method for the order models: @@ -75,11 +79,6 @@ class Order(MetadataMixin, ReferenceIndexingMixin): super().save(*args, **kwargs) - class Meta: - """Metaclass options. Abstract ensures no database table is created.""" - - abstract = True - description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_('Order description')) link = InvenTreeURLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page')) @@ -927,7 +926,6 @@ class OrderLineItem(models.Model): class Meta: """Metaclass options. Abstract ensures no database table is created.""" - abstract = True quantity = RoundingDecimalField( @@ -958,7 +956,6 @@ class OrderExtraLine(OrderLineItem): class Meta: """Metaclass options. Abstract ensures no database table is created.""" - abstract = True context = models.JSONField( diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index ffab3930aa..8b3479ba12 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -80,6 +80,40 @@ class AbstractExtraLineMeta: class PurchaseOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer): """Serializer for a PurchaseOrder object.""" + class Meta: + """Metaclass options.""" + + model = order.models.PurchaseOrder + + fields = [ + 'pk', + 'issue_date', + 'complete_date', + 'creation_date', + 'description', + 'line_items', + 'link', + 'overdue', + 'reference', + 'responsible', + 'responsible_detail', + 'supplier', + 'supplier_detail', + 'supplier_reference', + 'status', + 'status_text', + 'target_date', + 'notes', + 'total_price', + ] + + read_only_fields = [ + 'status' + 'issue_date', + 'complete_date', + 'creation_date', + ] + def __init__(self, *args, **kwargs): """Initialization routine for the serializer""" supplier_detail = kwargs.pop('supplier_detail', False) @@ -131,40 +165,6 @@ class PurchaseOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer) responsible_detail = OwnerSerializer(source='responsible', read_only=True, many=False) - class Meta: - """Metaclass options.""" - - model = order.models.PurchaseOrder - - fields = [ - 'pk', - 'issue_date', - 'complete_date', - 'creation_date', - 'description', - 'line_items', - 'link', - 'overdue', - 'reference', - 'responsible', - 'responsible_detail', - 'supplier', - 'supplier_detail', - 'supplier_reference', - 'status', - 'status_text', - 'target_date', - 'notes', - 'total_price', - ] - - read_only_fields = [ - 'status' - 'issue_date', - 'complete_date', - 'creation_date', - ] - class PurchaseOrderCancelSerializer(serializers.Serializer): """Serializer for cancelling a PurchaseOrder.""" @@ -195,6 +195,11 @@ class PurchaseOrderCancelSerializer(serializers.Serializer): class PurchaseOrderCompleteSerializer(serializers.Serializer): """Serializer for completing a purchase order.""" + class Meta: + """Metaclass options.""" + + fields = [] + accept_incomplete = serializers.BooleanField( label=_('Accept Incomplete'), help_text=_('Allow order to be closed with incomplete line items'), @@ -212,11 +217,6 @@ class PurchaseOrderCompleteSerializer(serializers.Serializer): return value - class Meta: - """Metaclass options.""" - - fields = [] - def get_context_data(self): """Custom context information for this serializer.""" order = self.context['order'] @@ -247,6 +247,47 @@ class PurchaseOrderIssueSerializer(serializers.Serializer): class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): """Serializer class for the PurchaseOrderLineItem model""" + + class Meta: + """Metaclass options.""" + + model = order.models.PurchaseOrderLineItem + + fields = [ + 'pk', + 'quantity', + 'reference', + 'notes', + 'order', + 'order_detail', + 'overdue', + 'part', + 'part_detail', + 'supplier_part_detail', + 'received', + 'purchase_price', + 'purchase_price_currency', + 'destination', + 'destination_detail', + 'target_date', + 'total_price', + ] + + def __init__(self, *args, **kwargs): + """Initialization routine for the serializer""" + part_detail = kwargs.pop('part_detail', False) + + order_detail = kwargs.pop('order_detail', False) + + super().__init__(*args, **kwargs) + + if part_detail is not True: + self.fields.pop('part_detail') + self.fields.pop('supplier_part_detail') + + if order_detail is not True: + self.fields.pop('order_detail') + @staticmethod def annotate_queryset(queryset): """Add some extra annotations to this queryset: @@ -272,21 +313,6 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): return queryset - def __init__(self, *args, **kwargs): - """Initialization routine for the serializer""" - part_detail = kwargs.pop('part_detail', False) - - order_detail = kwargs.pop('order_detail', False) - - super().__init__(*args, **kwargs) - - if part_detail is not True: - self.fields.pop('part_detail') - self.fields.pop('supplier_part_detail') - - if order_detail is not True: - self.fields.pop('order_detail') - quantity = serializers.FloatField(min_value=0, required=True) def validate_quantity(self, quantity): @@ -352,31 +378,6 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): return data - class Meta: - """Metaclass options.""" - - model = order.models.PurchaseOrderLineItem - - fields = [ - 'pk', - 'quantity', - 'reference', - 'notes', - 'order', - 'order_detail', - 'overdue', - 'part', - 'part_detail', - 'supplier_part_detail', - 'received', - 'purchase_price', - 'purchase_price_currency', - 'destination', - 'destination_detail', - 'target_date', - 'total_price', - ] - class PurchaseOrderExtraLineSerializer(AbstractExtraLineSerializer, InvenTreeModelSerializer): """Serializer for a PurchaseOrderExtraLine object.""" @@ -530,6 +531,14 @@ class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer): class PurchaseOrderReceiveSerializer(serializers.Serializer): """Serializer for receiving items against a purchase order.""" + class Meta: + """Metaclass options.""" + + fields = [ + 'items', + 'location', + ] + items = PurchaseOrderLineItemReceiveSerializer(many=True) location = serializers.PrimaryKeyRelatedField( @@ -619,14 +628,6 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer): # Catch model errors and re-throw as DRF errors raise ValidationError(detail=serializers.as_serializer_error(exc)) - class Meta: - """Metaclass options.""" - - fields = [ - 'items', - 'location', - ] - class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer): """Serializers for the PurchaseOrderAttachment model.""" @@ -644,6 +645,37 @@ class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer): class SalesOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer): """Serializers for the SalesOrder object.""" + class Meta: + """Metaclass options.""" + + model = order.models.SalesOrder + + fields = [ + 'pk', + 'creation_date', + 'customer', + 'customer_detail', + 'customer_reference', + 'description', + 'line_items', + 'link', + 'notes', + 'overdue', + 'reference', + 'responsible', + 'status', + 'status_text', + 'shipment_date', + 'target_date', + 'total_price', + ] + + read_only_fields = [ + 'status', + 'creation_date', + 'shipment_date', + ] + def __init__(self, *args, **kwargs): """Initialization routine for the serializer""" customer_detail = kwargs.pop('customer_detail', False) @@ -693,37 +725,6 @@ class SalesOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer): return reference - class Meta: - """Metaclass options.""" - - model = order.models.SalesOrder - - fields = [ - 'pk', - 'creation_date', - 'customer', - 'customer_detail', - 'customer_reference', - 'description', - 'line_items', - 'link', - 'notes', - 'overdue', - 'reference', - 'responsible', - 'status', - 'status_text', - 'shipment_date', - 'target_date', - 'total_price', - ] - - read_only_fields = [ - 'status', - 'creation_date', - 'shipment_date', - ] - class SalesOrderAllocationSerializer(InvenTreeModelSerializer): """Serializer for the SalesOrderAllocation model. @@ -731,20 +732,28 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): This includes some fields from the related model objects. """ - part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) - order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) - serial = serializers.CharField(source='get_serial', read_only=True) - quantity = serializers.FloatField(read_only=False) - location = serializers.PrimaryKeyRelatedField(source='item.location', many=False, read_only=True) + class Meta: + """Metaclass options.""" - # Extra detail fields - order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) - part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) - item_detail = stock.serializers.StockItemSerializer(source='item', many=False, read_only=True) - location_detail = stock.serializers.LocationSerializer(source='item.location', many=False, read_only=True) - customer_detail = CompanyBriefSerializer(source='line.order.customer', many=False, read_only=True) + model = order.models.SalesOrderAllocation - shipment_date = serializers.DateField(source='shipment.shipment_date', read_only=True) + fields = [ + 'pk', + 'line', + 'customer_detail', + 'serial', + 'quantity', + 'location', + 'location_detail', + 'item', + 'item_detail', + 'order', + 'order_detail', + 'part', + 'part_detail', + 'shipment', + 'shipment_date', + ] def __init__(self, *args, **kwargs): """Initialization routine for the serializer""" @@ -771,33 +780,74 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): if not customer_detail: self.fields.pop('customer_detail') - class Meta: - """Metaclass options.""" + part = serializers.PrimaryKeyRelatedField(source='item.part', read_only=True) + order = serializers.PrimaryKeyRelatedField(source='line.order', many=False, read_only=True) + serial = serializers.CharField(source='get_serial', read_only=True) + quantity = serializers.FloatField(read_only=False) + location = serializers.PrimaryKeyRelatedField(source='item.location', many=False, read_only=True) - model = order.models.SalesOrderAllocation + # Extra detail fields + order_detail = SalesOrderSerializer(source='line.order', many=False, read_only=True) + part_detail = PartBriefSerializer(source='item.part', many=False, read_only=True) + item_detail = stock.serializers.StockItemSerializer(source='item', many=False, read_only=True) + location_detail = stock.serializers.LocationSerializer(source='item.location', many=False, read_only=True) + customer_detail = CompanyBriefSerializer(source='line.order.customer', many=False, read_only=True) - fields = [ - 'pk', - 'line', - 'customer_detail', - 'serial', - 'quantity', - 'location', - 'location_detail', - 'item', - 'item_detail', - 'order', - 'order_detail', - 'part', - 'part_detail', - 'shipment', - 'shipment_date', - ] + shipment_date = serializers.DateField(source='shipment.shipment_date', read_only=True) class SalesOrderLineItemSerializer(InvenTreeModelSerializer): """Serializer for a SalesOrderLineItem object.""" + class Meta: + """Metaclass options.""" + + model = order.models.SalesOrderLineItem + + fields = [ + 'pk', + 'allocated', + 'allocations', + 'available_stock', + 'customer_detail', + 'quantity', + 'reference', + 'notes', + 'order', + 'order_detail', + 'overdue', + 'part', + 'part_detail', + 'sale_price', + 'sale_price_currency', + 'shipped', + 'target_date', + ] + + def __init__(self, *args, **kwargs): + """Initializion routine for the serializer: + + - Add extra related serializer information if required + """ + part_detail = kwargs.pop('part_detail', False) + order_detail = kwargs.pop('order_detail', False) + allocations = kwargs.pop('allocations', False) + customer_detail = kwargs.pop('customer_detail', False) + + super().__init__(*args, **kwargs) + + if part_detail is not True: + self.fields.pop('part_detail') + + if order_detail is not True: + self.fields.pop('order_detail') + + if allocations is not True: + self.fields.pop('allocations') + + if customer_detail is not True: + self.fields.pop('customer_detail') + @staticmethod def annotate_queryset(queryset): """Add some extra annotations to this queryset: @@ -832,30 +882,6 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer): return queryset - def __init__(self, *args, **kwargs): - """Initializion routine for the serializer: - - - Add extra related serializer information if required - """ - part_detail = kwargs.pop('part_detail', False) - order_detail = kwargs.pop('order_detail', False) - allocations = kwargs.pop('allocations', False) - customer_detail = kwargs.pop('customer_detail', False) - - super().__init__(*args, **kwargs) - - if part_detail is not True: - self.fields.pop('part_detail') - - if order_detail is not True: - self.fields.pop('order_detail') - - if allocations is not True: - self.fields.pop('allocations') - - if customer_detail is not True: - self.fields.pop('customer_detail') - customer_detail = CompanyBriefSerializer(source='order.customer', many=False, read_only=True) order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) part_detail = PartBriefSerializer(source='part', many=False, read_only=True) @@ -875,39 +901,10 @@ class SalesOrderLineItemSerializer(InvenTreeModelSerializer): sale_price_currency = InvenTreeCurrencySerializer(help_text=_('Sale price currency')) - class Meta: - """Metaclass options.""" - - model = order.models.SalesOrderLineItem - - fields = [ - 'pk', - 'allocated', - 'allocations', - 'available_stock', - 'customer_detail', - 'quantity', - 'reference', - 'notes', - 'order', - 'order_detail', - 'overdue', - 'part', - 'part_detail', - 'sale_price', - 'sale_price_currency', - 'shipped', - 'target_date', - ] - class SalesOrderShipmentSerializer(InvenTreeModelSerializer): """Serializer for the SalesOrderShipment class.""" - allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True) - - order_detail = SalesOrderSerializer(source='order', read_only=True, many=False) - class Meta: """Metaclass options.""" @@ -927,6 +924,10 @@ class SalesOrderShipmentSerializer(InvenTreeModelSerializer): 'notes', ] + allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True) + + order_detail = SalesOrderSerializer(source='order', read_only=True, many=False) + class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer): """Serializer for completing (shipping) a SalesOrderShipment.""" diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 3e6d61de3a..13d5875ccc 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -16,6 +16,20 @@ from stock.models import StockLocation class PartResource(InvenTreeResource): """Class for managing Part data import/export.""" + class Meta: + """Metaclass definition""" + model = models.Part + skip_unchanged = True + report_skipped = False + clean_model_instances = True + exclude = [ + 'bom_checksum', 'bom_checked_by', 'bom_checked_date', + 'lft', 'rght', 'tree_id', 'level', + 'image', + 'metadata', + 'barcode_data', 'barcode_hash', + ] + id = Field(attribute='pk', column_name=_('Part ID'), widget=widgets.IntegerWidget()) name = Field(attribute='name', column_name=_('Part Name'), widget=widgets.CharWidget()) description = Field(attribute='description', column_name=_('Part Description'), widget=widgets.CharWidget()) @@ -68,20 +82,6 @@ class PartResource(InvenTreeResource): if max_cost is not None: return float(max_cost.amount) - class Meta: - """Metaclass definition""" - model = models.Part - skip_unchanged = True - report_skipped = False - clean_model_instances = True - exclude = [ - 'bom_checksum', 'bom_checked_by', 'bom_checked_date', - 'lft', 'rght', 'tree_id', 'level', - 'image', - 'metadata', - 'barcode_data', 'barcode_hash', - ] - def get_queryset(self): """Prefetch related data for quicker access.""" query = super().get_queryset() @@ -175,18 +175,6 @@ class PartStocktakeReportAdmin(admin.ModelAdmin): class PartCategoryResource(InvenTreeResource): """Class for managing PartCategory data import/export.""" - id = Field(attribute='pk', column_name=_('Category ID')) - name = Field(attribute='name', column_name=_('Category Name')) - description = Field(attribute='description', column_name=_('Description')) - parent = Field(attribute='parent', column_name=_('Parent ID'), widget=widgets.ForeignKeyWidget(models.PartCategory)) - parent_name = Field(attribute='parent__name', column_name=_('Parent Name'), readonly=True) - default_location = Field(attribute='default_location', column_name=_('Default Location ID'), widget=widgets.ForeignKeyWidget(StockLocation)) - default_keywords = Field(attribute='default_keywords', column_name=_('Keywords')) - pathstring = Field(attribute='pathstring', column_name=_('Category Path')) - - # Calculated fields - parts = Field(attribute='item_count', column_name=_('Parts'), widget=widgets.IntegerWidget(), readonly=True) - class Meta: """Metaclass definition""" model = models.PartCategory @@ -201,6 +189,18 @@ class PartCategoryResource(InvenTreeResource): 'icon', ] + id = Field(attribute='pk', column_name=_('Category ID')) + name = Field(attribute='name', column_name=_('Category Name')) + description = Field(attribute='description', column_name=_('Description')) + parent = Field(attribute='parent', column_name=_('Parent ID'), widget=widgets.ForeignKeyWidget(models.PartCategory)) + parent_name = Field(attribute='parent__name', column_name=_('Parent Name'), readonly=True) + default_location = Field(attribute='default_location', column_name=_('Default Location ID'), widget=widgets.ForeignKeyWidget(StockLocation)) + default_keywords = Field(attribute='default_keywords', column_name=_('Keywords')) + pathstring = Field(attribute='pathstring', column_name=_('Category Path')) + + # Calculated fields + parts = Field(attribute='item_count', column_name=_('Parts'), widget=widgets.IntegerWidget(), readonly=True) + def after_import(self, dataset, result, using_transactions, dry_run, **kwargs): """Rebuild MPTT tree structure after importing PartCategory data""" @@ -247,6 +247,20 @@ class PartTestTemplateAdmin(admin.ModelAdmin): class BomItemResource(InvenTreeResource): """Class for managing BomItem data import/export.""" + class Meta: + """Metaclass definition""" + model = models.BomItem + skip_unchanged = True + report_skipped = False + clean_model_instances = True + + exclude = [ + 'checksum', + 'id', + 'part', + 'sub_part', + ] + level = Field(attribute='level', column_name=_('BOM Level'), readonly=True) bom_id = Field(attribute='pk', column_name=_('BOM Item ID')) @@ -335,20 +349,6 @@ class BomItemResource(InvenTreeResource): return fields - class Meta: - """Metaclass definition""" - model = models.BomItem - skip_unchanged = True - report_skipped = False - clean_model_instances = True - - exclude = [ - 'checksum', - 'id', - 'part', - 'sub_part', - ] - class BomItemAdmin(ImportExportModelAdmin): """Admin class for the BomItem model""" @@ -373,6 +373,13 @@ class ParameterTemplateAdmin(ImportExportModelAdmin): class ParameterResource(InvenTreeResource): """Class for managing PartParameter data import/export.""" + class Meta: + """Metaclass definition""" + model = models.PartParameter + skip_unchanged = True + report_skipped = False + clean_model_instance = True + part = Field(attribute='part', widget=widgets.ForeignKeyWidget(models.Part)) part_name = Field(attribute='part__name', readonly=True) @@ -381,13 +388,6 @@ class ParameterResource(InvenTreeResource): template_name = Field(attribute='template__name', readonly=True) - class Meta: - """Metaclass definition""" - model = models.PartParameter - skip_unchanged = True - report_skipped = False - clean_model_instance = True - class ParameterAdmin(ImportExportModelAdmin): """Admin class for the PartParameter model""" diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 95222641a7..f1934bc105 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -34,16 +34,16 @@ class BomMatchItemForm(MatchItemForm): class PartPriceForm(forms.Form): """Simple form for viewing part pricing information.""" - quantity = forms.IntegerField( - required=True, - initial=1, - label=_('Quantity'), - help_text=_('Input quantity for price calculation') - ) - class Meta: """Metaclass defines fields for this form""" model = Part fields = [ 'quantity', ] + + quantity = forms.IntegerField( + required=True, + initial=1, + label=_('Quantity'), + help_text=_('Input quantity for price calculation') + ) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index c7faa25d6b..88976811f3 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -3045,6 +3045,10 @@ class PartAttachment(InvenTreeAttachment): class PartSellPriceBreak(common.models.PriceBreak): """Represents a price break for selling this part.""" + class Meta: + """Metaclass providing extra model definition""" + unique_together = ('part', 'quantity') + @staticmethod def get_api_url(): """Return the list API endpoint URL associated with the PartSellPriceBreak model""" @@ -3057,14 +3061,14 @@ class PartSellPriceBreak(common.models.PriceBreak): verbose_name=_('Part') ) - class Meta: - """Metaclass providing extra model definition""" - unique_together = ('part', 'quantity') - class PartInternalPriceBreak(common.models.PriceBreak): """Represents a price break for internally selling this part.""" + class Meta: + """Metaclass providing extra model definition""" + unique_together = ('part', 'quantity') + @staticmethod def get_api_url(): """Return the list API endpoint URL associated with the PartInternalPriceBreak model""" @@ -3076,10 +3080,6 @@ class PartInternalPriceBreak(common.models.PriceBreak): verbose_name=_('Part') ) - class Meta: - """Metaclass providing extra model definition""" - unique_together = ('part', 'quantity') - class PartStar(models.Model): """A PartStar object creates a subscription relationship between a User and a Part. @@ -3091,10 +3091,6 @@ class PartStar(models.Model): user: Link to a User object """ - part = models.ForeignKey(Part, on_delete=models.CASCADE, verbose_name=_('Part'), related_name='starred_users') - - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('User'), related_name='starred_parts') - class Meta: """Metaclass providing extra model definition""" unique_together = [ @@ -3102,6 +3098,10 @@ class PartStar(models.Model): 'user' ] + part = models.ForeignKey(Part, on_delete=models.CASCADE, verbose_name=_('Part'), related_name='starred_users') + + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('User'), related_name='starred_parts') + class PartCategoryStar(models.Model): """A PartCategoryStar creates a subscription relationship between a User and a PartCategory. @@ -3111,10 +3111,6 @@ class PartCategoryStar(models.Model): user: Link to a User object """ - category = models.ForeignKey(PartCategory, on_delete=models.CASCADE, verbose_name=_('Category'), related_name='starred_users') - - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('User'), related_name='starred_categories') - class Meta: """Metaclass providing extra model definition""" unique_together = [ @@ -3122,6 +3118,10 @@ class PartCategoryStar(models.Model): 'user', ] + category = models.ForeignKey(PartCategory, on_delete=models.CASCADE, verbose_name=_('Category'), related_name='starred_users') + + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('User'), related_name='starred_categories') + class PartTestTemplate(models.Model): """A PartTestTemplate defines a 'template' for a test which is required to be run against a StockItem (an instance of the Part). @@ -3292,6 +3292,11 @@ class PartParameter(models.Model): data: The data (value) of the Parameter [string] """ + class Meta: + """Metaclass providing extra model definition""" + # Prevent multiple instances of a parameter for a single part + unique_together = ('part', 'template') + @staticmethod def get_api_url(): """Return the list API endpoint URL associated with the PartParameter model""" @@ -3306,11 +3311,6 @@ class PartParameter(models.Model): units=str(self.template.units) ) - class Meta: - """Metaclass providing extra model definition""" - # Prevent multiple instances of a parameter for a single part - unique_together = ('part', 'template') - part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='parameters', verbose_name=_('Part'), help_text=_('Parent Part')) template = models.ForeignKey(PartParameterTemplate, on_delete=models.CASCADE, related_name='instances', verbose_name=_('Template'), help_text=_('Parameter Template')) @@ -3424,6 +3424,17 @@ class BomItem(DataImportMixin, models.Model): } } + class Meta: + """Metaclass providing extra model definition""" + verbose_name = _("BOM Item") + + def __str__(self): + """Return a string representation of this BomItem instance""" + return "{n} x {child} to make {parent}".format( + parent=self.part.full_name, + child=self.sub_part.full_name, + n=decimal2string(self.quantity)) + @staticmethod def get_api_url(): """Return the list API endpoint URL associated with the BomItem model""" @@ -3638,17 +3649,6 @@ class BomItem(DataImportMixin, models.Model): except Part.DoesNotExist: raise ValidationError({'sub_part': _('Sub part must be specified')}) - class Meta: - """Metaclass providing extra model definition""" - verbose_name = _("BOM Item") - - def __str__(self): - """Return a string representation of this BomItem instance""" - return "{n} x {child} to make {parent}".format( - parent=self.part.full_name, - child=self.sub_part.full_name, - n=decimal2string(self.quantity)) - def get_overage_quantity(self, quantity): """Calculate overage quantity.""" # Most of the time overage string will be empty diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 643f1d8d2d..21802fc50a 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -46,6 +46,25 @@ from .models import (BomItem, BomItemSubstitute, Part, PartAttachment, class CategorySerializer(InvenTreeModelSerializer): """Serializer for PartCategory.""" + class Meta: + """Metaclass defining serializer fields""" + model = PartCategory + fields = [ + 'pk', + 'name', + 'description', + 'default_location', + 'default_keywords', + 'level', + 'parent', + 'part_count', + 'pathstring', + 'starred', + 'url', + 'structural', + 'icon', + ] + def get_starred(self, category): """Return True if the category is directly "starred" by the current user.""" return category in self.context.get('starred_categories', []) @@ -69,25 +88,6 @@ class CategorySerializer(InvenTreeModelSerializer): starred = serializers.SerializerMethodField() - class Meta: - """Metaclass defining serializer fields""" - model = PartCategory - fields = [ - 'pk', - 'name', - 'description', - 'default_location', - 'default_keywords', - 'level', - 'parent', - 'part_count', - 'pathstring', - 'starred', - 'url', - 'structural', - 'icon', - ] - class CategoryTree(InvenTreeModelSerializer): """Serializer for PartCategory tree.""" @@ -118,8 +118,6 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer): class PartTestTemplateSerializer(InvenTreeModelSerializer): """Serializer for the PartTestTemplate class.""" - key = serializers.CharField(read_only=True) - class Meta: """Metaclass defining serializer fields""" model = PartTestTemplate @@ -135,16 +133,12 @@ class PartTestTemplateSerializer(InvenTreeModelSerializer): 'requires_attachment', ] + key = serializers.CharField(read_only=True) + class PartSalePriceSerializer(InvenTreeModelSerializer): """Serializer for sale prices for Part model.""" - quantity = InvenTreeDecimalField() - - price = InvenTreeMoneySerializer(allow_null=True) - - price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item')) - class Meta: """Metaclass defining serializer fields""" model = PartSellPriceBreak @@ -156,18 +150,16 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): 'price_currency', ] + quantity = InvenTreeDecimalField() + + price = InvenTreeMoneySerializer(allow_null=True) + + price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item')) + class PartInternalPriceSerializer(InvenTreeModelSerializer): """Serializer for internal prices for Part model.""" - quantity = InvenTreeDecimalField() - - price = InvenTreeMoneySerializer( - allow_null=True - ) - - price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item')) - class Meta: """Metaclass defining serializer fields""" model = PartInternalPriceBreak @@ -179,6 +171,14 @@ class PartInternalPriceSerializer(InvenTreeModelSerializer): 'price_currency', ] + quantity = InvenTreeDecimalField() + + price = InvenTreeMoneySerializer( + allow_null=True + ) + + price_currency = InvenTreeCurrencySerializer(help_text=_('Purchase currency of this stock item')) + class PartThumbSerializer(serializers.Serializer): """Serializer for the 'image' field of the Part model. @@ -193,6 +193,13 @@ class PartThumbSerializer(serializers.Serializer): class PartThumbSerializerUpdate(InvenTreeModelSerializer): """Serializer for updating Part thumbnail.""" + class Meta: + """Metaclass defining serializer fields""" + model = Part + fields = [ + 'image', + ] + def validate_image(self, value): """Check that file is an image.""" validate = imghdr.what(value) @@ -202,13 +209,6 @@ class PartThumbSerializerUpdate(InvenTreeModelSerializer): image = InvenTreeAttachmentSerializerField(required=True) - class Meta: - """Metaclass defining serializer fields""" - model = Part - fields = [ - 'image', - ] - class PartParameterTemplateSerializer(InvenTreeModelSerializer): """JSON serializer for the PartParameterTemplate model.""" @@ -227,6 +227,17 @@ class PartParameterTemplateSerializer(InvenTreeModelSerializer): class PartParameterSerializer(InvenTreeModelSerializer): """JSON serializers for the PartParameter model.""" + class Meta: + """Metaclass defining serializer fields""" + model = PartParameter + fields = [ + 'pk', + 'part', + 'template', + 'template_detail', + 'data' + ] + def __init__(self, *args, **kwargs): """Custom initialization method for the serializer. @@ -242,23 +253,10 @@ class PartParameterSerializer(InvenTreeModelSerializer): template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True) - class Meta: - """Metaclass defining serializer fields""" - model = PartParameter - fields = [ - 'pk', - 'part', - 'template', - 'template_detail', - 'data' - ] - class PartBriefSerializer(InvenTreeModelSerializer): """Serializer for Part (brief detail)""" - thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) - class Meta: """Metaclass defining serializer fields""" model = Part @@ -286,6 +284,8 @@ class PartBriefSerializer(InvenTreeModelSerializer): 'barcode_hash', ] + thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) + class DuplicatePartSerializer(serializers.Serializer): """Serializer for specifying options when duplicating a Part. @@ -400,22 +400,65 @@ class PartSerializer(RemoteImageMixin, InvenTreeModelSerializer): Used when displaying all details of a single component. """ - def get_api_url(self): - """Return the API url associated with this serializer""" - return reverse_lazy('api-part-list') + class Meta: + """Metaclass defining serializer fields""" + model = Part + partial = True + fields = [ + 'active', + 'allocated_to_build_orders', + 'allocated_to_sales_orders', + 'assembly', + 'barcode_hash', + 'category', + 'category_detail', + 'component', + 'default_expiry', + 'default_location', + 'default_supplier', + 'description', + 'full_name', + 'image', + 'in_stock', + 'variant_stock', + 'ordering', + 'building', + 'IPN', + 'is_template', + 'keywords', + 'last_stocktake', + 'link', + 'minimum_stock', + 'name', + 'notes', + 'parameters', + 'pk', + 'purchaseable', + 'remote_image', + 'revision', + 'salable', + 'starred', + 'stock_item_count', + 'suppliers', + 'thumbnail', + 'trackable', + 'unallocated_stock', + 'units', + 'variant_of', + 'virtual', + 'pricing_min', + 'pricing_max', + 'responsible', - def skip_create_fields(self): - """Skip these fields when instantiating a new Part instance""" - - fields = super().skip_create_fields() - - fields += [ + # Fields only used for Part creation 'duplicate', 'initial_stock', 'initial_supplier', ] - return fields + read_only_fields = [ + 'barcode_hash', + ] def __init__(self, *args, **kwargs): """Custom initialization method for PartSerializer: @@ -443,6 +486,23 @@ class PartSerializer(RemoteImageMixin, InvenTreeModelSerializer): for f in self.skip_create_fields()[1:]: self.fields.pop(f) + def get_api_url(self): + """Return the API url associated with this serializer""" + return reverse_lazy('api-part-list') + + def skip_create_fields(self): + """Skip these fields when instantiating a new Part instance""" + + fields = super().skip_create_fields() + + fields += [ + 'duplicate', + 'initial_stock', + 'initial_supplier', + ] + + return fields + @staticmethod def annotate_queryset(queryset): """Add some extra annotations to the queryset. @@ -553,66 +613,6 @@ class PartSerializer(RemoteImageMixin, InvenTreeModelSerializer): write_only=True, required=False, ) - class Meta: - """Metaclass defining serializer fields""" - model = Part - partial = True - fields = [ - 'active', - 'allocated_to_build_orders', - 'allocated_to_sales_orders', - 'assembly', - 'barcode_hash', - 'category', - 'category_detail', - 'component', - 'default_expiry', - 'default_location', - 'default_supplier', - 'description', - 'full_name', - 'image', - 'in_stock', - 'variant_stock', - 'ordering', - 'building', - 'IPN', - 'is_template', - 'keywords', - 'last_stocktake', - 'link', - 'minimum_stock', - 'name', - 'notes', - 'parameters', - 'pk', - 'purchaseable', - 'remote_image', - 'revision', - 'salable', - 'starred', - 'stock_item_count', - 'suppliers', - 'thumbnail', - 'trackable', - 'unallocated_stock', - 'units', - 'variant_of', - 'virtual', - 'pricing_min', - 'pricing_max', - 'responsible', - - # Fields only used for Part creation - 'duplicate', - 'initial_stock', - 'initial_supplier', - ] - - read_only_fields = [ - 'barcode_hash', - ] - @transaction.atomic def create(self, validated_data): """Custom method for creating a new Part instance using this serializer""" @@ -708,16 +708,6 @@ class PartSerializer(RemoteImageMixin, InvenTreeModelSerializer): class PartStocktakeSerializer(InvenTreeModelSerializer): """Serializer for the PartStocktake model""" - quantity = serializers.FloatField() - - user_detail = UserSerializer(source='user', read_only=True, many=False) - - cost_min = InvenTreeMoneySerializer(allow_null=True) - cost_min_currency = InvenTreeCurrencySerializer() - - cost_max = InvenTreeMoneySerializer(allow_null=True) - cost_max_currency = InvenTreeCurrencySerializer() - class Meta: """Metaclass options""" @@ -742,6 +732,16 @@ class PartStocktakeSerializer(InvenTreeModelSerializer): 'user', ] + quantity = serializers.FloatField() + + user_detail = UserSerializer(source='user', read_only=True, many=False) + + cost_min = InvenTreeMoneySerializer(allow_null=True) + cost_min_currency = InvenTreeCurrencySerializer() + + cost_max = InvenTreeMoneySerializer(allow_null=True) + cost_max_currency = InvenTreeCurrencySerializer() + def save(self): """Called when this serializer is saved""" @@ -757,10 +757,6 @@ class PartStocktakeSerializer(InvenTreeModelSerializer): class PartStocktakeReportSerializer(InvenTreeModelSerializer): """Serializer for stocktake report class""" - user_detail = UserSerializer(source='user', read_only=True, many=False) - - report = InvenTreeAttachmentSerializerField(read_only=True) - class Meta: """Metaclass defines serializer fields""" @@ -774,6 +770,10 @@ class PartStocktakeReportSerializer(InvenTreeModelSerializer): 'user_detail', ] + user_detail = UserSerializer(source='user', read_only=True, many=False) + + report = InvenTreeAttachmentSerializerField(read_only=True) + class PartStocktakeReportGenerateSerializer(serializers.Serializer): """Serializer class for manually generating a new PartStocktakeReport via the API""" @@ -843,6 +843,32 @@ class PartStocktakeReportGenerateSerializer(serializers.Serializer): class PartPricingSerializer(InvenTreeModelSerializer): """Serializer for Part pricing information""" + class Meta: + """Metaclass defining serializer fields""" + model = PartPricing + fields = [ + 'currency', + 'updated', + 'scheduled_for_update', + 'bom_cost_min', + 'bom_cost_max', + 'purchase_cost_min', + 'purchase_cost_max', + 'internal_cost_min', + 'internal_cost_max', + 'supplier_price_min', + 'supplier_price_max', + 'variant_cost_min', + 'variant_cost_max', + 'overall_min', + 'overall_max', + 'sale_price_min', + 'sale_price_max', + 'sale_history_min', + 'sale_history_max', + 'update', + ] + currency = serializers.CharField(allow_null=True, read_only=True) updated = serializers.DateTimeField(allow_null=True, read_only=True) @@ -882,32 +908,6 @@ class PartPricingSerializer(InvenTreeModelSerializer): required=False, ) - class Meta: - """Metaclass defining serializer fields""" - model = PartPricing - fields = [ - 'currency', - 'updated', - 'scheduled_for_update', - 'bom_cost_min', - 'bom_cost_max', - 'purchase_cost_min', - 'purchase_cost_max', - 'internal_cost_min', - 'internal_cost_max', - 'supplier_price_min', - 'supplier_price_max', - 'variant_cost_min', - 'variant_cost_max', - 'overall_min', - 'overall_max', - 'sale_price_min', - 'sale_price_max', - 'sale_history_min', - 'sale_history_max', - 'update', - ] - def save(self): """Called when the serializer is saved""" data = self.validated_data @@ -921,9 +921,6 @@ class PartPricingSerializer(InvenTreeModelSerializer): class PartRelationSerializer(InvenTreeModelSerializer): """Serializer for a PartRelated model.""" - part_1_detail = PartSerializer(source='part_1', read_only=True, many=False) - part_2_detail = PartSerializer(source='part_2', read_only=True, many=False) - class Meta: """Metaclass defining serializer fields""" model = PartRelated @@ -935,13 +932,13 @@ class PartRelationSerializer(InvenTreeModelSerializer): 'part_2_detail', ] + part_1_detail = PartSerializer(source='part_1', read_only=True, many=False) + part_2_detail = PartSerializer(source='part_2', read_only=True, many=False) + class PartStarSerializer(InvenTreeModelSerializer): """Serializer for a PartStar object.""" - partname = serializers.CharField(source='part.full_name', read_only=True) - username = serializers.CharField(source='user.username', read_only=True) - class Meta: """Metaclass defining serializer fields""" model = PartStar @@ -953,12 +950,13 @@ class PartStarSerializer(InvenTreeModelSerializer): 'username', ] + partname = serializers.CharField(source='part.full_name', read_only=True) + username = serializers.CharField(source='user.username', read_only=True) + class BomItemSubstituteSerializer(InvenTreeModelSerializer): """Serializer for the BomItemSubstitute class.""" - part_detail = PartBriefSerializer(source='part', read_only=True, many=False) - class Meta: """Metaclass defining serializer fields""" model = BomItemSubstitute @@ -969,10 +967,60 @@ class BomItemSubstituteSerializer(InvenTreeModelSerializer): 'part_detail', ] + part_detail = PartBriefSerializer(source='part', read_only=True, many=False) + class BomItemSerializer(InvenTreeModelSerializer): """Serializer for BomItem object.""" + class Meta: + """Metaclass defining serializer fields""" + model = BomItem + fields = [ + 'allow_variants', + 'inherited', + 'note', + 'optional', + 'consumable', + 'overage', + 'pk', + 'part', + 'part_detail', + 'pricing_min', + 'pricing_max', + 'quantity', + 'reference', + 'sub_part', + 'sub_part_detail', + 'substitutes', + 'validated', + + # Annotated fields describing available quantity + 'available_stock', + 'available_substitute_stock', + 'available_variant_stock', + + # Annotated field describing quantity on order + 'on_order', + ] + + def __init__(self, *args, **kwargs): + """Determine if extra detail fields are to be annotated on this serializer + + - part_detail and sub_part_detail serializers are only included if requested. + - This saves a bunch of database requests + """ + part_detail = kwargs.pop('part_detail', False) + sub_part_detail = kwargs.pop('sub_part_detail', False) + + super(BomItemSerializer, self).__init__(*args, **kwargs) + + if part_detail is not True: + self.fields.pop('part_detail') + + if sub_part_detail is not True: + self.fields.pop('sub_part_detail') + quantity = InvenTreeDecimalField(required=True) def validate_quantity(self, quantity): @@ -1005,23 +1053,6 @@ class BomItemSerializer(InvenTreeModelSerializer): available_substitute_stock = serializers.FloatField(read_only=True) available_variant_stock = serializers.FloatField(read_only=True) - def __init__(self, *args, **kwargs): - """Determine if extra detail fields are to be annotated on this serializer - - - part_detail and sub_part_detail serializers are only included if requested. - - This saves a bunch of database requests - """ - part_detail = kwargs.pop('part_detail', False) - sub_part_detail = kwargs.pop('sub_part_detail', False) - - super(BomItemSerializer, self).__init__(*args, **kwargs) - - if part_detail is not True: - self.fields.pop('part_detail') - - if sub_part_detail is not True: - self.fields.pop('sub_part_detail') - @staticmethod def setup_eager_loading(queryset): """Prefetch against the provided queryset to speed up database access""" @@ -1116,45 +1147,10 @@ class BomItemSerializer(InvenTreeModelSerializer): return queryset - class Meta: - """Metaclass defining serializer fields""" - model = BomItem - fields = [ - 'allow_variants', - 'inherited', - 'note', - 'optional', - 'consumable', - 'overage', - 'pk', - 'part', - 'part_detail', - 'pricing_min', - 'pricing_max', - 'quantity', - 'reference', - 'sub_part', - 'sub_part_detail', - 'substitutes', - 'validated', - - # Annotated fields describing available quantity - 'available_stock', - 'available_substitute_stock', - 'available_variant_stock', - - # Annotated field describing quantity on order - 'on_order', - ] - class CategoryParameterTemplateSerializer(InvenTreeModelSerializer): """Serializer for the PartCategoryParameterTemplate model.""" - parameter_template_detail = PartParameterTemplateSerializer(source='parameter_template', many=False, read_only=True) - - category_detail = CategorySerializer(source='category', many=False, read_only=True) - class Meta: """Metaclass defining serializer fields""" model = PartCategoryParameterTemplate @@ -1167,6 +1163,10 @@ class CategoryParameterTemplateSerializer(InvenTreeModelSerializer): 'default_value', ] + parameter_template_detail = PartParameterTemplateSerializer(source='parameter_template', many=False, read_only=True) + + category_detail = CategorySerializer(source='category', many=False, read_only=True) + class PartCopyBOMSerializer(serializers.Serializer): """Serializer for copying a BOM from another part.""" diff --git a/InvenTree/plugin/serializers.py b/InvenTree/plugin/serializers.py index 913ed66011..c7c892fe77 100644 --- a/InvenTree/plugin/serializers.py +++ b/InvenTree/plugin/serializers.py @@ -18,11 +18,6 @@ class MetadataSerializer(serializers.ModelSerializer): metadata = serializers.JSONField(required=True) - def __init__(self, model_type, *args, **kwargs): - """Initialize the metadata serializer with information on the model type""" - self.Meta.model = model_type - super().__init__(*args, **kwargs) - class Meta: """Metaclass options.""" @@ -30,6 +25,11 @@ class MetadataSerializer(serializers.ModelSerializer): 'metadata', ] + def __init__(self, model_type, *args, **kwargs): + """Initialize the metadata serializer with information on the model type""" + self.Meta.model = model_type + super().__init__(*args, **kwargs) + def update(self, instance, data): """Perform update on the metadata field: @@ -48,9 +48,6 @@ class MetadataSerializer(serializers.ModelSerializer): class PluginConfigSerializer(serializers.ModelSerializer): """Serializer for a PluginConfig.""" - meta = serializers.DictField(read_only=True) - mixins = serializers.DictField(read_only=True) - class Meta: """Meta for serializer.""" model = PluginConfig @@ -62,10 +59,21 @@ class PluginConfigSerializer(serializers.ModelSerializer): 'mixins', ] + meta = serializers.DictField(read_only=True) + mixins = serializers.DictField(read_only=True) + class PluginConfigInstallSerializer(serializers.Serializer): """Serializer for installing a new plugin.""" + class Meta: + """Meta for serializer.""" + fields = [ + 'url', + 'packagename', + 'confirm', + ] + url = serializers.CharField( required=False, allow_blank=True, @@ -83,14 +91,6 @@ class PluginConfigInstallSerializer(serializers.Serializer): help_text=_('This will install this plugin now into the current instance. The instance will go into maintenance.') ) - class Meta: - """Meta for serializer.""" - fields = [ - 'url', - 'packagename', - 'confirm', - ] - def validate(self, data): """Validate inputs. diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index ea454d581d..668fe0509c 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -180,6 +180,10 @@ class ReportTemplateBase(ReportBase): Able to be passed context data """ + class Meta: + """Metaclass options. Abstract ensures no database table is created.""" + abstract = True + # Pass a single top-level object to the report template object_to_print = None @@ -255,11 +259,6 @@ class ReportTemplateBase(ReportBase): help_text=_('Report template is enabled'), ) - class Meta: - """Metaclass options. Abstract ensures no database table is created.""" - - abstract = True - class TestReport(ReportTemplateBase): """Render a TestReport against a StockItem object.""" diff --git a/InvenTree/report/serializers.py b/InvenTree/report/serializers.py index 50e9592373..155da6e7cc 100644 --- a/InvenTree/report/serializers.py +++ b/InvenTree/report/serializers.py @@ -10,8 +10,6 @@ from .models import (BillOfMaterialsReport, BuildReport, PurchaseOrderReport, class TestReportSerializer(InvenTreeModelSerializer): """Serializer class for the TestReport model""" - template = InvenTreeAttachmentSerializerField(required=True) - class Meta: """Metaclass options.""" @@ -25,12 +23,12 @@ class TestReportSerializer(InvenTreeModelSerializer): 'enabled', ] + template = InvenTreeAttachmentSerializerField(required=True) + class BuildReportSerializer(InvenTreeModelSerializer): """Serializer class for the BuildReport model""" - template = InvenTreeAttachmentSerializerField(required=True) - class Meta: """Metaclass options.""" @@ -44,10 +42,11 @@ class BuildReportSerializer(InvenTreeModelSerializer): 'enabled', ] + template = InvenTreeAttachmentSerializerField(required=True) + class BOMReportSerializer(InvenTreeModelSerializer): """Serializer class for the BillOfMaterialsReport model""" - template = InvenTreeAttachmentSerializerField(required=True) class Meta: """Metaclass options.""" @@ -62,10 +61,11 @@ class BOMReportSerializer(InvenTreeModelSerializer): 'enabled', ] + template = InvenTreeAttachmentSerializerField(required=True) + class PurchaseOrderReportSerializer(InvenTreeModelSerializer): """Serializer class for the PurchaseOrdeReport model""" - template = InvenTreeAttachmentSerializerField(required=True) class Meta: """Metaclass options.""" @@ -80,10 +80,11 @@ class PurchaseOrderReportSerializer(InvenTreeModelSerializer): 'enabled', ] + template = InvenTreeAttachmentSerializerField(required=True) + class SalesOrderReportSerializer(InvenTreeModelSerializer): """Serializer class for the SalesOrderReport model""" - template = InvenTreeAttachmentSerializerField(required=True) class Meta: """Metaclass options.""" @@ -97,3 +98,5 @@ class SalesOrderReportSerializer(InvenTreeModelSerializer): 'filters', 'enabled', ] + + template = InvenTreeAttachmentSerializerField(required=True) diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 7f16d2d5c8..0703688be5 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -20,16 +20,6 @@ from .models import (StockItem, StockItemAttachment, StockItemTestResult, class LocationResource(InvenTreeResource): """Class for managing StockLocation data import/export.""" - id = Field(attribute='pk', column_name=_('Location ID')) - name = Field(attribute='name', column_name=_('Location Name')) - description = Field(attribute='description', column_name=_('Description')) - parent = Field(attribute='parent', column_name=_('Parent ID'), widget=widgets.ForeignKeyWidget(StockLocation)) - parent_name = Field(attribute='parent__name', column_name=_('Parent Name'), readonly=True) - pathstring = Field(attribute='pathstring', column_name=_('Location Path')) - - # Calculated fields - items = Field(attribute='item_count', column_name=_('Stock Items'), widget=widgets.IntegerWidget()) - class Meta: """Metaclass options.""" @@ -46,6 +36,16 @@ class LocationResource(InvenTreeResource): 'owner', 'icon', ] + id = Field(attribute='pk', column_name=_('Location ID')) + name = Field(attribute='name', column_name=_('Location Name')) + description = Field(attribute='description', column_name=_('Description')) + parent = Field(attribute='parent', column_name=_('Parent ID'), widget=widgets.ForeignKeyWidget(StockLocation)) + parent_name = Field(attribute='parent__name', column_name=_('Parent Name'), readonly=True) + pathstring = Field(attribute='pathstring', column_name=_('Location Path')) + + # Calculated fields + items = Field(attribute='item_count', column_name=_('Stock Items'), widget=widgets.IntegerWidget()) + def after_import(self, dataset, result, using_transactions, dry_run, **kwargs): """Rebuild after import to keep tree intact.""" super().after_import(dataset, result, using_transactions, dry_run, **kwargs) @@ -80,6 +80,23 @@ class LocationAdmin(ImportExportModelAdmin): class StockItemResource(InvenTreeResource): """Class for managing StockItem data import/export.""" + class Meta: + """Metaclass options.""" + + model = StockItem + skip_unchanged = True + report_skipped = False + clean_model_instance = True + + exclude = [ + # Exclude MPTT internal model fields + 'lft', 'rght', 'tree_id', 'level', + # Exclude internal fields + 'serial_int', 'metadata', + 'barcode_hash', 'barcode_data', + 'owner', + ] + id = Field(attribute='pk', column_name=_('Stock Item ID')) part = Field(attribute='part', column_name=_('Part ID'), widget=widgets.ForeignKeyWidget(Part)) part_name = Field(attribute='part__full_name', column_name=_('Part Name'), readonly=True) @@ -114,23 +131,6 @@ class StockItemResource(InvenTreeResource): # Rebuild the StockItem tree(s) StockItem.objects.rebuild() - class Meta: - """Metaclass options.""" - - model = StockItem - skip_unchanged = True - report_skipped = False - clean_model_instance = True - - exclude = [ - # Exclude MPTT internal model fields - 'lft', 'rght', 'tree_id', 'level', - # Exclude internal fields - 'serial_int', 'metadata', - 'barcode_hash', 'barcode_data', - 'owner', - ] - class StockItemAdmin(ImportExportModelAdmin): """Admin class for StockItem.""" diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 58871a55b2..99b0866792 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -46,10 +46,6 @@ class LocationBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer): class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer): """Brief serializers for a StockItem.""" - part_name = serializers.CharField(source='part.full_name', read_only=True) - - quantity = InvenTreeDecimalField() - class Meta: """Metaclass options.""" @@ -69,6 +65,10 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer): 'barcode_hash', ] + part_name = serializers.CharField(source='part.full_name', read_only=True) + + quantity = InvenTreeDecimalField() + def validate_serial(self, value): """Make sure serial is not to big.""" if abs(extract_int(value)) > 0x7fffffff: @@ -83,6 +83,60 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer): - Includes serialization for the item location """ + class Meta: + """Metaclass options.""" + + model = StockItem + fields = [ + 'allocated', + 'batch', + 'belongs_to', + 'build', + 'customer', + 'delete_on_deplete', + 'expired', + 'expiry_date', + 'is_building', + 'link', + 'location', + 'location_detail', + 'notes', + 'owner', + 'packaging', + 'part', + 'part_detail', + 'purchase_order', + 'purchase_order_reference', + 'pk', + 'quantity', + 'sales_order', + 'sales_order_reference', + 'serial', + 'stale', + 'status', + 'status_text', + 'stocktake_date', + 'supplier_part', + 'supplier_part_detail', + 'tracking_items', + 'barcode_hash', + 'updated', + 'purchase_price', + 'purchase_price_currency', + ] + + """ + These fields are read-only in this context. + They can be updated by accessing the appropriate API endpoints + """ + read_only_fields = [ + 'allocated', + 'barcode_hash', + 'stocktake_date', + 'stocktake_user', + 'updated', + ] + part = serializers.PrimaryKeyRelatedField( queryset=part_models.Part.objects.all(), many=False, allow_null=False, @@ -197,60 +251,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer): if supplier_part_detail is not True: self.fields.pop('supplier_part_detail') - class Meta: - """Metaclass options.""" - - model = StockItem - fields = [ - 'allocated', - 'batch', - 'belongs_to', - 'build', - 'customer', - 'delete_on_deplete', - 'expired', - 'expiry_date', - 'is_building', - 'link', - 'location', - 'location_detail', - 'notes', - 'owner', - 'packaging', - 'part', - 'part_detail', - 'purchase_order', - 'purchase_order_reference', - 'pk', - 'quantity', - 'sales_order', - 'sales_order_reference', - 'serial', - 'stale', - 'status', - 'status_text', - 'stocktake_date', - 'supplier_part', - 'supplier_part_detail', - 'tracking_items', - 'barcode_hash', - 'updated', - 'purchase_price', - 'purchase_price_currency', - ] - - """ - These fields are read-only in this context. - They can be updated by accessing the appropriate API endpoints - """ - read_only_fields = [ - 'allocated', - 'barcode_hash', - 'stocktake_date', - 'stocktake_user', - 'updated', - ] - class SerializeStockItemSerializer(serializers.Serializer): """A DRF serializer for "serializing" a StockItem. @@ -567,23 +567,6 @@ class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer): class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer): """Detailed information about a stock location.""" - @staticmethod - def annotate_queryset(queryset): - """Annotate extra information to the queryset""" - - # Annotate the number of stock items which exist in this category (including subcategories) - queryset = queryset.annotate( - items=stock.filters.annotate_location_items() - ) - - return queryset - - url = serializers.CharField(source='get_absolute_url', read_only=True) - - items = serializers.IntegerField(read_only=True) - - level = serializers.IntegerField(read_only=True) - class Meta: """Metaclass options.""" @@ -607,6 +590,23 @@ class LocationSerializer(InvenTree.serializers.InvenTreeModelSerializer): 'barcode_hash', ] + @staticmethod + def annotate_queryset(queryset): + """Annotate extra information to the queryset""" + + # Annotate the number of stock items which exist in this category (including subcategories) + queryset = queryset.annotate( + items=stock.filters.annotate_location_items() + ) + + return queryset + + url = serializers.CharField(source='get_absolute_url', read_only=True) + + items = serializers.IntegerField(read_only=True) + + level = serializers.IntegerField(read_only=True) + class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSerializer): """Serializer for StockItemAttachment model.""" @@ -624,21 +624,6 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer): """Serializer for the StockItemTestResult model.""" - user_detail = InvenTree.serializers.UserSerializer(source='user', read_only=True) - - key = serializers.CharField(read_only=True) - - attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=False) - - def __init__(self, *args, **kwargs): - """Add detail fields.""" - user_detail = kwargs.pop('user_detail', False) - - super().__init__(*args, **kwargs) - - if user_detail is not True: - self.fields.pop('user_detail') - class Meta: """Metaclass options.""" @@ -664,30 +649,24 @@ class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializ 'date', ] - -class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer): - """Serializer for StockItemTracking model.""" - def __init__(self, *args, **kwargs): """Add detail fields.""" - item_detail = kwargs.pop('item_detail', False) user_detail = kwargs.pop('user_detail', False) super().__init__(*args, **kwargs) - if item_detail is not True: - self.fields.pop('item_detail') - if user_detail is not True: self.fields.pop('user_detail') - label = serializers.CharField(read_only=True) + user_detail = InvenTree.serializers.UserSerializer(source='user', read_only=True) - item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True) + key = serializers.CharField(read_only=True) - user_detail = InvenTree.serializers.UserSerializer(source='user', many=False, read_only=True) + attachment = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=False) - deltas = serializers.JSONField(read_only=True) + +class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer): + """Serializer for StockItemTracking model.""" class Meta: """Metaclass options.""" @@ -713,6 +692,27 @@ class StockTrackingSerializer(InvenTree.serializers.InvenTreeModelSerializer): 'tracking_type', ] + def __init__(self, *args, **kwargs): + """Add detail fields.""" + item_detail = kwargs.pop('item_detail', False) + user_detail = kwargs.pop('user_detail', False) + + super().__init__(*args, **kwargs) + + if item_detail is not True: + self.fields.pop('item_detail') + + if user_detail is not True: + self.fields.pop('user_detail') + + label = serializers.CharField(read_only=True) + + item_detail = StockItemSerializerBrief(source='item', many=False, read_only=True) + + user_detail = InvenTree.serializers.UserSerializer(source='user', many=False, read_only=True) + + deltas = serializers.JSONField(read_only=True) + class StockAssignmentItemSerializer(serializers.Serializer): """Serializer for a single StockItem with in StockAssignment request. @@ -1124,15 +1124,6 @@ class StockRemoveSerializer(StockAdjustmentSerializer): class StockTransferSerializer(StockAdjustmentSerializer): """Serializer for transferring (moving) stock item(s).""" - location = serializers.PrimaryKeyRelatedField( - queryset=StockLocation.objects.all(), - many=False, - required=True, - allow_null=False, - label=_('Location'), - help_text=_('Destination stock location'), - ) - class Meta: """Metaclass options.""" @@ -1142,6 +1133,15 @@ class StockTransferSerializer(StockAdjustmentSerializer): 'location', ] + location = serializers.PrimaryKeyRelatedField( + queryset=StockLocation.objects.all(), + many=False, + required=True, + allow_null=False, + label=_('Location'), + help_text=_('Destination stock location'), + ) + def save(self): """Transfer stock.""" request = self.context['request'] diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index c4b81fc174..f5d5ce2148 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -562,6 +562,14 @@ class Owner(models.Model): owner: Returns the Group or User instance combining the owner_type and owner_id fields """ + class Meta: + """Metaclass defines extra model properties""" + # Ensure all owners are unique + constraints = [ + UniqueConstraint(fields=['owner_type', 'owner_id'], + name='unique_owner') + ] + @classmethod def get_owners_matching_user(cls, user): """Return all "owner" objects matching the provided user. @@ -594,14 +602,6 @@ class Owner(models.Model): """Returns the API endpoint URL associated with the Owner model""" return reverse('api-owner-list') - class Meta: - """Metaclass defines extra model properties""" - # Ensure all owners are unique - constraints = [ - UniqueConstraint(fields=['owner_type', 'owner_id'], - name='unique_owner') - ] - owner_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True) owner_id = models.PositiveIntegerField(null=True, blank=True) diff --git a/InvenTree/users/serializers.py b/InvenTree/users/serializers.py index 153fd02d8e..9b4fd48353 100644 --- a/InvenTree/users/serializers.py +++ b/InvenTree/users/serializers.py @@ -12,10 +12,6 @@ from .models import Owner class OwnerSerializer(InvenTreeModelSerializer): """Serializer for an "Owner" (either a "user" or a "group")""" - name = serializers.CharField(read_only=True) - - label = serializers.CharField(read_only=True) - class Meta: """Metaclass defines serializer fields.""" model = Owner @@ -26,6 +22,10 @@ class OwnerSerializer(InvenTreeModelSerializer): 'label', ] + name = serializers.CharField(read_only=True) + + label = serializers.CharField(read_only=True) + class GroupSerializer(InvenTreeModelSerializer): """Serializer for a 'Group'"""