How to validate an inline formset within a create view - python

I have an inline formset and 2 different forms within a create view of one of the forms.
All my fields in the model are blank=False and the inline formset has a minimum validation of 1.
If I try to send the form without any fields from the create view form, it displays the classic message "Please fill out this field" without rerendering the page. I want to achieve the same thing when the formset is not filled correctly. Also the same for the second form (this form is only displayed when the formset is filled based on the choice).
I am able to get the error messages re rendering the form but I'd like to do it before sending it to the server. Is it possible changing something in the model or I need to do it via js on the front?
Many thanks.
Views:
class SolicitacionReferenciaCreate(CreateView):
template_name = 'microcredito/solicitacion_form.html'
model = ContactoInfo
fields = ['nombre', 'destinoPrestamo', 'apellido']
success_url = reverse_lazy('accounts/login')
def get_context_data(self, **kwargs):
data = super(SolicitacionReferenciaCreate, self).get_context_data(**kwargs)
referencias_form_1 = ReferenciaForm()
data['referencias_form_1'] = referencias_form_1
if self.request.POST:
data['plataformas'] = IngresosFormSet(self.request.POST)
else:
data['plataformas'] = IngresosFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
plataformas = context['plataformas']
with transaction.atomic():
#ContactoInfo form
if plataformas.is_valid() and form.is_valid():
print("VALID")
self.object = form.save()
plataformas.instance = self.object
cuposPlataformas = []
for f in plataformas:
cd = f.cleaned_data
try:
score = ScorePersonas.objects.filter(idPlataforma=cd.get('idPlataforma'), numberPlataforma=cd.get('number_plataforma'),
aprobado=1)[0]
cuposPlataformas.append(score.cupo)
except:
try:
score = ScorePersonas.objects.filter(idPlataforma=cd.get('idPlataforma'), numberPlataforma=cd.get('number_plataforma'),
aprobado=0)[0]
cuposPlataformas.append(0)
except:
pass
plataformas.save()
self.object.save()
referenciaCreado1 = Referencia.objects.create_Referencia(self.object, plataformaObjeto, rt_referenciado_1, celular_referenciado_1,
nombre_referencia, apellido_referencia)
referenciaCreado1.save()
else:
#messages.error(request, "Error")
return self.render_to_response(self.get_context_data(form=form))
return render(self.request, 'microcredito/solicitacion_form.html', context)
Template:
<form action="" method="post" id="contactForm">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.source.errors }}
{{ form.source }}
{{ form.nombre.errors }}
{{ form.nombre|as_crispy_field }}
<table class="table">
{{ plataformas.management_form }}
{% for form in plataformas.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %} formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<div class="form-row hide-element-form" id="ref_div_id_1">
<div class="form-group col-md-6 mb-0">
{{ referencias_form_1.numberPlataforma_referencia|as_crispy_field }}
</div>
</div>
<input onsubmit="formSent" id="form_button" type="submit" value="Save"/>
</form>
Formset:
IngresosFormSet = inlineformset_factory(ContactoInfo, IngresosPersonas,
form=IngresosForm, extra=1, min_num=1, max_num=8, validate_min=True)

Related

Error submitting a modified field of a form

I am having a problem with my form, and it is that when trying to persist my data in the database this is not done correctly, because in my form the developer field is not being validated in the correct way because it does not show an error to the client, but the browser console shows me the following error, an invalid form control with name = 'developer' cannot be focused.
This is my form:
class ProjectForm(ModelForm):
developer = forms.ModelMultipleChoiceField(
queryset=User.objects.filter(groups__name='desarrolladores'),
label='Desarrolladores',
)
class Meta:
model = Project
fields = (
'developer', 'name', 'status'
)
labels = {
'name': 'Nombre de Proyecto',
'status': 'Estado',
}
def clean_developer(self):
developer = self.cleaned_data.get('developer')
if developer is None:
raise forms.ValidationError('El campo es obligatorio')
return developer
This is my view:
class ProjectCreateView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
login_url = 'users:login'
template_name = 'projects/add_project.html'
form_class = ProjectForm
permission_required = 'projects.add_project'
success_message = 'Proyecto creado exitosamente'
success_url = reverse_lazy('projects:project')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['formset'] = ProjectFormSet(self.request.POST)
else:
data['formset'] = ProjectFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
formset = context['formset']
with transaction.atomic():
self.object = form.save()
if formset.is_valid():
formset.instance = self.object
formset.save()
return super().form_valid(form)
This is my template:
<form id="form-container" method="post">
{% csrf_token %}
{{ form.as_p }}
<h2>Tareas</h2>
<table>
{{ formset.management_form }}
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr id="projects_data">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<button type="submit" class="button success"><span class="mif-add icon"></span> {{ message }}</button>
<span class="mif-cancel icon"></span> Cancelar
</form>
This is error

django-dynamic-formset issue when delete

When I'm trying to remove a row, the formset send me the following error [{}, {}, {}, {'id': ['This field is required']}].
If I change the following parameter can_delete=True inside the modelformset_factory, I can delete object but rows stays display even after I pressed the remove button.
I tried to add {% if form.instance.pk %}{{ form.DELETE }}{% endif %} in the template, it didn't change anything.
I am using django 2.2 and django-dynamic-formset, the country_fr/en come from modeltranslation.
Views
#login_required
def view_countries(request):
CountryFormSet = modelformset_factory(
Country,
fields=('country_fr', 'country_en'),
formset=BaseCountryFormSet,
can_delete=True)
if request.method == 'POST':
formset = CountryFormSet(request.POST)
if formset.is_valid():
formset.save()
else:
formset = CountryFormSet()
context = {
'formset': formset,
'menu': 'cards',
'menu_cards': 'countries',
'model': _('countries'),
'fields': [_('countries')+' [fr]', _('countries')+' [en]'],
}
return render(request, 'cards/index_formset.html', context)
Html
<form method="post" class="mt-2">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div id="form" class="row_formset d-flex text-center mb-1">
{% if form.instance.pk %}{{ form.DELETE }}{% endif %}
{% for field in form.hidden_fields %}
<div class="invisible">
{{ field }}
</div>
{% endfor %}
{% for field in form.visible_fields %}
<div class="flex-fill field">
{{ field }}
</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-flex mt-2 justify-content-center">
<input type="submit" value="{% trans 'submit'|capfirst %}" class="btn btn-primary"/>
</div>
</form>
Forms
class BaseCountryFormSet(BaseModelFormSet):
def clean(self):
if any(self.errors):
raise forms.ValidationError(
_('Errors : ')+f'{self.errors}'+'.',
code='unknow_error'
)
countries_fr = []
countries_en = []
duplicates = False
for form in self.forms:
if form.cleaned_data:
country_fr = form.cleaned_data['country_fr']
country_en = form.cleaned_data['country_en']
# Check that no row have the same country_fr or country_en
if country_fr and country_en:
if country_fr in countries_fr:
duplicates = True
countries_fr.append(country_fr)
if country_en in countries_en:
duplicates = True
countries_en.append(country_en)
if duplicates:
raise forms.ValidationError(
_('Some entries are duplicated.'),
code='duplicate_row'
)
# Check that all row have both country_fr and country_en
if country_en and not country_fr:
raise forms.ValidationError(
_('Some french entries are missing.'),
code='missing_country_fr'
)
elif country_fr and not country_en:
raise forms.ValidationError(
_('Some english entries are missing.'),
code='missing_country_en'
)
Models
class Country(models.Model):
country = models.CharField(_('country'), max_length=100)
slug = models.SlugField(editable=False, max_length=100)
def save(self, *args, **kwargs):
self.slug_fr = slugify(self.country_fr, allow_unicode=False)
self.slug_en = slugify(self.country_en, allow_unicode=False)
super().save(*args, **kwargs)
So the problem come from bootstrap, the d-flex of each form of the formset contain the attribut !important wich override the display: none; of the django-dynamic-formset-script.
I made my own css class to replace d-flex. I kept can_delete=True, it's obviously useful; and I removed {% if form.instance.pk %}{{ form.DELETE }}{% endif %}.

Django. DRY. Same context in get request

I am trying to make a voting app in django.
Here the view that display a vote.
class DetailVoteView(View):
"""
Displays the vote
If vote is active allows to vote
If vote is completed shows winners and top 5
"""
vote = None
vote_for_chars = None
page_title = ''
def dispatch(self, request, *args, **kwargs):
self.vote = Vote.objects.get(id=kwargs['id'])
self.vote_for_chars = self.vote.voteforcharacter_set.all()
self.page_title = self.vote.title
return super(DetailVoteView, self).dispatch(request, *args, **kwargs)
def get(self, request, id):
if is_active_vote(self.vote):
nominates = self.vote_for_chars
return render(request, 'vote_detail/active_vote_detail.html', context={
'page_title': self.page_title,
'vote': self.vote,
'votes_name': self.page_title,
'nominates': nominates,
})
else:
winners = []
top_fives = []
if self.vote.voteforcharacter_set.count() != 0:
sorted = self.vote.voteforcharacter_set.order_by('-votes_number')
winner_number_votes = sorted[0].votes_number
winners = self.vote_for_chars.filter(votes_number__exact=winner_number_votes)
top_fives = sorted[winners.count():5 + winners.count()]
return render(request, 'vote_detail/completed_vote_detail.html', context={
'page_title': self.page_title,
'vote': self.vote,
'votes_name': self.page_title,
'winners': winners,
'top_fives': top_fives
})
def post(self, request, id):
vote_for_chars = self.vote_for_chars
id_to_vote = request.POST.get("id_to_vote")
char_to_vote = vote_for_chars.get(character__id=id_to_vote)
char_to_vote.votes_number += 1
char_to_vote.save()
return HttpResponseRedirect('/')
The code is not DRY as I suppose. Because, there is the same context in get reuqest.
I need to use a mixins feature?
How can I improve it? Thanks.
EDITED.
Added code for templates.
acvtive_vite_detail.html
{% extends 'base/base.html' %}
{% block content %}
<form action="." method="POST">{% csrf_token %}
<p>Vote: {{ vote }}.</p>
<p>Choose:</p>
<div>
{% for item in nominates %}
{% include "base/listOutput.html" %}
<input type="radio" id="{{ item.id }}"
name="id_to_vote" value="{{ item.character.id }}" checked>
<label for="{{ item.character.name }}">{{ item.character.name }}
</label>
{% endfor %}
</div>
<div>
<button type="submit">Vote!</button>
</div>
</form>
{% endblock content %}
completed_vote_detail.html
{% extends 'base/base.html' %}
{% block content %}
<p>Vote: {{ vote }}.</p>
<p>Winners: </p>
{% for item in winners %}
<p>{% include "base/listOutput.html" %}</p>
{% endfor %}
<p>Top5: </p>
{% for item in topFives %}
<p>{% include "base/listOutput.html" %}</p>
{% endfor %}
{% endblock content %}
listOfOutput.html
<div style="border-style: solid; width: 25%">
<img src="{{ item.character.image.url }}" alt="img" class="img-responsive img-rounded" width="100%" height=auto>
<p>Name: {{ item.character.name }}</p>
<p>Age: {{ item.character.age }}</p>
<p>Votes number: {{ item.votes_number }}</p>
</div>

Django Form Wizard Render to Response on Step

I am working with the Django form wizard and i am trying to emulate the typical
render_to_response('index.html',{'name', name})
but without redirect and only in the steps.
So the idea would be i have a form wizard like this
TEMPLATES = {"0": "MyApp/checkout_template1.html",
"1": "MyApp/checkout_template2.html",
"2": "MyApp/checkout_template3.html",
}
class MyWizard(SessionWizardView):
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def get_context_data(self, form, **kwargs):
context = super(MyWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == '3':
data_1 = self.get_cleaned_data_for_step('1')
((HERE I NEED TO PASS THE data_1["first_name"] TO SHOW ON NEXT TEMPLATE))
print(data_1["first_name"])
return context
def done(self, form_list, **kwargs):
if self.request.method == 'POST':
process_form_data(form_list)
return render(self.request,'MyApp/congratulations.html')
then in templates id have something like this
{% extends "MyApp/base.html" %}
{% load staticfiles %}
{% load humanize %}
{% block head %}{{ wizard.form.media }}{% endblock %}
{% endblock %}
{% block body_block%}
<div class="confirmation-body">
<div class="confirmation-body-container">
<div class="form-title-container">
<h1>Information</h1>
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="/checkout/" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form in wizard.form.forms %}
{{form}}
{% endfor %}
{% else %}
<div class="checkout-title">Information</div>
//////////////////////
{{ name }} #something like this that prints in the next step
//////////////////////
{{wizard.form.name_on_card}}
{% endif %}
</table>
<hr />
<div class="checkout-nav-controls">
<div>
{% if wizard.steps.prev %}
<button id="prev-step-button" name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">back</button>
{% endif %}
</div>
<input id="my-submit-button" type="submit" value="Next"/>
</div>
</form>
</div>
</div>
{% endblock %}
So just to reiterate the question for clairity. I want to:
get the data from the previous step (lets say first step and name)
in the second step pass that data to the corresponding html
template {{name}}
render that last step so i can see the name on
the front end
thanks in advance for your help
Maybe something like this whould do the trick, using get_form method:
def get_form(self, step=None, data=None, files=None):
form = super(MyWizard, self).get_form(step, data, files)
if step is None:
step = self.steps.current
if step == '3':
data_1 = self.get_cleaned_data_for_step('1')['first_name']
form.fields['first_form_field'].label = data1
return form
So in this way, you will have the field first_name from the first steps' form, as a label to the third step's form's first field
You can supply additional context variables by using the get_context_data() method of your WizardView subclass.
def get_context_data(self, form, **kwargs):
context = super(MyWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'my_step_name':
category=self.get_cleaned_data_for_step('category')['name']
context.update({'another_var': 'category'})
return context
get_context_data() docs

How to use multiple forms with a Form Wizard form in the same template (Django)

I'm using multiple forms in the same template and they all work until I add my Form Wizard, when it becomes that either the FormWizard - form works or the rest of the forms works but not both simultaniousely.
When I have the URL's with postid (?P\d+) -url placed prior to the ContactWizard.as_view -urls the forms in the view - function one_labeling are displayed but not the Form Wizard/ContactWizard.as_view in views.py class ContactWizard(SessionWizardView)
url(r'^label$', LabelingIndex),
url(r'^label(?P<postID>\d+)$',one_labeling),# <----- here
url(r'^label',ContactWizard.as_view([Form1, Form2, Form3])),
url(r'^label(?P<one_labeling>\d+)/$', 'formwizard.views.one_labeling'),
and vice versa, when the URL's for the Form Wizard is placed before the postID - url for the forms in the view function one_labeling then the FormWizard is displayed (and works) but the other forms aren't displayed/evoked.
url(r'^label$', LabelingIndex),
url(r'^label',ContactWizard.as_view([Form1,Form2, Form3])),
url(r'^label(?P<one_labeling>\d+)/$', 'formwizard.views.one_labeling'),
url(r'^label(?P<postID>\d+)$',one_labeling), #<----- here
I'm not sure on how to prefix the Wizard Form so that I could use it as {{ form9.as_p }} like with {{ form3.as_p }} instead of {{ form1 }}{{ wizard.management_form }} or {{ form }} in the done.html below, so that it would work simultaniousely with the other forms in template one_labeling_index.html.
in template done.html
{% extends 'base_one_labeling.html' %}
{% block content %}
{% for form in form_data %}
{{ form }}
{% endfor %}
{% endblock %}
in views.py
class ContactWizard(SessionWizardView):
template_name = "one_labeling_index.html"
def done(self, form_list, **kwargs):
form_data = process_form_data(form_list)
return render_to_response("done.html",{"form_data":form_data})
def process_form_data(form_list):
form_data = [form.cleaned_data for form in form_list]
logr.debug(form_data[0]['subject'])
logr.debug(form_data[1]['sender'])
logr.debug(form_data[2]['message'])
send_mail(form_data[0]['subject'],form_data[1]['sender'],
form_data[2]['message'], 'xxxx#gmail.com',
fail_silently=False)
return form_data
in views.py,LabelingIndex,function evoking template labeling_index.html
def LabelingIndex(request):
#labelings, objects in class Labeling() in models.py
labelings = Labeling.objects.all()
c ={"labelings":labelings}
c.update(csrf(request))
return render(request,"labeling_index.html", c)
in views.py,one_labeling, views function one_labeling
def one_labeling(request,postID):
#one_labeling, object in class Labeling
one_labeling= Labeling.objects.get(id=postID)
template = one_labeling_index.html
if request.method == "POST":
# forms used in one_labeling_index.html
form = SentenceForm(request.POST, prefix="sentence")
form2 = OpenFileForm(request.POST, prefix="file")
form3 = LabelingForm(request.POST,prefix="form3")
form9 =LabelingForm2(request.POST,prefix="labeling")
if form.is_valid() and 'button1' in request.POST:
# do ....
if form3.is_valid() and 'button2' in request.POST:
post_one_labeling(request.POST, one_labeling)
else:
form = SentenceForm()
form2 = OpenFileForm()
form3 = LabelingForm()
form9 = LabelingRRGForm2()
c = {"one_labeling":one_labeling,"form3":form3,"form":form,"form9":form9...... }
c.update(csrf(request))
return render(request,template,c,context_instance=RequestContext(request))
in template Labeling_index.html
{% for one_labeling in labelings %}
# Link to new id (postid) - page; template one_labeling.html
&nbsp Subject: <a href="/label{{ one_labeling.id }}">{{ one_labeling.title }}<a/><br/>
{% endfor %}
in template one_labeling_index.html
# extending base_one_labeling.html
{% extends "base_one_labeling.html" %}
{% block content %}
# form3
<form3 action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form3.as_p }}
</form3>
# Form Wizard
<p>Label {{ wizard.steps.step1 }} out of {{ wizard.steps.count }} ({{ wizard.steps.step1 }}/{{ wizard.steps.count }})</p>
{% for field in form %}
{{field.error}}
{% endfor %}
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
{{ wizard.form.management_form }}
{% for form1 in wizard.form.forms %}
{{ form1 }}
{% endfor %}
{% else %}
{{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
{% endif %}
<html>
<body>
The line
url(r'^label',ContactWizard.as_view([Form1, Form2, Form3])),
matches any url beginning by "label", so any of your other url you put after are never matched.
Also, except the trailing "/", the lines:
url(r'^label(?P<one_labeling>\d+)/$', 'formwizard.views.one_labeling'),
url(r'^label(?P<postID>\d+)$',one_labeling), #<----- here
match the same thing.
So you have to order carefully your urls, and distinguish them somehow to avoid any ambiguity.

Categories

Resources