2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-26 05:15:55 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into price-history

This commit is contained in:
2021-05-11 13:32:14 +02:00
191 changed files with 5621 additions and 4631 deletions

View File

@@ -206,12 +206,12 @@ class PartAttachmentCreate(AjaxCreateView):
class PartAttachmentEdit(AjaxUpdateView):
""" View for editing a PartAttachment object """
model = PartAttachment
form_class = part_forms.EditPartAttachmentForm
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit attachment')
def get_data(self):
return {
'success': _('Part attachment updated')
@@ -247,7 +247,7 @@ class PartTestTemplateCreate(AjaxCreateView):
model = PartTestTemplate
form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Create Test Template")
def get_initial(self):
initials = super().get_initial()
@@ -301,7 +301,7 @@ class PartSetCategory(AjaxUpdateView):
category = None
parts = []
def get(self, request, *args, **kwargs):
""" Respond to a GET request to this view """
@@ -366,7 +366,7 @@ class PartSetCategory(AjaxUpdateView):
ctx['category'] = self.category
return ctx
class MakePartVariant(AjaxCreateView):
""" View for creating a new variant based on an existing template Part
@@ -503,17 +503,17 @@ class PartDuplicate(AjaxCreateView):
valid = form.is_valid()
name = request.POST.get('name', None)
if name:
matches = match_part_names(name)
if len(matches) > 0:
# Display the first five closest matches
context['matches'] = matches[:5]
# Enforce display of the checkbox
form.fields['confirm_creation'].widget = CheckboxInput()
# Check if the user has checked the 'confirm_creation' input
confirmed = str2bool(request.POST.get('confirm_creation', False))
@@ -567,7 +567,7 @@ class PartDuplicate(AjaxCreateView):
initials = super(AjaxCreateView, self).get_initial()
initials['bom_copy'] = str2bool(InvenTreeSetting.get_setting('PART_COPY_BOM', True))
initials['parameters_copy'] = str2bool(InvenTreeSetting.get_setting('PART_COPY_PARAMETERS', True))
return initials
@@ -577,7 +577,7 @@ class PartCreate(AjaxCreateView):
""" View for creating a new Part object.
Options for providing initial conditions:
- Provide a category object as initial data
"""
model = Part
@@ -638,9 +638,9 @@ class PartCreate(AjaxCreateView):
context = {}
valid = form.is_valid()
name = request.POST.get('name', None)
if name:
matches = match_part_names(name)
@@ -648,17 +648,17 @@ class PartCreate(AjaxCreateView):
# Limit to the top 5 matches (to prevent clutter)
context['matches'] = matches[:5]
# Enforce display of the checkbox
form.fields['confirm_creation'].widget = CheckboxInput()
# Check if the user has checked the 'confirm_creation' input
confirmed = str2bool(request.POST.get('confirm_creation', False))
if not confirmed:
msg = _('Possible matches exist - confirm creation of new part')
form.add_error('confirm_creation', msg)
form.pre_form_warning = msg
valid = False
@@ -707,7 +707,7 @@ class PartCreate(AjaxCreateView):
initials['keywords'] = category.default_keywords
except (PartCategory.DoesNotExist, ValueError):
pass
# Allow initial data to be passed through as arguments
for label in ['name', 'IPN', 'description', 'revision', 'keywords']:
if label in self.request.GET:
@@ -736,7 +736,7 @@ class PartNotes(UpdateView):
def get_success_url(self):
""" Return the success URL for this form """
return reverse('part-notes', kwargs={'pk': self.get_object().id})
def get_context_data(self, **kwargs):
@@ -769,7 +769,7 @@ class PartDetail(InvenTreeRoleMixin, DetailView):
- If '?editing=True', set 'editing_enabled' context variable
"""
context = super().get_context_data(**kwargs)
part = self.get_object()
if str2bool(self.request.GET.get('edit', '')):
@@ -808,7 +808,7 @@ class PartDetailFromIPN(PartDetail):
pass
except queryset.model.DoesNotExist:
pass
return None
def get(self, request, *args, **kwargs):
@@ -886,7 +886,7 @@ class PartImageDownloadFromURL(AjaxUpdateView):
# Check for valid response code
if not response.status_code == 200:
form.add_error('url', f"{_('Invalid response')}: {response.status_code}")
form.add_error('url', _('Invalid response: {code}').format(code=response.status_code))
return
response.raw.decode_content = True
@@ -1019,7 +1019,7 @@ class BomDuplicate(AjaxUpdateView):
ajax_form_title = _('Duplicate BOM')
ajax_template_name = 'part/bom_duplicate.html'
form_class = part_forms.BomDuplicateForm
def get_form(self):
form = super().get_form()
@@ -1220,7 +1220,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
def handleBomFileUpload(self):
""" Process a BOM file upload form.
This function validates that the uploaded file was valid,
and contains tabulated data that can be extracted.
If the file does not satisfy these requirements,
@@ -1301,13 +1301,13 @@ class BomUpload(InvenTreeRoleMixin, FormView):
- If using the Part_ID field, we can do an exact match against the PK field
- If using the Part_IPN field, we can do an exact match against the IPN field
- If using the Part_Name field, we can use fuzzy string matching to match "close" values
We also extract other information from the row, for the other non-matched fields:
- Quantity
- Reference
- Overage
- Note
"""
# Initially use a quantity of zero
@@ -1377,7 +1377,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
# Check if there is a column corresponding to "Note" field
if n_idx >= 0:
row['note'] = row['data'][n_idx]
# Supply list of part options for each row, sorted by how closely they match the part name
row['part_options'] = part_options
@@ -1392,7 +1392,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
try:
if row['part_ipn']:
part_matches = [part for part in self.allowed_parts if part.IPN and row['part_ipn'].lower() == str(part.IPN.lower())]
# Check for single match
if len(part_matches) == 1:
row['part_match'] = part_matches[0]
@@ -1466,7 +1466,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
col_id = int(s[3])
except ValueError:
continue
if row_id not in self.row_data:
self.row_data[row_id] = {}
@@ -1532,7 +1532,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
if col in self.column_selections.values():
part_match_found = True
break
# If not, notify user
if not part_match_found:
for col in BomUploadManager.PART_MATCH_HEADERS:
@@ -1548,7 +1548,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
self.getTableDataFromPost()
valid = len(self.missing_columns) == 0 and not self.duplicates
if valid:
# Try to extract meaningful data
self.preFillSelections()
@@ -1559,7 +1559,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
return self.render_to_response(self.get_context_data(form=None))
def handlePartSelection(self):
# Extract basic table data from POST request
self.getTableDataFromPost()
@@ -1597,7 +1597,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
row['errors']['quantity'] = _('Enter a valid quantity')
row['quantity'] = q
except ValueError:
continue
@@ -1650,7 +1650,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
if key.startswith(field + '_'):
try:
row_id = int(key.replace(field + '_', ''))
row = self.getRowByIndex(row_id)
if row:
@@ -1716,7 +1716,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
return self.render_to_response(ctx)
def getRowByIndex(self, idx):
for row in self.bom_rows:
if row['index'] == idx:
return row
@@ -1734,7 +1734,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
self.form = self.get_form(self.get_form_class())
# Did the user POST a file named bom_file?
form_step = request.POST.get('form_step', None)
if form_step == 'select_file':
@@ -1755,7 +1755,7 @@ class PartExport(AjaxView):
def get_parts(self, request):
""" Extract part list from the POST parameters.
Parts can be supplied as:
- Part category
- List of part PK values
"""
@@ -1958,10 +1958,9 @@ class PartPricing(AjaxView):
form_class = part_forms.PartPriceForm
role_required = ['sales_order.view', 'part.view']
def get_quantity(self):
""" Return set quantity in decimal format """
return Decimal(self.request.POST.get('quantity', 1))
def get_part(self):
@@ -1971,12 +1970,7 @@ class PartPricing(AjaxView):
return None
def get_pricing(self, quantity=1, currency=None):
# try:
# quantity = int(quantity)
# except ValueError:
# quantity = 1
""" returns context with pricing information """
if quantity <= 0:
quantity = 1
@@ -1987,7 +1981,7 @@ class PartPricing(AjaxView):
scaler = Decimal(1.0)
part = self.get_part()
ctx = {
'part': part,
'quantity': quantity,
@@ -2041,7 +2035,7 @@ class PartPricing(AjaxView):
if min_bom_price:
ctx['min_total_bom_price'] = min_bom_price
ctx['min_unit_bom_price'] = min_unit_bom_price
if max_bom_price:
ctx['max_total_bom_price'] = max_bom_price
ctx['max_unit_bom_price'] = max_unit_bom_price
@@ -2077,12 +2071,22 @@ class PartPricing(AjaxView):
ret.append(line)
ctx['price_history'] = ret
# part pricing information
part_price = part.get_price(quantity)
if part_price is not None:
ctx['total_part_price'] = round(part_price, 3)
ctx['unit_part_price'] = round(part_price / quantity, 3)
return ctx
def get(self, request, *args, **kwargs):
def get_initials(self):
""" returns initials for form """
return {'quantity': self.get_quantity()}
return self.renderJsonResponse(request, self.form_class(), context=self.get_pricing())
def get(self, request, *args, **kwargs):
init = self.get_initials()
qty = self.get_quantity()
return self.renderJsonResponse(request, self.form_class(initial=init), context=self.get_pricing(qty))
def post(self, request, *args, **kwargs):
@@ -2091,16 +2095,19 @@ class PartPricing(AjaxView):
quantity = self.get_quantity()
# Retain quantity value set by user
form = self.form_class()
form.fields['quantity'].initial = quantity
form = self.form_class(initial=self.get_initials())
# TODO - How to handle pricing in different currencies?
currency = None
# check if data is set
try:
data = self.data
except AttributeError:
data = {}
# Always mark the form as 'invalid' (the user may wish to keep getting pricing data)
data = {
'form_valid': False,
}
data['form_valid'] = False
return self.renderJsonResponse(request, form, data=data, context=self.get_pricing(quantity, currency))
@@ -2202,7 +2209,7 @@ class PartParameterDelete(AjaxDeleteView):
model = PartParameter
ajax_template_name = 'part/param_delete.html'
ajax_form_title = _('Delete Part Parameter')
class CategoryDetail(InvenTreeRoleMixin, DetailView):
""" Detail view for PartCategory """
@@ -2257,7 +2264,7 @@ class CategoryEdit(AjaxUpdateView):
"""
Update view to edit a PartCategory
"""
model = PartCategory
form_class = part_forms.EditCategoryForm
ajax_template_name = 'modal_form.html'
@@ -2278,9 +2285,9 @@ class CategoryEdit(AjaxUpdateView):
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
@@ -2296,7 +2303,7 @@ class CategoryDelete(AjaxDeleteView):
"""
Delete view to delete a PartCategory
"""
model = PartCategory
ajax_template_name = 'part/category_delete.html'
ajax_form_title = _('Delete Part Category')
@@ -2380,7 +2387,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView):
"""
form = super(AjaxCreateView, self).get_form()
form.fields['category'].widget = HiddenInput()
if form.is_valid():
@@ -2475,7 +2482,7 @@ class CategoryParameterTemplateEdit(AjaxUpdateView):
"""
form = super(AjaxUpdateView, self).get_form()
form.fields['category'].widget = HiddenInput()
form.fields['add_to_all_categories'].widget = HiddenInput()
form.fields['add_to_same_level_categories'].widget = HiddenInput()
@@ -2529,7 +2536,7 @@ class BomItemCreate(AjaxCreateView):
"""
Create view for making a new BomItem object
"""
model = BomItem
form_class = part_forms.EditBomItemForm
ajax_template_name = 'modal_form.html'
@@ -2557,13 +2564,13 @@ class BomItemCreate(AjaxCreateView):
try:
part = Part.objects.get(id=part_id)
# Hide the 'part' field
form.fields['part'].widget = HiddenInput()
# Exclude the part from its own BOM
sub_part_query = sub_part_query.exclude(id=part.id)
# Eliminate any options that are already in the BOM!
sub_part_query = sub_part_query.exclude(id__in=[item.id for item in part.getRequiredParts()])
@@ -2668,7 +2675,7 @@ class PartSalePriceBreakCreate(AjaxCreateView):
model = PartSellPriceBreak
form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Add Price Break')
def get_data(self):
return {
'success': _('Added new price break')
@@ -2679,7 +2686,7 @@ class PartSalePriceBreakCreate(AjaxCreateView):
part = Part.objects.get(id=self.request.GET.get('part'))
except (ValueError, Part.DoesNotExist):
part = None
if part is None:
try:
part = Part.objects.get(id=self.request.POST.get('part'))
@@ -2724,7 +2731,7 @@ class PartSalePriceBreakEdit(AjaxUpdateView):
return form
class PartSalePriceBreakDelete(AjaxDeleteView):
""" View for deleting a sale price break """