[Django] Salvare un oggetto dopo i relativi inlines

Pubblicatodi il Ago 7, 2019 in Programmazione
Nessun commento

Il problema che mi sono trovato a dover risolvere è il seguente.

Il nostro software aziendale, scritto utilizzando Django, tra le varie cose, si occupa di generare le fatture.

Tralasciando per il momento tutta una serie di campi, la fattura è composta dai dati anagrafici del cliente e imponibile più tasse.

I prodotti/servizi acquistati dal cliente vengono gestiti tramite una relazione many-to-many.

Ogni fattura può avere uno o più oggetti associati ognuno con una sua causale, imponibile, aliquota IVA e quantità.

All’interno dell’admin relativo alle fatture la relazione many-to-many viene rappresentata tramite un inline.

Una volta inserite le linee della fattura relative ai prodotti/servizi acquistati dal cliente ho la necessità, per semplificare i lavori dell’amministrazione, che il software mi calcoli in automatico imponibile e aliquota IVA della fattura, facendo ovviamente la somma di tutte le linee associate.

La prima soluzione è quella di aggiungere nell’admin.py relativo alle fatture la funzione di calcolo dei totali all’interno della funzione save_model.

Per chi ha un minimo di esperienza con django sa perfettamente che questa soluzione non può funzionare in quanto all’interno della funzione save_model viene salvato l’oggetto ma non i relativi inlines che vengono invece salvati in una funziona a parte.

Questo perché ovviamente non sarebbe possibile salvare gli oggetti nella relazione many-to-many se prima non fosse stato salvato l’oggetto a cui sono stati associati.

Come risolvere quindi?

save_formset

All’interno del file admin.py del nostro oggetto possiamo fare l’override della funziona save_formset che è la funzione che, sostanzialmente, si occupa di salvare ogni singolo inline associato all’oggetto principale.

Ovviamente all’interno di questa funzione l’oggetto principale è già stato salvato. Noi dobbiamo solo occuparci di salvare gli inlines e successivamente utilizzarli per calcolare il totale.

Il risultato sarà qualcosa di simile:

    def save_formset(self, request, form, formset, change):
        if not change:
            formset.save()
            obj = form.instance

            amount = 0
            vat = 0
            receipt_line = ReceiptLine.objects.filter(manual_receipt=obj).values('vat__perc', 'amount', 'quantity')

            for r in receipt_line:
                tmp_vat = round(((r['amount'] * r['quantity']) + (((r['amount'] * r['quantity']) * r['vat__perc']) / 100)) - (r['amount'] * r['quantity']), 2)
                amount += round((r['amount'] * r['quantity']), 2)
                vat += tmp_vat

            amount = round(amount, 2)
            vat = round(vat, 2)

            obj.amount = str(amount)
            obj.vat_amount = str(vat)
            obj.save()

La funzione save_formset accetta 5 parametri. Quelli che interessano a noi sono form, formset e change.

L’oggetto form contiene il form dell’oggetto principale da cui possiamo estrapolarlo con la linea:

obj = form.instance

All’interno della variabile obj avrò quindi l’oggetto contenente la mia fattura.

Il parametro formset contiene invece tutti i form relativi alle inlines. Prima di effettuare i calcoli dovrò quindi occuparmi salvare l’oggetto.

formset.save()

La variabile change ci dice se l’oggetto su cui stiamo lavorando è un oggetto modificato (quindi che già esistente) oppure è stato appena creato.

Nel nostro caso preferiamo che il totale venga calcolato solo al primo salvataggio e poi lasciato libero. Questo perché nel caso ci fosse qualche errore nei totali (di solito di arrotondamento) in questo modo possiamo modificarlo, altrimenti non sarebbe possibile in quanto verrebbe calcolato in automatico ad ogni salvataggio.

Una volta quindi salvati gli oggetti presenti negli inline mi è quindi possibile scorrerli con un ciclo for ed effettuare la somma totale.

Una volta calcolate le somme di imponibile e IVA posso associarli al mio oggetto principale e infine salvarlo.

# # # #

Rispondi