Implement Add Stock modal

This commit is contained in:
Mario Hüttel 2021-11-08 23:11:05 +01:00
parent b4e561279b
commit fbb60b455f
8 changed files with 173 additions and 14 deletions

View File

@ -47,7 +47,7 @@ class PartsComponentViewSet(viewsets.ModelViewSet):
serializer_class = ComponentSerializer serializer_class = ComponentSerializer
permission_classes = [permissions.DjangoModelPermissions] permission_classes = [permissions.DjangoModelPermissions]
filter_backends = [filters.SearchFilter] filter_backends = [filters.SearchFilter]
search_fields = ['name', 'package__name', 'manufacturer__name'] search_fields = ['id', 'name', 'package__name', 'manufacturer__name']
class PartsManufacturerViewSet(viewsets.ModelViewSet): class PartsManufacturerViewSet(viewsets.ModelViewSet):
queryset = parts_models.Manufacturer.objects.all() queryset = parts_models.Manufacturer.objects.all()

View File

@ -72,3 +72,36 @@ class EditStockAmountForm(forms.Form):
amount = -amount amount = -amount
return stock.atomic_increment(amount) return stock.atomic_increment(amount)
class AddStockForm(forms.Form):
watermark_active = forms.BooleanField(required=False)
watermark = forms.IntegerField(min_value=0, required=True, initial=0)
amount = forms.IntegerField(min_value=0, required=True, initial=1)
component_uuid = forms.UUIDField(required=True)
def clean(self):
cleaned_data = super().clean()
id = cleaned_data.get('component_uuid')
if not id:
raise ValidationError('No valid component selected!')
component = None
try:
component = parts_models.Component.objects.get(id=id)
except:
raise ValidationError("Invalid component selected!")
cleaned_data['component'] = component
return cleaned_data
def save(self, storage):
component = self.cleaned_data.get('component')
amount = self.cleaned_data.get('amount')
watermark = -1
if self.cleaned_data.get('watermark-active'):
watermark = self.cleaned_data.get('watermark')
new_stock = parts_models.Stock.objects.create(storage=storage, component=component, watermark=watermark, amount=amount)
new_stock.save()

View File

@ -13,7 +13,7 @@ from .models import Storage, Stock
from .qr_parser import QrCodeValidator from .qr_parser import QrCodeValidator
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from .forms import MyTestForm, AddSubStorageForm, DeleteStockForm, EditWatermarkForm, EditStockAmountForm from .forms import MyTestForm, AddSubStorageForm, DeleteStockForm, EditWatermarkForm, EditStockAmountForm, AddStockForm
from django.db.models import Q from django.db.models import Q
from django.db.models.functions import Lower from django.db.models.functions import Lower
import uuid import uuid
@ -223,6 +223,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
add_storage_form.fields['responsible'].initial = self.request.user.username add_storage_form.fields['responsible'].initial = self.request.user.username
context['add_storage_form'] = add_storage_form context['add_storage_form'] = add_storage_form
context['delete_storage_error'] = None context['delete_storage_error'] = None
context['add_stock_form'] = AddStockForm()
return context return context
@ -293,6 +294,24 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
return self.render_to_response(context) return self.render_to_response(context)
def handle_add_stock_post(self, request, **kwargs):
f = AddStockForm(data=request.POST)
error_occured = False
if f.is_valid():
try:
f.save(self.object)
except Exception as ex:
f.add_error('', str(ex))
error_occured = True
else:
error_occured = True
context = self.get_context_data(**kwargs)
if error_occured:
context['add_stock_form'] = f
return self.render_to_response(context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -308,4 +327,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
return self.handle_amount_change_post(request, False, **kwargs) return self.handle_amount_change_post(request, False, **kwargs)
elif 'submit-amount-increase' in request.POST: elif 'submit-amount-increase' in request.POST:
return self.handle_amount_change_post(request, True, **kwargs) return self.handle_amount_change_post(request, True, **kwargs)
elif 'submit-add-stock' in request.POST:
return self.handle_add_stock_post(request, **kwargs)
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)

View File

@ -1,3 +1,31 @@
function fill_add_stock_modal_component(data) {
// component has been selected. Process it:
document.getElementById('add-stock-search').value = '';
console.log(data);
var template = document.querySelector('#add-stock-modal-component-template');
var container = document.querySelector('#add-stock-modal-component-container');
container.innerHTML = '';
var comp_elem = template.content.cloneNode(true);
if (data.ro_image) {
comp_elem.querySelector('#add-stock-cmp-img').setAttribute('src', data.ro_image);
}
var description_container = comp_elem.querySelector('#add-stock-cmp-desc-container');
var heading = document.createElement('h6');
heading.appendChild(document.createTextNode(data.name));
description_container.appendChild(heading);
if (data.package_data && data.package_data.name) {
description_container.appendChild(document.createTextNode('in '+data.package_data.name));
}
if (data.ro_manufacturer_name) {
description_container.appendChild(document.createElement('br'));
description_container.appendChild(document.createTextNode('by '+data.ro_manufacturer_name));
}
document.querySelector('#add-stock-modal-comp-uuid').value = data.id;
console.log("Selected element: "+data.id);
container.appendChild(comp_elem);
}
new AutocompleteCustomUi('add-stock-search', 'add-stock-search-ac-dropdown', new AutocompleteCustomUi('add-stock-search', 'add-stock-search-ac-dropdown',
function(search, autocomplete_obj) { function(search, autocomplete_obj) {
api_search_component(search, function(results) { api_search_component(search, function(results) {
@ -36,9 +64,9 @@ function(search, autocomplete_obj) {
node.appendChild(img_container); node.appendChild(img_container);
node.appendChild(text_container); node.appendChild(text_container);
test.push({'ui': node, 'data': c.url}) test.push({'ui': node, 'data': c})
} }
autocomplete_obj.show_results(test, autocomplete_obj.show_results(test,
function(data) {console.log(data);}); function(data){fill_add_stock_modal_component(data);});
}, function(){}); }, function(){});
}); });

View File

@ -11,6 +11,8 @@ class AutocompleteCustomUi {
this.query_callback(document.getElementById(this.text_id).value, this); this.query_callback(document.getElementById(this.text_id).value, this);
}, autocomplete_query_delay_ms).bind(this)); }, autocomplete_query_delay_ms).bind(this));
this.dropdown_data = {};
} }
/** /**
@ -24,15 +26,17 @@ class AutocompleteCustomUi {
var ul = document.getElementById(this.dropdown_id); var ul = document.getElementById(this.dropdown_id);
ul.innerHTML = ''; ul.innerHTML = '';
this.select_data = {}; this.select_data = {};
this.dropdown_data = {};
for (var i = 0; i < nodes_to_add.length; i++) { for (var i = 0; i < nodes_to_add.length; i++) {
var ui = nodes_to_add[i]['ui']; var ui = nodes_to_add[i]['ui'];
var data = nodes_to_add[i]['data'] var data = nodes_to_add[i]['data']
var dropdown_node = document.createElement('li'); var dropdown_node = document.createElement('li');
dropdown_node.dataset.ac_clicked = data; dropdown_node.dataset.ac_clicked = i;
this.dropdown_data[i] = data;
dropdown_node.addEventListener('click', dropdown_node.addEventListener('click',
(e) => { (e) => {
data_clicked_callback(e.currentTarget.dataset.ac_clicked); data_clicked_callback(this.dropdown_data[e.currentTarget.dataset.ac_clicked]);
var text_box = document.getElementById(this.text_id); var text_box = document.getElementById(this.text_id);
var dropdown = bootstrap.Dropdown.getOrCreateInstance(text_box); var dropdown = bootstrap.Dropdown.getOrCreateInstance(text_box);
dropdown.hide(); dropdown.hide();

View File

@ -32,3 +32,7 @@ function api_search_user(search, onSuccess, onFail) {
function api_search_component(search, onSuccess, onFail) { function api_search_component(search, onSuccess, onFail) {
return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail);
} }
function api_get_component_from_id(id, onSuccess, onFail) {
return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(id)}`, function(method, url, json) {onSuccess(json.results[0]);}, onFail);
}

View File

@ -2,6 +2,7 @@
Input context: Input context:
- form: A stock-create-form - form: A stock-create-form
{% endcomment %} {% endcomment %}
{% load static %}
<div class="modal fade" id="add-stock-modal"> <div class="modal fade" id="add-stock-modal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -14,12 +15,59 @@ Input context:
<input class="form-control" autocomplete="off" data-bs-toggle="dropdown" type="search" id="add-stock-search" placeholder="Search Component" aria-label="Search for Component"> <input class="form-control" autocomplete="off" data-bs-toggle="dropdown" type="search" id="add-stock-search" placeholder="Search Component" aria-label="Search for Component">
<ul class="dropdown-menu" aria-labelledby="add-stock-search" id="add-stock-search-ac-dropdown"> <ul class="dropdown-menu" aria-labelledby="add-stock-search" id="add-stock-search-ac-dropdown">
</div> </div>
<hr> </div>
<form method="post">
{% csrf_token %}
<div class="modal-body">
<div id="add-stock-modal-component-container" class="mb-3">
<h4>No component selected.<h4>
<!-- Will be filled dynamically by selecting a component from the dropdown list -->
</div>
<input type="hidden" name="{{form.component_uuid.name}}" id="add-stock-modal-comp-uuid" value="{{form.component_uuid.value}}" required>
<div class="mb-3">
<label for="add-stock-form-amount" class="form-label">Amount</label>
<input id="add-stock-form-amount" type="number" class="form-control{% if form.amount.errors %} is-invalid{% endif %}" min="0" name="{{form.amount.name}}" value="{{form.amount.value}}" required aria-describedby="add-stock-form-amount-err">
<div id="add-stock-form-amount-err" class="invalid-feedback">
{% for msg in form.amount.errors %}
{{msg}}
{% endfor %}
</div>
</div>
<div class="mb-3">
<label for="add-stock-form-watermark" class="form-label">Watermark</label>
<div class="input-group">
<div class="input-group-text">
<input class="form-check-input mt-0" type="checkbox" {% if form.watermark_active.value %}checked{% endif %} name="{{form.watermark_active.name}}">
</div>
<input id="add-stock-form-watermark" type="number" class="form-control{% if form.watermark.errors %} is-invalid{% endif %}" name="{{form.watermark.name}}" value="{{form.watermark.value}}" min="0" required aria-describedby="add-stock-form-watermark-err">
<div id="add-stock-form-watermark-err" class="invalid-feedback">
{% for msg in form.watermark.errors %}
{{msg}}
{% endfor %}
</div>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<input type="submit" class="btn btn-primary" value="Add Stock" name="submit-add-stock"> <input type="submit" class="btn btn-primary" value="Add Stock" name="submit-add-stock">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="text-danger text-center">{{ error }}</p>
{% endfor %}
{% endif %}
</div> </div>
</form>
</div> </div>
</div> </div>
</div> </div>
<template id="add-stock-modal-component-template">
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<img id="add-stock-cmp-img" src="{% static 'css/icons/card-image.svg' %}" style="width:64px;max-height:64px;">
</div>
<div class="flex-grow-1 ms-1" id="add-stock-cmp-desc-container">
</div>
</div>
</template>

View File

@ -121,6 +121,7 @@
{% endblock content %} {% endblock content %}
{% block custom_scripts %} {% block custom_scripts %}
<script type="text/javascript" src="{% static 'js/add-stock-modal.js' %}"></script>
<script type="text/javascript"> <script type="text/javascript">
{% if add_storage_form.errors %} {% if add_storage_form.errors %}
@ -133,6 +134,26 @@
var d_modal = bootstrap.Modal.getOrCreateInstance(deleteStorageModal); var d_modal = bootstrap.Modal.getOrCreateInstance(deleteStorageModal);
d_modal.show(); d_modal.show();
{% endif %} {% endif %}
{% if add_stock_form.errors %}
var uuid = "{{add_stock_form.component_uuid.value}}";
if (uuid && uuid != '' && uuid != 'None') {
api_get_component_from_id(uuid, function(component){
var add_stock_modal_div = document.querySelector('#add-stock-modal');
var add_stock_modal = bootstrap.Modal.getOrCreateInstance(add_stock_modal_div);
fill_add_stock_modal_component(component);
add_stock_modal.show();
console.log(component)
}, function(){
var add_stock_modal_div = document.querySelector('#add-stock-modal');
var add_stock_modal = bootstrap.Modal.getOrCreateInstance(add_stock_modal_div);
add_stock_modal.show();
});
} else {
var add_stock_modal_div = document.querySelector('#add-stock-modal');
var add_stock_modal = bootstrap.Modal.getOrCreateInstance(add_stock_modal_div);
add_stock_modal.show();
}
{% endif %}
new AutocompleteText('{{add_storage_form.responsible.id_for_label}}', '{{add_storage_form.responsible.id_for_label}}-ac-dropdown', new AutocompleteText('{{add_storage_form.responsible.id_for_label}}', '{{add_storage_form.responsible.id_for_label}}-ac-dropdown',
function(search, autocomplete_obj) { function(search, autocomplete_obj) {
@ -150,7 +171,6 @@ function(search, autocomplete_obj) {
<script type="text/javascript"> <script type="text/javascript">
const fallback_img_path = "{% static 'css/icons/card-image.svg' %}"; const fallback_img_path = "{% static 'css/icons/card-image.svg' %}";
</script> </script>
<script type="text/javascript" src="{% static 'js/add-stock-modal.js' %}"></script>
{% endblock custom_scripts %} {% endblock custom_scripts %}