I'm trying to include an template and utilize it in two different views, where the first one gets one url param and the second gets the same plus another one. Inside my included template there is an iteration with a {% url %} tag that I need to pass both params, since the second view needs them, but doing this causes NoReverseMatch when trying to render my first view, probably because it only accepts one param. Is there any way to specifying the second param is optional?
Here is my code:
urls.py
...
url(r'^portfolio/(?P<category_slug>[-\w]+)/$', views.category, name='category'),
url(r'^portfolio/(?P<category_slug>[-\w]+)/(?P<album_slug>[-\w]+)/$', views.album, name='album'),
...
models.py
...
class Album(models.Model):
cover = models.ImageField()
title = models.CharField(max_length=200, unique=True)
description = models.CharField(max_length=200, blank=True)
posts = models.ManyToManyField(Post, blank=True)
slug = models.SlugField(max_length=200)
class Category(models.Model):
cover = models.ImageField()
title = models.CharField(max_length=200, unique=True)
albums = models.ManyToManyField(Album, blank=True)
slug = models.SlugField(max_length=200)
#receiver(pre_save, sender=Album)
#receiver(pre_save, sender=Category)
def save_slug(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.title)
...
views.py
...
def index(request):
main_categories = Category.objects.filter(...)
return render(request, 'index.html', {'main_categories': main_categories})
def category(request, category_slug):
try:
category_selected = Category.objects.get(slug=category_slug)
except Category.DoesNotExist:
category_selected = None
albums = category_selected.albums.all()
return render(request, 'category.html', {
'category_selected': category_selected,
'albums': albums
})
def album(request, category_slug, album_slug):
try:
category_selected = Category.objects.get(slug=category_slug)
album_selected = Album.objects.get(slug=album_slug)
except Category.DoesNotExist:
category_selected = None
except Album.DoesNotExist:
album_selected = None
posts = album_selected.posts.all()
return render(request, 'album.html', {
'category_selected': category_selected,
'album_selected': album_selected,
'posts': posts
})
...
includedtemplate.html
...
{% for obj in objects %}
...
<a href="{% url view category_slug=obj.slug album_slug=obj.slug %}">
...
{% endfor %}
...
index.html
...
{% include 'includedtemplate.html' with objects=main_categories view='category' %}
...
EDIT:
I've managed to solve this by separating my urls with only one different slug each. This is simpler and fits my situation better, considering that I had a M2M for Category and Album and this could cause many urls for a single album.
You can combine views and set None for album_slug.
def combined_view(request, category_slug, album_slug=None):
category_selected = Category.objects.filter(slug=category_slug).first()
album_selected = None
posts = None
template = 'category.html'
if album_slug:
album_selected = Album.objects.filter(slug=album_slug).first()
posts = album_selected.posts.all()
template = 'album.html'
return render(request, template, {
'category_selected': category_selected,
'album_selected': album_selected,
'posts': posts
})
Also the order of urls is important - first url should be with one parameter, second with two parameters:
url(r'^portfolio/(?P<category_slug>[-\w]+)/$', views.combined_view, name='cview'),
url(r'^portfolio/(?P<category_slug>[-\w]+)/(?P<album_slug>[-\w]+)/$', views.combined_view, name='cview'),
P.S. Instead of try..except in views you can use filter().first(). It is faster to write.
Related
I am able to render list of all courses and list of topics corresponding to the courses in different templates.
I need help to view list of all courses and when each course is clicked,a new page should show the list of associated topics
models.py
class Topic(models.Model):
topic_name = models.CharField(max_length=200, null=True)
topic_file = models.FileField(upload_to = "topic_file", blank=True, null=True)
def __str__(self):
return self.topic_name
class Course(models.Model):
course_name = models.CharField(max_length=200, null=True)
course_image = models.ImageField(upload_to="images", blank=True, null=True)
related_topic = models.ManyToManyField(Topic)
def __str__(self):
return self.course_name
Views.py
def view_course(request):
course_list = Course.objects.all()
context = {'course_list':course_list}
return render(request, 'upskill/view_course.html',context)
def course_topic(request,pk):
course_topic_list = Course.objects.get(id=pk)
var = course_topic_list.related_topic.all
context = {'var':var}
return render(request, 'upskill/course_topic.html',context)
Here is how you could get the related topics inside of a template.
{% for course in course_list %}
... data
{% for topic in course.related_topic.all %}
...data
{% endfor %}
{% endfor %}
If you don't want to do a query every single iteration of the {{course}} loop, I recommend you this in your views:
course_list = Course.objects.all().prefetch_related('related_topic')
And for a single object:
def course_topic(request,pk):
course = Course.objects.prefetch_related('related_topic').get(id=pk)
context = {'course ':course }
return render(request, 'upskill/course_topic.html',context)
And then in the template:
{{course.data...}}
{% for topic in course.related_topic.all %}
...data
{% endfor %}
To only have topics:
def topic_view(request, pk)
topics = Topic.objects.filter(course__pk=pk) #The reverse name of Course model.
# You can set a related name the "related_topic" field.
# Then access the above filter with that related name.
.... data
context = {
"topics":topics
}
return render(request, 'template.html', context)
I'm new to CBV. Not sure why this isn't working...
views.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
context_object_name = 'item'
# With model specified, following code would be redundant, wouldn't it?? However...
# def get_object(self):
# return get_object_or_404(Item, pk=self.kwargs.get('item_pk'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
context['categories'] = Category.objects.all()
print('\ncontext= ', context, '\n')
return context
AttributeError at /item_8/ Generic detail view ItemDetailView must be called with either an object pk or a slug in the URLconf.
And the context isn't being printed out.
If I add get_object to the code, it's working fine:
class ItemDetailView(DetailView):
'''display an individual item'''
# model = Item
template_name = 'boutique/item.html'
# context_object_name = 'item'
def get_object(self):
return get_object_or_404(Item, pk=self.kwargs.get('item_pk'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# add categories for navbar link texts
context['categories'] = Category.objects.all()
print('\ncontext= ', context, '\n')
return context
However, if I changed get_object function to:
def get_object(self):
obj = super().get_object()
obj = get_object_or_404(Item, pk=self.kwargs.get('item_pk'))
# obj = obj.filter(pk=self.kwargs.get('item_pk')) # doesn't work, same error
# obj = obj.get(pk=self.kwargs.get('item_pk')) # doesn't work, same error
return obj
ImproperlyConfigured at /item_8/ ItemDetailView is missing a QuerySet. Define ItemDetailView.model, ItemDetailView.queryset, or override ItemDetailView.get_queryset().
I'm confused... DetailView should work without having to define get_object at all no??
additional files:
url.py
app_name = 'boutique'
urlpatterns = [
# show index page
path('', views.IndexView.as_view(), name='index'),
# show a specific item
path('item_<int:item_pk>/', views.ItemDetailView.as_view(), name='item'),
# show categories of products for men or women
path('<slug:gender>/', views.CategoryListView.as_view(), name='show-all'),
# show a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/', views.CategoryListView.as_view(), name='category'),
# show a specific subcategory under a specific category for men or women
path('<slug:gender>/cat_<int:category_pk>/subcat_<int:subcategory_pk>/', views.CategoryListView.as_view(), name='subcategory'),
]
models.py
class Item(models.Model):
'''Each item represents a product'''
category = models.ForeignKey(Category, on_delete=models.CASCADE)
subcategory = models.ForeignKey(
SubCategory, on_delete=models.CASCADE, null=True, blank=True)
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.IntegerField(default='0')
discount = models.IntegerField(null=True, blank=True)
uploaded_date = models.DateTimeField(
auto_now_add=True, null=True, blank=True)
class Meta:
ordering = ['-uploaded_date']
def __str__(self):
return self.name
def discounted_price(self):
'''to calculate the price after discount'''
return int(self.price * (100 - self.discount) * 0.01)
def get_item_url(self):
return reverse('boutique:item', kwargs={'item_pk': self.pk})
item.html
<!-- display each item in its own box -->
<div class="col-6 col-md-4 col-lg-3 px-1 px-sm-2 d-flex flex-column">
<!-- image anchor -->
<a href="{{ item.get_item_url }}">
<img class="rounded-sm" src="{{item.itemimage_set.first.image.url}}" width="100%"
alt=""></a>
<!-- /image anchor -->
<!-- item price tag -->
<div class="text-left p-1 mt-auto" style="font-size: 16px;">
<div class="font-weight-light pt-2">
{{item}}
</div>
<div class="font-weight-lighter">
{% if item.discount %}
<p>
<strike class="text-muted">₽ {{item.price}}</strike>
<b class="text-danger">₽ {{item.discounted_price}}</b>
<small class="text-danger">(-{{item.discount}}%)</small>
</p>
{% else %}
<p>₽ {{item.price}}</p>
{% endif %}
</div>
</div>
<!-- /item price tag -->
</div>
Your first example is correct, you don't need to define get_object() explicitly but you should use pk argument instead of item_pk in url path when using details CBV:
path('item_<int:pk>/', views.ItemDetailView.as_view(), name='item'),
Since by default get_object() method using self.kwargs["pk"] to search object.
If you still want to use item_pk you need to specify in in view using pk_url_kwarg:
class ItemDetailView(DetailView):
pk_url_kwarg = 'item_pk'
From docs:
The URLconf here uses the named group pk - this name is the default
name that DetailView uses to find the value of the primary key used to
filter the queryset.
If you want to call the group something else, you can set pk_url_kwarg
on the view. More details can be found in the reference for DetailView
I have been going through "Tango with Django" and have been unable to solve this problem myself or by looking online. Would anyone know how to approach it?
The relevant page should be opened when I click on the link, but none are going through, which makes me assume something in my view.py file is wrong or even in my url.py or model.py file (index.html seems to be working correctly).
Views.py
# Create your views here.
from django.http import HttpResponse
from django.shortcuts import render
from Spaces.models import Category, Page
def index(request):
# Query the databse 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 context_dict dictionary
# that will be passed to the template engine.
category_list = Category.objects.order_by('-likes')[:5]
context_dict = {'categories': category_list}
# Render the response and send it back!
return render(request, 'Spaces/index.html', context=context_dict)
def about(request):
context_dict = {'boldmessage':"Crunchy, creamy, cookie, candy, cupcake!"}
return render(request, 'Spaces/about.html', context=context_dict)
def show_category(request, category_name_slug):
# Create a context dictionary which we can pass
# to the template rendering engine.
context_dict = {}
try:
# Can we find a category name slug 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(slug=category_name_slug)
# Retrieve all of the associated pages.
# Note that filter() will return a list of page objects or an empty list
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 will display the "no category" message for us.
context_dict['category'] = None
context_dict['pages'] = None
# Go render the response and return it to the client.
return render(request, 'Spaces/category.html', context_dict)
Urls.py
from django.conf.urls import url
from Spaces import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^about/$', views.about, name='about'),
url(r'^category/(?P<category_name_slug>[\w\-]+)/$',
views.show_category, name='show_category'),
]
models.py
from django.db import models
from django.template.defaultfilters import slugify
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
views = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
slug = models.SlugField(unique=True, blank=True, null=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
class Meta:
verbose_name_plural = 'categories'
def __str__(self):
return self.name
class Page(models.Model):
category = models.ForeignKey(Category, on_delete=models.PROTECT)
title = models.CharField(max_length=128)
url = models.URLField()
views = models.IntegerField(default=0)
def __str__(self): # For Python 2, use __unicode__ too
return self.title
index.html
<!DOCTYPE html>
{% load staticfiles %}
<html>
<head>
<title>Spaces</title>
</head>
<body>
<h1>Spaces says...</h1>
<div>hey there partner!</div>
<div>
{% if categories %}
<ul>
{% for category in categories %}
<li>
{{ category.name }}
</li>
{% endfor %}
</ul>
{% else %}
<strong>There are no categories present.</strong>
{% endif %}
</div>
<div>
About Space<br />
<img src="{% static 'images/Spaces.jpg' %}" alt="Picture of Rango" />
</div>
</body>
</html>
populate_spaces.py (test script)
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'Space.settings')
import django
django.setup()
from Spaces.models import Category, Page
def populate():
#First, we will create lists of dictionaries containing the pages
# we want to add into each category.
# Then we will create a dictionary of dictionaries for our categories.
# This might seem a little bit confusing, but it allows us to iterate
# through each data structure, and add the data to our models.
python_pages = [
{"title": "Prahran",
"url":"http://docs.python.org/2/tutorial/", "views":20},
{"title": "South Yarra",
"url":"http://docs.python.org/2/tutorial/", "views":25},
{"title": "etcetera",
"url":"http://docs.python.org/2/tutorial/", "views":35}
]
django_pages = [
{"title" : "Official Django Tutorial",
"url" :"https://docs.djangoproject.com/en/1.9/intro/tutorial01/", "views":36},
{"title":"Django Rocks",
"url":"http://www.djangorocks.com/", "views":23},
{"title":"How to Tango with Django",
"url":"http://www.tangowithdjango.com/", "views":45}
]
other_pages = [
{"title":"Bottle",
"url":"http://bottlepy.org/docs/dev/", "views":3},
{"title":"Flask",
"url":"http://flask.pocoo.org",
"views":34}]
cats = {"Python": {"pages": python_pages, "views": 128, "likes":64},
"Django": {"pages": django_pages, "views": 64, "likes":32},
"Other Frameworks": {"pages": other_pages, "views": 32, "likes":16} }
# If you want to add more categories or pages,
# Add them to the dictionaries above.
# The code below goes through the cats dictionary, then adds each category
# and then adds all the associated pages for that category.
for cat, cat_data in cats.items():
c = add_cat(cat,cat_data)
for p in cat_data["pages"]:
add_page(c, p["title"], p["url"], p["views"])
#Print out the categories we have added.
for c in Category.objects.all():
for p in Page.objects.filter(category=c):
print("-{0})-{1}".format(str(c), str(p)))
def add_page(cat, title, url, views=0):
p = Page.objects.get_or_create(category=cat, title=title)[0]
p.url=url
p.views=views
p.save()
return p
def add_cat(name, cat_data):
c = Category.objects.get_or_create(name=name)[0]
c.likes = cat_data["likes"]
c.views = cat_data["views"]
c.save()
return c
# Start execution here!
if __name__ == '__main__':
print("Starting Spaces population script...")
populate()
I fixed the issue.
Essentially I had indented a return function in my view.py file incorrectly!
I have a reddit-like website where users post links and those links can be commented on. I want to display the number of comments under each link before being taken to the comment page for that link. What is the most query efficient way to do this in django that doesn't slow down the rendering of my site? I assume I'll have to go through a for loop of each link on the page to count the number of posts for each one and populate a list of some sort with the number returned from the .count() of each queryset? Here's what I have:
class Post(models.Model):
newlinktag = models.ForeignKey('NewLink', null=False)
childtag = models.ForeignKey('Post', blank=True, null=True)
postcontent = models.CharField(max_length=1024)
def __unicode__(self):
return self.postcontent
class NewLink(models.Model):
posttag = models.ForeignKey('PageInfo') #the page each link belongs to
linkcomment = models.CharField(max_length=512)
postlinkdate = models.DateTimeField(auto_now_add=True) #submission datestamp.
url = models.URLField(max_length = 1024)
linkowner = models.ForeignKey(User, null=True, blank=True)
def __unicode__(self):
return self.url
Jape gave you a good answer, but it is always more efficient to preform counting in the database rather than in python loops.
views.py
from django.db.models import Count
def view(request):
# Calculate the counts at the same time we fetch the NewLink(s)
links = NewLink.objects.annotate(post_count=Count('post_set'))
return render(request, 'template.html', {'links': links})
html
{% for link in links %}
{{ link.post_count }}
{% endfor %}
In your model, I would create a cached_property and then when you run the for loop in your template, call the property to get the count.
For example,
models.py:
class NewLink(models.Model):
posttag = models.ForeignKey('PageInfo') #the page each link belongs to
linkcomment = models.CharField(max_length=512)
postlinkdate = models.DateTimeField(auto_now_add=True) #submission datestamp.
url = models.URLField(max_length = 1024)
linkowner = models.ForeignKey(User, null=True, blank=True)
def __unicode__(self):
return self.url
# Might also want to flush this after a post_save method in your signals
#cached_property
def linkcomment_count(self):
return self.linkcomment.count()
views.py:
def view(request):
# Could do a 'select_related' relationship to save hits on the database
comments = NewLink.objects.all()
return render(request, 'template.html', {'comments': comments})
html:
{% for link in comments %}
{{ link.linkcomment_count }}
{% endfor %}
Did I understand your question correctly?
Update #4:
The for loop in slider.html is currently not pulling content after the last update. Slider.html was randomized; however, I'm getting four of the same story and the urls are not going to their appropriate detailed view page anymore.
List.html has been fixed and is now random.
slider.html - This section is still wonky, (updated - 4:19 p.m.)
{% for random_article in random_articles %}
<div class="slider">
<div class="group visible">
<div class="sliderItem">
<img src="{{random_article.relatedImage}}" alt="" class="sliderPicture">
<p class="related">{{random_article.title}}</p>
</div><!-- /.sliderItem -->
</div><!-- /.group -->
</div><!-- /.slider -->
{% endfor %}
Here is the URL error when I click to detailed view:
NoReverseMatch at /last-us
Reverse for 'detailed' with arguments '()' and keyword arguments '{u'slug': ''}' not found. 1 pattern(s) tried: ['(?P<slug>\\S+)']
New culprits (for why slider.html isn't working)
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"),
)
views.py (updated - 4:19 p.m.)
Added context['random_slider'] = FullArticle.objects.order_by('?')[:4] but I don't think this is the right approach. So that I can get four different articles vs. four of the same article randomized.
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"
def get_context_data(self, **kwargs):
context = super(BlogIndex, self).get_context_data(**kwargs)
context['random_article'] = FullArticle.objects.order_by('?').first()
return context
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
def get_context_data(self, **kwargs):
context = super(BlogDetail, self).get_context_data(**kwargs)
context['object_list'] = models.FullArticle.objects.published()
return context
def get_context_data(self, **kwargs):
context = super(BlogDetail, self).get_context_data(**kwargs)
context['random_articles'] = FullArticle.objects.exclude(
pk=self.get_object().pk
).order_by('?')[:4]
return context
Original Problem
I'm using FullArticle.objects.order_by('?').first() to get a random article from my database, but it's currently giving the same article when I refresh the page. There is probably something missing from my models, view or how I'm calling it (using slice) in list.html or slider.html that is causing the problem.
The two parts I'm looking to make random on page load:
list.html (changed so that it's {{random_article.}} ) - This section of the problem is fixed.
<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 %}
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"]
The problem is that you are setting the value of a class attribute at "compile time" and not each time the view is called. Instead, you could do:
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
def random_article(self):
return = FullArticle.objects.order_by('?').first()
Or:
class BlogIndex(generic.ListView):
queryset = models.FullArticle.objects.published()
template_name = "list.html"
def get_context_data(self, **kwargs):
context = super(BlogIndex, self).get_context_data(**kwargs)
context['random_article'] = FullArticle.objects.order_by('?').first()
return context
[update]
In list html, I only need one random article. In slider.html, I need four random articles, would I just tack on FullArticle.objects.order_by('?')[:4] somewhere in that def get_context_data snippet?
Yes. Make it plural in the view (don't forget to exclude the main article from the side list):
class BlogDetail(generic.DetailView):
model = models.FullArticle
template_name = "detailed.html"
def get_context_data(self, **kwargs):
context = super(BlogDetail, self).get_context_data(**kwargs)
context['random_articles'] = FullArticle.objects.exclude(
pk=self.get_object().pk
).order_by('?')[:4]
return context
At the template, do:
{% for random_article in random_articles %}
<div class="sliderItem">
<img src="{{random_article.relatedImage}}" alt="" class="sliderPicture">
<p class="related">{{random_article.title}}</p>
</div><!-- /.sliderItem -->
{% endfor %}
The generic listview just passes an object_list as context based on the queryset. In your case it means you have to either change the value of queryset in your view or override the get_context_data method and add your random item to it.
https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#django.views.generic.list.MultipleObjectMixin.get_context_data