Django, creating a functional search bar and search results page - python

I'm having trouble working with inherited code to make a functional search bar. I've been having the most trouble properly creating a search_results page.
I stripped down my search_results page to having only one line. search_results.html: <div>You searched for {{ query }}</div> but right now, the search_results page doesn't render {{ query }}. No text that the user previously entered appears. All that shows up on that page is "You searched for"
searchbox.html
<form class="search" action="{% url 'search' %}" method='post'>
{% csrf_token %}
<input type="search" placeholder="Search here..." name="usr_query"
value='{{ query }}' required>
<button type="submit">Search</button>
</form>
views.py
def search(request):
query = request.POST['usr_query']
print "QUERY: "
print query
t = loader.get_template('gtr_site/test_search_results.html')
c = Context({ 'query': query,})
return HttpResponse(t.render(c))
I was getting a little cautious and added that "print" statement... and it does print out what the user enters in the search bar. But that isn't being generated on my search_results page.
Whats the reasoning for this?
edit:
Adding urls.py
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^about/$', views.about, name='about'),
url(r'^contact/$', views.contact, name='contact'),
url(r'^search_engine/$', views.statement_search_engine, name='statement-search') # <- url for searchbox.html,
url(r'^test_search_results/$', views.search, name='test-search'), # <- url for searchresults.
url(r'^(?P<statement_id>.+)/$', views.statement_page, name='statement'),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

Change value='{{ request.GET.usr_query }}' to value="{{ query }}"
Right now you are forcing it to show the GET parameter. That works on initial page load (which is typically a GET) but at that point you haven't done any searching. You submit the search (properly in my opinion, but it is debatable) as a POST. The search function uses the POST parameter and returns in in context as query, which is correct. But then you display the GET value of usr_query - which does not exist at this point because the page is now a POSTed page. Change the value= and it should work.

Short answer:
Seems like this slipped by:
c = Context({ 'query': query,})
This is in views.py. Context() doesn't cause any error messages but... In order to get the functinality I needed, all I had to do was remove this function and make the c variable a regular dictionary.
I included the Context function because of this stackoverflow question Writing a very basic search form in Django

Your Searchbox.html method is a 'post' instead of a 'get'
<form class="search" action="{% url 'search' %}" method='get'>
{% csrf_token %}
<input type="search" placeholder="Search here..." name="usr_query"
value='{{ query }}' required>
<button type="submit">Search</button>
</form>
views.py
class search(ListView):
model = YourModel #The model model you want to search
template_name = 'gtr_site/test_search_results.html'
def get_queryset(self):
query = self.request.GET.get('usr_query')
object_list = YourModel.objects.filter(modelfield__icontains=query)
return object_list
The in your webpage probably index.html you should add the return.
{% for result in object_list %}
{{ result.name }}
{% endfor %}

Related

Django - How to edit and submit multiple forms individually then render back to the same page?

So I'm building this learning log app where users can create multiple topics. I'm trying to create a single page where users can edit/save changes/delete their topic names. My logic in the edit_topic view function is to display the original topics in forms with save and delete button, code below or feel free to check GitHub Repo:
views.py
def edit_topics(request, topic_pk=None):
'''edit existing topics mainly the names'''
# modify the model data according to the request method and name
if request.method == 'POST' and topic_pk != None:
if 'save' in request.POST:
topic_to_change = get_object_or_404(Topic, pk=topic_pk)
form = TopicForm(instance=topic_to_change, data=request.POST)
if form.is_valid():
form.save()
elif 'delete' in request.POST:
topic_to_delete = get_object_or_404(Topic, pk=topic_pk)
topic_to_delete.delete()
return redirect('learning_logs:edit_topics')
# get the original/modified data passing to render
topics = Topic.objects.all()
topic_lst = []
for topic in topics:
form = TopicForm(instance=topic)
topic_lst.append(form)
context = {'topics': topics, 'topic_lst': topic_lst}
return render(request, 'learning_logs/edit_topics.html', context)
edit_topics.html
{% extends 'learning_logs/base.html' %}
{% block content %}
<section class="container">
<h3>Topics you have created</h3>
<ul class="">
{% for form in topic_lst %}
<li class="mb-5">
<form class="d-flex align-items-center"
action="{% url 'learning_logs:edit_topics' form.instance.pk %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" name="submit">save</button>
<button class="btn btn-danger" name="delete">delete</button>
</form>
</li>
{% endfor %}
</ul>
<a class="btn btn-info" href="{% url 'learning_logs:topics' %}">Done</a>
</section>
{% endblock content %}
url.py
from django.urls import path
from . import views
app_name="learning_logs"
urlpatterns = [
path('', views.index, name='index'),
path('topics/', views.topics, name='topics'),
path('topics/edit/', views.edit_topics, name='edit_topics'),
path('new_topic/', views.new_topic, name='new_topic'),
path('topic_<int:topic_pk>/', views.topic, name='topic'),
path('topic_<int:topic_pk>/new_entry/', views.new_entry, name='new_entry'),
path('topic_<int:topic_pk>/entry_<int:entry_pk>/', views.entry, name='entry'),
path('topic_<int:topic_pk>/entry_<int:entry_pk>/edit_entry/', views.edit_entry, name='edit_entry'),
]
However, I think there might be some logic error, it keeps raising NoReverseMatch(msg) django.urls.exceptions.NoReverseMatch: Reverse for 'edit_topics' with arguments '(1,)' not found. 1 pattern(s) tried: ['topics/edit$']
If I delete form.instance.pk in edit_topics.html file, no error is raised. But consequently, the if logic in the view function wouldn't take action... I'm fairly new to programming, I'm sure there're some logic errors as well --
I'm struggling with the topic_pk optional argument in the view function because I'm not sure how to pass it in correctly.
Ideally, I hope the users can edit, save or delete the topics on this page one by one, and after they click save or delete the page will redirect back to this view and render the updated topic text. At the end, when they are done, they can click the Done button at the end...
Please help! Thanks!!
Feel free to check GitHub Repo for Learning_Log app
Update your url like this:
path('topics/edit/', views.edit_topics, name='edit_topics'),
path('topics/edit/<int:topic_pk>/', views.edit_topics, name='edit_topic'),
And in html, change this line:
<form class="d-flex align-items-center" action="{% url 'learning_logs:edit_topic' form.instance.pk %}" method="POST">
^^^^^^^^^^^
Here, what I did is that, have 2 urls point to same view, one with topic_pk and another is without it.

NoReverseMatch at URL

I've been trying to create a movie survey in Django for an assignment, and I am currently working on the function. I can't seem to understand why won't it recognize the URL I pass.
I tried removing the hardcoded URL as shown in the Django tutorial on the framework's site, but that doesn't make the error go away.
Here's an excerpt from urls.py:
urlpatterns = [
url(r'^$', views.index, name="index"),
path('movie=<int:movie_id>&user=<int:user_id>/', views.movie, name='movie'),
path('ratings/', views.ratings, name='movie'),
path('rating/<int:movie_id>/', views.rating, name='movie'),
path('movie=<int:movie_id>&user=<int:user_id>/vote/', views.vote, name='vote'),
path('register/',views.register, name='register'),
]
This is my movie view( supposed to present a movie and a star rating radio for the user to rate the movie), where the URL is constructed and passed to HTML:
def movie(request,movie_id,user_id):
movie = get_object_or_404(Movie, pk=movie_id)
voteURL = '/polls/movie=' + str(movie_id) + '&user='+str(user_id)+'/vote/'
context = {
'mymoviecaption':movie.Title,
'moviePoster': 'https://image.tmdb.org/t/p/original'+tmdb.Movies(movie.TMDBID).images().get('posters')[0].get('file_path'),
'myrange': range(10,0,-1),
'myuserid':user_id,
'voteurl': voteURL,
'mymovieid':movie_id
}
#print(nextURL)
translation.activate('en')
return HttpResponse(render(request, 'movieview.html', context=context))
The HTML excerpt, where the vote view is called:
<form action="{% url voteurl %}" method="post">
{% for i in myrange %}
<input id="star-{{i}}" type="radio" name="rating" value={{i}}>
<label for="star-{{i}}" title="{{i}} stars">
<i class="active fa fa-star" aria-hidden="true"></i>
</label>
{% endfor %}
<input type="submit">Vote!</input>
</form>
The vote view( should save to database and redirect to the next movie, doesn't save to database yet, because I didn't want to clutter it with records until I am sure I got the function to work):
def vote(request, movie_id,user_id):
try:
nextmovie=get_object_or_404(Movie, pk=movie_id+1)
nextURL = '/polls/movie=' + str(movie_id + 1) + '&user='+str(user_id)+'/'
except Http404:
nextURL = '/polls/ratings'
try:
myrating = int(request.POST['rating'])
print(myrating)
except:
# Redisplay the question voting form.
return render(request, '/polls/movie=' + str(movie_id + 1) + '&user='+str(user_id)+'/', {
'error_message': "You didn't select a choice.",
})
return HttpResponseRedirect(nextURL)
No matter what I try, I get the NoReverseMatch at /polls/movie=1&user=9/ whenever I try to load the first movie page, despite said URL being defined in urlpatterns.
This is not the way how you provide two pk in the urls
It should be like this
path('movie/<int:movie_id>/<int:user_id>/', views.movie, name='movie'),
Also this is not the way of providing url in the template so it should be something like this
<form action="{% url 'vote' %}" method="post">
# {% url 'url_name' %}
# if app_name provided {% url 'app_name:url_name' %}
And please follow the tutorials step by step

NoReverseMatch during render

I'm receiving this error:
NoReverseMatch at /comments_page/1/post_comment/
Reverse for 'post_comment' with arguments '('',)' not found. 1 pattern(s) tried: ['comments_page/(?P[0-9]+)/post_comment/$']
My views.py
def post_comment(request, product_id):
host_product = Product.objects.get(pk=product_id)
comment = Comment()
comment.product = host_product
comment.author = request.POST["author"]
comment.comment_text = request.POST["comment"]
comment.save()
return render(request, 'comments_page/detail.html', {"host_product": host_product})
My comments_page\urls.py
from django.conf.urls import url
from . import views
app_name = "comments_page"
urlpatterns = [
# /comments_page/
url(r'^$', views.index, name="index"),
# /comments_page/1/
url(r'^(?P<product_id>[0-9]+)/$', views.detail, name="detail"),
# /comments_page/1/post_comment/
url(r'^(?P<product_id>[0-9]+)/post_comment/$', views.post_comment, name='post_comment'),]
My detail.html
<form action="{% url 'comments_page:post_comment' product.id %}" method="post">
{% csrf_token %}
Name: <input type="text" id="author" name="author">
Comment:
<textarea id="comment" name="comment"></textarea><br>
<input type="submit" value="Post Comment">
I think I've identified the problem as being in the product.id here
{% url 'comments_page:post_comment' product.id %}
in the html page. I've tried formatting this a couple of different ways, but I haven't had any luck. Do note, the comment is going through and the form and it works as far as updating the database and loading the entry on the page goes, but the page is not being redirected. I have to reload it manually. Any help would be appreciated.
The error message shows that the argument you pass to the {% url %} tag does not exist and resolves to an empty string. Your view indeed does not pass in a product variable, only a host_product variable. You need to change the tag accordingly:
{% url 'comments_page:post_comment' host_product.id %}
For those who may wonder, the fix is to change the return function in views.py to this
return render(request, 'comments_page/detail.html', {"product": host_product})
I do not understand why this works, but it does. Any suggestions as to how to clean up my post_comment function would be appreciated. I feel it's overly convoluted by using host_product

Writing a very basic search form in Django

So I'm trying to get something very simple accomplished. I want to enter a term into my search box, and display it on the resulting page.
My HTML for the form is
<form method="get" action="/results/" class="navbar-form pull-right">
<input type="text" id="searchBox" class="input-medium search-query" name="q" placeholder="Search">
<input type="submit" class="btn" value="Search" >
</form>
The views.py looks like this:
def search(request):
query = request.GET['q']
t = loader.get_template('template/results.html')
c = Context({ 'query': query,})
return HttpResponse(t.render(c))
And finally the result template contains:
<div>You searched for: {{ query }} </div>
Here's the urls.py
urlpatterns = patterns('',
url(r'^home/$', 'search.views.home'),
url(r'^results/$', 'search.views.results'),
Nothing is showing up in the {{ query }} space.
Ok so the action handling the search in your views.py is supposed to be search but as I suspected in your urls.py you don't call the search method anywhere.
Where do you execute search method?
Urls should be like this:
urlpatterns = patterns('',
url(r'^home/$', 'search.views.home'),
url(r'^results/$', 'search.views.search'),
# or at least have a url for the search view
Note the action attribute in your form
It is action="/results/". This means result view is the one who is supposed to be handling the form. You may also change this to action="/search/" and have your urls like this:
urlpatterns = patterns('',
url(r'^home/$', 'search.views.home'),
url(r'^results/$', 'search.views.results'),
url(r'^search/$', 'search.views.search'),

How to redirect django.contrib.auth.views.login after login?

I added django.contrib.auth.views.login everywhere in my webpage, for that I had to load a templatetag (that returns the AuthenticationForm) in my base.html. This templatetags includes the registration/login.html template.
The login is working ok but I want it to redirect the users to the same page they are before login. Now, it redirects me to /wherever_i_am/login wich shows registration/login.html with the 'login ok' or 'login fails' messages but without the rest of base.html.
I have followed django documentation and a few SO questions like this but I cannot redirect correctly. I have modified the next variable but it doesn't seem to work (next={{ request.get_full_path }} redirects me to /wherever_i_am/login ...again)
Have you tried something similar? any ideas?
UPDATE1
Now, the question could be something like: Do I have to declare my own login view if I want to include the login form everywhere in my web page?
Thank you.
Found answer:
Change settings.LOGIN_REDIRECT_URL in your settings.py,
below code is copy from django:
if request.method == "POST":
form = authentication_form(data=request.POST)
if form.is_valid():
# Ensure the user-originating redirection url is safe.
if not is_safe_url(url=redirect_to, host=request.get_host()):
redirect_to = settings.LOGIN_REDIRECT_URL
...
The below allows redirects the user to the page they were attempting to access after they log in, but without having to write a custom view. It contains all the code you need to add to make it work. (As an aside, not all the TEMPLATE_CONTEXT_PROCESSORS are needed, but if you set a value to it explicitly you overwrite the defaults so need to re-add them.)
settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.request",
"django.core.context_processors.static",
)
urls.py
from django.contrib.auth.views import login, logout
...the other imports for your app ...
urlpatterns = patterns('',
(r'^login/$', login, {'template_name':'login.html'} ),
(r'^logout/$', logout,{'template_name':'logout.html'}),
...the other urls for your app...
)
login.html
<html>
<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
{{form}}<br/>
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
</html>
logout.html
<html>
<p>You are logged out. To log in again, click here.</p>
</html>
views.py
#login_required(login_url="/login/")
def view1(request):
....this is a view you want to protect with security...
#login_required(login_url="/login/")
def view1(request):
....this is a view you want to protect with security...
I used something like this with default login view:
{% if form.errors %}
<p class="error">Sorry, that's not a valid username or password</p>
{% endif %}
<form action="{% url login %}" method="post">
{% csrf_token%}
<label for="username">User name:</label>
<input type="text" name="username" value="" id="username">
<label for="password">Password:</label>
<input type="password" name="password" value="" id="password">
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ request.get_full_path }}" />
</form>
# or if it's not declareв шт urls:
<form action="{% url django.contrib.auth.views.login %}?next={{ request.get_full_path }}" method="post">
everything worked fine.
PS: are you absolutely sure that "context_processors.request" is included in settings? Forgetting to include it is a common problem.
UPD: As far as I know, there are no way to make default login view to redirect on failed login (It just doesn't work that way).
Still i may be wrong
Finally I created a login view that calls django.contrib.auth.views.login internally.
I'd suggest to pass a previous url as a parameter within the url:
/accounts/login/?next=my_previous_url
and then use this value in a view
request.next
{{request.get_full_path}} gives you the current path, so is normal that the redirect points to the same place, change it for {{next}} in your registration/login.html template
Adding up to #Sean's anwer. Code for iterating over each form field in order to write field error above the miss-typed field.
So, in Sean's login.html is the existing code:
login.html
<html>
<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
{{form}}<br/> <!-- I can change! -->
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
</html>
Now what you should do is replace the "I can change!" line (4th line in the above code snippet) with following code:
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small"> {{ field.errors }}</span>
</div>
<label class="control-label col-sm-2">{{ field.label_tag }}</label>
<div class="col-sm-10"> {{ field }}</div>
</div>
{% endfor %}
You can use this snippet for other forms too (for example registration). :)
I stumble upon this question in my process of implementing Facebook Account Linking. The problem is the same: how do I correctly redirect django after successful login?
Remember this: your settings.py contain LOGIN_REDIRECT_URL right? So, that's the only place where you should do the logic of redirecting. To do that, first connect this signal (put this in your views.py):
def after_success_login(sender, user, request, **kwargs):
alt = request.GET.get('account_linking_token')
if alt is not None:
uri = request.GET.get('redirect_uri')
request.session['fb_redirect_uri'] = uri
user_logged_in.connect(after_success_login)
The logic above may not reflect your case, but the idea is setting up a session variable to be read in the route defined as LOGIN_REDIRECT_URL.
So, in my case:
def index(request):
if not request.user.is_authenticated():
form = SignUpForm()
return render(request, 'index.html', {'form': form})
else:
# FB ACCOUNT LINKING!
if 'fb_redirect_uri' in request.session:
redirect_uri = request.session['fb_redirect_uri']
del request.session['fb_redirect_uri']
to = '{}&authorization_code={}'.format(redirect_uri, request.user.username)
print('to', to)
return redirect(to)
That's it!
Add a decorator before the view function should be OK.
#login_required
see here for details

Categories

Resources