Form-View Mapping Issue in Django - python

This is becoming very frustrating, like all of my Django form endeavors have been thus far...
I have a search bar form that is supposed to send the user to a url '/project/search/<query>/' and the url works fine if I type in a url but my form is not mapping correctly. I am implementing this first in the search results page, which will still have a search bar, and whenever I type in a value to the search bar I get redirected to '/project/search/'. Where have I gone wrong? I have spent a solid two days on this to no avail.
I am really struggling with this and I have no idea what I am doing wrong. I wish I had at least an error or something to fix but this is just not working.
Here is my form class and view:
from django import forms
class SearchForm(forms.Form):
search_string = forms.CharField(initial='Search Article Text',max_length=100)
def search(request, search_query):
form = SearchForm()
context = RequestContext(request)
search_string = search_query.replace('_',' ')
search_terms = search_query.split('_')
search_results = Article.objects.all()
for term in search_terms:
search_results = search_results.filter(article__icontains=term)
context_dict = {
'search_string':search_string,
'search_results':search_results,
'form':form,
}
if request.method == 'POST':
form = SearchForm(request.POST)
context_dict['form'] = form
if form.is_valid():
search_string = form.cleaned_data['search_string']
search_query = search_string.replace(' ','_')
###return HttpResponseRedirect(reverse('search', args=(search_query,)))
search_url = '/project/search/' + search_query + '/'
return HttpResponseRedirect(search_url)
return render_to_response('search.html', context_dict, context)
The html:
<form action='/beacon/search/' class="navbar-form navbar-right" method='POST'>
<div class="form-group">
{% csrf_token %}
{{ form.search_string }}
</div>
<input type='submit' value='Submit' class='btn btn-default'/>
</form>

I don't really understand your question. You're getting redirected because that's what you've told the view to do: you explicitly return an HttpResponseRedirect. If you don't want to redirect, don't do that.

Ok after troubleshooting for probably 5 or so days I have realized what my issue was (I also had a little help from the Django Users Google group).
I am answering this in case anybody also has my problem in the future btw. I'm obviously not an expert on the forms part of Django.
This all had to do with the actual HTML writeup I had. In my form tag the action was to '/project/search/' which just redirected me to that URL because django thought /project/search/ was different than project/search/query . Therefore all I needed to do for this part was change the action to refer to any URL that would validate my search view- so I picked /project/search/search_query/ but anything after /project/search/ would have worked.
My second issue was with my input. I needed to include a name in my input -'search_string'- so my search view would understand what values the form itself was carrying.
Therefore my html in the end looks like:
<form action='/beacon/search/search_query/' class="navbar-form navbar-right" method='POST'>
<div class="form-group">
{% csrf_token %}
<input type="text" name='search_string' class="form-control" placeholder="Search Article Text"/>
</div>
<!--<button type="submit" class="btn btn-default" value='Submit'>Submit</button>-->
<input type='submit' value='Submit' class='btn btn-default'/>
</form>
Credit to Branko Majic to helping my on Dj Users Group as well. Seriously.

Related

How to render results from API based on the user's search using Django

As you can see in the picture below I'm trying to have the user search for a given country, start/end date and get the result of "Confirmed Cases" and "Date" back from the API, but I'm not sure how to do it.
I tried using this API, to fill the drop-down menu of the countries -->
https://api.covid19api.com/summary
but this is the other API that I have to use but while changing the parameters for the country and dates -->
https://api.covid19api.com/country/afghanistan/status/confirmed?from=2020-09-06T00:00:00Z&to=2020-09-11T00:00:00Z
Here are snippets of my code:
views.py
def home(request):
# second_response = requests.get('https://api.covid19api.com/country/afghanistan/status/confirmed?from=2020-09-06T00:00:00Z&to=2020-09-11T00:00:00Z').json()
second_response = requests.get('https://api.covid19api.com/summary').json()
my_list = []
for i in range(0, len(second_response['Countries'])):
my_list.append(second_response['Countries'][i]['Country'])
if request.method=='POST':
selected_country = request.POST['selected_country']
print('here', selected_country)
return render(request, 'home.html', {'my_list': my_list})
home.html
<div class="container justify-content-center">
<form action="{% url 'home' %}" method="post">
{% csrf_token %}
<label for="selected_country" style="margin-right: 5px;"> Select a Country, Start & End Dates : </label>
<select name="selected_country" >
{% for object in my_list %}
<option value="">{{object}}</option>
{% endfor %}
</select>
<label for="startdate"></label>
<input type="date" id="startdate">
<label for="enddate"></label>
<input type="date" id="enddate">
<input type="submit" value="Search" />
</form>
</div>
PLUS: when I click on "search" i should get the value of the selected_country because I tried printing it, but it doesn't show for some reason, so the method is post but for some reason I can't get back the selected_country
Any help is appreciated
JAVASCRIPT
if you have any solid grasp of javascript i recommend you do that in javascript, because it will just make it better and easier
otherwise :
view.py
def handler(request):
if request.method=='POST':
selected_country = request.POST['selected_country']
startDate= request.POST['startdate']
endDate= request.POST['enddate']
request_handler = requests.get(f"https://api.covid19api.com/country/{selected_country}/status/confirmed?from={startDate}T00:00:00Z&to={endDate}T00:00:00Z")
if request_handler.status_code=200:
#To prevent errors
request_json=request_handler.json()
else:
pass # do something
return render(request, 'result.html', {"json":request_json})
#you should handle the data at the front end using jinja blocks
note : i don't know much about Django so the code may break

How to create query in django with query in url, and return the filtered result based on the url?

I found several similar answers, but I could not solve the problem (maybe I searched wrong) anyway.
I'm trying to do the following:
Filter a result by getting the value of "q" where is the input containing the value to search
Add the "q" value in the url, like "/search?q=test"
Copy the url above and from there get the filtered result
My problem so far is:
The value of "q" is only added in the url after another search, ie the url contains "q" with the previous search value, which is bad
I can not access the url and have the results filtered, since when I enter this url, a new request object with no value for "q" is created, so I get a ValueError
So far what I've done is:
template
<form method="POST" action="{% url 'blog:post_search' %}?q={{ query }}" class="form-inline">
<div class="md-form my-0">
<input name="q" class="form-control form-control-sm mr-3 w-75" type="text" placeholder="Pesquisar" aria-label="Search">
</div>
<button class="btn btn-outline-white btn-sm my-0" type="submit">GO</button>
{% csrf_token %}
</form>
urls.py
urlpatterns = [
# ....
path('search', views.post_search, name='post_search'),
]
views.py
def post_search(request):
template = "blog/post_search.html"
query = request.POST.get('q')
if query:
posts = Post.objects.filter(title__icontains=query)
else:
posts = Post.objects.all()
context = {'posts': posts, 'query': query}
return render(request, template, context)
You are confusing POST and GET methods here. Query parameters in the url are typically only used with get requests.
So in your form, you can change this:
<form method="POST" action="{% url 'blog:post_search' %}?q={{ query }}" class="form-inline">
to
<form action="{% url 'blog:post_search' %}" class="form-inline">
The default method for a html form is GET, and named inputs will automatically be included in the action url by the browser when you submit the form. So you should not have a querystring in the from action attribute action='/search' is enough.
In the view you access url parameters from request.GET instead of request.POST.
def search(request):
query = request.GET.get('q')

How to unit test a form submission when multiple forms on a route?

I want to add unit tests to my flask app that tests form behavior on valid and invalid logins + signups. Currently, I have the signup form and a login form hosted on one page and route, and am using a hidden input field to identify which of the two forms is submitted / determine next actions.
My question is - how do I write a unit test that targets a specific form on a page? All the examples I've seen so far post data to a specific route, which is currently what I am doing. But that is failing because I need an added way to say "and we're submitting x form".
So is there a way to add "and we're submitting x form" in the post request?
**
edited to add, here are the different ways I've tried to pass the hidden form data in the post data dict, with no success.
data = dict(username="test#gmail.com", password="test", login_form)
data = dict(username="test#gmail.com", password="test", "login_form")
data = dict(username="test#gmail.com", password="test", login_form=True)
login unit test:
from app import app
import unittest
class FlaskTestCase(unittest.TestCase):
#ensure that login works with correct credentials
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
login route in views.py:
#app.route('/login', methods=['POST', 'GET'])
def login():
login_form = LoginForm()
signup_form = SignupForm()
error_login = ''
error_signup = ''
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
return render_template('login.html', login_form=login_form, signup_form=signup_form, error=error)
login.html:
<div class="login-form form-400">
<h3>Log In To Your Account</h3>
<form action="" method="post">
<input type="hidden" name="login_form">
{% if error_login != '' %}
<label class="error">
{{ error_login }}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
<input type="submit" value="Login" class="button button-blue">
</form>
</div>
<p class="login-divider">or</p>
<div class="signup-form form-400">
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
{% if error_signup != '' %}
<label class="error">
{{ error_signup | safe}}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ signup_form.hidden_tag() }}
{{ render_field(signup_form.username, placeholder="Pick a Username", class="form-item__full") }}
{{ render_field(signup_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(signup_form.password, placeholder="Create a Password", class="form-item__full") }}
<input type="submit" value="Sign Up" class="button button-green">
</form>
</div>
Ok I figured it out. To pass the login_form info, I had to end up passing an empty string on the login_form like this:
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test", login_form=""),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
I did this by throwing a print request.form in my views.py for this route and then saw the empty string.
It was still failing, but because the login_form.validate() was failing because of the csrf token added by the WTForms module. In the end, this discussion had the answer.
Flask-WTF / WTForms with Unittest fails validation, but works without Unittest
Thanks #drdrez for your suggestions!
Update:
Thanks for updating your question with what you've already tried! I have a few other ideas about what's causing the issue.
Let's continue to look at the HTML and try to understand the technologies your program is built on top of. In the server side login.html file, notice these lines:
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
It isn't HTML, and is probably being processed on the server side to produce HTML and serve to the client. The line that contains login_form.hidden_tag() looks interesting, so I would recommend loading this page in your browser and inspecting the HTML served to the client. Unfortunately, I haven't used Flask before, so I can't give any more direct help.
However, my advice is to continue digging into how Flask and the HTML Form works. The nice thing about Python is you have access to libraries' source code, which allows you to figure out how they work so you can learn how to use them and fix bugs in your application that uses them.
Sorry I can't give you more direct help, good luck!
Let's look at login.html. When you submit a form, how does the login route in views.py know which form was submitted? If you know HTML Forms, <input> elements nested in a form are used to, in this case, post data to your server/application.
Back to login.html, notice these two lines:
...
<h3>Log In To Your Account</h3>
<input type="hidden" name="login_form">
...
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
...
Those are <input> elements, with a type of "hidden", so they won't display, with names of "login_form" and "signup_form", which are included in the data that is submitted by the form.
Now in the login route in views.py, you'll notice there two lines:
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
Those are testing to see if the phrase "login_form" or "signup_form" are in present in the list request.form. Back to your unit test now:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
Notice the data you are passing in the dict, this is mimicking the form data, so you should probably include either "login_form" or "signup_form" to mimic the behavior of the HTML form correctly.
If you're unfamiliar with HTML Forms and HTTP Post, I would suggest searching for some tutorials, or just reading documentation on MDN or elsewhere. When building software on top of a technology (like HTTP and HTML), it can be helpful to understand how those technologies work when you run into bugs in your own software.
Hope this helps, let me know if I can clarify anything!
You might be experiencing a problem because you have not flagged the request as being of the form application content type. I note you are trying to access request.form, which requires that the data package is parsed in a certain way. You could try to do something like the following:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True,
headers = {"Content-Type":"application/x-www-form-urlencoded"}
)

Using Django FormPreview the right way

My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')

Processing POST request in Django

Hi I got a simple form for a POST request and it works when I'm only having one input, but not two inputs together. Can someone show me some light on this?
index.html
<form name="input" action="{% url 'sending' %}" method="post">
{% csrf_token %}
Recipient: <input type="text" name="recipient">
<br>
Message: <input type="text" name="content">
<br>
<input type="submit">
</form>
views.py
def sending(request):
recipient = request.POST.get('recipient','')
content = request.POST.get('content','') #not working when I am doing this...
someMethod(recipient, content)
return HttpResponseRedirect(reverse('results'))
Adding a "forms" portion to your setup will help you greatly... see the quickstart docs on forms here: https://docs.djangoproject.com/en/1.6/topics/forms/
In particular, check out "using a form in a view": https://docs.djangoproject.com/en/1.6/topics/forms/#using-a-form-in-a-view
Basically, you end up with a "forms.py" file which defines your form fields. Then, after it all processes, you get a simplier API into your form fields that looks like this:
form.cleaned_data['recipient']
form.cleaned_data['content']
etc.

Categories

Resources