How do I perform a Django form validation for an unknown number of fields specified by the user? In my case, form allows a user to create a music album with any number of tracks and associate it to an artist. The view asks for how many tracks there are and then generates a form with that many input fields.
Form:
class NumberOfTracks(forms.Form):
track_no = forms.IntegerField()
class CustomAlbumAdmin(forms.Form):
artist = forms.CharField(max_length=150)
album = forms.CharField(max_length=150)
track_no = forms.IntegerField()
track = forms.CharField(max_length=150)
View:
def album_admin(request):
if request.GET.get('track_no'):
number_of_tracks = request.GET.get('track_no')
artists = Artist.objects.all()
return render(request, 'customadmin/album_admin1.html', {
'number_of_tracks': number_of_tracks,
'tracks': range(1, int(number_of_tracks) + 1),
'artists': artists,
})
elif request.method == 'POST':
form = CustomAlbumAdmin(request.POST)
print form
artist = request.POST['artist']
album = request.POST['album']
all_tracks = request.POST.getlist('track')
create_album = CreateAlbum(artist=artist, album=album, tracks=all_tracks)
create_album.save_album()
create_album.save_tracks()
form = NumberOfTracks()
return render(request, 'customadmin/no_tracks1.html', {
'form': form,
})
else:
form = NumberOfTracks()
return render(request, 'customadmin/no_tracks1.html', {
'form': form,
})
(Just so it's clear, I used if form.is_valid() and form.cleaned_data but to get this to work thus far, I've had to bypass that in favor of getting the raw POST data)
Part of what's confusing me is that I've customized my form template to add a number input fields with name="track" depending on user input (EX: create an album with 13 tracks). When I go into my view to print form = CustomAlbumAdmin(request.POST) it gives a very simple table based on my form: one artist, one album, one track, and one track_no so validating against this will of course return False unless I have an album with just one track.
Here's the template:
{% extends 'base.html' %}
{% block content %}
<form action="/customadmin/album1/" method="POST">{% csrf_token %}
<select name="artist">
{% for entry in artists %}
<option value="{{ entry.name }}">{{ entry.name }}</option>
{% endfor %}
</select>
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.album.errors }}
<label for="id_album">Album:</label>
<input id="id_album" maxlength="150" name="album" type="text" />
</div>
<div class="fieldWrapper">
{{ form.track.errors }}
<input type="hidden" name="number_of_tracks" value="{{ number_of_tracks }}">
{% for e in tracks %}
<label for="id_track">Track No. {{ forloop.counter }}</label>
<input id="id_track_{{ forloop.counter }}" maxlength="150" name="track" type="text" /></br>
{% endfor %}
</div>
<p><input type="submit" value="Save album" /></p>
</form>
{% endblock %}
The one way I was thinking of approaching this was to create a custom clean_track method that takes a list of all the tracks entered as I've done in the view with all_tracks = request.POST.getlist('track') but not sure how to do that.
A related question I have is if I can customize validation based on POST data. The first way I approached this was to generate incremented inputs with name="track_1", name="track_2", etc.,. and then trying to validate based on that. However, I wouldn't be able to use request.POST.getlist('track') in that case.
It might be a better approach to use formsets instead.
class AlbumForm(forms.Form):
artist = forms.CharField(max_length=150)
name = forms.CharField(max_length=150)
class TrackForm(forms.Form):
track_no = forms.IntegerField()
name = forms.CharField(max_length=150)
# In view
from django.forms.formsets import formset_factory
TrackFormSet = formset_factory(TrackForm)
if request.method == 'POST':
track_formset = TrackFormSet(request.POST)
album_form = AlbumForm(request.POST)
if track_formset.is_valid() and album_form.is_valid():
save_album(album_form, track_formset)
else:
track_formset = TrackFormSet()
album_form = AlbumForm()
And in save_album you can just iterate through track_formset.ordered_forms to get each form in the formset:
for form in track_formset.ordered_forms:
data = form.cleaned_data
# Do what you want with the data
This can be even more powerful if you use model formsets because you can set a foreign key in the track model that points to the album model, and Django can save them automatically for you.
Related
in my django website I'm trying to building a page in which there are multiple forms (In particular 3: the first is simply a checkbox, as the second one. The third one requires the entering of two text fields). I've already managed the presence of multiple forms and I've no problem when the user performs the first assignment. The problem is instead during the updating of the answers that the user gave the first time: there are no problems when adding new instances of the text fields of the third form, while instead if I've selected one checkbox of the first two forms and I want to change them unchecking them it seems like django doesn't save the new values. Any idea of why it's happening?
Here's the view associated:
def new_4e2(request, pk, var=None):
if Specifiche.objects.filter(ID_rich = pk).exists():
specs = Specifiche.objects.filter(ID_rich = pk)
choicesp =[]
lista =[]
for spec in specs:
choicesp+=[(spec.id, str(spec.rif))]
lista+=[spec.id]
att = MAIN.objects.get(id=pk)
unform = FormUn(instance=att)
data = {'ID_rich': pk}
form = UnicitaForm(initial=data)
form.fields['rifext'].choices = choicesp
if Unicita.objects.filter(rifext__in = lista).exists():
uns=Unicita.objects.filter(rifext__in = lista)
context={
'att': att,
'uns': uns,
'var':var,
'specs': specs
}
else:
context = {
'att': att,
'var':var,
'specs': specs,
}
form = UnicitaForm(initial = data)
form.fields['rifext'].choices = choicesp
similiform = SimiliForm(instance=att)
if request.method=='POST':
if 'Aggiungi' in request.POST:
form = UnicitaForm(request.POST, initial=data)
form.fields['rifext'].choices = choicesp
if form.is_valid():
form.save()
return redirect(f'/new_4e2/{pk}/{var}//')
if 'Simili' in request.POST:
similiform = SimiliForm(request.POST, instance=att)
if similiform.is_valid():
similiform.save()
return redirect(f'/new_4e2/{pk}/{var}//')
if 'Unicita' in request.POST:
unform = FormUn(request.POST, instance=att)
if unform.is_valid():
unform.save()
return redirect(f'/new_4e2/{pk}/{var}//')
context = context | {'form': form, 'unform':unform, 'similiform': similiform}
return render(request, 'new_4e2.html', context)
The two forms for which I have this problems are: 'unform' and 'similiform'
And here is my template.html
<form method="POST">
{% csrf_token %} 4.1 | {{unform}}
<input type="submit" name="Unicita">
</form>
{% if not att.Unicita %}
<div style="position:relative; left:50 px; height: 10 px; width:500 px;">
<form method="POST">
{% csrf_token %} {{similiform}}
<input type="submit" name="Simili">
</form>
<form action ="" method="POST">
{% csrf_token %}
<h4>Insert Unicita</h4>
{{form}}
<input type="submit" name="Aggiungi">
</form>
...
views.py
#login_required(login_url='login/')
def add_country(request):
if request.method == 'POST':
form = CountryForm(request.POST,request.FILES)
if form.is_valid():
new_form = form.save(commit=False)
new_form.edited_by = request.user
new_form.save()
return redirect('country_details')
else:
form = CountryForm()
context = {'form':form}
return render(request,'add_country.html',context)
models.py
class Countries(models.Model):
CONTINENTS = [
('Asia','Asia'),
('Europe','Europe'),
('Africa','Africa'),
('Oceania','Oceania'),
('North America','North America'),
('South America','South America'),
]
name = models.CharField(max_length=75)
continent = models.CharField(max_length=50,choices=CONTINENTS,null=True)
landmark = models.CharField(max_length=100,null=True)
food = models.CharField(max_length=100,null=True)
entertainment = models.CharField(max_length=100,null=True)
flag = models.FileField(upload_to='flags', default='default.png',null=True)
image = models.FileField(upload_to='travel', default='default.png',null=True)
edited_by = models.OneToOneField(User,on_delete=models.CASCADE,null=True)
last_updated = models.DateTimeField(auto_now_add=True,null=True)
def __str__(self):
return self.name
add_country.html
<form method="POST" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ form.name.label }}<br>
{{ form.name }}<br><br>
{{ form.landmark.label }}<br>
{{ form.landmark }}<br><br>
{{ form.food.label }}<br>
{{ form.food }}<br><br>
{{ form.entertainment.label }}<br>
{{ form.entertainment }}<br><br>
{{ form.flag.label }}<br>
{{ form.flag }}<br><br>
{{ form.image.label }}<br>
{{ form.image }}<br><br>
<input type="submit" class="btn btn-primary" value="Add">
</form>
I have an issue that after I added the edited_by to assign the currently logged in user into that column then the form could not be submitted and only stayed on the same page instead of redirecting to the page that I want. I have tried different ways to make the form being submitted such as put request.method == "POST" and the page didn't work. However before I added edited_by into the models the form could be submitted accordingly and the data is being updated. May I ask what is the method to assign the user into the column edited_by after that user has added a post?
I think you have included edited_by field in your forms too.
If you are handling this field by yourself in the views then remove this field from your forms.
class CountryForm(forms.ModelForm):
class Meta:
model = Country
exclude = ['edited_by'] # or specify only required fields in form
Now your view will work fine.
Note: You can display your form's errors with {{form.errors}} in your template.
My form has initial values in it. I use form.as_hidden to hide the values and pass those values through a POST request. However, the hidden values are not passing through. Is there a way through this?
views.py
def car_detail_view(request, id):
if request.method == "POST":
form = CarForm(request.POST)
print(form.is_valid())
if form.is_valid():
car_save = form.instance
get_car = Car.objects.get(number_plate=car_save.number_plate)
get_car.available = False
get_car.save()
return redirect('/')
else:
print(form.errors)
else:
car = Car.objects.get(id=id)
form = CarForm(initial={'brand':car.brand, 'number_plate':car.number_plate, 'price':car.price,
'available':car.available})
args = {
'car':car,
'form':form
}
return render(request, 'map/confirmation.html', args)
confirmation.html
<h1>Confirmation of Booking</h1>
{% block content %}
<p>Brand: {{ car.brand }}</p>
<p>Number Plate: {{ car.number_plate }}</p>
<p>Price: {{ car.price }}</p>
<p> Are you sure you want to book? <p>
<form class="" method="post">
{% csrf_token %}
{{ form.as_hidden }}
<input type="submit" value="Book {{ car.brand }}">
</form>
{% endblock %}
Error
<ul class="errorlist"><li>brand<ul class="errorlist"><li>This field is required.</li></ul></li><li>number_plate<ul class="errorlist"><li>This field is required.</li></ul></li><li>price<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
Django doesn't have a form.as_hidden method. Therefore {{ form.as_hidden }} will render as the empty string '' in your template.
You can use the as_hidden method for individual form fields.
{{ form.number_plate.as_hidden }}
If you use values from hidden fields, you might need to add code to prevent the user altering the field values (e.g. with their browser's developer tools). However, in your case you don't need to get the values from the form, you can fetch them from the database.
def car_detail_view(request, id):
if request.method == "POST":
car = Car.objects.get(id=id)
car.available = False
car.save()
return redirect('/')
else:
car = Car.objects.get(id=id)
args = {
'car':car,
}
return render(request, 'map/confirmation.html', args)
Once you've got this working, you might want to think about what happens if two users try to book the same car at once.
I have one field that is custom tag.
When I get that particular field from model and try to print all stored values in that field, It will displays as Id's of each value.
How can I get that values which I had stored as string.
views.py
def patfirst(request):
if request.method == "GET":
return render(request, 'personal/patfirst.html')
if request.POST.get('Next'):
newSymp = request.POST.get('newSymptom')
didata = Disease.objects.all().values_list('symptoms')
args = {'newSymp' : newSymp,'didata':didata}
return render(request, 'personal/patfirst.html',args)
models.py
class TaggedSymptoms(TaggedItemBase):
content_object = models.ForeignKey("Disease")
class Disease(models.Model):
did = models.AutoField(verbose_name='Disease Id', primary_key=True,default=0)
dName = models.CharField(max_length=100)
symptoms = TaggableManager(verbose_name='symptoms list', through=TaggedSymptoms)
symptoms.rel.related_name = "+"
patfirst.html
<h1>search disease</h1>
<form method="post" action="#">
{% csrf_token %}
Enter Symptom: <input type="text" name="newSymptom"/><br><br>
<h3>
{% for s in didata %}
{{ s }}
{% endfor %}
</h3>
<input type="submit" value="Next" name="Next"/>
<input type="submit" value="None of these" name="NoneOfThese"/>
</form>
output I got is like this:
Without you providing much detail, I can guess that you can do so by printing it this way.(provide more information in order to get a more precise answer)
tags = your_object.tags
for tag in tags:
print tag, tag.your_string_field
The form, that is described below, is not valid. And expression {{ search_id_form.errors }} doesn't show anything.
Django template:
<form method="POST">
{% csrf_token %}
{{ search_id_form.idtype.label_tag }}
{{ search_id_form.idtype }}
{{ search_id_form.index.label_tag }}
{{ search_id_form.index }}<br>
<input type="submit" name="id_search_button" value="Submit">
</form>
Python class:
class IDSearchForm(forms.Form):
idtype = forms.ChoiceField(
choices=[('idx', 'Our Database ID'), ('uprot', 'UniProt'), ('ncbi', 'NCBI')],
initial='idx',
widget=forms.RadioSelect,
label="Which identifier to use:"
)
index = forms.CharField(label="Identifier:")
View:
def search(request):
if request.method == 'POST':
# handling other forms ...
# find a toxin by id
if 'id_search_button' in request.POST:
search_id_form = IDSearchForm()
if search_id_form.is_valid():
idtype = search_id_form.cleaned_data['idtype']
index = search_id_form.cleaned_data['index']
return render(request, 'ctxdb/result_ctx.html', {
# here I try to use predefined object to pass to renderer (for debugging)
'ctx': get_object_or_404(CTX, idx='21')
})
# handling other forms ...
# other forms
search_id_form = IDSearchForm()
# other forms
return render(request, 'ctxdb/search.html', {
# other forms
'search_id_form': search_id_form,
# other forms
})
In the view function I handle four different forms on single page. Other forms work correctly. What is the problem here?
When calling .is_valid, you need to pass the data to search_id_form which you are not doing.
Change
search_id_form = IDSearchForm()
to
search_id_form = IDSearchForm(request.POST)