From 5cf30a850dfd040ad857d2eb040c705055fa9b55 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 4 May 2022 17:00:37 +1000 Subject: [PATCH] Prune a lot of dead code --- InvenTree/part/forms.py | 18 - InvenTree/part/views.py | 39 -- InvenTree/stock/forms.py | 170 -------- InvenTree/stock/views.py | 818 --------------------------------------- 4 files changed, 1045 deletions(-) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 2ba60d111b..87210901d2 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -95,24 +95,6 @@ class EditPartParameterTemplateForm(HelperForm): ] -class EditCategoryForm(HelperForm): - """ Form for editing a PartCategory object """ - - field_prefix = { - 'default_keywords': 'fa-key', - } - - class Meta: - model = PartCategory - fields = [ - 'parent', - 'name', - 'description', - 'default_location', - 'default_keywords', - ] - - class EditCategoryParameterTemplateForm(HelperForm): """ Form for editing a PartCategoryParameterTemplate object """ diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 41e734ce2a..efaf83ae95 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1001,45 +1001,6 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView): return context -class CategoryEdit(AjaxUpdateView): - """ - Update view to edit a PartCategory - """ - - model = PartCategory - form_class = part_forms.EditCategoryForm - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Edit Part Category') - - def get_context_data(self, **kwargs): - context = super(CategoryEdit, self).get_context_data(**kwargs).copy() - - try: - context['category'] = self.get_object() - except: - pass - - return context - - def get_form(self): - """ Customize form data for PartCategory editing. - - Limit the choices for 'parent' field to those which make sense - """ - - form = super(AjaxUpdateView, self).get_form() - - category = self.get_object() - - # Remove any invalid choices for the parent category part - parent_choices = PartCategory.objects.all() - parent_choices = parent_choices.exclude(id__in=category.getUniqueChildren()) - - form.fields['parent'].queryset = parent_choices - - return form - - class CategoryDelete(AjaxDeleteView): """ Delete view to delete a PartCategory diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index 3860936ae7..8e0017b3fd 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -32,23 +32,6 @@ class ReturnStockItemForm(HelperForm): ] -class EditStockLocationForm(HelperForm): - """ - Form for editing a StockLocation - - TODO: Migrate this form to the modern API forms interface - """ - - class Meta: - model = StockLocation - fields = [ - 'name', - 'parent', - 'description', - 'owner', - ] - - class ConvertStockItemForm(HelperForm): """ Form for converting a StockItem to a variant of its current part. @@ -63,159 +46,6 @@ class ConvertStockItemForm(HelperForm): ] -class CreateStockItemForm(HelperForm): - """ - Form for creating a new StockItem - - TODO: Migrate this form to the modern API forms interface - """ - - expiry_date = DatePickerFormField( - label=_('Expiry Date'), - help_text=_('Expiration date for this stock item'), - ) - - serial_numbers = forms.CharField(label=_('Serial Numbers'), required=False, help_text=_('Enter unique serial numbers (or leave blank)')) - - def __init__(self, *args, **kwargs): - - self.field_prefix = { - 'serial_numbers': 'fa-hashtag', - 'link': 'fa-link', - } - - super().__init__(*args, **kwargs) - - class Meta: - model = StockItem - fields = [ - 'part', - 'supplier_part', - 'location', - 'quantity', - 'batch', - 'serial_numbers', - 'packaging', - 'purchase_price', - 'expiry_date', - 'link', - 'delete_on_deplete', - 'status', - 'owner', - ] - - # Custom clean to prevent complex StockItem.clean() logic from running (yet) - def full_clean(self): - self._errors = ErrorDict() - - if not self.is_bound: # Stop further processing. - return - - self.cleaned_data = {} - - # If the form is permitted to be empty, and none of the form data has - # changed from the initial data, short circuit any validation. - if self.empty_permitted and not self.has_changed(): - return - - # Don't run _post_clean() as this will run StockItem.clean() - self._clean_fields() - self._clean_form() - - -class SerializeStockForm(HelperForm): - """ - Form for serializing a StockItem. - - TODO: Migrate this form to the modern API forms interface - """ - - destination = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Destination'), required=True, help_text=_('Destination for serialized stock (by default, will remain in current location)')) - - serial_numbers = forms.CharField(label=_('Serial numbers'), required=True, help_text=_('Unique serial numbers (must match quantity)')) - - note = forms.CharField(label=_('Notes'), required=False, help_text=_('Add transaction note (optional)')) - - quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5, label=_('Quantity')) - - def __init__(self, *args, **kwargs): - - # Extract the stock item - item = kwargs.pop('item', None) - - if item: - self.field_placeholder['serial_numbers'] = item.part.getSerialNumberString(item.quantity) - - super().__init__(*args, **kwargs) - - class Meta: - model = StockItem - - fields = [ - 'quantity', - 'serial_numbers', - 'destination', - 'note', - ] - - -class UninstallStockForm(forms.ModelForm): - """ - Form for uninstalling a stock item which is installed in another item. - - TODO: Migrate this form to the modern API forms interface - """ - - location = TreeNodeChoiceField(queryset=StockLocation.objects.all(), label=_('Location'), help_text=_('Destination location for uninstalled items')) - - note = forms.CharField(label=_('Notes'), required=False, help_text=_('Add transaction note (optional)')) - - confirm = forms.BooleanField(required=False, initial=False, label=_('Confirm uninstall'), help_text=_('Confirm removal of installed stock items')) - - class Meta: - - model = StockItem - - fields = [ - 'location', - 'note', - 'confirm', - ] - - -class EditStockItemForm(HelperForm): - """ Form for editing a StockItem object. - Note that not all fields can be edited here (even if they can be specified during creation. - - location - Must be updated in a 'move' transaction - quantity - Must be updated in a 'stocktake' transaction - part - Cannot be edited after creation - - TODO: Migrate this form to the modern API forms interface - """ - - expiry_date = DatePickerFormField( - label=_('Expiry Date'), - help_text=_('Expiration date for this stock item'), - ) - - class Meta: - model = StockItem - - fields = [ - 'supplier_part', - 'serial', - 'batch', - 'status', - 'expiry_date', - 'purchase_price', - 'packaging', - 'link', - 'delete_on_deplete', - 'owner', - ] - - class TrackingEntryForm(HelperForm): """ Form for creating / editing a StockItemTracking object. diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 3bca77ec9e..1b9aba04c8 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -19,7 +19,6 @@ from django.utils.translation import gettext_lazy as _ from moneyed import CURRENCIES -from InvenTree.views import AjaxView from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView from InvenTree.views import QRCodeView from InvenTree.views import InvenTreeRoleMixin @@ -135,139 +134,6 @@ class StockItemDetail(InvenTreeRoleMixin, DetailView): return super().get(request, *args, **kwargs) -class StockLocationEdit(AjaxUpdateView): - """ - View for editing details of a StockLocation. - This view is used with the EditStockLocationForm to deliver a modal form to the web view - - TODO: Remove this code as location editing has been migrated to the API forms - - Have to still validate that all form functionality (as below) as been ported - - """ - - model = StockLocation - form_class = StockForms.EditStockLocationForm - context_object_name = 'location' - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Edit Stock Location') - - def get_form(self): - """ Customize form data for StockLocation editing. - - Limit the choices for 'parent' field to those which make sense. - If ownership control is enabled and location has parent, disable owner field. - """ - - form = super(AjaxUpdateView, self).get_form() - - location = self.get_object() - - # Remove any invalid choices for the 'parent' field - parent_choices = StockLocation.objects.all() - parent_choices = parent_choices.exclude(id__in=location.getUniqueChildren()) - - form.fields['parent'].queryset = parent_choices - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if not stock_ownership_control: - # Hide owner field - form.fields['owner'].widget = HiddenInput() - else: - # Get location's owner - location_owner = location.owner - - if location_owner: - if location.parent: - try: - # If location has parent and owner: automatically select parent's owner - parent_owner = location.parent.owner - form.fields['owner'].initial = parent_owner - except AttributeError: - pass - else: - # If current owner exists: automatically select it - form.fields['owner'].initial = location_owner - - # Update queryset or disable field (only if not admin) - if not self.request.user.is_superuser: - if type(location_owner.owner) is Group: - user_as_owner = Owner.get_owner(self.request.user) - queryset = location_owner.get_related_owners(include_group=True) - - if user_as_owner not in queryset: - # Only owners or admin can change current owner - form.fields['owner'].disabled = True - else: - form.fields['owner'].queryset = queryset - - return form - - def save(self, object, form, **kwargs): - """ If location has children and ownership control is enabled: - - update owner of all children location of this location - - update owner for all stock items at this location - """ - - self.object = form.save() - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if stock_ownership_control and self.object.owner: - # Get authorized users - authorized_owners = self.object.owner.get_related_owners() - - # Update children locations - children_locations = self.object.get_children() - for child in children_locations: - # Check if current owner is subset of new owner - if child.owner and authorized_owners: - if child.owner in authorized_owners: - continue - - child.owner = self.object.owner - child.save() - - # Update stock items - stock_items = self.object.get_stock_items() - - for stock_item in stock_items: - # Check if current owner is subset of new owner - if stock_item.owner and authorized_owners: - if stock_item.owner in authorized_owners: - continue - - stock_item.owner = self.object.owner - stock_item.save() - - return self.object - - def validate(self, item, form): - """ Check that owner is set if stock ownership control is enabled """ - - parent = form.cleaned_data.get('parent', None) - - owner = form.cleaned_data.get('owner', None) - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if stock_ownership_control: - if not owner and not self.request.user.is_superuser: - form.add_error('owner', _('Owner is required (ownership control is enabled)')) - else: - try: - if parent.owner: - if parent.owner != owner: - error = f'Owner requires to be equivalent to parent\'s owner ({parent.owner})' - form.add_error('owner', error) - except AttributeError: - # No parent - pass - - class StockLocationQRCode(QRCodeView): """ View for displaying a QR code for a StockLocation object """ @@ -366,261 +232,6 @@ class StockItemQRCode(QRCodeView): return None -class StockItemUninstall(AjaxView, FormMixin): - """ - View for uninstalling one or more StockItems, - which are installed in another stock item. - - Stock items are uninstalled into a location, - defaulting to the location that they were "in" before they were installed. - - If multiple default locations are detected, - leave the final location up to the user. - """ - - ajax_template_name = 'stock/stock_uninstall.html' - ajax_form_title = _('Uninstall Stock Items') - form_class = StockForms.UninstallStockForm - role_required = 'stock.change' - - # List of stock items to uninstall (initially empty) - stock_items = [] - - def get_stock_items(self): - - return self.stock_items - - def get_initial(self): - - initials = super().get_initial().copy() - - # Keep track of the current locations of stock items - current_locations = set() - - # Keep track of the default locations for stock items - default_locations = set() - - for item in self.stock_items: - - if item.location: - current_locations.add(item.location) - - if item.part.default_location: - default_locations.add(item.part.default_location) - - if len(current_locations) == 1: - # If the selected stock items are currently in a single location, - # select that location as the destination. - initials['location'] = next(iter(current_locations)) - elif len(current_locations) == 0: - # There are no current locations set - if len(default_locations) == 1: - # Select the single default location - initials['location'] = next(iter(default_locations)) - - return initials - - def get(self, request, *args, **kwargs): - - """ Extract list of stock items, which are supplied as a list, - e.g. items[]=1,2,3 - """ - - if 'items[]' in request.GET: - self.stock_items = StockItem.objects.filter(id__in=request.GET.getlist('items[]')) - else: - self.stock_items = [] - - return self.renderJsonResponse(request, self.get_form()) - - def post(self, request, *args, **kwargs): - - """ - Extract a list of stock items which are included as hidden inputs in the form data. - """ - - items = [] - - for item in self.request.POST: - if item.startswith('stock-item-'): - pk = item.replace('stock-item-', '') - - try: - stock_item = StockItem.objects.get(pk=pk) - items.append(stock_item) - except (ValueError, StockItem.DoesNotExist): - pass - - self.stock_items = items - - # Assume the form is valid, until it isn't! - valid = True - - confirmed = str2bool(request.POST.get('confirm')) - - note = request.POST.get('note', '') - - location = request.POST.get('location', None) - - if location: - try: - location = StockLocation.objects.get(pk=location) - except (ValueError, StockLocation.DoesNotExist): - location = None - - if not location: - # Location is required! - valid = False - - form = self.get_form() - - if not confirmed: - valid = False - form.add_error('confirm', _('Confirm stock adjustment')) - - data = { - 'form_valid': valid, - } - - if valid: - # Ok, now let's actually uninstall the stock items - for item in self.stock_items: - item.uninstallIntoLocation(location, request.user, note) - - data['success'] = _('Uninstalled stock items') - - return self.renderJsonResponse(request, form=form, data=data) - - def get_context_data(self): - - context = super().get_context_data() - - context['stock_items'] = self.get_stock_items() - - return context - - -class StockItemEdit(AjaxUpdateView): - """ - View for editing details of a single StockItem - """ - - model = StockItem - form_class = StockForms.EditStockItemForm - context_object_name = 'item' - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Edit Stock Item') - - def get_form(self): - """ Get form for StockItem editing. - - Limit the choices for supplier_part - """ - - form = super(AjaxUpdateView, self).get_form() - - # Hide the "expiry date" field if the feature is not enabled - if not common.settings.stock_expiry_enabled(): - form.fields['expiry_date'].widget = HiddenInput() - - item = self.get_object() - - # If the part cannot be purchased, hide the supplier_part field - if not item.part.purchaseable: - form.fields['supplier_part'].widget = HiddenInput() - - form.fields.pop('purchase_price') - else: - query = form.fields['supplier_part'].queryset - query = query.filter(part=item.part.id) - form.fields['supplier_part'].queryset = query - - # Hide the serial number field if it is not required - if not item.part.trackable and not item.serialized: - form.fields['serial'].widget = HiddenInput() - - location = item.location - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if not stock_ownership_control: - form.fields['owner'].widget = HiddenInput() - else: - try: - location_owner = location.owner - except AttributeError: - location_owner = None - - # Check if location has owner - if location_owner: - form.fields['owner'].initial = location_owner - - # Check location's owner type and filter potential owners - if type(location_owner.owner) is Group: - user_as_owner = Owner.get_owner(self.request.user) - queryset = location_owner.get_related_owners(include_group=True) - - if user_as_owner in queryset: - form.fields['owner'].initial = user_as_owner - - form.fields['owner'].queryset = queryset - - elif type(location_owner.owner) is get_user_model(): - # If location's owner is a user: automatically set owner field and disable it - form.fields['owner'].disabled = True - form.fields['owner'].initial = location_owner - - try: - item_owner = item.owner - except AttributeError: - item_owner = None - - # Check if item has owner - if item_owner: - form.fields['owner'].initial = item_owner - - # Check item's owner type and filter potential owners - if type(item_owner.owner) is Group: - user_as_owner = Owner.get_owner(self.request.user) - queryset = item_owner.get_related_owners(include_group=True) - - if user_as_owner in queryset: - form.fields['owner'].initial = user_as_owner - - form.fields['owner'].queryset = queryset - - elif type(item_owner.owner) is get_user_model(): - # If item's owner is a user: automatically set owner field and disable it - form.fields['owner'].disabled = True - form.fields['owner'].initial = item_owner - - return form - - def validate(self, item, form): - """ Check that owner is set if stock ownership control is enabled """ - - owner = form.cleaned_data.get('owner', None) - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if stock_ownership_control: - if not owner and not self.request.user.is_superuser: - form.add_error('owner', _('Owner is required (ownership control is enabled)')) - - def save(self, object, form, **kwargs): - """ - Override the save method, to track the user who updated the model - """ - - item = form.save(commit=False) - - item.save(user=self.request.user) - - return item - - class StockItemConvert(AjaxUpdateView): """ View for 'converting' a StockItem to a variant of its current part. @@ -655,435 +266,6 @@ class StockItemConvert(AjaxUpdateView): return stock_item -class StockLocationCreate(AjaxCreateView): - """ - View for creating a new StockLocation - A parent location (another StockLocation object) can be passed as a query parameter - - TODO: Remove this class entirely, as it has been migrated to the API forms - - Still need to check that all the functionality (as below) has been implemented - - """ - - model = StockLocation - form_class = StockForms.EditStockLocationForm - context_object_name = 'location' - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Create new Stock Location') - - def get_initial(self): - initials = super(StockLocationCreate, self).get_initial().copy() - - loc_id = self.request.GET.get('location', None) - - if loc_id: - try: - initials['parent'] = StockLocation.objects.get(pk=loc_id) - except StockLocation.DoesNotExist: - pass - - return initials - - def get_form(self): - """ Disable owner field when: - - creating child location - - and stock ownership control is enable - """ - - form = super().get_form() - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if not stock_ownership_control: - # Hide owner field - form.fields['owner'].widget = HiddenInput() - else: - # If user did not selected owner: automatically match to parent's owner - if not form['owner'].data: - try: - parent_id = form['parent'].value() - parent = StockLocation.objects.get(pk=parent_id) - - if parent: - form.fields['owner'].initial = parent.owner - if not self.request.user.is_superuser: - form.fields['owner'].disabled = True - except StockLocation.DoesNotExist: - pass - except ValueError: - pass - - return form - - def save(self, form): - """ If parent location exists then use it to set the owner """ - - self.object = form.save(commit=False) - - parent = form.cleaned_data.get('parent', None) - - if parent: - # Select parent's owner - self.object.owner = parent.owner - - self.object.save() - - return self.object - - def validate(self, item, form): - """ Check that owner is set if stock ownership control is enabled """ - - parent = form.cleaned_data.get('parent', None) - - owner = form.cleaned_data.get('owner', None) - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if stock_ownership_control: - if not owner and not self.request.user.is_superuser: - form.add_error('owner', _('Owner is required (ownership control is enabled)')) - else: - try: - if parent.owner: - if parent.owner != owner: - error = f'Owner requires to be equivalent to parent\'s owner ({parent.owner})' - form.add_error('owner', error) - except AttributeError: - # No parent - pass - - -class StockItemCreate(AjaxCreateView): - """ - View for creating a new StockItem - Parameters can be pre-filled by passing query items: - - part: The part of which the new StockItem is an instance - - location: The location of the new StockItem - - If the parent part is a "tracked" part, provide an option to create uniquely serialized items - rather than a bulk quantity of stock items - """ - - model = StockItem - form_class = StockForms.CreateStockItemForm - context_object_name = 'item' - ajax_template_name = 'modal_form.html' - ajax_form_title = _('Create new Stock Item') - - def get_part(self, form=None): - """ - Attempt to get the "part" associted with this new stockitem. - - - May be passed to the form as a query parameter (e.g. ?part=) - - May be passed via the form field itself. - """ - - # Try to extract from the URL query - part_id = self.request.GET.get('part', None) - - if part_id: - try: - part = Part.objects.get(pk=part_id) - return part - except (Part.DoesNotExist, ValueError): - pass - - # Try to get from the form - if form: - try: - part_id = form['part'].value() - part = Part.objects.get(pk=part_id) - return part - except (Part.DoesNotExist, ValueError): - pass - - # Could not extract a part object - return None - - def get_form(self): - """ Get form for StockItem creation. - Overrides the default get_form() method to intelligently limit - ForeignKey choices based on other selections - """ - - form = super().get_form() - - # Hide the "expiry date" field if the feature is not enabled - if not common.settings.stock_expiry_enabled(): - form.fields['expiry_date'].widget = HiddenInput() - - part = self.get_part(form=form) - - if part is not None: - - # Add placeholder text for the serial number field - form.field_placeholder['serial_numbers'] = part.getSerialNumberString() - - form.rebuild_layout() - - if not part.purchaseable: - form.fields.pop('purchase_price') - - # Hide the 'part' field (as a valid part is selected) - # form.fields['part'].widget = HiddenInput() - - # Trackable parts get special consideration: - if part.trackable: - form.fields['delete_on_deplete'].disabled = True - else: - form.fields['serial_numbers'].disabled = True - - # If the part is NOT purchaseable, hide the supplier_part field - if not part.purchaseable: - form.fields['supplier_part'].widget = HiddenInput() - else: - # Pre-select the allowable SupplierPart options - parts = form.fields['supplier_part'].queryset - parts = parts.filter(part=part.id) - - form.fields['supplier_part'].queryset = parts - - # If there is one (and only one) supplier part available, pre-select it - all_parts = parts.all() - - if len(all_parts) == 1: - - # TODO - This does NOT work for some reason? Ref build.views.BuildItemCreate - form.fields['supplier_part'].initial = all_parts[0].id - - else: - # No Part has been selected! - # We must not provide *any* options for SupplierPart - form.fields['supplier_part'].queryset = SupplierPart.objects.none() - - form.fields['serial_numbers'].disabled = True - - # Otherwise if the user has selected a SupplierPart, we know what Part they meant! - if form['supplier_part'].value() is not None: - pass - - location = None - try: - loc_id = form['location'].value() - location = StockLocation.objects.get(pk=loc_id) - except StockLocation.DoesNotExist: - pass - except ValueError: - pass - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - if not stock_ownership_control: - form.fields['owner'].widget = HiddenInput() - else: - try: - location_owner = location.owner - except AttributeError: - location_owner = None - - if location_owner: - # Check location's owner type and filter potential owners - if type(location_owner.owner) is Group: - user_as_owner = Owner.get_owner(self.request.user) - queryset = location_owner.get_related_owners() - - if user_as_owner in queryset: - form.fields['owner'].initial = user_as_owner - - form.fields['owner'].queryset = queryset - - elif type(location_owner.owner) is get_user_model(): - # If location's owner is a user: automatically set owner field and disable it - form.fields['owner'].disabled = True - form.fields['owner'].initial = location_owner - - return form - - def get_initial(self): - """ Provide initial data to create a new StockItem object - """ - - # Is the client attempting to copy an existing stock item? - item_to_copy = self.request.GET.get('copy', None) - - if item_to_copy: - try: - original = StockItem.objects.get(pk=item_to_copy) - initials = model_to_dict(original) - self.ajax_form_title = _("Duplicate Stock Item") - except StockItem.DoesNotExist: - initials = super(StockItemCreate, self).get_initial().copy() - - else: - initials = super(StockItemCreate, self).get_initial().copy() - - part = self.get_part() - - loc_id = self.request.GET.get('location', None) - sup_part_id = self.request.GET.get('supplier_part', None) - - location = None - supplier_part = None - - if part is not None: - initials['part'] = part - initials['location'] = part.get_default_location() - initials['supplier_part'] = part.default_supplier - - # If the part has a defined expiry period, extrapolate! - if part.default_expiry > 0: - expiry_date = datetime.now().date() + timedelta(days=part.default_expiry) - initials['expiry_date'] = expiry_date - - currency_code = common.settings.currency_code_default() - - # SupplierPart field has been specified - # It must match the Part, if that has been supplied - if sup_part_id: - try: - supplier_part = SupplierPart.objects.get(pk=sup_part_id) - - if part is None or supplier_part.part == part: - initials['supplier_part'] = supplier_part - - currency_code = supplier_part.supplier.currency_code - - except (ValueError, SupplierPart.DoesNotExist): - pass - - # Location has been specified - if loc_id: - try: - location = StockLocation.objects.get(pk=loc_id) - initials['location'] = location - except (ValueError, StockLocation.DoesNotExist): - pass - - currency = CURRENCIES.get(currency_code, None) - - if currency: - initials['purchase_price'] = (None, currency) - - return initials - - def validate(self, item, form): - """ - Extra form validation steps - """ - - data = form.cleaned_data - - part = data.get('part', None) - - quantity = data.get('quantity', None) - - owner = data.get('owner', None) - - if not part: - return - - if not quantity: - return - - try: - quantity = Decimal(quantity) - except (ValueError, InvalidOperation): - form.add_error('quantity', _('Invalid quantity provided')) - return - - if quantity < 0: - form.add_error('quantity', _('Quantity cannot be negative')) - - # Trackable parts are treated differently - if part.trackable: - sn = data.get('serial_numbers', '') - sn = str(sn).strip() - - if len(sn) > 0: - try: - serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt()) - except ValidationError as e: - serials = None - form.add_error('serial_numbers', e.messages) - - if serials is not None: - existing = part.find_conflicting_serial_numbers(serials) - - if len(existing) > 0: - exists = ','.join([str(x) for x in existing]) - - form.add_error( - 'serial_numbers', - _('Serial numbers already exist') + ': ' + exists - ) - - # Is ownership control enabled? - stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') - - if stock_ownership_control: - # Check if owner is set - if not owner and not self.request.user.is_superuser: - form.add_error('owner', _('Owner is required (ownership control is enabled)')) - return - - def save(self, form, **kwargs): - """ - Create a new StockItem based on the provided form data. - """ - - data = form.cleaned_data - - part = data['part'] - - quantity = data['quantity'] - - if part.trackable: - sn = data.get('serial_numbers', '') - sn = str(sn).strip() - - # Create a single stock item for each provided serial number - if len(sn) > 0: - serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt()) - - for serial in serials: - item = StockItem( - part=part, - quantity=1, - serial=serial, - supplier_part=data.get('supplier_part', None), - location=data.get('location', None), - batch=data.get('batch', None), - delete_on_deplete=False, - status=data.get('status'), - link=data.get('link', ''), - ) - - item.save(user=self.request.user) - - # Create a single StockItem of the specified quantity - else: - form._post_clean() - - item = form.save(commit=False) - item.user = self.request.user - item.save(user=self.request.user) - - return item - - # Non-trackable part - else: - - form._post_clean() - - item = form.save(commit=False) - item.user = self.request.user - item.save(user=self.request.user) - - return item - - class StockLocationDelete(AjaxDeleteView): """ View to delete a StockLocation