I have a page with two forms: a SearchForm and a QuickAddForm. Processing two forms on the same page in Django is not easy, and I'm spent the past two or three days trying to get it to work. I have several unittests that I've been using: a test for each form to make sure it's displaying on the page, a test that a submission of a valid search to the searchform returns the correct results, and three tests for the quickadd form testing that 1) a submission of valid fields results in a new Entry saving to the database; 2) that after an entry is saved, the form redirects to the same page; and 3) that the success message is displayed on that page. For example:
def test_quick_add_form_saves_entry(self):
self.client.post('/editor/', {'quickadd_pre-hw' : 'كلمة'}, follow=True)
self.assertEqual(Entry.objects.count(), 1)
After a lot of work (and helped greatly by the answers and comments on this page: [Proper way to handle multiple forms on one page in Django). I finally got the form working, so that the submitted entry was successfully saved in the database and was redirecting after post. Yay!
Except ... When I open up the page in a browser and try to enter a new Entry, it's clear that it's doing no such thing. You fill out the forms and hit "Save" and ... nothing happens. The page doesn't reload, and the entry isn't added to the database. But the tests (like the one above) testing those very features are passing. (It was at this point that I added the test for the success message, which, at least, has the decency to be failing.) The searchform is working.
I don't know what could possibly be preventing the quickadd form from functioning. Also, I don't understand how the tests could be passing, when the form is not working. What's going on???
Here's the code (Python3, Django1.8 - slightly simplified for clarity):
editor.html
<form id="id_searchform" role="form" method="POST" action="/editor/">
{% csrf_token %}
{{ searchform.as_p }}
<button type="submit" class="btn btn-primary btn-lg"
name="{{searchform.prefix}}">Search</button>
</form>
...
<h1>New entry</h1>
<form id="id_quick_add_form" method="POST" action='/editor/' class="post-form">
{% csrf_token %}
{{ quickaddform.as_p }}
<button type="submit" class="save btn btn-primary"
name='{{quickaddform.prefix}}'>Save</button>
</form>
views.py
def _get_form(request, formcls, prefix):
data = request.POST if prefix in next(iter(request.POST.keys())) else None
return formcls(data, prefix=prefix)
def editor_home_page(request):
if request.method == 'POST':
searchform = _get_form(request, SearchForm, 'searchform_pre')
quickaddform = _get_form(request, QuickAddForm, 'quickadd_pre')
if searchform.is_bound and searchform.is_valid():
query = searchform.cleaned_data['searchterm']
results = Entry.objects.filter(bare_hw__icontains=query)
return render(request, 'dict/editor.html', {
'query' : query,
'results' : results,
'searchform' : searchform,
'quickaddform' : quickaddform,
})
elif quickaddform.is_bound and quickaddform.is_valid():
hw = quickaddform.cleaned_data['hw']
e = Entry.objects.create(hw=hw)
messages.success(request, "Entry %s successfully created." % e.hw)
return redirect('/editor/', {
'searchform': SearchForm(prefix='searchform_pre'),
'quickaddform': QuickAddForm(prefix='quickadd_pre'),
})
else:
return render(request, 'dict/editor.html', {
'searchform': SearchForm(prefix='searchform_pre'),
'quickaddform': QuickAddForm(prefix='quickadd_pre')
})
else:
return render(request, 'dict/editor.html', {
'searchform': SearchForm(prefix='searchform_pre'),
'quickaddform': QuickAddForm(prefix='quickadd_pre')
})
forms.py
class SearchForm(Form):
searchterm = forms.CharField(
max_length=100,
widget=forms.TextInput(attrs={
'placeholder': 'English | العربية',
'class' : 'form-control input-lg',
}),
required=True)
class QuickAddForm(Form):
hw = forms.CharField(
max_length=100,
widget=forms.TextInput(attrs={
'placeholder': 'ex: مَضْروب',
'class' : 'form-control',
}),
required=True)
Related
I am trying to set default values for my form fields in my Django application, but nothing I do seemed to work. I am using Django 2.2.3.
This is my code in views.py:
def my_view(request, template_name="path/to/template"):
form = MyForm(request.POST, initial={
'field_one': field_one_default,
'field_two': field_two_default,
})
try:
if request.method == 'POST':
if form.is_valid():
field_one = form.cleaned_data['field_one']
field_two = form.cleaned_data['field_two']
my_model.field_one = field_one
my_model.field_two = field_two
my_model.save()
return redirect('redirected_page')
return render(request, template_name, {'form': form})
return render(request, template_name, {'form': form})
except Http404 as e:
return Http404("An error occurred.")
This is my code in forms.py:
class MyForm(forms.Form):
field_one_choices = [
('Choice One', 'Choice One'),
('Choice Two', 'Choice Two'),
('Choice Three', 'Choice Three'),
]
field_one = forms.ChoiceField(choices=field_one_choices, required=True)
field_two = forms.IntegerField(
validators=[MaxValueValidator(100), MinValueValidator(1)],
required=True
)
And this is my code in the template:
{% load fontawesome_5 %}
{% load bootstrap4 %}
<div class="container spacing-top">
<form method="post">{% csrf_token %}
{% bootstrap_form form %}
<input class="btn btn-info" type="submit" value="Save">
<a class="btn btn-secondary" href="{% url 'another_page_url' %}">Cancel</a>
</form>
</div>
I have also tried setting the default values in the forms.py, but that did not work either:
field_one = forms.ChoiceField(choices=field_one_choices, required=True, initial=field_one_default)
field_two = forms.IntegerField(
validators=[MaxValueValidator(100), MinValueValidator(1)],
required=True,
initial=field_two_default
)
Any help is greatly appreciated! Thanks in advance!
So I finally found my mistake. Reading the Django documentation tells me the following:
This is why initial values are only displayed for unbound forms. For bound forms, the HTML output will use the bound data.
This part of the documentation explains the meaning of bound and unbound forms, giving form = NameForm(request.POST) as an example of a bound form.
Therefore, to solve the posted problem, I changed my views.py code as such:
def my_view(request, template_name="path/to/template"):
form = MyForm(request.POST or None, initial={
'field_one': field_one_default,
'field_two': field_two_default,
})
...
The users are able to create an aircraft post at this url:
url(r'^upload/aircraft/$', aircraft_create, name="aircraft_create"),
I've created a summary page where all of the users posts are displayed. They're able to edit and delete their posts here. The url:
url(r'^account/uploads/$', upload_overview, name="account_uploads"),
However, I want the user to be able to edit their posts on their summary page. The way I got it set it now, is that they can edit at upload/aircraft/edit, but I want to to be account/uploads/edit.
I've set it up like that but it's not doing anything? Any clues as to what it might be?
Aircraft/views.py
def aircraft_create(request):
form = aircraft_form(request.POST or None)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
messages.success(request, "Your upload has been successfully added!")
return HttpResponseRedirect(instance.get_absolute_url())
else:
messages.error(request, "There seems to be something wrong. Have a look again..!")
context = {"form":form,}
return render(request,'aircraft/aircraft_form.html', context)
Update view
def aircraft_update(request, id=None):
aircraft = get_object_or_404(Aircraft, id=id)
form = aircraft_form(request.POST or None, instance=aircraft)
if form.is_valid():
form.save()
messages.success(request, "Your edit has been successfully been saved!")
return HttpResponseRedirect(aircraft.get_absolute_url())
return render(request,'aircraft/aircraft_form.html',
{"aircraft": aircraft, "form": form})
Template
{% if UploadedAircraft %}
{% for upload in UploadedAircraft %}
<div class="col-lg-offset-0 col-md-4 col-sm-3 item">
<div class="box"><img src="{{ upload.image.url }}" width="200px" height="200px" alt="{{ upload.title }}"/>
<h3 class="name">{{ upload.name }}</h3>
<button class="btn">Edit</button>
<button class="btn">Delete</button>
</div>
Summary page view
def upload_overview(request):
uploaded_aircraft = Aircraft.objects.filter(user=request.user)
return render(request,'account/upload_overview.html',{'UploadedAircraft':uploaded_aircraft)
url.py
#aircraft/detail/1
url('^aircraft/detail/(?P<id>\d+)/$', aircraft_detail, name='aircraft_detail'),
#account/uploads (Display Users uploads)
url(r'^account/uploads/$', upload_overview, name="account_uploads"),
#upload/aircraft (Create Aircraft)
url(r'^upload/aircraft/$', aircraft_create, name="aircraft_create"),
#Edit/aircraft
url('^account/uploads/$', aircraft_update, name='aircraft_update'),
EDIT
def airline_update(request, id=None):
airline = get_object_or_404(Airline, id=id)
form = airline_form(request.POST or None, instance=airline)
if form.is_valid():
form.save()
return HttpResponseRedirect(airline.get_absolute_url())
return render(request,'airline/airline_form.html',
{"airline": airline, "form": form})
Url
#account/upload/edit/ (Edit airline)
url(r'^account/uploads/edit/(?P<id>[0-9]+)$', airline_update, name='airline_update'),
Template
<button class="btn">Edit</button>
Assuming you're trying to edit an aircraft with a specific ID, you would need something like this in url.py (assuming that the IDs are integers):
url('^account/uploads/edit/(?P<id>[0-9]+)$', aircraft_update, name='aircraft_update')
And in your template you would need to update your anchor link to include the ID:
<a href="{% url 'aircraft_update' id=upload.id %}">
NOTE that this assumes that the upload object (in your template's loop) includes an id property, and that the id property corresponds to the aircraft ID that you want to update. (It is possible that you have named this property something else.)
EDIT: This would be sufficient for executing a GET request. However, I notice that your view definition for aircraft_update also attempts to check whether a form is valid. Where is this form in your template? It does not appear to be in your loop.
I am setting up a simple html page, the page captures the information that the user entered and based on the information that the user entered makes a new page. The problem is that I cant get back the information entered by the user at the backed and I dont understand where I am going wrong.
My views file is setup like this:
def suggestion(request):
if request.method == 'POST':
form = BusinessName(request.POST)
if form.is_valid():
data=form.cleaned_data
context = insert_function_here(data)
return render( request,'mainpage.html', context)
else:
form = BusinessName()
context = {'form':form}
return render( request,'mainpage.html', context)
My forms.py is setup like this:
class BusinessName(forms.Form):
business_name = forms.CharField(widget = forms.HiddenInput(), required = False)
The relevant part of my html is set up like this:
<form id="user_input_form" method="post" action="http://127.0.0.1:8000/textinsighters/suggestion">
Enter Your Business Name : <input type="text" list="browsers" name="browser" id="user_input">
{% csrf_token %}
{{ form }}
<datalist id="browsers">
<option value="Internet Explorer">
<option value="Firefox">
<option value="Chrome">
<option value="Opera">
<option value="Safari">
</datalist>
<button onclick="myFunction()">Submittt</button>
</form>
<script>
function myFunction() {
document.getElementById("id_business_name").value = document.getElementById("user_input").value;
document.getElementById("user_input_form").submit();
}
</script>
I want an auto-completing list so thats why I am creating a form in html. I get the user input, set the value of the Django form field to the value that the user entered and submit it. I should get something back but the variable 'data' in views doesnt contain the user input.
Thanks
You are using a forms.HiddenInput() as the widget and then add the form field yourself. This doesn't work that way. What if you change the field class to TextInput:
class BusinessName(forms.Form):
business_name = forms.CharField(widget = forms.TextInput())
If you're goal is to add custom attributes to the widget, then this can be done by providing an attrs dictionary:
class BusinessName(forms.Form):
business_name = forms.CharField(widget = forms.TextInput(attrs={
'list': 'browser'
}))
Or you could have a look at the django-widget-tweaks package to add attributes in the template.
I already used the answer to this question, but for some reason I'm not getting a good result.
I'm trying to use the same template for my edit form and my add form. Here's my urls.py:
url(r'^app/student/new/$', 'edit_student', {}, 'student_new'),
url(r'^app/student/edit/(?P<id>\d+)/$', 'edit_student', {}, 'student_edit'),
And my views.py:
def edit_student(request, id=None, template_name='student_edit_template.html'):
if id:
t = "Edit"
student = get_object_or_404(Student, pk=id)
if student.teacher != request.user:
raise HttpResponseForbidden()
else:
t = "Add"
student = Student(teacher=request.user)
if request.POST:
form = StudentForm(request.POST, instance=student)
if form.is_valid():
form.save()
# If the save was successful, redirect to another page
redirect_url = reverse(student_save_success)
return HttpResponseRedirect(redirect_url)
else:
form = StudentForm(instance=student)
return render_to_response(template_name, {
'form': form,
't': t,
}, context_instance=RequestContext(request))
And my forms.py:
class StudentForm(ModelForm):
class Meta:
model = Student
exclude = ('teacher',)
And finally my template student_edit_template.html:
<h1>{{ t }} Student</h1>
<form action="/app/student/edit/{{ student.id }}" method="post"> {% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
For some reason, this is throwing a 404:
Page not found (404)
Request Method: POST
Request URL: http://192.168.1.3:5678/app/student/edit/
I'm probably missing something easy here, but at this point I need another set of eyes on it at the very least.
Thanks in advance!
You're getting the 404 because /student/edit/ requires an id at the tail end otherwise there's no route, and when you're coming from /student/new/ you don't have an id yet. Create a route and view for /student/edit/ and put logic in there to handle the case for when you're creating a record on POST.
I've created a form in django which works fine on desktop browsers, but on mobile browsers when the form submits it just displays a blank screen (doesn't even load the title or an error), am I missing something?
If there's no way to make it work using post, how could I submit a form to locations/<zip code> or use get locations/?zip_code=<zip code>? (I'm a django noob, sorry)
here's the relevant code:
template:
<form action="" method="post">
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
form.py:
class ZipForm(forms.Form):
zip_code = forms.CharField(max_length=10)
views.py:
def zip(request):
if request.method == "POST":
form = ZipForm(request.POST)
if form.is_valid():
zip_code = form.cleaned_data['zip_code']
## get result
return render_to_response("zip.html",{
'title':'Closest Locations',
'results' : results,
'form' : form,
})
else:
form = ZipForm()
return render_to_response("zip.html", {
'form' : form,
'title' : 'Find Locations',
})
url.py:
url(r'^locations/$', 'app.views.zip'),
I wish there was a decent debugger for developing on mobile phones, ugh.
If the form is valid then you do all this fancy stuff... but you forgot to handle the case where the form isn't valid.