mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Set part category for multiple parts at once
This commit is contained in:
parent
ea44f28bc7
commit
d6fcf85cd2
@ -251,6 +251,23 @@ class Part(models.Model):
|
|||||||
|
|
||||||
return ' | '.join(elements)
|
return ' | '.join(elements)
|
||||||
|
|
||||||
|
def set_category(self, category):
|
||||||
|
|
||||||
|
if not type(category) == PartCategory:
|
||||||
|
raise ValidationError({
|
||||||
|
'category': _('Invalid object supplied to part.set_category')
|
||||||
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Already in this category!
|
||||||
|
if category == self.category:
|
||||||
|
return
|
||||||
|
except PartCategory.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.category = category
|
||||||
|
self.save()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
""" Return the web URL for viewing this part """
|
""" Return the web URL for viewing this part """
|
||||||
return reverse('part-detail', kwargs={'pk': self.id})
|
return reverse('part-detail', kwargs={'pk': self.id})
|
||||||
|
58
InvenTree/part/templates/part/set_category.html
Normal file
58
InvenTree/part/templates/part/set_category.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends "modal_form.html" %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
<form method="post" action='' class='js-modal-form' enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
<div class='control-group'>
|
||||||
|
<label class='control-label requiredField'>Location</label>
|
||||||
|
<div class='controls'>
|
||||||
|
<select class='select' name='part_category'>
|
||||||
|
<option value=''>---------</option>
|
||||||
|
{% for cat in categories %}
|
||||||
|
<option value='{{ cat.id }}' {% if category and category.id == cat.id %}selected='selected'{% endif %}>{{ cat.pathstring }} - {{ cat.description }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% if category %}
|
||||||
|
<p class='help-block'>Select Part Category</p>
|
||||||
|
{% else %}
|
||||||
|
<p class='help-inline'>Select Part Category</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class='control-label'>Parts</label>
|
||||||
|
<p class='help-block'>Set category for the following parts</p>
|
||||||
|
|
||||||
|
<table class='table table-striped'>
|
||||||
|
<tr>
|
||||||
|
<th>Part</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>
|
||||||
|
</tr>
|
||||||
|
{% for part in parts %}
|
||||||
|
<tr id='part_row_{{ part.id }}'>
|
||||||
|
<input type='hidden' name='part_id_{{ part.id }}' value='1'/>
|
||||||
|
<td>
|
||||||
|
{% include "hover_image.html" with image=part.image hover=False %}
|
||||||
|
{{ part.full_name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ part.description }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ part.category.pathstring }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class='btn btn-default btn-remove' title='Remove part' type='button'>
|
||||||
|
<span row='part_row_{{ part.id }}' onClick='removeRowFromModalForm()' class='glyphicon glyphicon-small glyphicon-remove'></span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -82,6 +82,9 @@ part_urls = [
|
|||||||
# Part attachments
|
# Part attachments
|
||||||
url(r'^attachment/', include(part_attachment_urls)),
|
url(r'^attachment/', include(part_attachment_urls)),
|
||||||
|
|
||||||
|
# Change category for multiple parts
|
||||||
|
url(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
|
||||||
|
|
||||||
# Bom Items
|
# Bom Items
|
||||||
url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
|
url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ Django views for interacting with Part app
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
@ -125,6 +125,78 @@ class PartAttachmentDelete(AjaxDeleteView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PartSetCategory(AjaxView):
|
||||||
|
""" View for settings the part category for multiple parts at once """
|
||||||
|
|
||||||
|
ajax_template_name = 'part/set_category.html'
|
||||||
|
ajax_form_title = 'Set Part Category'
|
||||||
|
|
||||||
|
category = None
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
""" Respond to a GET request to this view """
|
||||||
|
|
||||||
|
self.request = request
|
||||||
|
|
||||||
|
if 'parts[]' in request.GET:
|
||||||
|
self.parts = Part.objects.filter(id__in=request.GET.getlist('parts[]'))
|
||||||
|
else:
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, context=self.get_context_data())
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
""" Respond to a POST request to this view """
|
||||||
|
|
||||||
|
self.parts = []
|
||||||
|
|
||||||
|
for item in request.POST:
|
||||||
|
if item.startswith('part_id_'):
|
||||||
|
pk = item.replace('part_id_', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
part = Part.objects.get(pk=pk)
|
||||||
|
except (Part.DoesNotExist, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.parts.append(part)
|
||||||
|
|
||||||
|
self.category = None
|
||||||
|
|
||||||
|
if 'part_category' in request.POST:
|
||||||
|
pk = request.POST['part_category']
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.category = PartCategory.objects.get(pk=pk)
|
||||||
|
except (PartCategory.DoesNotExist, ValueError):
|
||||||
|
self.category = None
|
||||||
|
|
||||||
|
valid = self.category is not None
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'form_valid': valid,
|
||||||
|
'success': _('Set category for {n} parts'.format(n=len(self.parts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
for part in self.parts:
|
||||||
|
part.set_category(self.category)
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, data=data, context=self.get_context_data())
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
""" Return context data for rendering in the form """
|
||||||
|
ctx = {}
|
||||||
|
|
||||||
|
ctx['parts'] = self.parts
|
||||||
|
ctx['categories'] = PartCategory.objects.all()
|
||||||
|
ctx['category'] = self.category
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MakePartVariant(AjaxCreateView):
|
class MakePartVariant(AjaxCreateView):
|
||||||
""" View for creating a new variant based on an existing template Part
|
""" View for creating a new variant based on an existing template Part
|
||||||
|
|
||||||
|
@ -195,6 +195,18 @@ function modalSubmit(modal, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function removeRowFromModalForm(e) {
|
||||||
|
/* Remove a row from a table in a modal form */
|
||||||
|
e = e || window.event;
|
||||||
|
|
||||||
|
var src = e.target || e.srcElement;
|
||||||
|
|
||||||
|
var row = $(src).attr('row');
|
||||||
|
|
||||||
|
$('#' + row).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function renderErrorMessage(xhr) {
|
function renderErrorMessage(xhr) {
|
||||||
|
|
||||||
var html = '<b>' + xhr.statusText + '</b><br>';
|
var html = '<b>' + xhr.statusText + '</b><br>';
|
||||||
|
@ -224,4 +224,21 @@ function loadPartTable(table, url, options={}) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#multi-part-category").click(function() {
|
||||||
|
var selections = $(table).bootstrapTable("getSelections");
|
||||||
|
|
||||||
|
var parts = [];
|
||||||
|
|
||||||
|
selections.forEach(function(item) {
|
||||||
|
parts.push(item.pk);
|
||||||
|
});
|
||||||
|
|
||||||
|
launchModalForm("/part/set-category/", {
|
||||||
|
data: {
|
||||||
|
parts: parts,
|
||||||
|
},
|
||||||
|
reload: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
@ -66,7 +66,7 @@ class AdjustStockForm(forms.ModelForm):
|
|||||||
destination = forms.ChoiceField(label='Destination', required=True, help_text='Destination stock location')
|
destination = forms.ChoiceField(label='Destination', required=True, help_text='Destination stock location')
|
||||||
note = forms.CharField(label='Notes', required=True, help_text='Add note (required)')
|
note = forms.CharField(label='Notes', required=True, help_text='Add note (required)')
|
||||||
# transaction = forms.BooleanField(required=False, initial=False, label='Create Transaction', help_text='Create a stock transaction for these parts')
|
# transaction = forms.BooleanField(required=False, initial=False, label='Create Transaction', help_text='Create a stock transaction for these parts')
|
||||||
confirm = forms.BooleanField(required=False, initial=False, label='Confirm Stock Movement', help_text='Confirm movement of stock items')
|
confirm = forms.BooleanField(required=False, initial=False, label='Confirm stock adjustment', help_text='Confirm movement of stock items')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
Loading…
Reference in New Issue
Block a user