I am new to Django and as such I have a few gaps in my knowledge, particularly with regards to the get_context_data() in views. My aim is to try and make use of the DRY aspect of Django so that I have a list of categories on my webpage and each category reveals a list of posts that fall under it. To achieve this, I have a ListView and DetailView class (shown below). Note, the categories and posts are all objects of the same model.
My issues are the following:
I would like to display only some of the posts in my ListView not all of them. Is there a dynamic way to do this?
In my DetailView, I want to display a list of posts but from a single choice, for eg say choice1 is selected when creating a post, then only all posts with choice 1 selected will be displayed. I would like to do that dynamically, currently its hardcoded to only display choice1. I have tried to use the get_context_data() to do this but this only outputs a queryset in my template which leads me to problem 3
My HTML file currently displays all posts of choice1 as a QuerySet but not the actual content
Views.py
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
class PostDetailView(DetailView):
model = Post
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data(*args, **kwargs)
list_of_relevant_articles = Post.objects.filter(choice = "choice1")
context.update({'list_of_relevant_articles': list_of_relevant_articles})
return context
Models.py
POST_CHOICES = (
('choice1','CHOICE1'),
('choice2', 'CHOICE2'),
('choice3','CHOICE3'),
('choice4','CHOICE4'),
)
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
image = models.ImageField(blank=True, null=True)
choice = models.CharField(max_length=14, choices = POST_CHOICES, default ='choice1')
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog-post', kwargs={'pk': self.pk})
Html file
{% block content %}
<!-- This for loop does not output anything but I believe i need one to iterate through
a queryset-->
{% for post in object_list %}
{{ list_of_relevant_articles }}
{% endfor %}
{% endblock content %}
Any help/advice will be greatly appreciated
1
you can use context['posts'] = context['posts'][:3] and a normal loop in template, but it is better to use pagination in general.
class PostListView(ListView):
model = Post
template_name = 'blog/post.html'
context_object_name = 'posts'
ordering = ['-date_posted']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['posts'] = context['posts'][:3]
return context
{% for post in posts %}
{{ post }} <br>
{% endfor %}
The other way is do not use get_context_data and inside the template use slicing like:
{% for post in posts|slice:":2" %}
2, 3
class PostDetailView(DetailView):
model = Post
template_name = 'blog/detail.html'
slug_url_kwarg = "choice"
slug_field = "choice"
context_object_name = 'posts'
def get_object(self, queryset=None):
return Post.objects.filter(choice=self.kwargs.get("choice"))
{% for post in posts %}
{{ post }} <br>
{% endfor %}
urls.py
urlpatterns = [
# ...
path('post/', views.PostListView.as_view(), name='list'),
path('post/<str:choice>/', views.PostDetailView.as_view(), name='detail'),
]
so in the browser: .../post/choice2/, .../post/choice3/, ...
Related
I have a problem with showing a queryset of a specific model in my Gym project. I have tried many different query's but none is working
the models:
class Workout(models.Model):
name = models.CharField(max_length = 30,blank=True, null=True)
class Exercise(models.Model):
workout = models.ForeignKey(Workout, on_delete=models.CASCADE, related_name='exercises',blank=True, null=True)
name = models.CharField(max_length = 30, blank=True, null=True)
class Breakdown(models.Model):
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, related_name='excercise',blank=True, null=True)
repetitions = models.IntegerField()
I am trying to showing the repetitions in the Breakdown model which has a ForeignKey relation with Exercise which has a ForeignKey relation with Workout
views.py
class home(ListView):
model = Workout
template_name = 'my_gym/home.html'
context_object_name = 'workouts'
class workout_details(ListView):
model = Exercise
template_name = 'my_gym/start_workout.html'
context_object_name = 'exercises'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['breakdown'] = Exercise.objects.filter(breakdown=self.breakdown)
return context
urls.py
urlpatterns = [
path('', home.as_view(), name='home'),
path('workout/<int:pk>/', workout_details.as_view(), name='workout'),
]
template:
{% for e in exercises %}
{{ e.name }}
{{ breakdown.repetitions }}
{% endfor %}
My question what is my mistake here that is either getting me an error or not showing the required data set. my objective is the choose from the home page a Workout from list and next page to be the list of exercises with the repitions related to it.
get_context_data() is a method calculated for view, not a loop to use inside a template or something similar. You have set nice relations, use them properly.
Firstfully, change related_name here to something that will not be confusing:
class Breakdown(models.Model):
exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE, related_name='breakdowns', blank=True, null=True)
With that done, delete whole get_context_data() and change template to:
{% for e in exercises %}
{{ e.name }}
{% for b in e.breakdowns.all %}
{{ b.repetitions }}
{% endfor %}
{% endfor %}
class ManageStudentView(ListView):
model = Student
template_name = 'student/manage_student.html'
def get_context_data(self, **kwargs):
kwargs = super(ManageStudentView,
self).get_context_data(**kwargs)
kwargs['student'] = User.objects.filter(user_type=STUDENT)
return kwargs
def get_paginate_by(self, queryset):
self.paginate_by = settings.PAGINATION_NUMBER
return self.paginate_by
First make sure that import your models and ListView
class YourViewNameHere(ListView):
paginate_by = 2
model = Your Model name
This limits the number of objects per page and adds a paginator and page_obj to the context. To allow your users to navigate between pages, add links to the next and previous page, in your template like this:
{% for some in page_obj %}
{# Each "some" is a Your model object. #}
{{ some.full_name|upper }}<br>
...
{% endfor %}
I have a comment form for my posts. It looks like this
view.py
def add_comment_to_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.author = request.user
#comment.author.photo = object.author.profile.image.url
comment.save()
return redirect('Post-detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/add_comment_to_post.html', {'form': form})
models.py
class Comment(models.Model):
post = models.ForeignKey('blog.Post', on_delete=models.CASCADE, related_name='comments')
author = models.CharField(max_length=20)
text = models.TextField(max_length=200, verbose_name='内容')
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
forms.py
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text',)
widgets = { #size of textbox
'text': forms.Textarea(attrs={'rows':4}),
}
Where should I add pagination function to my comments to make it works?
I have a pagination for my posts, but posts are using a DetailView class and I dont know how to make it work for comment function
Basically you need to do that in Post-detail view. As DetailView does not provide pagination, so you need to provide them on your own. You can override the get context method. For example:
from django.core.paginator import Paginator
class PostDetailView(DetailView):
...
def get_context_data(self):
context = super(PostDetailView, self).get_context_data()
_list = Comment.objects.filter(post=self.kwargs.get('pk'))
paginator = Paginator(_list, 25) # Show 25 contacts per page
page = request.GET.get('page')
context['comments'] = paginator.get_page(page)
return context
And render comments in Template like this:
<div class="pagination">
<span class="step-links">
{% if comments.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ comments.number }} of {{ comments.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
next
last »
{% endif %}
</span>
</div>
More information can be found in documentation as well.
This is how it can be done.
from rest_framework.pagination import PageNumberPagination
# Create a custom pagination class.
class CommentPagination(PageNumberPagination):
page_size = 1000
page_size_query_param = 'page_size'
max_page_size = 10000
# Define the Pagination class in PostDetailView.
class PostDetailView(DetailView):
pagination_class = CommentPagination
# This is how api can be called.
# something.com/comment/?page=4
I have a problem when saving/creating new post for blog.
I already have Post model and each post has it's own category. so I have Category model too. In CreateView template I already got all categories from DB and displayed in select/option tag. The issue is I want to save category_id when I create new post. But I don't know how? How can I say if form POSTED get category_id and save it for Post model that has category_id field?
View:
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all().order_by('id')
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
Model:
class Category(models.Model):
title = models.CharField(max_length=255)
def __str__(self):
return self.title
class Post(models.Model):
title = models.CharField(max_length=255)
short_text = models.TextField()
long_text = models.TextField()
post_pic = models.ImageField(default="post_pic.jpg",
blank=False, upload_to='post_pics')
date_published = models.DateTimeField(default=timezone.now())
date_upadated = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, default=None,
on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})`
post_form.html
{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title_head %}
New Post
{% endblock title_head %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4 pb-2">New Post</legend>
{{ form|crispy }}
<select class="form-control" name="category">
{% for category in categories %}
<option value="{{ category.id }}">{{ category }}</option>
{% endfor %}
</select>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-outline-info">Post</button>
</div>
</form>
{% endblock content %}
There are a few ways to achieve this. Here is the simplest:
If you expect the Catagories to remain the same then you can hard code them and use a CharField with the choices= keyword on your Post model.
class Post(models.Model):
CATEGORY_CHOICES = (
('Horror', 'Horror'),
('Romance', 'Romance'),
('Comedy', 'Comedy'),
)
category = models.CharField(choices=CATEGORY_CHOICES)
Then all you need to do is add 'category' to your fields list in the CreateView.
Side Note You should probs rename your CreateView it's a view not a form. This may cause confusion.
Solved!
we have access category in list of fields so it's useless to fetch all categories then displaying them in select-option tag in html page. just add category field in list of fields
Change PostCreateForm
change it from this
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
context['categories'] = Category.objects.all().order_by('id')
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
to this
class PostCreateForm(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'short_text', 'long_text', 'post_pic', 'category']
def get_context_data(self, **kwargs):
context = super(PostCreateForm, self).get_context_data(**kwargs)
return context
def form_valid(self, form, **kwargs):
form.instance.author = self.request.user
return super().form_valid(form)
I am a amature django developer. I have a model with two class called "Post" and "Catagory". I want to read Category items in my template. How can I import my category in my template and show it's data in my page?
models.py
from django.db import models
from taggit.managers import TaggableManager
class Category(models.Model):
title = models.CharField(max_length=40)
def __unicode__(self):
return self.title
class Post (models.Model):
title = models.CharField(max_length=150)
body = models.TextField()
date = models.DateTimeField()
tags = TaggableManager ()
cats = models.ManyToManyField(Category)
def __unicode__ (self):
return self.title
Thank you.
If you are using class based views and want to list all categories you could do:
# urls.py
url(regex=r'^category/$',
view=CategoriesListView.as_view(),
name='category_list_all'),
# views.py
class CategoriesListView(ListView):
model = Category
# category_list.html
<h2>Category list</h2>
<ul>
{% for cat in category_list %}
<li>
{{ cat.category }}
</li>
{% endfor %}
</ul>
You would place the html file in <project_route>/<app_name>/templates/<app_name>/ or <project_route>/templates/<app_name>/
If you have an existing function based view that's working with the Post model then you can just do something like:
# views.py
...
post = get_object_or_404(Post, pk=pass_in_pk)
return render(request, 'post.html', {'post': post})
# post.html
<h2>Category list</h2>
<ul>
{% for category in post.cats %}
<li>{{ category.title }}</li>
{% endfor %}
</ul>
If you have a class based view based on a Post model using the DetailView then you can also use the above html, just place it in post_detail.html in the appropriate folder.
It`s like get the category value and assign into settings and passed into view html will work
def viewfuncion(request):
template_vars = {}
settings = Category.objects.get(pk=1)
template_vars['title_show'] = settings.title
t = loader.get_template('view.html')
c = Context(template_vars)
return HttpResponse(t.render(c), content_type = "application/xhtml")
So in your HTML { title_show } will print the content