Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive forms #934

Merged
merged 10 commits into from
Aug 26, 2020
178 changes: 176 additions & 2 deletions InvenTree/InvenTree/static/script/inventree/modals.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,136 @@
function makeOption(id, title) {
function makeOption(text, value, title) {
/* Format an option for a select element
*/
return "<option value='" + id + "'>" + title + "</option>";

var html = `<option value='${value || text}'`;

if (title) {
html += ` title='${title}'`;
}

html += `>${text}</option>`;

return html;
}

function makeOptionsList(elements, textFunc, valueFunc, titleFunc) {
/*
* Programatically generate a list of <option> elements,
* from the (assumed array) of elements.
* For each element, we pass the element to the supplied functions,
* which (in turn) generate display / value / title values.
*
* Args:
* - elements: List of elements
* - textFunc: Function which takes an element and generates the text to be displayed
* - valueFunc: optional function which takes an element and generates the value
* - titleFunc: optional function which takes an element and generates a title
*/

var options = [];

elements.forEach(function(element) {

var text = textFunc(element);
var value = null;
var title = null;

if (valueFunc) {
value = valueFunc(element);
} else {
value = text;
}

if (titleFunc) {
title = titleFunc(element);
}

options.push(makeOption(text, value, title));
});

return options;
}


function setFieldOptions(fieldName, optionList, options={}) {
/* Set the options for a <select> field.
*
* Args:
* - fieldName: The name of the target field
* - Options: List of formatted <option> strings
* - append: If true, options will be appended, otherwise will replace existing options.
*/


var append = options.append || false;

var modal = options.modal || '#modal-form';

var field = getFieldByName(modal, fieldName);

var addEmptyOption = options.addEmptyOption || true;

// If not appending, clear out the field...
if (!append) {
field.find('option').remove();
}

if (addEmptyOption) {
// Add an 'empty' option at the top of the list
field.append(makeOption('---------', '', '---------'));
}

optionList.forEach(function(option) {
field.append(option);
});

}


function reloadFieldOptions(fieldName, options) {
/* Reload the options for a given field,
* using an AJAX request.
*
* Args:
* - fieldName: The name of the field
* - options:
* -- url: Query url
* -- params: Query params
* -- value: A function which takes a returned option and returns the 'value' (if not specified, the `pk` field is used)
* -- text: A function which takes a returned option and returns the 'text'
* -- title: A function which takes a returned option and returns the 'title' (optional!)
*/

inventreeGet(options.url, options.params, {
success: function(response) {
var opts = makeOptionsList(response,
function(item) {
return options.text(item);
},
function(item) {
if (options.value) {
return options.value(item);
} else {
// Fallback is to use the 'pk' field
return item.pk;
}
},
function(item) {
if (options.title) {
return options.title(item);
} else {
return null;
}
}
);

// Update the target field with the new options
setFieldOptions(fieldName, opts);
},
error: function(response) {
console.log("Error GETting field options");
}
});
}


Expand Down Expand Up @@ -397,6 +526,13 @@ function injectModalForm(modal, form_html) {
}


function getFieldByName(modal, name) {
/* Find the field (with the given name) within the modal */

return $(modal).find(`#id_${name}`);
}


function insertNewItemButton(modal, options) {
/* Insert a button into a modal form, after a field label.
* Looks for a <label> tag inside the form with the attribute "for='id_<field>'"
Expand Down Expand Up @@ -476,6 +612,39 @@ function attachSecondaries(modal, secondaries) {
}


function attachFieldCallback(modal, callback) {
/* Attach a 'callback' function to a given field in the modal form.
* When the value of that field is changed, the callback function is performed.
*
* options:
* - field: The name of the field to attach to
* - action: A function to perform
*/

// Find the field input in the form
var field = getFieldByName(modal, callback.field);

field.change(function() {

if (callback.action) {
// Run the callback function with the new value of the field!
callback.action(field.val(), field);
} else {
console.log(`Value changed for field ${callback.field} - ${field.val()}`);
}
});
}


function attachCallbacks(modal, callbacks) {
/* Attach a provided list of callback functions */

for (var i = 0; i < callbacks.length; i++) {
attachFieldCallback(modal, callbacks[i]);
}
}


function handleModalForm(url, options) {
/* Update a modal form after data are received from the server.
* Manages POST requests until the form is successfully submitted.
Expand Down Expand Up @@ -575,6 +744,7 @@ function launchModalForm(url, options = {}) {
* no_post - If true, only display form data, hide submit button, and disallow POST
* after_render - Callback function to run after form is rendered
* secondary - List of secondary modals to attach
* callback - List of callback functions to attach to inputs
*/

var modal = options.modal || '#modal-form';
Expand Down Expand Up @@ -615,6 +785,10 @@ function launchModalForm(url, options = {}) {
attachSecondaries(modal, options.secondary);
}

if (options.callback) {
attachCallbacks(modal, options.callback);
}

if (options.no_post) {
modalShowSubmitButton(modal, false);
} else {
Expand Down
39 changes: 35 additions & 4 deletions InvenTree/company/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,41 @@ def get_queryset(self):

queryset = super().get_queryset()

return queryset

def filter_queryset(self, queryset):
"""
Custom filtering for the queryset.
"""

queryset = super().filter_queryset(queryset)

params = self.request.query_params

# Filter by manufacturer
manufacturer = params.get('manufacturer', None)

if manufacturer is not None:
queryset = queryset.filter(manufacturer=manufacturer)

# Filter by supplier
supplier = params.get('supplier', None)

if supplier is not None:
queryset = queryset.filter(supplier=supplier)

# Filter by EITHER manufacturer or supplier
company = self.request.query_params.get('company', None)
company = params.get('company', None)

if company is not None:
queryset = queryset.filter(Q(manufacturer=company) | Q(supplier=company))

# Filter by parent part?
part = params.get('part', None)

if part is not None:
queryset = queryset.filter(part=part)

return queryset

def get_serializer(self, *args, **kwargs):
Expand All @@ -129,6 +158,11 @@ def get_serializer(self, *args, **kwargs):
kwargs['manufacturer_detail'] = str2bool(self.request.query_params.get('manufacturer_detail', None))
except AttributeError:
pass

try:
kwargs['pretty'] = str2bool(self.request.query_params.get('pretty', None))
except AttributeError:
pass

kwargs['context'] = self.get_serializer_context()

Expand All @@ -147,9 +181,6 @@ def get_serializer(self, *args, **kwargs):
]

filter_fields = [
'part',
'supplier',
'manufacturer',
]

search_fields = [
Expand Down
4 changes: 4 additions & 0 deletions InvenTree/company/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,10 @@ def purchase_orders(self):

return [line.order for line in self.purchase_order_line_items.all().prefetch_related('order')]

@property
def pretty_name(self):
return str(self)

def __str__(self):
s = "{supplier} ({sku})".format(
sku=self.SKU,
Expand Down
8 changes: 8 additions & 0 deletions InvenTree/company/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,17 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
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', many=False, read_only=True)

pretty_name = serializers.CharField(read_only=True)

def __init__(self, *args, **kwargs):

part_detail = kwargs.pop('part_detail', False)
supplier_detail = kwargs.pop('supplier_detail', False)
manufacturer_detail = kwargs.pop('manufacturer_detail', False)
prettify = kwargs.pop('pretty', False)

super(SupplierPartSerializer, self).__init__(*args, **kwargs)

Expand All @@ -99,6 +103,9 @@ def __init__(self, *args, **kwargs):
if manufacturer_detail is not True:
self.fields.pop('manufacturer_detail')

if prettify is not True:
self.fields.pop('pretty_name')

supplier = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_supplier=True))

manufacturer = serializers.PrimaryKeyRelatedField(queryset=Company.objects.filter(is_manufacturer=True))
Expand All @@ -109,6 +116,7 @@ class Meta:
'pk',
'part',
'part_detail',
'pretty_name',
'supplier',
'supplier_detail',
'SKU',
Expand Down
14 changes: 2 additions & 12 deletions InvenTree/company/templates/company/supplier_part_stock.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,13 @@ <h4>{% trans "Supplier Part Stock" %}</h4>
});

$("#item-create").click(function() {
launchModalForm("{% url 'stock-item-create' %}", {
reload: true,
createNewStockItem({
data: {
part: {{ part.part.id }},
supplier_part: {{ part.id }},
},
secondary: [
{
field: 'location',
label: '{% trans "New Location" %}',
title: '{% trans "Create New Location" %}',
url: "{% url 'stock-location-create' %}",
}
]
reload: true,
});

return false;
});


Expand Down