Move Meta class to top of class definition (#4363)

This commit is contained in:
Oliver 2023-02-18 18:51:00 +11:00 committed by GitHub
parent 139274f356
commit cc2e7ee8a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1085 additions and 1084 deletions

View File

@ -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):

View File

@ -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:

View File

@ -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"""

View File

@ -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()

View File

@ -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."""

View File

@ -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,

View File

@ -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.

View File

@ -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"""

View File

@ -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.

View File

@ -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):

View File

@ -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',
]

View File

@ -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)

View File

@ -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."""

View File

@ -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."""

View File

@ -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(

View File

@ -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."""

View File

@ -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"""

View File

@ -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')
)

View File

@ -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

View File

@ -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."""

View File

@ -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.

View File

@ -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."""

View File

@ -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)

View File

@ -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."""

View File

@ -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']

View File

@ -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)

View File

@ -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'"""