mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Move Meta class to top of class definition (#4363)
This commit is contained in:
parent
139274f356
commit
cc2e7ee8a5
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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"""
|
||||
|
@ -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()
|
||||
|
@ -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."""
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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"""
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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."""
|
||||
|
@ -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(
|
||||
|
@ -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."""
|
||||
|
@ -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"""
|
||||
|
@ -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')
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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'"""
|
||||
|
Loading…
Reference in New Issue
Block a user