Trouble paginating results from a 3rd party API in Django - django

I'm making a portfolio project where I'm using the Google Books API to do a books search, and the Django Paginator class to paginate the results. I've been able to get search results using a CBV FormView and a GET request, but I can't seem to figure out how to get pagination working for the API response.
The solution I can think of is to append &page=1 to the url of the first search, then pull that param on every GET request and use that to paginate. The problem is, I can't figure out how to append that param on the first search, and I don't know how I'd increment that param value when clicking the pagination buttons.
Here's what I've got now:
Form:
class SearchForm(forms.Form):
search = forms.CharField(label='Search:', max_length=150)
View:
class HomeView(FormView):
template_name = "home.html"
form_class = SearchForm
pageIndex = 0
def get(self, request, *args, **kwargs):
# get submitted results in view and display them on results page. This will be swapped out for an AJAX call eventually
if "search" in request.GET:
# getting search from URL params
search = request.GET["search"]
kwargs["search"] = search
context = super().get_context_data(**kwargs)
# Rest API request
response = requests.get(
f'https://www.googleapis.com/books/v1/volumes?q={search}&startIndex={self.pageIndex}&key={env("BOOKS_API_KEY")}'
)
response = response.json()
items = response.get("items")
# pagination...needs work
paginator = Paginator(items, 2)
page_obj = paginator.get_page(1)
context["results"] = page_obj
return self.render_to_response(context)
else:
return self.render_to_response(self.get_context_data())
Template:
{% extends "base.html" %}
{% block content %}
<form action="/">
{{ form }}
<input type="submit" value="Submit">
</form>
<h1>Books</h1>
<ul>
{% for result in results %}
<li>{{ result.volumeInfo.title }} : {{result.volumeInfo.authors.0}}</li>
{% empty %}
<li>Search to see results</li>
{% endfor %}
</ul>
{% if results %}
<div class="pagination">
<span class="step-links">
{% if results.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ results.number }} of {{ results.paginator.num_pages }}
</span>
{% if results.has_next %}
next
last »
{% endif %}
</span>
</div>
{% endif %}
{% endblock content %}
I also looked at Django REST Framework for this, but the Google Books API response doesn't contain any info on next page, previous page, etc. I've done this kind of pagination in React and it's not difficult, I'm just having trouble adjusting my mental model for how to do this to Django. If anyone could offer some advice on how to make this work, I'd be very grateful.

Related

How to get paginated answer's response?

I am using django as backend for website
There will be 10 qwestions
i want to paginate them (one qwestion's ans is given then go to next so on ) and atlast give marks calculated based on all the answers given
Any hints on how to do it?
I found somthing similar but dont know how to implement in django
What's the most efficient way to calculate a running total/balance when using pagination (PHP, MySQL)
Have you looked to Django Pagination docs?
https://docs.djangoproject.com/en/2.1/topics/pagination/
From that page
Your view:
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.shortcuts import render
def listing(request):
contact_list = Contacts.objects.all()
paginator = Paginator(contact_list, 25) # 25 is the items per page, in Your case would be 1
page = request.GET.get('page')
contacts = paginator.get_page(page)
return render(request, 'list.html', {'contacts': contacts})
Your template:
{% for contact in contacts %}
{# Each "contact" is a Contact model object. #}
{{ contact.full_name|upper }}<br>
...
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if contacts.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
next
last »
{% endif %}
</span>
</div>
To go to a page you should add to the url ?page=1 where 1 is the page number.

Using redirect sends me to /tag/?search=input instead of /tag/input (Django URL argument from form)

I have a page where there is a path /tag/name_of_tag and you can see all posts tagged with that tag.
Inside the page, you can also select another tag in a form and go to that tag.
The problem is that instead of going to /tag/searched_tag, it goes to /tag/?search=searched_tag
How can I change it doesn't leave the ?search= part?
urls.py:
url(r'tag/(?P<input_tag>\w+)$', views.tag_view, name='tag'),
views.py:
def tag_view(request, input_tag):
form = TagSearchForm()
if request.method == 'GET':
form = TagSearchForm(request.GET)
if form.is_valid():
input = form.cleaned_data['search']
print(input)
return redirect('fortykwords:tag_view', input)
else:
form = SearchForm()
latest_post_list = Post.objects.filter(tags=input_tag, status__exact="published")
paginator = Paginator(latest_post_list, 3)
page = request.GET.get('page')
posts = paginator.get_page(page)
context = {'latest_post_list': latest_post_list, 'page_tag': input_tag, 'form': form}
return render(request, 'fortykwords/tag.html', context)
forms.py:
class TagSearchForm(forms.Form):
search = tagulous.forms.SingleTagField(
tag_options=tagulous.models.TagOptions(
autocomplete_view='fortykwords:post_tags_autocomplete'
),
label='Tags',
required=True,
help_text=_('Filter by lead tags. You can organize leads by any tag you want.'),
)
tag.html:
{% extends "base_generic.html" %}
{% block content %}
<form action="." method="get">
{{ form }}
<input type="submit" value="Submit" />
</form>
<h3>Posts with the tag {{ page_tag }}</h3>
{% if latest_post_list %}
<ul>
{% for post in latest_post_list %}
<li> {{ post.author }} {{ post.pub_date }}
<br>
{{ post.title }}</li>
{% for tag in post.tags.all %}
{{ tag.name }}
{% endfor %}
{% endfor %}
</ul>
{% else %}
<p>No posts are available.</p>
{% endif %}
{% endblock %}
You need to provide the argument input to redirect method as input_tag=input.
Example:
return redirect('fortykwords:tag_view', input_tag=input)
It's showing as /tag/?search=searched_tag because your form is submitting by GET but never getting to the redirect. It seems is_valid() is returning False.
I've tested a very similar version of your code and don't think it's a bug in tagulous, but would still be interested to know what had gone wrong (I wrote tagulous). Spotted a couple of places you can streamline your code a bit, so try::
def tag_view(request, input_tag):
# Can't see any POSTs in your example, so you can pass the form GET here
# Might also be nice to pass the original tag in so it shows in the form
form = TagSearchForm(request.GET, initial={'search': input_tag})
# The form already has the GET, so you can go straight into the is_valid
if form.is_valid():
input = form.cleaned_data['search']
print('Valid: ', input)
return redirect('fortykwords:tag_view', input)
else:
print('Invalid: ', form.errors, form.non_field_errors)
# You can remove the else for if not GET, which would never be reached
# on to pagination as before
(although fwiw I'd recommend ipdb instead of print)

No reverse match error but the function exists?

I am trying to implement a renew function for a key inventory system. But when I render that page, it shows a Reversematcherror even though I mapped the correct URL and used the correct function name.
Here is my template:(The URL tag is on the super long line all the way to the right)
{% block content %}
<h1>All Borrowed Keys</h1>
{% if keyinstance_list %}
<ul>
{% for keyinst in keyinstance_list %}
<li class="{% if keyinst.is_overdue %}text-danger{% endif %}">
{{keyinst.roomkey}}
({{ keyinst.due_back }})
{% if user.is_staff %}
- {{ keyinst.borrower }}
{% endif %}
{% if perms.catalog.can_mark_returned %}
- Renew
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>There are no keys borrowed.</p>
{% endif %}
{% endblock %}
My urls.py:
path('key/<uuid:pk>/renew/', views.renew_key_user, name='renew-key-user'),
path('key/<int:pk>/detail', views.KeyDetailView.as_view(), name='roomkey-detail'),
Views.py:
#permission_required('catalog.can_mark_returned')
def renew_key_user(request, pk):
"""
View function for renewing a specific keyInstance by admin
"""
key_inst=get_object_or_404(KeyInstance, pk = pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewKeyForm(request.POST)
# Check if the form is valid:
if form.is_valid():
# process the data in form.cleaned_data as required (here we just write it to the model due_back field)
key_inst.due_back = form.cleaned_data['renewal_date']
key_inst.save()
# redirect to a new URL:
return HttpResponseRedirect(reverse('all-borrowed-keys') )
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewKeyForm(initial={'renewal_date': proposed_renewal_date,})
return render(request, 'catalog/roomkey_renew_user.html', {'form': form, 'keyinst':key_inst})
class KeyDetailView(generic.DetailView):
model = RoomKey
The error is saying
Reverse for 'views.renew_key_user' not found. 'views.renew_key_user'
is not a valid view function or pattern name.
Update this line in your template.
Renew
as name in url is renew-key-user
path('key/<uuid:pk>/renew/', views.renew_key_user, name='renew-key-user'),
Your URL name contains - hyphen not _ underscore
change this renew_key_user to renew-key-user in your template
Renew
Your template is asking for 'roomkey-detail' but the urls snippet you've provided only shows a url named 'renew-key-user'. Unless there are more url definitions you're not showing us, the code is failing as expected since it can't find a URL with the name you're asking for.

Where does the "page" GET parameter come from?

I am a beginner in Django, and I am really thoughtful about how things really work under the Django hood. I am currently implementing pagination in my web app.
Take a look at this view.py file:
def post_list(request):
object_list = Post.published.all(); # '.published' is a manager.
paginator = Paginator(object_list, 3); # 3 posts in each page.
page = request.GET.get("page");
try:
posts = paginator.page(page);
except PageNotAnInteger:
posts = paginator.page(1);
except EmptyPage:
posts = paginator.page(paginator.num_pages);
return render(request, "post/list.html", {"page": page, "posts": posts});
Isn't request.GET a dictionary object containing all the the GET request parameters that are in the url, and the .get() method used to return the value
for the given key inside the parameter? As my URL is currently just localhost:8000 when I start the application, why does it work if I pass the key "page"?
My list.html file:
{% extends "base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
<h1>My Blog</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2> <!-- How does this absurl work?-->
<p class="date">Published {{ post.publish }} by {{ post.author }}</p> <!-- What does '.publish' print?-->
{{ post.body|truncatewords:30|linebreaks }}
{% endfor %}
{% include "pagination.html" with page=posts %}
<!-- The statement above is the little menu: "Page 1 of 2. Next" -->
<!-- It also sends the 'page' variable as a GET parameter. -->
{% endblock %}
My pagination.html file:
<!-- This pagination template expects a paginator object. -->
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
Previous
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}. <!-- what!? -->
</span>
{% if page.has_next %}
Next
{% endif %}
</span>
</div>
When there are no parameters in the request (when you hit http://localhost:8000 directly), the value of page will be None. That is the default behaviour of request.GET.get() when it can't find the key you're asking for - the same as a normal Python dictionary (because GET extends it).
# page will be None
page = request.GET.get("page")
This means that None is passed to paginator.page():
try:
# Passing None here
posts = paginator.page(page)
except PageNotAnInteger:
Which likely means (although we can't see the code of paginagor) that a PageNotAnInteger exception is raised, and thus a value of 1 is passed to paginagor.page():
try:
posts = paginator.page(page) # Raises PageNotAnInteger because None passed
except PageNotAnInteger:
# Posts are retrieved for page 1
posts = paginator.page(1)
The posts from the above call, and the value of page (still None) are then passed to the template.
return render(request, "post/list.html", {"page": page, "posts": posts});
The template list.html then iterates the posts and displays them.
Rather confusingly, when the pagination.html template is included, it defines a context variable called page to the current value of posts:
<!-- Pass the value of posts using a variable name of page -->
{% include "pagination.html" with page=posts %}
So the places where the pagination.html template refers to page, it is actually using the value of posts.
<!-- Really posts.number and posts.paginator.num_pages -->
Page {{ page.number }} of {{ page.paginator.num_pages }}
Hope that helps to explain things.
One other thing, you don't need to add a semi-colon to the end of every line in Python.

Django - get() returned more than one it returned 700

I am having issues with a search form. I am trying to make a search field that queries the database and returns results on my results page. I have the page returning 1 result with a certain query and the other query with multiple rows I get this error get() returned more than one MyModel -- it returned 791!. This may seem silly as I am new to Django Forms. Please let me know if you need any other info. I have tried using .filter but that returns nothing. I have looked at multiple SO questions and some have helped but still having a little issue. My code is below:
views.py
from django.shortcuts import render
from .models import Model
def index(request):
return render(request, 'index.html')
def search(request):
query = request.GET.get('q')
if query:
query = str(query)
results = myModel.objects.get(
site=query
)
context = {"results": results}
return render(request, 'results.html', context)
results.html
{% if results %}
<ul>
<li><p>{{ results.url }}</p></li>
</ul>
{% else %}
<p>Nothing Available.</p>
{% endif %}
index.html
<form action="/results/" method="GET">
{% csrf_token %}
<input id="search_box" type="text" name="q" placeholder="Search...">
<button id="search_submit" type="submit" class="btn btn-defaultbtnlg"><i class="fa fa-search fa-fw"></i> <span class="networkname">Search</span></button>
</form>
Your search query can return more than one result, so you should use filter() instead of get().
results = myModel.objects.filter(
site=query
)
Then in your template, loop through the results
{% if results %}
<ul>
{% for result in results %}
<li><p>{{ result.url }}</p></li>
{% endfor %}
</ul>
{% else %}
<p>Nothing Available.</p>
{% endif %}