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.
Related
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)
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/.
I'm trying to get all the post by a single user and display it using DetailView and I also want to pass the username of the user on the URL.
this is my urls.py:
from django.urls import path
from .views import ProfileDetail
from . import views
urlpatterns = [
path('<str:username>/', ProfileDetail.as_view(), name = 'profile'),
]
this is my views.py:
from django.views.generic import (DetailView)
from django.shortcuts import render , redirect, get_object_or_404
from django.contrib.auth.models import User
from blog.models import Post
class ProfileDetail(DetailView):
model = Post
template_name = 'users/myprofile.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author = user).order_by('-date_posted')
I have a class-based view almost exactly just like this one and it is working.
This one always gives me this AttributeError: Generic detail view ProfileDetail must be called with either an object pk or a slug in the URLconf.
If you want to display multiple posts, then a ListView with model = Post would be more suitable.
from django.views.generic import ListView
class ProfileDetail(List):
model = Post
template_name = 'users/myprofile.html'
context_object_name = 'posts'
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author = user).order_by('-date_posted')
Alternatively, if you want to use DetailView, then you should have model = User because you are showing the posts for a single user. You can avoid the "must be called with either an object pk or a slug" error by overriding get_object.
from django.views.generic import DetailView
class ProfileDetail(ListView):
model = User
template_name = 'users/myprofile.html'
def get_object(self):
return User.objects.get(username=self.kwargs['username'])
Then, in the template, you can loop over the user's posts with something like:
{% for post in user.post_set.all %}
{{ post }}
{% endfor %}
Note that by switching to DetailView, you lose the pagination features of ListView.
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.
I have multiple related tables defined in my Django models:
# first models.py
from django.db import models
class Character(models.Model):
first_field = models.DateTimeField()
second_field = models.TextField()
# second models.py
from django.db import models
class Op(models.Model):
fk_character = models.ForeignKey('Character')
some_field = models.DateTimeField()
other_field = models.TextField()
class Participant(models.Model):
fk_op = models.ForeignKey('Op')
fk_character = models.ForeignKey('Character')
some_other_field = models.IntegerField(default=0)
For now, I'm sending this data from a view to template in a way like this:
# views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from second.models import MainModel
def home(request):
data = Op.objects.filter(some_field__isnull=True).order_by('-date')
rc = RequestContext(request, {'data':data})
return render_to_response('index.html', rc)
In this way I do have all the Op related data I need in my index.html template, but I'm struggling with logic to display this data in my template in a specific way. For example:
display a list of all Ops,
for each list item, check if Character is also a Participant in current Op item,
if it isn't, display some button, if it is than don't display the button
I know that template shouldn't handle any programming logic, but I'm also not sure what would be the best approach to solve this. Should I do all the logic in my view and construct a new object and send that object to my view or is there an easy way to solve this in template with current object I'm sending?
Update your model:
class Op(models.Model):
fk_character = models.ForeignKey('Character')
some_field = models.DateTimeField()
other_field = models.TextField()
def check_if_participant(self):
return bool(self.participant_set.all())
Display list of all Ops:
{% for op in data %}
{{op.some_field}}
{% if op.check_if_participant %}Yes - Character is participant {% endif %}
{% endfor %}