Django function doesn't return HTML page - python

I'm adding pagination on my website. This is my function:
views.py
def ProductList(request, category_slug=None, page_number=1):
content = {}
category = None
categories = Category.objects.all()
products = Product.objects.filter(available=True)
if category_slug:
category = get_object_or_404(Category, slug=category_slug)
products = products.filter(category=category)
current_page = Paginator(products, 2)
content['category'] = category
content['categories'] = categories
content['products'] = current_page.page(page_number)
return render(request, 'product/list.html', content)
urls.py
urlpatterns = [
url(r'^$', views.ProductList, name='ProductList'),
url(r'^page/(\d+)/$', views.ProductList, name='ProductList'),
url(r'^(?P<category_slug>[-\w]+)/$', views.ProductList, name='ProductListByCategory'),
url(r'^(?P<id>\d+)/(?P<slug>[-\w]+)/$', views.ProductDetail, name='ProductDetail'),
]
But every time I'm trying to get next page I see this error:
Page not found (404)
Request Method: GET
Request URL: http://127.0.0.1:8000/page/2/
Raised by: shop.views.ProductList
I can't really understand what I'm doing wrong. Thank you.

You haven't given a name to the group that captures the page number in your regex. So Django passes it to the view as a positional argument, not a keyword argument; and since the first positional argument after request is category_slug, that is the variable the number will be assigned to.
To fix this, use a named argument, like you do for id later:
url(r'^page/(?P<page_number>\d+)/$', views.ProductList, name='ProductList'),
Note, though, page number is usually passed as a querystring parameter, rather than in the URL path itself.

Related

Django: passing a URL parameter to every instance of a reverse URL lookup in a template tag

Sorry if the title is unclear, I'm not sure the best way to describe the issue. I have an application with a Ticket model and a Team model. All Tickets are associated with a single Team. The issue I'm having is a problem of URL reversing. I'm trying to set it up my URLs like so: /<team_pk>/tickets/ displays a list of tickets associated with the Team specified by team_pk. So /1/tickets/ would display all of the tickets for the first Team. Both of these objects are in the app tracker.
To do this, I've set up my project/urls.py files like so:
project/urls.py
urlpatterns = [ path('<team_pk>/', include('tracker.urls', namespace='tracker')), ]
tracker/urls.py
urlpatterns = [ path('tickets/', views.TicketTable.as_view(), name='ticket_list'), ]
Then, inside my html templates, I have the following URL tag:
href="{% url 'tracker:ticket_list' %}"
This results in a NoReverseMatch error:
NoReverseMatch at /1/tickets/
Reverse for 'ticket_list' with no arguments not found. 1 pattern(s) tried: ['(?P<team_pk>[^/]+)/tickets/$']
What I would like is for the reverse match to just use the current value for the team_pk URL kwarg.
What I have tried to fix it:
I have found the following solution to the problem, but it involves a lot of repetition, and I feel like there must be a DRYer way.
First, I extend the get_context_data() method for every view on the site.
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['current_team_pk'] = self.kwargs['team_pk']
return context
Then I reference this context in every URL template tag:
href="{% url 'tracker:ticket_list' team_pk=current_team_pk %}"
This results in the desired behavior, but it's a lot of repetition. So, is there a better way?
Edit:
Based on Willem Van Onsem's suggestion, I changed the URL template tag to href="{% url 'tracker:ticket_list' team_pk=team_pk %}", referencing the URL kwarg directly. But this doesn't seem to be working reliably. On the index page /<team_pk>/ loads just fine, and it includes two relevant URLs: /<team_pk>/ and /<team_pk>/tickets/. When I navigate to /<team_pk>/tickets/, however, I get another NoReverseMatch error:
NoReverseMatch at /1/tickets/
Reverse for 'home' with keyword arguments '{'team_pk': ''}' not found. 1 pattern(s) tried: ['(?P<team_pk>[^/]+)/$']
It seems the URL kwarg <team_pk> is not being passed for some reason. But the only link to 'home' is part of my base.html, which the other templates are extending. So the relevant template tags are the same.
Edit 2:
The view in question:
class TicketTable(LoginRequiredMixin, SingleTableMixin, FilterView):
table_class = my_tables.TicketTable
template_name = 'tracker/ticket_list.html'
filterset_class = TicketFilter
context_object_name = 'page'
table_pagination = {"per_page": 10}
PAGE_TITLE = 'Open Tickets'
TICKET_STATUS_TOGGLE = 'Show Closed Tickets'
TICKET_STATUS_TOGGLE_URL = 'tracker:closed_ticket_list'
DISPLAY_DEV_FILTER = True
DISPLAY_STATUS_FILTER = True
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page_title'] = self.PAGE_TITLE
context['ticket_status_toggle'] = self.TICKET_STATUS_TOGGLE
context['ticket_status_toggle_url'] = self.TICKET_STATUS_TOGGLE_URL
context['display_dev_filter'] = self.DISPLAY_DEV_FILTER
context['display_status_filter'] = self.DISPLAY_STATUS_FILTER
return context
def get_queryset(self):
return models.Ticket.objects.filter_for_team(self.kwargs['team_pk']).filter_for_user(self.request.user).exclude(status='closed')
Edit 3:
I found a solution to access the URL kwargs without modifying context data. I'm not sure why team_pk=team_pk didn't work in the URL tag, but team_pk=view.kwargs.team_pk does work.
Based on the comments and responses from Willem Van Onsem, a friend of mine, and some of my own digging, I found what I think is the DRYest way to do what I was trying to do. I created a custom mixin:
class CommonTemplateContextMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.kwargs.get('team_pk'):
context.setdefault('team_pk', self.kwargs.get('team_pk'))
else:
context.setdefault('team_pk', self.request.user.teams.all()[0].pk)
return context
Then I subclass this mixin with all my views. Then I have a team_pk template tag inside all my templates, and I just add team_pk=team_pk to all my URL template tags. The only advantage to using this instead of team_pk=view.kwargs.team_pk is that I can add some additional logic for the case when the URL kwarg isn't available.

Pass slug or id on GET request

I'm trying to test an API view for my Django project:
BOOK_URL = reverse('api:book') // '/api/book/'
book_id = 1
res = APIClient().get(f'BOOK_URL${book_id}/')
This works, but as you can see I need to interpolate the book_id into the string. Is there a way I can send a request without interpolating?
I tried:
res = APIClient().get(BOOK_URL, data={'book_id': book_id})
This is my views.py
class BookView(APIView):
def get(self, request, book_id):
book = get_object_or_404(
Book.objects.all(),
id=book_id
)
return book
The best you can do (and you should do at least) is to use the reverse (django.url.reverse) to get the URL endpoint for the detail URL from view name and URL keyword arguments (like you're doing for the list URL).
For example:
url = reverse('api:book', kwargs={'book_id': book_id})
Assuming the URL keyword argument is book_id.
The default keyword argument is pk (takes the value of viewset's lookup_field if you don't set lookup_url_kwarg); so for pk this would be:
url = reverse('api:book', kwargs={'pk': book_id})

Django Redirect - URL with String to URL With Slug

Not sure if I should be doing this or not... but here goes.
I have a url that looks like this:
/deals/category/Apparel/
I changed it to category/Apparel to shorten it.
but also notice the capitalized Apparel --as it is using the category name.
So I added a slug to my Category model and I'm trying to redirect the
deals/category/Apparel to category/apparel where the latter represents the slug
In my deals app I have this URL:
path('category/<str:category>', RedirectView.as_view(pattern_name='category', permanent=True)),
and which I'm trying to redirect to (in my core urls file)
path('category/<slug:slug>', deals_by_category, name='category')
My view for the `deals_by_category' looks like this:
def deals_by_category(request,slug):
category_deals = Deal.objects.filter(category__slug=slug).order_by('expired','-date_added')
category = category_deals[0].category
return render(request, 'deals/category.html', {'category_deals': category_deals, 'category':category})
so when I go to deals/category/Apparel it is redirecting to category/Apparel which is not what I want... and I get an error like this:
Reverse for 'category' with keyword arguments '{'category': 'Apparel'}' not found. 1 pattern(s) tried: ['category\\/(?P<slug>[-a-zA-Z0-9_]+)$']
I guess I understand that it is looking at the category name and trying to match against a slug, but not exactly sure how to correctly redirect this with the correct format.
path('category/<str:category>', RedirectView.as_view(pattern_name='category', permanent=True)),
When you use pattern_name, Django will try to reverse the URL with the same args and kwargs, in this case category='Apparel'.
If you want to use the slug in the URL instead, then you'll have to subclass RedirectView and override get_redirect_url.
from django.shortcuts import get_object_or_404
class CategoryRedirectView(RedirectView):
permanent = True
def get_redirect_url(self, *args, **kwargs):
category = get_object_or_404(Category, name=self.kwargs['category'])
return reverse('category', kwargs={'slug': category.slug})
Then use your view in your URL pattern:
path('category/<slug:slug>', CategoryRedirectView.as_view(), name='category')
I wouldn't set permanent = True until you are sure that the redirect is working as expected. Otherwise browsers may cache incorrect redirects.

why is this not working writing ValueError (though i used Tango_with_django book 1.9)?

Am using Django 2.0.2 for Django 1.9 Tutorial and couldn't figure out what the problem was after reconstruct my URL. Error Display (The view rango.views.category didn't return an HttpResponse object. It returned None instead.)
from django.template import RequestContext
from django.shortcuts import render_to_response
from rango.models import Category, Page
def index(request):
# Obtain the context from the HTTP request.
context = RequestContext(request)
# Query the database for a list of ALL categories currently stored.
# Order the categories by no. likes in descending order.
# Retrieve the top 5 only - or all if less than 5.
# Place the list in our context_dict dictionary which will be passed to the template engine.
# Query for categories - add the list to our context dictionary.
category_list = Category.objects.order_by('-likes')[:5]
context_dict = {'categories': category_list}
# looping through each category returned, and create a URL attribute.
# This attribute stores an encoded URL (e.g spaces replaced with underscores).
for category in category_list:
category.url = category.name.replace(' ', '_')
# Render the response and return to the client!
return render_to_response('rango/index.html', context_dict, context)
def category(request, category_name_url):
# Request our context from the request passed to us.
context = RequestContext(request)
# Change underscores in the category name to spaces.
# URLs don't handle spaces well, so we encode them as underscores.
# We can then simply replace the underscores with spaces again to get the name.
category_name = category_name_url.replace('_', ' ')
# Create a context dictionary which we can pass to the template rendering engine.
# We start by containing the name of the category passed by the user.
context_dict = {'category_name': category_name}
try:
# Can we find a category with the given name?
# If we can't, the .get() method raises a DoesNotExist exception.
# So the .get() method returns one model instance or raises an exception.
category = Category.objects.get(name=category_name)
# Retrieve all of the associated pages.
# Note that filter returns >= 1 model instance.
pages = Page.objects.filter(category=category)
# Adds our results list to the template context under name pages.
context_dict['pages'] = pages
# We also add the category object from the database to the context dictionary.
# We'll use this in the template to verify that the category exists.
context_dict['category'] = category
except Category.DoesNotExist:
# We get here if we didn't find the specified category.
# Don't do anything - the template displays the "no category" message for us.
pass
# Go render the response and return it to the client.
return render_to_response('rango/category.html', context_dict, context)
urls.py:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('', views.index, name='index'),
re_path(r'^category/(?P<category_name_url>\w+)/$', views.category, name='category'),
]
Put more faith into error messages. It says your category() function did not return a value, because it did not return a value. Its return is indented to the same level as pass and thus it belongs to the exception handler. Indent it to the level of the except keyword.

Django reverse and redirect after ajax request returns django.urls.exceptions.NoReverseMatch

I call a specific url from and ajax function which will calls the respective view function. In view function I want to redirect the page by calling another view (because I can't render after ajax request).
Here are my urls:
urlpatterns = [
url(r'^$', views.search, name='search'),
url(r'^search_result/.+$', views.search_result, name='search_result'),
url(r'^new_search_result/$',
views.new_search_result,
kwargs={'selected': '', 'keyword': ''},
name='new_search_result')
]
And here is the search_result view:
#csrf_exempt
def search_result(request):
keyword = request.POST.get('keyword')
selected = request.POST.get('selected')
url = reverse('new_search_result',
kwargs={'keyword': keyword,
'selected': selected})
return HttpResponseRedirect(url)
# return render(request, 'SearchEngine/search_result.html', {'all_results': result})
And here is the new_search_result view:
def new_search_result(request, selected={}, keyword=''):
# code blocks
But in consul I get this error:
django.urls.exceptions.NoReverseMatch: Reverse for 'new_search_result' with keyword arguments '{'selected': '{"PANTHER":"ftp.pantherdb.org","Pasteur Insitute":"ftp.pasteur.fr","Rat Genome Database":"ftp.rgd.mcw.edu"}', 'keyword': 'dfasdf'}' not found. 1 pattern(s) tried: ['searchengine/new_search_result/$']
[22/Jul/2017 12:52:12] "POST /searchengine/search_result/dfasdf HTTP/1.1" 500 16814
The kwargs argument to url
The extra kwargs argument you provide to the call to url allows you to define extra parameters that are then passed on to the view. They do not define extra arguments that are provided when fetching the url - the call to reverse does not know about these extra arguments, the call to reverse only knows about the arguments defined in the url pattern.
So if you have:
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', myview,
kwargs={'foo': 'bar'}),
]
This means that when your url is fetched as blog/1111 then view myview is invoked with two parameters: year with value 1111 and foo with value bar.
Only the view sees the extra argument foo - it is not in the url, and it is not provided to the reverse function. The kwargs argument of the reverse function actually refers to the arguments defined in the url pattern.
Passing parameters to a view
So looking at what you're trying to achieve: you want to redirect the user's browser to a different URL, such that the resulting view will have access to the keyword and selected parameters.
If you wanted that data to be hidden from the user, you would have to store it in the session. Assuming this is not the case you have three ways to pass parameters to a view: via GET parameters, via POST data and view the url. In your case as you're redirecting to another page, you can't use a POST request. You could use the url but for a search page I would think the best option would be to use GET parameters.
So in your search_result view you could add a query string to the URL ie. ?keyword=...&selected=.... For example:
import urllib
#csrf_exempt
def search_result(request):
args = {
'keyword': request.POST.get('keyword'),
'selected': request.POST.get('selected')
}
url = '%s?%s' % (
reverse('new_search_result'),
urllib.urlencode(args)
)
return HttpResponseRedirect(url)
And your new_search_result view would read those from the request:
def new_search_result(request):
selected = request.GET.get('selected')
keyword = request.GET.get('keyword')
# ...

Categories

Resources