How to get different querysets in Django templates from Django views - python

In my blog app I want to allow unkown users to see articles, but I also want to allow logged users to see in the same page (somewhere else) their own articles; something like:
YOUR ARTICLES: list (only if user is logged)
ALL ARTICLES: list
Note that I need to show articles based on the user logged in because the url must be this:
path('<int:user_id>/', views.IndexView.as_view(), name='index'),
index.html:
{% if user.is_authenticated %}
Your articles:
<div class="container py-5">
{% if article_list %}
{% for article in article_list %}
<div class="container">
<div class="row">
<div class="col">
{{article.author}}
</div>
<div class="col">
{{article.title}}
</div>
<div class="col">
{{article.pub_date}}
</div>
<a href=" {% url 'blog_app:detail' user_id = user.id %} ">
<div class="col">
Open article
</div>
</a>
</div>
</div>
{% endfor %}
{% else %}
<b>No articles!</b>
{% endif %}
</div>
{% endif %}
views.py:
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
context_object_name = 'article_list'
#return articles of a particular author
def get_queryset(self):
self.article = get_object_or_404(Article, author_id=self.kwargs['user_id'])
return Article.objects.filter(
author = self.article.author
)
My question is: How can I get from IndexView two different querysets? One with all articles and one with articles filtered by author?
Bonus question:
Can I allow unkown users to reach the articles page if the url needs to specify the user id?

After answers, this is one possible correct solution (don't focus on year and month filters, I added them but obviusly aren't related to the solution):
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_article_list'] = Article.objects.all()
context['author_article_list'] = Article.objects.filter(
pub_date__year = self.kwargs['year'],
pub_date__month = self.kwargs['month'],
author = self.kwargs['user_id']
).order_by('-pub_date')
return context
In django templates I used these context names to iter articles:
Author articles:
{% if user.is_authenticated %}
{% if author_article_list %}
{% for article in author_article_list %}
...
{% endfor %}
{% endif %}
{% endif %}
All articles:
{% if all_article_list %}
{% for article in all_article_list %}
...
{% endfor %}
{% endif %}

You need to specify:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['all_articles'] = Article.objects.all()
return context
Then you can also use an if statement in th template, to check if the {{all_articles}} exists.
"Can I allow unkown users to reach the articles page if the url needs to specify the user id?"
Unauthenticated users do not have an ID, this will result in an error. If you want users to go to the author of the current article being viewed, wouldn't it be {{article.author.id}}? (Not sure if this is what you want.)

Just use a standard context. Add this metod to you view (changing names, obviously):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book_list'] = Book.objects.all()
return context
Bonus answer:
Well, anyone can enter every instance of such view. The only thing would be to change manually number in the browser, i.e. anyone can access this link:
http://example.com/1/
But if someone is not authenticated, that link: <a href=" {% url 'blog_app:detail' user_id = user.id %} "> would raise error, but of course cause of {% if user.is_authenticated %} it's not rendered anyway.
You need to set proper permissions to your view.

I think you can also override the get_queryset() method according to different conditions, so:
class IndexView(ListView):
model = Article
template_name = 'blog_app/index.html'
context_object_name = 'article_list'
def get_queryset(self):
qs=super().get_queryset()
if self.request.user.is_authenticated:
article=get_object_or_404(Article,author_id=self.kwargs['user_id'])
return qs.filter(author=article.author) #filtered queryset
else:
return qs #default queryset

Related

Dajngo template not rendering content

When I render blogpost.html page I can't see any content in my page.
My urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='Blog_home'),
path('<slug:slug>', views.blogpost, name='blogpost'),
]
my views.py
When I print blogpost function's query I got null value
from django.shortcuts import render
from django.http import HttpResponse
from blog.models import Post
# Create your views here.
def index(request):
post = Post.objects.all()
context = {'post':post}
return render(request, 'blog/bloghome.html', context)
def blogpost(request, post_id):
post = Post.objects.filter(slug=slug)
print(post)
context = {'post':post}
return render(request, 'blog/blogpost.html', context)
Template Name:- blogpost.html
{% extends 'basic.html' %}
{% block title %}Blog{% endblock title %}
{% block body %}
<div class="contaier">
<div class="row">
<div class="col-md-8 py-4">
<h2 class=" blog-post-title">{{post.title}}</h2>
</div>
</div>
</div>
{% endblock body %}
If I write like this my blogpost.html template it works.
{% extends 'basic.html' %}
{% block title %}Blog{% endblock title %}
{% block body %}
<div class="contaier">
<div class="row">
<div class="col-md-8 py-4">
<h2 class=" blog-post-title">Django</h2>
</div>
</div>
</div>
{% endblock body %}
You're passing a queryset as a context. Your post object contains a queryset of Post objects, so you can't retrieve post.title, you need either to pass only one Post object to your template, or loop through all of your objects and then display post.title for each of them.
You probably need the first option, so you need to change several things.
In your urls.py, you defined your blogpost view by blogpost(request, post_id) whereas in your urls.py you defined your url as
path('<slug:slug>', views.blogpost, name='blogpost')
If you want to get an id from your url, you should define it as
path('<int:post_id>', views.blogpost, name='blogpost')
And in your blogpost view, you do
post = Post.objects.filter(slug=slug)
but your slug isn't defined because your named it post_id.
Once again, if you want to retrieve only a post_id, you should use
post = Post.objects.get(pk=post_id)
The problem is that you are not retrieving a post with this:
post = Post.objects.filter(slug=slug)
It's a queryset, which returns zero, one, or possibly >1 objects (the latter if the slugfield isn't specified unique)
Try:
post = Post.objects.get(slug=slug)
or to better handle failure
post = get_object_or_404( Post, slug=slug)
Django template language fails quietly. If something in {{ }} fails, you get a null string substituted, not a runtime error.

Making Pagination work with django-filter library and CBV

I know a related question has been asked before here:
how can I use pagination with django_filter but I really tried to make it work with mine but because I use a custom LinkWidget or class i find it hard to included the pagination in to my ResultsFilter class or even get it to work with views and template.
Here is my code so far:
filter.py
# I didn't do much just changed so filters would be displayed as text/url just like django admin works instead of FORMS
# and i also add style to the returned <li>
class MyLinkWidget(widgets.LinkWidget):
"""docstring for ClassName"""
def __init__(self, **karg):
# super().__init__()
super(widgets.LinkWidget, self).__init__(**karg)
def option_string(self):
return '<li class="list-group-item list-group-item-dark"><a%(attrs)s href="?%(query_string)s">%(label)s</a></li>'
class ResultsFilter(FilterSet):
important = AllValuesFilter(widget=MyLinkWidget(attrs={"class":"list-group"}))
viewed = AllValuesFilter(widget=MyLinkWidget(attrs={"class":"list-group"}))
class Meta:
model = Results
fields = ['viewed', 'important',]
views.py
class ResultView(ListView):
paginate_by = 3
model = Results
template_name = 'results_filter.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = ResultsFilter(self.request.GET, queryset=self.get_queryset())
return context
and finally template file is:
results_filter.html
<div class="filter_header">
<span class="l">Filter by Viewed</span>
{{filter.form.viewed}}
</div>
<div class="filter_header">
<span class="l>Filter by Viewed</span>
{{filter.form.important}}
</div>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
{% endif %}
</span>
</div>
EDIT
So basically, when i am at http://127.0.0.1:8000/ it shows all records ignoring paginate_by = 3 and i click next url becomes http://127.0.0.1:8000/?page=2 but still showing all records. Which means pagination is not working but clicking filter for important or entering url as http://127.0.0.1:8000/results/?page=3&important=False. I noticed only data i.e (20 records) where importance is False are shown but i need just 3 page records so i can click next to view others.
Bottom line is i think paginate_by is not linked to the queryset returned by Django-Filter based on my classes above
This is a little late, but try this:
from django_filters.views import FilterView
class ResultView(FilterView):
filterset_class = ResultsFilter
paginate_by = 3
Then in you html use object_list, it will contain the paginated results.
{% for object in object_list %}
...
{% endfor %}

I can't display posts only with a boolean field set to true in my template

In short - I have a bootstrap carousel and it works nicely, however I can't get it to display only fields with 'featured' set to 'true'
I have tried doing for post in posts.objects.featured (the carousel literally does not show up at all then) and variations like posts.objects.filter(featured=True) (it says it can't parse the remainder).
Here's the code from the template where I am trying to display the carousel image only with items with featured=True
{% for post in posts.objects.featured %}
<div class="carousel-item {% if forloop.first %}active{% endif %} ">
{% image post.image fill-1920x500 %}
<div class="carousel-caption d-none d-md-block">
<h2 id="inner-carousel-title">{{post.title}}</h2>
<h4><a href="{% pageurl post %}" style="color:white;text-shadow:2px 2px 4px #000000" >something</a></h4>
</div>
</div>
{% endfor %}
Again, I just want the carousel to show up only with featured posts
As a side note- it'd be awesome if it only showed 3 posts.
EDIT - Here's my model.py for the page
class BlogPage(RoutablePageMixin, Page):
description = models.CharField(max_length=240, blank=True)
content_panels = Page.content_panels + \
[FieldPanel("description", classname="full")]
def get_context(self, request, *args, **kwargs):
context = super(BlogPage, self).get_context(request, *args, **kwargs)
context['posts'] = self.posts
context['blog_page'] = self
return context
If you really want to do this in the template do:
{% for post in posts %}
{% if post.featured %}
<div> ... <div/>
{% endif %}
{% endfor %}
But you can also pass only the featured posts to your template in your view. Just add:
...
featured_posts = Post.objects.filter(featured=True)[:4]
return render('post_list.html', {'featured_posts': featured_posts, ...})
If you’re using Django’s generic ListView and you’re only showing the featured posts, you can set the queryset property to filter only the featured posts. If you’re also showing the other posts in your ListView, add the featured_posts to your context by overriding get_context_data().
You can try this if the way I suggested in the comment doesn't work
{% for post in posts %}
{% if post.featured %}
// write down your stuff
{% endif %}
{% endfor %}

How do I iterate through a ManyToManyField in django-jinja project?

I am trying to create a blog app which has posts and each posts have title, date, link and tags.
This is my models.py
# models.py
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class Post(models.Model):
title = models.CharField(max_length=300)
date = models.DateTimeField()
link = models.URLField()
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
#property
def tags_name(self):
return [x.name for x in self.tags]
class Meta:
ordering = ('date',)
This is my views.py
# views.py
from django.conf.urls import url, include
from django.views.generic import ListView
from blog.models import Post
urlpatterns = [
url(r'^$', ListView.as_view(queryset=Post.objects.all().order_by("-date"), template_name="blog/blog_list.html")),
]
This is my blog_list.html
<!-- blog_list.html -->
{% extends "mysite/layout.html" %}
{% block content %}
<h1>my blog posts</h1>
<ul>
{% for post in object_list %}
<li><span class="title">{{ post.title }}</span></li>
<p>{{ post.date|date:"d-m-Y" }}</p>
{% endfor %}
</ul>
{% endblock %}
{% block sidebar %}
<h4 id="sidenav">tags</h4>
{% for post in object_list %}
<ul>
<!-- I want to show the tags here -->
</ul>
{% endfor %}
{% endblock %}
In the blog_list.html, I am showing all the post details and on the sidebar, I want to show all the tags present from all the blog posts available. Since post.tags is ManyToManyField, how can I iterate through it?
You want to use .all in the template to get all the elements in the relationship:
{% for tag in post.tags.all %}
{{ tag }}
{% endfor %}
Thanks to #hansTheFranz for correcting my bracket issue.
Regarding not repeating tags, this would be very difficult with the current context. You might want to look into instead getting the posts in your View and extracting the tags there, where you have more freedom to check for duplicates. Something like this:
def tags(request):
posts = Post.objects.all()
tag_list = []
for post in posts:
tags = post.tags.all()
for tag in tag:
if not (tag in tag_list):
tag_list.append(tag)
context_dict = { "tags": tag_list, "posts": posts }
return render(request, 'blog/blog_list.html', context_dict)
urlpatterns = [
url(r'^$', tags, name="tags"),
]
And then change your template to be more like:
{% block sidebar %}
<h4 id="sidenav">tags</h4>
<ul>
{% for tag in tags %}
<li>{{ tag }}</li>
{% endfor %}
</ul>
{% endblock %}
Additionally, instead of referencing object_list you can now access the list of posts by referencing posts, because we have defined the list of posts as such in our context dictionary, which is being passed to the template.
I'm afraid I have not tested this and it may not be very efficient, but roughly speaking it should work. A lecturer at my university wrote this book: http://www.tangowithdjango.com/book17/, which encourages more of a style of writing views as I have done: separate from the URLs. If anything I've done seems unclear or contrary, you may want to have a look at the book and see if anything there makes more sense.

How to paginate a filtered queryset in listview

i am trying to add a search bar on my listview page. it will get all the post items first time, and if a query put in search box, and submitted, it will return a filtered queryset. it renders fine, but only has problem with pagination. for non-filtered queryset, the next page will get the next two posts without any problem, but for filtered queryset, i can see the queryset is correctly reflected by see less pages, but the next page gets me the second page of non-filtered queryset not the filtered queryset. Can anyone give me some pointers on what i am doing wrong here. Thanks
My template looks like this:
{% block content %}
.....
<form action="" method="get">
<input type="text" name='q'>
<input type="submit" value="Search">
</form>
{% for post in object_list %}
....
{% endfor %}
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<<
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
>>
{% endif %}
</span>
</div>
{% endif %}
I have a listview like below.
class Postlist(ListView):
model=post
paginate_by = 2
query_string = ''
def get_queryset(self):
if ('q' in self.request.GET) and self.request.GET['q'].strip():
query_string = self.request.GET['q']
entry_query = get_query(query_string, ['title', 'body',]) ## call get_query() function
queryset = post.objects.filter(entry_query).order_by('-created')
else:
queryset=post.objects.all().order_by('-created')
return queryset
def get_context_data(self):
context = super(ListView, self).get_context_data(**kwargs)
context['q'] = query_string
## do sth here to pre-populate the input text box
return context
Let me close this. Two options: using hidden field to save the user search terms, and get this to filter off queryset on each request; or using session. Here is the session code sample. The only drawback i could think of using session cache is it caches a whole queryset in memory, so it will kill the memory for high traffic website. Since i am using it for personal website, it doesnt hurt at all.
View file
class Postlist(ListView):
model=post
paginate_by = 2
def get_queryset(self):
query_string = ''
if ('search' in self.request.GET) and self.request.GET['search'].strip():
query_string = self.request.GET['search']
entry_query = get_query(query_string, ['title', 'body',]) ## call get_query to clean search terms
queryset = post.objects.filter(entry_query).order_by('-created')
self.request.session['searchset']=queryset
else:
if self.request.session.get('searchset') and ('page' in self.request.GET):
queryset=self.request.session['searchset']
else:
queryset=post.objects.all().order_by('-created')
if self.request.session.get('searchset'):
del self.request.session['searchset']
return queryset
form file
from django import forms
class SearchForm(forms.Form):
search = forms.CharField(max_length=30)

Categories

Resources