parent
cfb2ceb2c3
commit
805c7d3dc0
10 changed files with 151 additions and 15 deletions
|
@ -12,7 +12,8 @@ class NummiForm(forms.ModelForm):
|
|||
template_name = "main/form/form_base.html"
|
||||
meta_fieldsets = []
|
||||
|
||||
def __init__(self, *args, user, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop("user", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,8 +1,32 @@
|
|||
@keyframes border-pulse {
|
||||
from {
|
||||
border-color: var(--green);
|
||||
.drop-zone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: transparent;
|
||||
display: grid;
|
||||
transition-property: backdrop-filter;
|
||||
transition-duration: 750ms;
|
||||
z-index: -1;
|
||||
|
||||
> span {
|
||||
font-weight: 650;
|
||||
font-size: 2rem;
|
||||
transition-property: color;
|
||||
transition-duration: inherit;
|
||||
}
|
||||
to {
|
||||
|
||||
main.highlight > & {
|
||||
z-index: 100;
|
||||
backdrop-filter: blur(0.1rem);
|
||||
> span {
|
||||
color: var(--green);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +38,10 @@ form {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
|
|
|
@ -85,6 +85,7 @@ footer {
|
|||
@media (width > 720px) {
|
||||
padding: 2rem;
|
||||
}
|
||||
background: var(--bg);
|
||||
}
|
||||
main {
|
||||
position: relative;
|
||||
|
|
|
@ -59,7 +59,7 @@ def messageicon(level):
|
|||
|
||||
@register.filter
|
||||
def extension(file):
|
||||
return file.name.split(".")[-1].upper()
|
||||
return file.name.split(".", 1)[1].upper()
|
||||
|
||||
|
||||
@register.filter
|
||||
|
|
|
@ -25,7 +25,7 @@ class StatementForm(NummiForm):
|
|||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
_user = kwargs.get("user")
|
||||
_user = kwargs.pop("user")
|
||||
_disable_account = kwargs.pop("disable_account", False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["account"].queryset = _user.account_set.exclude(archived=True)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import json
|
||||
|
||||
from category.forms import CategorySelect
|
||||
from django import forms
|
||||
from django.forms import formset_factory
|
||||
from main.forms import DatalistInput, NummiFileInput, NummiForm
|
||||
from statement.forms import StatementSelect
|
||||
|
||||
|
@ -46,7 +48,7 @@ class TransactionForm(NummiForm):
|
|||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
_user = kwargs.get("user")
|
||||
_user = kwargs.pop("user")
|
||||
_disable_statement = kwargs.pop("disable_statement", False)
|
||||
_autocomplete = kwargs.pop("autocomplete", False)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
@ -117,3 +119,29 @@ class InvoiceForm(NummiForm):
|
|||
widgets = {
|
||||
"file": NummiFileInput,
|
||||
}
|
||||
|
||||
|
||||
class MultipleFileInput(forms.ClearableFileInput):
|
||||
allow_multiple_selected = True
|
||||
|
||||
|
||||
class MultipleFileField(forms.FileField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("widget", MultipleFileInput())
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def clean(self, data, initial=None):
|
||||
single_file_clean = super().clean
|
||||
if isinstance(data, (list, tuple)):
|
||||
result = [single_file_clean(d, initial) for d in data]
|
||||
else:
|
||||
result = single_file_clean(data, initial)
|
||||
return result
|
||||
|
||||
|
||||
class MultipleInvoicesForm(forms.Form):
|
||||
prefix = "invoices"
|
||||
invoices = MultipleFileField()
|
||||
|
||||
|
||||
InvoicesFormSet = formset_factory(MultipleInvoicesForm)
|
||||
|
|
22
nummi/transaction/static/transaction/js/invoice_form.js
Normal file
22
nummi/transaction/static/transaction/js/invoice_form.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
const dropArea = document.querySelector("main");
|
||||
const form = document.querySelector("form.invoices");
|
||||
|
||||
dropArea.addEventListener("dragover", (event) => {
|
||||
event.preventDefault();
|
||||
dropArea.classList.add("highlight");
|
||||
});
|
||||
|
||||
dropArea.addEventListener("dragleave", () => {
|
||||
dropArea.classList.remove("highlight");
|
||||
});
|
||||
|
||||
dropArea.addEventListener("drop", (event) => {
|
||||
console.log(event);
|
||||
event.preventDefault();
|
||||
dropArea.classList.remove("highlight");
|
||||
const files = event.dataTransfer.files;
|
||||
console.log(files);
|
||||
const input = form.querySelector("input[type=file]");
|
||||
input.files = files;
|
||||
form.submit();
|
||||
});
|
|
@ -10,6 +10,7 @@
|
|||
{% css "main/css/form.css" %}
|
||||
{% css "main/css/table.css" %}
|
||||
{% css "main/css/plot.css" %}
|
||||
{% js "transaction/js/invoice_form.js" %}
|
||||
{% endblock link %}
|
||||
{% block body %}
|
||||
<h2>{{ transaction }}</h2>
|
||||
|
@ -44,5 +45,15 @@
|
|||
<section>
|
||||
<h3>{% translate "Invoices" %}</h3>
|
||||
{% invoice_table transaction %}
|
||||
<form class="hidden invoices"
|
||||
method="post"
|
||||
action="{% url "multiple_invoice" transaction=transaction.pk %}"
|
||||
enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ invoices_form }}
|
||||
</form>
|
||||
</section>
|
||||
<div class="drop-zone">
|
||||
<span class="wi">{{ "file-add"|remix }}{% translate "Add invoice" %}</span>
|
||||
</div>
|
||||
{% endblock body %}
|
||||
|
|
|
@ -31,6 +31,11 @@ urlpatterns = [
|
|||
views.InvoiceCreateView.as_view(),
|
||||
name="new_invoice",
|
||||
),
|
||||
path(
|
||||
"<transaction>/invoice/multiple",
|
||||
views.MultipleInvoiceCreateView.as_view(),
|
||||
name="multiple_invoice",
|
||||
),
|
||||
path(
|
||||
"<transaction>/invoice/<invoice>",
|
||||
views.InvoiceUpdateView.as_view(),
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
from account.models import Account
|
||||
from category.models import Category
|
||||
from django.contrib import messages
|
||||
from django.forms import ValidationError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.dates import MonthArchiveView, YearArchiveView
|
||||
from django.views.generic.edit import FormView
|
||||
from history.utils import history
|
||||
from main.views import (
|
||||
NummiCreateView,
|
||||
|
@ -13,7 +18,7 @@ from main.views import (
|
|||
UserMixin,
|
||||
)
|
||||
|
||||
from .forms import InvoiceForm, TransactionForm
|
||||
from .forms import InvoiceForm, MultipleInvoicesForm, TransactionForm
|
||||
from .models import Invoice, Transaction
|
||||
|
||||
|
||||
|
@ -57,6 +62,45 @@ class InvoiceCreateView(NummiCreateView):
|
|||
return reverse_lazy("transaction", args=(self.object.transaction.pk,))
|
||||
|
||||
|
||||
class MultipleInvoiceCreateView(FormView):
|
||||
form_class = MultipleInvoicesForm
|
||||
|
||||
def form_valid(self, form):
|
||||
transaction = get_object_or_404(
|
||||
self.request.user.transaction_set, pk=self.kwargs["transaction"]
|
||||
)
|
||||
|
||||
invoices = []
|
||||
for file in form.cleaned_data["invoices"]:
|
||||
invoice = Invoice(
|
||||
transaction=transaction,
|
||||
user=self.request.user,
|
||||
file=file,
|
||||
name=file.name.split(".", 1)[0],
|
||||
)
|
||||
try:
|
||||
invoice.full_clean()
|
||||
except ValidationError as err:
|
||||
for msg in err.messages:
|
||||
messages.error(
|
||||
self.request,
|
||||
format_html(
|
||||
"{msg} {file}. {err}",
|
||||
msg=_("Error processing file"),
|
||||
file=file.name,
|
||||
err=msg,
|
||||
),
|
||||
)
|
||||
else:
|
||||
invoices.append(invoice)
|
||||
|
||||
Invoice.objects.bulk_create(invoices)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("transaction", args=(self.kwargs["transaction"],))
|
||||
|
||||
|
||||
class TransactionUpdateView(NummiUpdateView):
|
||||
model = Transaction
|
||||
form_class = TransactionForm
|
||||
|
@ -70,12 +114,8 @@ class TransactionDetailView(NummiDetailView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super().get_context_data(**kwargs)
|
||||
transaction = data.get("transaction")
|
||||
|
||||
return data | {
|
||||
"statement": transaction.statement,
|
||||
"category": transaction.category,
|
||||
}
|
||||
data["invoices_form"] = MultipleInvoicesForm()
|
||||
return data
|
||||
|
||||
|
||||
class InvoiceUpdateView(NummiUpdateView):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue