Source Code

/ src / blog / archive.py

import calendar
from collections import defaultdict
from blog.models import Post


def get_month_posts(year, month):
    """
    Returns queryset of all posts in a given month.
    """
    return (
        Post.objects
        .filter(date__year=year, date__month=month)
        .order_by("date")
    )


def group_posts_by_day(posts_qs):
    """
    Returns: {"01": [...], "02": [...], ...}
    """
    grouped = defaultdict(list)
    for p in posts_qs:
        key = f"{p.date.day:02d}"
        grouped[key].append(p)
    return grouped


def get_calendar_weeks(year, month):
    """
    Returns calendar weeks layout.
    """
    cal = calendar.Calendar(firstweekday=0)
    return cal.monthdayscalendar(year, month)


def get_month_navigation(year, month):
    """
    Returns prev/next month info + existence of posts.
    """

    if month > 1:
        prev_month = month - 1
        prev_year = year
    else:
        prev_month = 12
        prev_year = year - 1

    if month < 12:
        next_month = month + 1
        next_year = year
    else:
        next_month = 1
        next_year = year + 1

    has_prev_posts = Post.objects.filter(
        date__year=prev_year,
        date__month=prev_month
    ).exists()

    has_next_posts = Post.objects.filter(
        date__year=next_year,
        date__month=next_month
    ).exists()

    return {
        "prev_year": prev_year,
        "next_year": next_year,
        "prev_month": prev_month,
        "next_month": next_month,
        "prev_month_str": f"{prev_month:02d}",
        "next_month_str": f"{next_month:02d}",
        "prev_month_name": calendar.month_name[prev_month],
        "next_month_name": calendar.month_name[next_month],
        "has_prev_posts": has_prev_posts,
        "has_next_posts": has_next_posts,
    }


def build_month_context(year, month):
    """
    Fully prepares the full month calendar structure
    used by both archive and post detail views.
    """
    posts_qs = get_month_posts(year, month)

    return {
        "year": year,
        "month": {
            "num": month,
            "name": calendar.month_name[month],
            "month_str": f"{month:02d}",
            "posts_by_day": group_posts_by_day(posts_qs),
            "weeks": get_calendar_weeks(year, month),
            "count": posts_qs.count(),
            "has_posts": posts_qs.exists(),
        },
        "posts": posts_qs,
        **get_month_navigation(year, month),
    }


def get_year_month_counts(year):
    """
    Returns {1: count, 2: count, ... 12: count}.
    """
    return {
        m: Post.objects.filter(date__year=year, date__month=m).count()
        for m in range(1, 13)
    }


def build_year_context(year):
    """
    Returns full context needed for archive_year view.
    """
    year = int(year)
    month_counts = get_year_month_counts(year)

    months = [
        {
            "num": m,
            "name": calendar.month_name[m],
            "month_str": f"{m:02d}",
            "count": month_counts[m],
            "has_posts": month_counts[m] > 0,
        }
        for m in range(1, 13)
    ]

    rows = [months[i:i+3] for i in range(0, 12, 3)]

    prev_year = year - 1
    next_year = year + 1
    has_prev = Post.objects.filter(date__year=prev_year).exists()
    has_next = Post.objects.filter(date__year=next_year).exists()

    return {
        "year": year,
        "rows": rows,
        "prev_year": prev_year,
        "next_year": next_year,
        "has_prev_year_posts": has_prev,
        "has_next_year_posts": has_next,
    }