Django not displaying two DetailView - python

Whenever I click on job_title link in index.html, django redirects me to detail url and from there it redirects to DetailView in views.py file and finally it opens detail.html file.
Same thing I am doing for category link. But it gives me an error: No job found matching the query. (Page not Found Raised by: JobPanel.views.DetailView). I'm getting right slugs from the database to index.html.
urls.py
path('<slug:detail_slug>/', DetailView.as_view(), name='detail'),
path('<slug:cat_slug>/', CategoryView.as_view(), name='category'),
views.py
class DetailView(generic.DetailView):
model = Job
template_name = 'JobPanel/detail.html'
slug_url_kwarg = 'detail_slug'
slug_field = 'slug'
class CategoryView(generic.DetailView):
model = Job
template_name = 'JobPanel/category.html'
slug_url_kwarg = 'cat_slug'
slug_field = 'slug'
index.html
{{ job.job_title }}
<br>
{% for cat in job.categories.all %}
{{ cat }}
{% endfor %}
models.py
class Category(models.Model):
category = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
def __str__(self):
return self.category
class Job(models.Model):
job_title = models.CharField(max_length=250)
job_description = models.TextField()
slug = models.SlugField(unique=True)

Short answer: make non-overlapping patterns.
The two patterns you defined completely overlap. Indeed, if you have a url with /some-category-slug, then that url will match the <slug:detail_slug>/ pattern and thus trigger the first view. The fact that another path also matches is irrelevant, since Django always will trigger the first view in the list that matches.
The best way to solve this is make non-overlapping patterns, like:
path('detail/<slug:detail_slug>/', DetailView.as_view(), name='detail'),
path('category/<slug:cat_slug>/', CategoryView.as_view(), name='category'),
If you now generate a url for a category, it will look like category/my-category-slug. This can not match with the first path(..) since that requires that the path starts with detail. The opposite holds as well.
Note that you forgot to set the model of your CategoryView correctly, it likely should be:
class CategoryView(generic.DetailView):
model = Category # change to Category
template_name = 'JobPanel/category.html'
slug_url_kwarg = 'cat_slug'
slug_field = 'slug'

You define the CategoryView like this:
class CategoryView(generic.DetailView):
model = Job
template_name = 'JobPanel/category.html'
slug_url_kwarg = 'cat_slug'
slug_field = 'slug'
which will look up the following:
Job.objects.get(slug=cat_slug)
Looking at your models this seems wrong if cat_slug is supposed to point at job.categories.slug rather than job.slug.
You have left out the code that creates the relation between Job and Category. It seems to be a 1-to-many relation because of job.categories in your template code.
In this case, your CategoryView should either:
subclass ListView, keeping model = Job and using slug_field=category__slug
or subclass DetailView, using model=Category, keeping the rest as is
EDIT:
Willem's point of overlapping URLs is of course the other issue here.
If you need URLs without nested paths (/:slug/ instead of /job/:slug/) - you can also use a pattern like /job-:slug/.

Related

Page not found (404) No Booking matches the given query - Django web application

I am having trouble figuring out where my issue is. I am parsing a slug to the url, via my view pulling from the SlugField in my model. For an object instance that exists in my database, the slug is being parsed successfully into the url. However, I am receiving the above error and cannot work out why.
The corresponding model is Booking, and the slug field is as follows:
booking_reference = models.SlugField(verbose_name="Slug Field", blank=False, unique=True)
(I wanted to use the booking_reference as the slug field).
My views.py is as follows:
class EditBookingView(UpdateView, NextUrlMixin):
model = Booking
form_class = BookingForm
template_name = 'bookings/edit_booking.html'
success_url = '/'
default_next = '/'
def form_valid(self, form):
form.instance.user = self.request.user
form.save()
next_path = self.get_next_url()
return redirect(next_path)
def get_context_data(self, **kwargs):
context = super(EditBookingView, self).get_context_data(**kwargs)
context['slug'] = self.kwargs['booking_reference']
return context
def get_object(self):
slug = self.kwargs.get('booking_reference')
return get_object_or_404(Booking, booking_reference=slug)
And my urls.py are:
from django.urls import path
from .views import (
CreateBookingView,
ViewBookingsView,
EditBookingView,
DeleteBookingView
)
app_name = 'bookings'
urlpatterns = [
path('createbooking/<str:slug>/', CreateBookingView.as_view(), name='create_booking'),
path('viewbookings/<str:slug>/', ViewBookingsView.as_view(), name='view_booking'),
path('editbooking/<str:slug>/', EditBookingView.as_view(), name='edit_booking'),
path('cancelbooking/<str:slug>/', DeleteBookingView.as_view(), name='delete_booking'),
]
Please note, I have a list view for which the booking reference is being displayed under each model instance successfully. It is from this view that I can then go into the above EditView with the following line in the listview template:
<div class="row text-center mt-3">
<a class="btn btn-primary justify-content-md-center mb-5" href='{% url "bookings:edit_booking" slug=item.booking_reference %}' role="button" >Edit Booking</a>
</div>
As I say I have double checked whether the slug being parsed into my url is in fact present in my database under the "booking_reference" field for the model instance, and it is.
To trace the problem back:
You are getting a 404
You have a get_object_or_404 in your get_object() method, where you look up a Booking based on a variable 'slug'
The variable slug is set when you ask for self.kwargs.get('booking_reference')
And the kwarg 'booking_reference' is set...?
In your URLs file the variable is <str:slug>. Django doesn't actually know much else about it in relation to the model until it is used in some way,eg, to get an instance.
So trying to get('booking_reference') is returning None. The model knows what a booking_reference is, but the URL doesn't provide that connection. Hence the 404.
To fix: you just need to switch to
self.kwargs.get('slug')
(You may also need to update your get_context() in a similar manner)

Django filter in a View

I'm just starting on Django, and currently stuck on a seemingly simple requirement/behaviour. I want a page rendered with a filtered set of entries based on the class's ForeignKey, and called from a rendered view of that other class.
A simplified version of my model.py is:
from django.db import models
class BookDay(models.Model):
bookdate = models.DateField()
bookevent = models.CharField(max_length=255)
class BookTime(models.Model):
booktime = models.TimeField()
bookdate = models.ForeignKey(BookDay, on_delete = models.CASCADE)
My view.py reads:
from django.http import HttpResponse
from django.views import generic
from .models import BookDay, BookTime
class DayView(generic.ListView):
template_name = 'booking/days.html'
context_object_name = 'bookdate_list'
def get_queryset(self):
return BookDay.objects.order_by('bookdate')
class TimeView(generic.ListView):
model = BookDay
template_name = 'booking/booktimes.html'
context_object_name = 'booktimes_list'
def get_queryset(self):
return BookTime.objects.filter(bookdate=bookday_id).order_by('booktime')
My urls.py contains:
from django.urls import path
from . import views
app_name = 'booking'
urlpatterns = [
path('', views.DayView.as_view(), name='bookdays'),
path('<int:pk>/', views.TimeView.as_view(), name='booktimes'),
]
The referenced days.html renders a set of links per this fragment:
{% for entry in bookdate_list %}
<li>{{ entry.bookevent }}</li>
{% endfor %}
On clicking any of the resulting links, the failure manifests as name 'bookday_id' is not defined.
I can put a fixed integer in place of bookday_id in views.py above, and it works fine (for that ForeignKey only, obviously). Also, I've played around with the filter() parameter name, the relevant url, and the html extensively to no avail.
How should I parameterise this to take the clicked link and filter the BookTimes entries correctly? Should I use Django-filter for this, or can it be done natively in Django?
Well like the error specifies, there is no bookday_id variable. If I understand it correctly, you are interested in the pk parameter of the URL. You can access these positional and named parameter in the self.args and self.kwargs of the View objects, so you can rewrite it to:
class TimeView(generic.ListView):
model = BookDay
template_name = 'booking/booktimes.html'
context_object_name = 'booktimes_list'
def get_queryset(self):
return BookTime.objects.filter(bookdate_id=self.kwargs['pk']).order_by('booktime')
Since the pk is an int, we thus filter on the bookdate_id (which is an integer here).
I would however advice to rename your bookdate foreignkey to bookday (the name of the model it refers to), since now it creates some confusion with the bookdate field of the BookDay model.

django url without pk

I have been trying for too long now to get my urls working with my simple models and view. There is something I am definitely not understanding.
I wish to create a url similar to:
.../department/team,
where I do not use the PK of department object but the name instead.
My model looks like:
class Department(models.Model):
name = models.CharField(max_length=50, blank=True, null=True)
def __str__(self):
return 'Department: ' + self.name
class Hold(models.Model):
name = models.CharField(max_length=50, blank=True, null=True)
department = models.ForeignKey(Department, on_delete=models.CASCADE)
my view looks like (UPDATED):
class IndexView(generic.ListView):
template_name = 'index.html'
context_object_name = 'departments_list'
def get_queryset(self):
return Department.objects.all()
class DepartmentView(generic.DetailView):
model = Department
template_name="depdetail.html"
slug_field = "name"
slug_url_kwarg = "name"
my url looks the following: UPDATED
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<name>', views.DepartmentView.as_view(), name='depdetail')
]
and finally my html:
<h1> Hello there {{ object.name }} student</h1> </br>
<b> Choose your team:<b> </br>
however i keep getting page not found or must be slug or pk..
I hope someone can help me out so I can wrap my head around this.
UPDATED
It works now :) Thank you for the replies.
By default Django will look for a pk or a slug field when you use a DetailView. You must override get_object() method to change this behaviour:
get_object() looks for a pk_url_kwarg argument in the arguments to the view; if this argument is found, this method performs a primary-key based lookup using that value. If this argument is not found, it looks for a slug_url_kwarg argument, and performs a slug lookup using the slug_field.
That being said, your approach has other problems. It is always better to use a slug instead of a name for other reasons. For example, name is not guaranteed to be unique and also it may have characters which are not URL safe. See this question for a detailed discussion on how to use slug fields.

Simple URL mapping issue

I would like to map my urls.py to include this url: http://127.0.0.1:8000/activity/Arts%20&%20Crafts/member
This url was created by this line of code in my activity.html template:
<li><a href='/activity/{{ activity.name }}/member'>{{ activity.name }}</a></li>
Arts & Crafts is the name of an object I've created under the model Activity:
(models.py)
class Activity(models.Model):
name = models.CharField(max_length=50)
comments = models.CharField(max_length=300, null=True, blank=True)
questions = models.ManyToManyField(Question, blank=True)
pub_date = models.DateTimeField('Date published')
def __str__(self):
return self.name
Right now, my url in urls.py looks like this:
url(r'^activity/{{ models.Activity.name }}/member/$', views.SelectView.as_view(), name='select_member'),
But it doesn't work. When I try to go to http://127.0.0.1:8000/activity/Arts%20&%20Crafts/member; I get a 'Page Not Found' error saying "The current URL, activity/Arts & Crafts/member, didn't match any of these."
Can someone tell me what is wrong with my url, and how to fix it? Thank you.
Extra info--
urls.py:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^detail/$', views.DetailView.as_view(), name='detail'),
url(r'^activity/$', views.ActivityView.as_view(), name='activity'),
url(r'^activity/{{ models.Activity.name }}/member/$', views.SelectView.as_view(), name='select_member'),
]
views.py:
class SelectView(generic.ListView):
template_name = 'expcore/select_member.html'
model = Activity
Update your urls.py to capture the name of the activity:
url(r'^activity/(?P<activityname>[^/]+)/member/$',
views.SelectView.as_view(), name='select_member'),
The regex will capture the name of the activity and pass it as a keyword arg to the appropriate method (get or post) of your view function. In that function you can then retrieve the activity name from kwargs['activityname'] and then retrieve the activity object with the associated name via Activity.objects.get(name=kwargs['activityname']).
Then in your template you can generate the url using
<li>{{ activity.name }}</li>
It is not the right way of defining a url. I recommend reading the relevant section in the Django documentation about how to create a url for a view.
url(r'^activity/(?P<name>\W+)/member/$', views.SelectView.as_view(), name='select_member'),
Apart from that, there is another issue that needs to be addressed. Since you need to fetch the object by its name, you need to change slug_url_kwarg and slug_field to name in your view class:
class SelectView(generic.ListView):
template_name = 'expcore/select_member.html'
model = Activity
slug_url_kwarg = 'name'
slug_field = 'name'
Make sure to always leverage {% url %} template tag when creating a url in your template:
<li>{{ activity.name }}</li>
I think that a model variable should be used in a template, but you cannot put a model variable in a url(). You should use:
url(r'/activity/(?P<activityid>[0-9a-zA-z\.\_]+)/member/$', views.SelectView.as_view),
and then inspect "activityid" to verify whether it corresponds to a known and existing page and eventually visualize it.
Refer this link
Your urls.py
url(r'^activity/{{ models.Activity.name }}/member/$', views.SelectView.as_view(), name='select_member'),
Change it to
url(r'^activity/(?P<activity_name>[^/]+)/member/$', views.SelectView.as_view(), name='select_member'),
And while printing the path in template use
<li>{{ activity.name }}</li>

Fetching a random item from the view/object list using Django

UPDATE #2
Status: Still not solved
Updated: Thurs. Dec. 18, 11:30 a.m.
I'm currently using FullArticle.objects.order_by('?').first() to get a random article from my database, but it's not working. There is probably something missing from my models, view or url.py that's missing.
models.py
from django.db import models
from django.core.urlresolvers import reverse
# Create your models here.
class FullArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(publish=True)
class FullArticle(models.Model):
title = models.CharField(max_length=150)
author = models.CharField(max_length=150)
slug = models.SlugField(max_length=200, unique=True)
pubDate = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
category = models.CharField(max_length=150)
heroImage = models.CharField(max_length=250, blank=True)
relatedImage = models.CharField(max_length=250, blank=True)
body = models.TextField()
publish = models.BooleanField(default=True)
gameRank = models.CharField(max_length=150, blank=True, null=True)
objects = FullArticleQuerySet.as_manager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("FullArticle_detailed", kwargs={"slug": self.slug})
class Meta:
verbose_name = "Blog entry"
verbose_name_plural = "Blog Entries"
ordering = ["-pubDate"]
views.py
from django.views import generic
from . import models
from .models import FullArticle
# Create your views here.
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
randomArticle = FullArticle.objects.order_by('?').first()
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
urls.py
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns(
'',
url(r'^$', views.BlogIndex.as_view(), name="list"),
url(r'^(?P<slug>\S+)', views.BlogDetail.as_view(), name="detailed"),
)
Section in list.html that I want to be random
<div class="mainContent clearfix">
<div class="wrapper">
<h1>Top 10 Video Games</h1>
{% for article in object_list|slice:":1" %}
<p class="date">{{article.pubDate|date:"l, F j, Y" }}</p> | <p class="author">{{article.author}}</p>
<img src="{{article.heroImage}}" alt="" class="mediumImage">
<p class="caption">{{article.body|truncatewords:"80"}}</p>
{% endfor %}
I assume that FullArticle.objects.order_by('?')[0] will give me a
random item from my class of FullArticle. But, let's say that out of
my model, I only want data associated with the specific parts of the
article: title, author, heroImage and body. How would I go about doing
that?
To get specific fields of an object, use values or values_list. The first will return dictionaries, the second tuples:
FullArticle.objects.order_by('?').values('title','author','heroImage','body').first()
The above would result in something like:
{'title': 'Foo', 'author': 'Foo Author', ... }
I've also tacked on your suggestion of random =
FullArticle.objects.order_by('?')[0] called it "random" instead.
Not sure what this is about, but try to avoid shadowing built-in libraries, like random.
1) Actually you almost did it.
try:
article = FullArticle.objects.order_by('?')[0]
except IndexError:
article = None
2) You could use this in models.py as well as in views.py. IMHO there is no need to extract this string to separate method so I would write this code wherever I need it.
3) Better use ORM don't convert db result to list to choose first item. This is can be really memory and CPU expensive.
Getting a random article would usually be done in a view, or as a modelmanager method, or as a class method. Fullarticle.random should not be a class attribute. That will not work as you expect.
# Used in a view.
article = FullArticle.objects.order_by('?').first()
# you can also make a random() method in your model manager.
def random(self):
return self.get_queryset().order_by('?').first()
# or as a class method inside FullArticle
#classmethod
def random(cls):
return cls.objects.order_by('?').first()
I'm not quite sure what exactly you mean by this.
I only want data associated with the specific parts of the article: title, author, heroImage and body. How would I go about doing that?
To access specific attributes you do this:
title = article.title
author = article.author
If you don't need to use article.category, just don't access it.
from django.views.generic import DetailView
from books.models import Publisher, Book
To pass data from your (class based) View to the template it has to be added to the context.
Here's an example from the official documentation:
class PublisherDetail(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherDetail, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context
source: https://docs.djangoproject.com/en/1.7/topics/class-based-views/generic-display/#adding-extra-context
Lots of people find Class Based Views in Django to be a bit confusing. I would recommend that you understand how function based views work before you start doing anything complicated with CBVs.

Categories