Compare commits
3 Commits
4b4a85d1aa
...
9bf0516d89
Author | SHA1 | Date |
---|---|---|
Luca | 9bf0516d89 | |
Luca | 715b65e9d7 | |
Luca | 43af8e9cc4 |
|
@ -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()
|
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);
|
||||||
|
}
|
|
@ -13,14 +13,14 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% block navbar %}
|
{% block navbar %}{% include "partials/navbar.html" %}{% endblock %}
|
||||||
{% include "partials/navbar.html" %}
|
<div class="container-fluid">
|
||||||
{% endblock %}
|
|
||||||
{% block messages %}
|
{% block messages %}
|
||||||
{% bootstrap_messages %}
|
{% bootstrap_messages %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<script src="{% static 'bootstrap.bundle.min.js' %}"></script>
|
<script src="{% static 'bootstrap.bundle.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -28,5 +28,6 @@
|
||||||
e.classList.remove('show');
|
e.classList.remove('show');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 %}
|
|
@ -3,3 +3,8 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block page_title %}{% translate "Unauthorized" %}{% endblock %}
|
{% block page_title %}{% translate "Unauthorized" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% translate "Unauthorized" %}</h1>
|
||||||
|
<p>{% translate "Please log in to use this application." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -4,6 +4,7 @@ from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.index, name="index"),
|
||||||
|
path("add-stock", views.add_stock, name="add_stock"),
|
||||||
path("unauthorized", views.unauthorized, name="unauthorized"),
|
path("unauthorized", views.unauthorized, name="unauthorized"),
|
||||||
path("admin/login/", views.admin_login),
|
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.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.http import QueryDict
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
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
|
@login_required
|
||||||
|
@ -13,6 +20,101 @@ def unauthorized(request):
|
||||||
return render(request, "core/unauthorized.html", {})
|
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):
|
def admin_login(request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
return redirect("unauthorized")
|
return redirect("unauthorized")
|
||||||
|
|
Loading…
Reference in New Issue