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
permission_classes = [permissions.DjangoModelPermissions]
filter_backends = [filters.SearchFilter]
search_fields = ['name', 'package__name', 'manufacturer__name']
search_fields = ['id', 'name', 'package__name', 'manufacturer__name']
class PartsManufacturerViewSet(viewsets.ModelViewSet):
queryset = parts_models.Manufacturer.objects.all()

View File

@ -71,4 +71,37 @@ class EditStockAmountForm(forms.Form):
if not increase:
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 django.core.paginator import Paginator
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.functions import Lower
import uuid
@ -223,6 +223,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
add_storage_form.fields['responsible'].initial = self.request.user.username
context['add_storage_form'] = add_storage_form
context['delete_storage_error'] = None
context['add_stock_form'] = AddStockForm()
return context
@ -293,6 +294,24 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
context = self.get_context_data(**kwargs)
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):
self.object = self.get_object()
@ -308,4 +327,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
return self.handle_amount_change_post(request, False, **kwargs)
elif 'submit-amount-increase' in request.POST:
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)

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',
function(search, autocomplete_obj) {
api_search_component(search, function(results) {
@ -36,9 +64,9 @@ function(search, autocomplete_obj) {
node.appendChild(img_container);
node.appendChild(text_container);
test.push({'ui': node, 'data': c.url})
test.push({'ui': node, 'data': c})
}
autocomplete_obj.show_results(test,
function(data) {console.log(data);});
function(data){fill_add_stock_modal_component(data);});
}, function(){});
});

View File

@ -11,6 +11,8 @@ class AutocompleteCustomUi {
this.query_callback(document.getElementById(this.text_id).value, this);
}, autocomplete_query_delay_ms).bind(this));
this.dropdown_data = {};
}
/**
@ -24,15 +26,17 @@ class AutocompleteCustomUi {
var ul = document.getElementById(this.dropdown_id);
ul.innerHTML = '';
this.select_data = {};
this.dropdown_data = {};
for (var i = 0; i < nodes_to_add.length; i++) {
var ui = nodes_to_add[i]['ui'];
var data = nodes_to_add[i]['data']
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',
(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 dropdown = bootstrap.Dropdown.getOrCreateInstance(text_box);
dropdown.hide();

View File

@ -31,4 +31,8 @@ function api_search_user(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);
}
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:
- form: A stock-create-form
{% endcomment %}
{% load static %}
<div class="modal fade" id="add-stock-modal">
<div class="modal-dialog">
<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">
<ul class="dropdown-menu" aria-labelledby="add-stock-search" id="add-stock-search-ac-dropdown">
</div>
<hr>
</div>
<div class="modal-footer">
<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">
</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 class="modal-footer">
<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">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="text-danger text-center">{{ error }}</p>
{% endfor %}
{% endif %}
</div>
</form>
</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 %}
{% block custom_scripts %}
<script type="text/javascript" src="{% static 'js/add-stock-modal.js' %}"></script>
<script type="text/javascript">
{% if add_storage_form.errors %}
@ -133,6 +134,26 @@
var d_modal = bootstrap.Modal.getOrCreateInstance(deleteStorageModal);
d_modal.show();
{% 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',
function(search, autocomplete_obj) {
@ -150,7 +171,6 @@ function(search, autocomplete_obj) {
<script type="text/javascript">
const fallback_img_path = "{% static 'css/icons/card-image.svg' %}";
</script>
<script type="text/javascript" src="{% static 'js/add-stock-modal.js' %}"></script>
{% endblock custom_scripts %}