feat: add form for adding stock
This commit is contained in:
parent
715b65e9d7
commit
9bf0516d89
|
@ -0,0 +1,17 @@
|
|||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class AddStockForm(forms.Form):
|
||||
part_number = forms.CharField(label=_("Component"))
|
||||
properties = forms.JSONField(initial={}, label=_("Properties"), required=False)
|
||||
quantity = forms.IntegerField(min_value=1, label=_("Quantity"))
|
||||
original_quantity = forms.IntegerField(min_value=1, label=_("Original quantity"))
|
||||
total_value = forms.DecimalField(
|
||||
min_value=0, max_digits=8, decimal_places=2, label=_("Total value")
|
||||
)
|
||||
location = forms.CharField(label=_("Location"), widget=forms.Textarea)
|
||||
owner = forms.CharField(label=_("Owner"))
|
||||
|
||||
|
||||
AddStockFormSet = forms.formset_factory(AddStockForm, extra=0)
|
|
@ -1,3 +1,21 @@
|
|||
from django.dispatch import Signal
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
populate_nav = Signal()
|
||||
|
||||
|
||||
@receiver(populate_nav, dispatch_uid="populate_nav_core")
|
||||
def populate_nav_core(sender, **kwargs):
|
||||
request = sender
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return []
|
||||
|
||||
return [
|
||||
{
|
||||
"is_active": request.resolver_match.url_name == "add_stock",
|
||||
"link": reverse("add_stock"),
|
||||
"text": _("Add stock"),
|
||||
},
|
||||
]
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
let totalForms = Object.defineProperty({}, 'value', {
|
||||
get() {
|
||||
return +document.getElementById('id_form-TOTAL_FORMS').value;
|
||||
},
|
||||
|
||||
set(value) {
|
||||
document.getElementById('id_form-TOTAL_FORMS').value = value;
|
||||
},
|
||||
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
const updateNames = (elem, i) => {
|
||||
const replaceIndex = s => s.replaceAll('__prefix__', `${i}`);
|
||||
|
||||
if (elem.hasAttribute('for')) {
|
||||
elem.htmlFor = replaceIndex(elem.htmlFor);
|
||||
}
|
||||
|
||||
if (elem.hasAttribute('id')) {
|
||||
elem.id = replaceIndex(elem.id);
|
||||
}
|
||||
|
||||
if (elem.hasAttribute('name')) {
|
||||
elem.name = replaceIndex(elem.name);
|
||||
}
|
||||
|
||||
for (const child of elem.children) {
|
||||
updateNames(child, i);
|
||||
}
|
||||
};
|
||||
|
||||
const autofillQuantity = i => {
|
||||
const originalQuantity = document.getElementById(`id_form-${i}-original_quantity`);
|
||||
const quantity = document.getElementById(`id_form-${i}-quantity`);
|
||||
|
||||
const autofillOnInput = (self, other) => () => {
|
||||
delete self.dataset.autofilled;
|
||||
|
||||
if (!other.value || other.dataset.autofilled) {
|
||||
other.value = self.value;
|
||||
other.dataset.autofilled = true;
|
||||
}
|
||||
};
|
||||
|
||||
originalQuantity.addEventListener('input', autofillOnInput(originalQuantity, quantity));
|
||||
quantity.addEventListener('input', autofillOnInput(quantity, originalQuantity));
|
||||
};
|
||||
|
||||
const addFormButton = document.getElementById('addForm');
|
||||
|
||||
const addForm = () => {
|
||||
const form = document.getElementById('formTemplate').cloneNode(true);
|
||||
const i = totalForms.value++;
|
||||
updateNames(form, i);
|
||||
|
||||
const heading = document.createElement('h3');
|
||||
heading.innerText = `#${i+1}`;
|
||||
|
||||
addFormButton.before(heading, ...form.children);
|
||||
|
||||
autofillQuantity(i);
|
||||
document.getElementById(`id_form-${i}-owner`).value = JSON.parse(document.getElementById('currentUser').textContent);
|
||||
};
|
||||
|
||||
addFormButton.addEventListener('click', addForm);
|
||||
|
||||
for (let i = 0; i < totalForms.value; ++i) {
|
||||
autofillQuantity(i);
|
||||
}
|
|
@ -28,5 +28,6 @@
|
|||
e.classList.remove('show');
|
||||
});
|
||||
</script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load django_bootstrap5 %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block page_title %}{% translate "Add stock" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% translate "Add stock" %}</h1>
|
||||
<form class="mb-3" method="post">
|
||||
{% csrf_token %}
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset layout="floating" %}
|
||||
{% for form in formset %}
|
||||
<h3>#{{ forloop.counter }}</h3>
|
||||
{% bootstrap_form form layout="floating" %}
|
||||
{% endfor %}
|
||||
{% bootstrap_button "+" button_class="btn-outline-secondary" button_type="button" id="addForm" %}
|
||||
{% translate "Add" as add_button_text %}
|
||||
{% bootstrap_button button_type="submit" content=add_button_text %}
|
||||
</form>
|
||||
<div class="d-none" id="formTemplate">
|
||||
{% bootstrap_form formset.empty_form layout="floating" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ properties|json_script:"properties" }}
|
||||
{{ request.user.username|json_script:"currentUser" }}
|
||||
<script src="{% static 'core/add_stock.js' %}"></script>
|
||||
{% endblock %}
|
|
@ -4,6 +4,7 @@ from . import views
|
|||
|
||||
urlpatterns = [
|
||||
path("", views.index, name="index"),
|
||||
path("add-stock", views.add_stock, name="add_stock"),
|
||||
path("unauthorized", views.unauthorized, name="unauthorized"),
|
||||
path("admin/login/", views.admin_login),
|
||||
]
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import transaction
|
||||
from django.forms.models import model_to_dict
|
||||
from django.http import QueryDict
|
||||
from django.shortcuts import redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from .forms import AddStockFormSet
|
||||
from .models import Component, Property, Stock, User, Value
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -13,6 +20,101 @@ def unauthorized(request):
|
|||
return render(request, "core/unauthorized.html", {})
|
||||
|
||||
|
||||
@login_required
|
||||
def add_stock(request):
|
||||
property_models = {p.name: p for p in Property.objects.all()}
|
||||
|
||||
if request.method == "POST":
|
||||
formset = AddStockFormSet(request.POST, initial=[{"owner": request.user}])
|
||||
if not formset.is_valid():
|
||||
return render(request, "core/add_stock.html", {"formset": formset})
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
for form in formset:
|
||||
part_number = form.cleaned_data["part_number"]
|
||||
component, created = Component.objects.get_or_create(
|
||||
part_number__iexact=part_number,
|
||||
defaults={"part_number": part_number},
|
||||
)
|
||||
|
||||
if created:
|
||||
properties = form.cleaned_data["properties"]
|
||||
for name in properties:
|
||||
if name not in property_models:
|
||||
property_kwargs = {
|
||||
k: v
|
||||
for k, v in properties[name].items()
|
||||
if k
|
||||
in (
|
||||
"filterable",
|
||||
"type",
|
||||
"searchable",
|
||||
"unit",
|
||||
"scale",
|
||||
)
|
||||
}
|
||||
property_models[name] = Property.objects.create(
|
||||
name=name, **property_kwargs
|
||||
)
|
||||
property = property_models[name]
|
||||
|
||||
value_kwargs = {
|
||||
k: v
|
||||
for k, v in properties[name].items()
|
||||
if k in ("integer", "text")
|
||||
}
|
||||
value_kwargs.setdefault("integer", 0)
|
||||
value_kwargs.setdefault("text", "")
|
||||
Value.objects.create(
|
||||
property=property, component=component, **value_kwargs
|
||||
)
|
||||
|
||||
owner_name = form.cleaned_data["owner"]
|
||||
owner = User.objects.filter(username=owner_name).first()
|
||||
|
||||
Stock.objects.create(
|
||||
component=component,
|
||||
quantity=form.cleaned_data["quantity"],
|
||||
original_quantity=form.cleaned_data["original_quantity"],
|
||||
total_value=form.cleaned_data["total_value"],
|
||||
location=form.cleaned_data["location"],
|
||||
owner=owner,
|
||||
owner_name=owner_name,
|
||||
)
|
||||
except Exception as e:
|
||||
messages.add_message(request, messages.ERROR, str(e))
|
||||
|
||||
return render(
|
||||
request,
|
||||
"core/add_stock.html",
|
||||
{
|
||||
"formset": formset,
|
||||
"properties": list(map(model_to_dict, property_models.values())),
|
||||
},
|
||||
)
|
||||
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.SUCCESS,
|
||||
ngettext(
|
||||
formset.total_form_count,
|
||||
"Added %(count)d stock entry",
|
||||
"Added %(count)d stock entries",
|
||||
)
|
||||
% {"count": formset.total_form_count},
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"core/add_stock.html",
|
||||
{
|
||||
"formset": AddStockFormSet(initial=[{"owner": request.user}]),
|
||||
"properties": list(map(model_to_dict, property_models.values())),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def admin_login(request):
|
||||
if request.user.is_authenticated:
|
||||
return redirect("unauthorized")
|
||||
|
|
Loading…
Reference in New Issue