Hello
There is something wrong with my code. I don’t know what. When I display the page, everything is ok. Company’data are ok. Related businesses are ok. But when I hit the “update” key, even if I do not change anthing, bussiness data show issues. The link to the details is not displayed anymore, and amount_pondered and amount_to_plan are set to 0 (when their value are 10 ans 100). Could someone tell me what is wrong. Here is the code:
Models.py
class Company(models.Model):
name = models.CharField('Nom',max_length=200,)
SIRET = models.CharField('SIRET',max_length=16, null = True, blank = True)
creation_date = models.DateField('Date de création',default=datetime.date.today,
null = True,blank = True)
last_modification_date = models.DateTimeField('Date de dernière modification',
auto_now= True,null = True, blank = True,)
class Meta:
ordering = ['name']
verbose_name = 'Client'
def total_business(self):
# Renvoie le montant total de chiffre d'affaire signé avec ce client
business=self.customer.all()
total_business = 0
for b in business:
b_ponderation = b.ponderation
if b_ponderation.weight == 100:
total_business += b.amount
return total_business
def total_to_bill(self):
# Renvoie le montant total à facturer à ce client
business=self.customer.all()
total_to_bill = 0
for b in business:
b_ponderation = b.ponderation
if b_ponderation.weight == 100:
total_to_bill += b.amount_to_bill()
return total_to_bill
def total_under_negotiation(self):
# Renvoie le montant pondéré des affaires en cours de négociation
business=self.customer.all()
total_under_negotiation = 0
for b in business:
b_ponderation = b.ponderation
if b_ponderation.weight != 100:
total_under_negotiation += b.amount_pondered()
return total_under_negotiation
def __str__(self):
"""Cette retourne une chaîne de caractère pour identifier l'instance de la classe d'objet."""
return self.name
def get_absolute_url(self):
return reverse('customer_detail_form', kwarg={'pk':self.pk})
class Framework_agreement(models.Model):
number = models.CharField('Numéro',max_length=30,)
company=models.ForeignKey(Company, on_delete=models.CASCADE, verbose_name='Client')
signature_date= models.DateField('Date de signature',null=True,blank=True)
def __str__(self):
"""String for representing the Model object (in Admin site etc.)"""
return self.number
class Meta:
ordering = ['signature_date']
verbose_name = 'Accord cadre'
class PO_market(models.Model):
number = models.CharField('Numéro',max_length=30,)
company=models.ForeignKey(Company, on_delete=models.CASCADE, verbose_name='Client')
signature_date= models.DateField('Date de signature',null=True,blank=True)
framework_agreement = models.ForeignKey(Framework_agreement, on_delete=models.RESTRICT, null=True, blank=True)
def __str__(self):
"""String for representing the Model object (in Admin site etc.)"""
return self.number
class Meta:
ordering = ['signature_date']
verbose_name = 'Marché à bons de commande'
class Business(models.Model):
name = models.CharField('Affaire',max_length=200, null = True, blank=True)
customer = models.ForeignKey(Company,related_name = 'customer',on_delete=models.CASCADE,verbose_name='Client')
beneficiary = models.ForeignKey(Company,related_name = 'beneficiary', on_delete=models.CASCADE,verbose_name='Bénéficiaire',
null = True,blank = True)
customer_ref = models.CharField('Référence client', max_length=250,
null=True, blank=True)
framework_agreement = models.ForeignKey(Framework_agreement, on_delete=models.SET_NULL, verbose_name='Accord-cadre', null=True, blank=True)
po_market = models.ForeignKey(PO_market, on_delete=models.SET_NULL, verbose_name='Marché à bons de commande', null=True, blank=True)
default_contact = models.ForeignKey(Contact, related_name = 'set_business_contact', on_delete=models.SET_NULL, \
null=True, blank=True, verbose_name='Contact client par défaut:')
default_addressee = models.ForeignKey(Contact, related_name="set_business_adressee" ,on_delete=models.SET_NULL, \
null=True, blank=True, verbose_name='Destinataire par défaut:')
default_address =models.ForeignKey(Address, on_delete=models.SET_NULL, \
null=True, blank=True, verbose_name='Adresse par défaut:')
account_exec = models.ForeignKey(Account_exec,on_delete=models.SET_NULL, null = True, blank = True, verbose_name='Chargé d\'affaires')
amount = models.DecimalField('Montant',max_digits=9, decimal_places=2, null=True,blank=True)
ponderation = models.ForeignKey(BusinessStatus, on_delete=models.RESTRICT, default=1)
creation_date = models.DateField('Date de création', default=datetime.date.today)
last_modification_date = models.DateTimeField('Date de dernière modification',
auto_now= True,null = True, blank = True,)
def amount_pondered(self):
status = BusinessStatus.objects.get(pk=self.ponderation_id)
taux = status.weight
if taux:
amount = self.amount * taux / 100
else:
amount = 0
return amount
def amount_to_plan(self):
invoices = self.invoice_set.all()
a = self.amount
for i in invoices:
if i.type == 'FA' or i.type == 'PR':
a -= i.amount
if i.type == 'AV':
a += i.amount
return a
def amount_to_bill(self):
status = BusinessStatus.objects.get(pk=self.ponderation_id)
taux = status.weight
invoices = self.invoice_set.all()
amount_to_bill = self.amount
if taux == 100:
for i in invoices:
if i.is_issued == True:
amount_to_bill -= i.amount
return amount_to_bill
def status(self):
return BusinessStatus.objects.get(pk=self.ponderation_id).text()
def weight(self):
return BusinessStatus.objects.get(pk=self.ponderation_id).weight
def __str__(self):
"""String for representing the Model object (in Admin site etc.)"""
return self.name
def get_absolute_url(self):
return reverse('customer_detail_form', kwarg={'pk':self.pk})
class Meta:
ordering = ['ponderation','amount']
verbose_name = 'Affaire'
Piece of forms.py
class BusinessForm(ModelForm):
def clean(self):
cleaned_data = super().clean()
name = cleaned_data.get('name')
account_exec = cleaned_data.get('account_exec')
amount = cleaned_data.get('amount')
if name:
"""if not(account_exec):
msg = 'Le responsable d\'affaires doit être saisi. account_exec='+str(account_exec)
self.add_error('account_exec', msg)"""
if not (amount):
msg = 'Le montant de l\'affaire doit être saisi'
self.add_error('amount', msg)
elif amount:
if not(name):
msg = 'Si un montant est indiqué, le libellé de l\'affaire doit être saisi'
self.add_error('name', msg)
if not(account_exec):
msg = 'Si un montant est indiqué, le responsable d\'affaires doit être saisi'
self.add_error('account_exec', msg)
def init_amount_pondered(self):
if self.instance.pk:
# si le business existe, on affiche le montant pondéréx²
amount = self.instance.amount_pondered()
else:
# si le business n'existe pas (pas encore créé), on affiche 0
amount = 0
return amount
def init_amount_to_plan(self):
if self.instance.pk:
amount = self.instance.amount_to_plan()
else:
amount = 0
return amount
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# On initialise les montants calculés (montant pondéré et montant restant à planifier)
self.fields['amount_to_plan'].initial = self.init_amount_to_plan()
self.fields['amount_pondered'].initial = self.init_amount_pondered()
for _, value in self.fields.items():
value.widget.attrs['class'] = 'form-control'
# Restreindre la liste de choix des po_market au client en cours
if self.instance.pk:
customer = self.instance.customer
self.fields['framework_agreement'].queryset = Framework_agreement.objects.filter(company=customer)
self.fields['po_market'].queryset = PO_market.objects.filter(company=customer)
else:
self.fields['framework_agreement'].queryset = Framework_agreement.objects.none()
self.fields['po_market'].queryset = PO_market.objects.none()
# On vérifie si l'affaire est déjà associée à un framework agreement
if self.instance.framework_agreement:
# Si oui, on réordonne les choix pour mettre le framework agreement associé en premier
framework_agreement = self.instance.framework_agreement
#choices = [(framework_agreement.id, str(framework_agreement))] + self.fields['framework_agreement'].choices
#self.fields['framework_agreement'].choices = choices
self.fields['framework_agreement'].initial = framework_agreement.id
if self.instance.po_market:
# Si oui, on réordonne les choix pour mettre le framework agreement associé en premier
po_market = self.instance.po_market
#choices = [(po_market.id, str(po_market))] + self.fields['po_market'].choices
#self.fields['po_market'].choices = choices
self.fields['po_market'].initial = po_market.id
amount_pondered = forms.CharField(disabled=True)
amount_to_plan = forms.CharField(disabled = True)
class Meta:
model = Business
fields = ['name', 'customer', 'beneficiary','framework_agreement','po_market','account_exec', 'amount','ponderation',]
widgets = {'creation_date': DatePickerInput,'signature_date': DatePickerInput}
type or paste code here
Views.py
class CompanyUpdateView(UpdateView):
template_name = 'salesforecast/company_update_form.html'
model = Company
fields = '__all__'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
company = get_object_or_404(Company, pk=self.kwargs['pk'])
context['company_form'] = CompanyForm(instance = company)
BusinessFormSet = inlineformset_factory(Company,Business,fk_name= "customer" ,can_delete=False,extra=1, form = BusinessForm)
business_formset = BusinessFormSet(instance=company)
context['business_formset'] = business_formset
ContactFormSet = inlineformset_factory(Company,Contact,can_delete=True,extra=1, form = ContactForm)
contact_formset = ContactFormSet(instance=company)
context['contact_formset'] = contact_formset
AddressFormSet = inlineformset_factory(Company,Address,can_delete=True,extra=1, form = AddressForm)
address_formset = AddressFormSet(instance=company)
context['address_formset'] = address_formset
return context
def post(self, request, *args, **kwargs):
#messages.warning(request,'post')
pk = self.kwargs['pk']
company = get_object_or_404(Company, pk=pk)
company_form = CompanyForm(instance=company, data=request.POST)
BusinessFormSet = inlineformset_factory(Company, Business, fk_name= "customer" ,can_delete=False, extra=1, form = BusinessForm)
business_formset = BusinessFormSet(instance=company, data= request.POST)
ContactFormSet = inlineformset_factory(Company,Contact,can_delete=True,extra=1, form = ContactForm)
contact_formset = ContactFormSet(instance=company, data=request.POST)
AddressFormSet = inlineformset_factory(Company,Address,can_delete=True,extra=1, form = AddressForm)
address_formset = AddressFormSet(instance=company, data=request.POST)
if 'update' in request.POST:
messages.warning(request, request.POST)
if company_form.has_changed():
if company_form.is_valid():
company_form.save()
messages.success(request, 'Les données Client ont été mises à jour.')
else:
messages.warning(request, 'les données Client sont incorrectes.')
if business_formset.has_changed():
messages.warning(request, 'business form has changed')
if business_formset.is_valid():
# A insérer: la sauvegarde des données dans l'historique
business_formset.save()
# On recharge la page avec les données mises à jour (pour faire apparaître une nouvelle ligne de saisie)
business_formset = BusinessFormSet(instance=company)
messages.success(request, 'Les données Affaires ont été mises à jour')
else:
messages.warning(request, 'Les données Affaires sont incorrectes.'+ str(business_formset.errors))
if contact_formset.has_changed():
if contact_formset.is_valid():
# A insérer: la sauvegarde des données dans l'historique
contact_formset.save()
# On recharge la page avec les données mises à jour (pour faire apparaître une nouvelle ligne de saisie)
contact_formset = ContactFormSet(instance=company)
messages.success(request, 'Les données Contacts ont été mises à jour')
else:
messages.warning(request, 'Les données Contacts sont incorrectes.')
if address_formset.has_changed():
if address_formset.is_valid():
# A insérer: la sauvegarde des données dans l'historique
address_formset.save()
# On recharge la page avec les données mises à jour (pour faire apparaître une nouvelle ligne de saisie)
address_formset = AddressFormSet(instance=company)
messages.success(request, 'Les données Adresses ont été mises à jour')
else:
messages.warning(request, 'Les données Adresses sont incorrectes.')
return render(request, 'salesforecast/company_update_form.html',\
{'company_form':company_form, 'business_formset':business_formset, \
'contact_formset':contact_formset,'address_formset': address_formset })
And the html code:
{% extends "base_generic.html" %}
{% block content %}
{% include 'navbar.html' with active='companies' %}
<div class="bloc"></div>
<form method="post">
<div class="formulaire shadow">
<h2>Dossier du client: {{company_form.name.value}} </h2>
<table class="table-hover">
{{ company_form }}
</table>
</div>
<div class="bloc"></div>
<div class="accordion" id="accordion">
<div class="accordion-item shadow" >
<h3 class="accordion-header" id="headingBusinesses">
<button class="accordion-button custom-color" type="button" data-bs-toggle="collapse" data-bs-target="#collapseBusinesses" aria-expanded="true" aria-controls="collapseBusinesses">
<h5>Les affaires</h5>
</button>
</h3>
<div id="collapseBusinesses" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordion">
<div class="accordion-body">
{% csrf_token %}
{{ business_formset.management_form }}
{% for form in business_formset %}
<div class="row">
<div class="col-md-1">
{% if form.id.value %}
<a href="/salesforecast/businesses/{{form.id.value}}">Lien</a>
{% endif %}
<label for="{{form.name.id_for_label}}">{{form.name.label}}</label>
</div>
<div class="col-md-2">{{form.name}}<div class="fieldWrapper">{{form.name.errors}}</div></div>
<div class="col-md-1">
<label for="{{form.beneficiary.id_for_label}}" class="mx-2">{{form.beneficiary.label}}</label>
</div>
<div class="col-md-1">{{ form.beneficiary }}<div class="fieldWrapper">{{form.beneficary.errors}}</div></div>
<div class=" col-md-1">
<label for="{{form.framework_agreement.id_for_label}}" class="mx-2">{{form.framework_agreement.label}}</label>
</div>
<div class="col-md-1">{{ form.framework_agreement }}<div class="fieldWrapper">{{form.framework_agreement.errors}}</div></div>
<div class="col-md-1">
<label for="{{form.po_market.id_for_label}}" class="mx-2">Marhé à BC</label>
</div>
<div class="col-md-1">{{ form.po_market }}<div class="fieldWrapper">{{form.po_market.errors}}</div></div>
<div class="col-md-1">
<label for="{{form.account_exec.id_for_label}}" class="mx-2">{{form.account_exec.label}}</label></div>
<div class="col-md-1">{{ form.account_exec }}<div class="fieldWrapper">{{form.account_exec.errors}}</div></div>
</div>
<div class="row">
<div class="col-md-3 form-group">
<label for="{{form.amount.id_for_label}}" class="mr-2">{{form.amount.label}}</label>
{{ form.amount }}
</div>
<div class="col-md-3 form-group">
<label for="{{form.ponderation.id_for_label}}" class="mx-2">{{form.ponderation.label}}</label>
{{ form.ponderation }}
</div>
<div class="col-md-3 form-group">
<label for="{{form.amount_pondered.id_for_label}}" class="mx-2">{{form.amount_pondered.label}}</label>
{{ form.amount_pondered }}
</div>
<div class="col-md-3">
<label for="{{form.amount_to_plan.id_for_label}}" class="mx-2">{{form.amount_to_plan.label}}</label>
{{ form.amount_to_plan }}
</div>
</div>
<div class="line"></div>
{% endfor %}
<!--
<table class="table table-hover ">
<tbody>
{% for form in business_formset %}
<tr>
<td class="no-display">{{ form.id }}</td>
{% if form.id.value %}
<td><a aria-label="lien vers l'affaire" class="article-title" href="/salesforecast/businesses/{{form.id.value}}"><i class='bx bx-detail nav_icon'></a></td>
{%else%}
<td></td>
{% endif %}
<td>{{ form.name }}</a></td>
<td>{{ form.beneficiary}}</td>
<td>{{ form.amount}}</td>
<td>{{ form.ponderation}}</td>
<td>{{ form.amount_pondered}}</td>
<td>{{ form.account_exec}}</td>
<td>{{ form.contract_type}}</td>
<td>{{ form.amount_to_plan}}</td>
</tr>
{% endfor %}
</tbody>
</table>
-->
</div>
</div>
</div>
<div class="bloc"></div>
<div class="accordion-item shadow" >
<h3 class="accordion-header" id="headingBusinesses">
<button class="accordion-button custom-color" type="button" data-bs-toggle="collapse" data-bs-target="#collapseContacts" aria-expanded="true" aria-controls="collapseContacts">
<h5>Les contacts</h5>
</button>
</h3>
<div id="collapseContacts" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordion">
<div class="accordion-body">
{% csrf_token %}
{{ contact_formset.management_form }}
<table class="table table-hover ">
<thead>
<tr>
<th></th>
<th></th>
<th>Prénom</th>
<th>Nom</th>
<th>Fonction</th>
<th>Adresse mail</th>
<th>Téléphone</th>
</tr>
</thead>
<tbody>
{% for form in contact_formset %}
<tr>
<td clas = "no-display">{{ form.id }}</td>
{% if form.id.value %}
<td><i class='bx bx-trash nav_icon'></i>{{ form.DELETE }}</td>
{%else%}
<td></td>
{%endif%}
<td>{{ form.first_name }}</td>
<td>{{ form.last_name}}</td>
<td>{{ form.job}}</td>
<td>{{ form.email}}</td>
<td>{{ form.phone}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="bloc"></div>
<div class="accordion-item shadow" >
<h5 class="accordion-header" id="headingAddresses">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAddresses" aria-expanded="false" aria-controls="collapseAddresses">
<h5>Les adresses</h5>
</button>
</h5>
<div id="collapseAddresses" class="accordion-collapse collapse" aria-labelledby="headingAddresses" data-bs-parent="#accordionExample">
<div class="accordion-body">
{% csrf_token %}
{{ address_formset.management_form }}
<table class="table table-hover ">
<thead>
<tr>
<th></th>
<th>Nom abrégé</th>
<th>N°</th>
<th>Voie</th>
<th>Code postal</th>
<th>Commune</th>
<th>Pays</th>
</tr>
</thead>
<tbody>
{% for form in address_formset %}
<tr>
<td class = "no-display">{{ form.id }}</td>
{% if form.id.value %}
<td><i class='bx bx-trash nav_icon'></i>{{ form.DELETE }}</td>
{%else%}
<td></td>
{%endif%}
<td>{{ form.label }}</td>
<td>{{ form.street_number}}</td>
<td>{{ form.street}} </td>
<td>{{ form.postal_code}}</td>
<td>{{ form.city}}</td>
<td>{{ form.country}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="bloc"></div>
<div>
<button id = "update" name="update" type="submit" class="btn btn-outline-secondary">
<i class='bx bx-save nav_icon'></i>
Sauvegarder
</button>
</div>
</form>
<style>
/* Header fermé */
.accordion-button.custom-color {
background-color: #fff;
color: #000;
}
/* Header ouvert */
.accordion-button.custom-color:not(.collapsed) {
background-color: #ccc;
color: #fff;
}
/* Flèche */
.accordion-button.custom-color:not(.collapsed) .accordion-button::after {
filter: invert(100%) sepia(100%) invert(100%) brightness(200%) grayscale(100%);
}
/* Flèche blanche */
.accordion-button.custom-color:not(.collapsed) .accordion-button::after {
filter: invert(100%) sepia(100%) invert(100%) brightness(200%) grayscale(100%);
}
/* Flèche par défaut */
.accordion-button.custom-color::after {
filter: invert(0%) sepia(0%) invert(0%) brightness(100%) grayscale(0%);
}
/* Flèche ouverte blanche */
.accordion-button.custom-color:not(.collapsed)::after {
filter: invert(100%) sepia(100%) invert(100%) brightness(200%) grayscale(100%);
color: white;
}
</style>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#update").click(function(){
if ($("input[name*='DELETE']:checked").length > 0) {
if(confirm("Confirmez-vous vouloir supprimer les lignes sélectionnées?")){
// Récupérer l'URL dynamique en fonction de l'ID particulier
var url = "/salesforecast/test/" + $("#themeId").val();
// Le code pour envoyer la requête POST avec l'URL dynamique
$.ajax({
type: "POST",
});
} else {
return false
}
}
});
});
</script>
{% endblock content %}
Among the messages I display in the view.py, I get:
business form has changed
and
Les données Affaires sont incorrectes.[{‘id’: [‘Ce champ est obligatoire.’]}, {‘id’: [‘Ce champ est obligatoire.’]}, {‘id’: [‘Ce champ est obligatoire.’]}, {}] -Id field is mandatory, (there are 3 businnesses for the company I am updating)
Any advice and hint is welcome
Richard