I am a noob in Django, and I am doing a project with lots of trials and errors. Here's a case. I want to implement this on my site:
Each entry will have posts grouped by the months and the years. If I click, I will get to see bunch of posts on that month and year.
My site only has 4 posts now, all of which were on last September. The look that I managed to do is the following, which obviously is wrong because Sep 2022 should be a single entry. There has to be some way to do the group by but I can't seem to achieve that:
I wanted to do it with an archive view, as I failed at that attempt, I am doing in this way. I'd like to know both ways. Here are the relevant files:
blog/models.py
class News(models.Model):
STATUS_CHOICES = (
('draft', 'Draft'),
('published', 'Published'),
)
news_title = models.CharField(max_length=250)
null=True)
slug = models.SlugField(max_length=300, unique_for_date='nw_publish')
news_author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='news_posts')
news_body = RichTextField()
image_header = models.ImageField(upload_to='featured_image/%Y/%m/%d/', null=True, blank=True) # this
nw_publish = models.DateTimeField(default=timezone.now)
nw_status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
tags = TaggableManager()
def __unicode__(self):
return '%name' % {'name': self.news_title}
class Meta:
ordering = ('nw_publish',)
def __str__(self):
return self.news_title
def get_absolute_url(self):
return reverse('news:news_detail', args=[self.slug])
blog/views.py
class NewsListView(TagMixin, ListView):
model = News
queryset = News.objects.all()
context_object_name = 'newss'
template_name = 'blog/news.html'
class NewsDetailView(TagMixin, DetailView):
model = News
date_field = "nw_publish"
context_object_name = 'news'
template_name = 'blog/news_detail.html'
def get_context_data(self, *, object_list=None, **kwargs):
data = super().get_context_data(**kwargs)
data['news_details_view'] = News.objects.all()
data['news_years'] = News.objects.annotate(year=ExtractYear('nw_publish')).values('year').annotate(total_entries=Count('year'))
return data
class NewsTagView(TagMixin, ListView):
model = News
context_object_name = 'newss'
template_name = 'blog/news.html'
def get_queryset(self):
return News.objects.filter(tags__slug=self.kwargs.get('tag_slug'))
class NewsYearArchiveView(YearArchiveView):
context_object_name = 'news_years'
date_field = "nw_publish"
year_format = '%Y'
make_object_list = True
allow_future = True
queryset = News.objects.filter(nw_status='published').order_by('nw_publish', 'news_title')
blog/urls.py
from django.urls import path, re_path
from . import views
from .views import Home, HomeDetail, Contact, ReportListView, ReportDetailView, NewsListView, NewsDetailView, \
MemberListView, ProjectListView, ProjectDetailView, SearchView, ReportYearArchiveView, PhotoGallery, AboutDetail, \
VideoGallery, FCListView, ReportTagView, NewsTagView, NewsYearArchiveView
from django.views.generic.base import RedirectView
# , Gallery
app_name = 'blog'
urlpatterns = [
# post views
path('', Home.as_view(), name='home'),
path("search", SearchView.as_view(), name='search'),
path('home/<slug:slug>/', HomeDetail.as_view(), name='details'),
path('news/', NewsListView.as_view(), name='news_list'),
path('news/<slug:slug>/', NewsDetailView.as_view(), name='news_detail'),
path('news/taggit/tag/<slug:tag_slug>/', NewsTagView.as_view(), name='post_tag'),
path('news/<int:year>/', NewsYearArchiveView.as_view(), name="news_year_archive"),
]
templates/news_details.html
{% extends 'base.html' %}
{% load static %}
{% block container %}
<div class="stricky-header stricked-menu main-menu">
<div class="sticky-header__content"></div><!-- /.sticky-header__content -->
</div><!-- /.stricky-header -->
<!--News Details Start-->
...
<div class="sidebar__single sidebar__category">
<h3 class="sidebar__title">Archives</h3>
<ul class="sidebar__category-list list-unstyled">
{% for y in news_years %}
<li><a href={{ y.get_absolute_url }}><i class="fas fa-arrow-circle-right"></i>{{ y }}</a></li>
{% endfor %}
</ul>
</div>
<div class="sidebar__single sidebar__tags">
<h3 class="sidebar__title">Popular Tags</h3>
<div class="sidebar__tags-list">
{% for tag in tags %}
<a href="{% url 'blog:post_tag' tag.slug %} ">{{ tag.name }}</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!--News Details End-->
{% endblock %}
What I am trying to do:
Sorry I am a newbie and I don't know Django that muuch; still learning hence I am stuck with this problem.
What I am trying to do:
Combine all the same months and years posts together Making the list clickable Showing a list of the posts after the click (how to get this list?) Sorry I am a newbie and I don't know Django that muuch; still learning hence I am stuck with this problem.
The only thing missing is a .order_by()
[Django-doc] (yes, I know that is strange), this will force a GROUP BY
statement:
data['news_years'] = (
News.objects.annotate(year=ExtractYear('nw_publish'))
.values('year')
.annotate(total_entries=Count('year'))
.order_by()
)
as for using months, it might be better to truncate than extract, since then you still have a date object:
from django.db.models.functions import TruncMonth
data['news_years'] = (
News.objects.values(month=TruncMonth('nw_publish'))
.annotate(total_entries=Count('month'))
.order_by()
)
Showing a list of the posts after the click (how to get this list?)
You can make a path with the year and month with:
path(
'posts/<int:year>/<int:month>/', SomeListView.as_view(), name='posts-by-month'
),
then in the template you link to this with:
{% for news_year in news_years %}
<a href="{% url 'posts-by-month' news_year.month.year news_year.month.year %}">{{ news_year.month }} ({{ news_year.total_entries }})</a>
{% endfor %}
in the SomeListView
, you can then filter with:
class SomeListView(ListView):
model = News
def get_queryset(self, *args, **kwargs):
return (
super()
.get_queryset(*args, **kwargs)
.filter(
nw_publish__year=self.kwargs['year'],
nw_publish__month=self.kwargs['month'],
)
)