Skip to content

Commit

Permalink
Merge pull request #30 from stuartmaxwell/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartmaxwell authored May 3, 2024
2 parents 4b29fc0 + bf64f8c commit e3580b6
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 93 deletions.
1 change: 1 addition & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,4 @@
CACHE_RECENT_PUBLISHED_POSTS: bool = False
RECENT_PUBLISHED_POSTS_COUNT: int = 20
BLOG_TITLE = "stuartm.nz"
MARKDOWN_EXTENSIONS = ["fenced_code", "codehilite"]
10 changes: 7 additions & 3 deletions djpress/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@ class PostFeed(Feed):

def items(self: "PostFeed") -> "models.QuerySet":
"""Return the most recent posts."""
return Post.post_objects.get_recent_published_content()
return Post.post_objects.get_recent_published_posts()

def item_title(self: "PostFeed", item: Post) -> str:
"""Return the title of the post."""
return item.title

def item_description(self: "PostFeed", item: Post) -> str:
"""Return the description of the post."""
"""Return the description of the post.
This is taken from the truncated content of the post converted to HTML from
Markdown.
"""
description = item.truncated_content_markdown
if item.is_truncated:
description += f'<p><a href="{self.item_link(item)}">Read more</a></p>'
return description

def item_link(self: "PostFeed", item: Post) -> str:
"""Return the link to the post."""
return reverse("djpress:content_detail", args=[item.slug])
return reverse("djpress:post_detail", args=[item.slug])
18 changes: 18 additions & 0 deletions djpress/migrations/0002_rename_content_type_post_post_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-05-03 11:04

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('djpress', '0001_initial'),
]

operations = [
migrations.RenameField(
model_name='post',
old_name='content_type',
new_name='post_type',
),
]
60 changes: 27 additions & 33 deletions djpress/models/post.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Post model."""

import logging
from typing import ClassVar

import markdown
Expand All @@ -12,27 +11,25 @@

from config.settings import (
CACHE_RECENT_PUBLISHED_POSTS,
MARKDOWN_EXTENSIONS,
RECENT_PUBLISHED_POSTS_COUNT,
TRUNCATE_TAG,
)
from djpress.models import Category

logger = logging.getLogger(__name__)
md = markdown.Markdown(extensions=MARKDOWN_EXTENSIONS, output_format="html")

markdown_extensions = ["fenced_code", "codehilite"]
md = markdown.Markdown(extensions=markdown_extensions, output_format="html")

PUBLISHED_CONTENT_CACHE_KEY = "published_content"
PUBLISHED_POSTS_CACHE_KEY = "published_posts"


class PostsManager(models.Manager):
"""Post custom manager."""

def get_queryset(self: "PostsManager") -> models.QuerySet:
"""Return the queryset for published posts."""
return super().get_queryset().filter(content_type="post").order_by("-date")
return super().get_queryset().filter(post_type="post").order_by("-date")

def _get_published_content(self: "PostsManager") -> models.QuerySet:
def _get_published_posts(self: "PostsManager") -> models.QuerySet:
"""Returns all published posts.
For a post to be considered published, it must meet the following requirements:
Expand All @@ -44,26 +41,25 @@ def _get_published_content(self: "PostsManager") -> models.QuerySet:
date__lte=timezone.now(),
)

def get_recent_published_content(self: "PostsManager") -> models.QuerySet:
def get_recent_published_posts(self: "PostsManager") -> models.QuerySet:
"""Return recent published posts.
If CACHE_RECENT_PUBLISHED_POSTS is set to True, we return the cached queryset.
"""
if CACHE_RECENT_PUBLISHED_POSTS:
return self._get_cached_recent_published_content()
return self._get_cached_recent_published_posts()

return self._get_published_content().prefetch_related("categories", "author")[
return self._get_published_posts().prefetch_related("categories", "author")[
:RECENT_PUBLISHED_POSTS_COUNT
]

def _get_cached_recent_published_content(self: "PostsManager") -> models.QuerySet:
def _get_cached_recent_published_posts(self: "PostsManager") -> models.QuerySet:
"""Return the cached recent published posts queryset.
If there are any future posts, we calculate the seconds until that post, then we
set the timeout to that number of seconds.
"""
queryset = cache.get(PUBLISHED_CONTENT_CACHE_KEY)
logger.debug(f"Getting posts from cache: {queryset=}")
queryset = cache.get(PUBLISHED_POSTS_CACHE_KEY)

if queryset is None:
queryset = (
Expand All @@ -78,25 +74,18 @@ def _get_cached_recent_published_content(self: "PostsManager") -> models.QuerySe
if future_posts.exists():
future_post = future_posts[0]
timeout = (future_post.date - timezone.now()).total_seconds()
logger.debug(f"Future post found, setting timeout to {timeout} seconds")
else:
logger.debug("No future posts found, setting timeout to None")
timeout = None

queryset = queryset.filter(date__lte=timezone.now())[
:RECENT_PUBLISHED_POSTS_COUNT
]
logger.debug(f"Setting posts in cache: {queryset=}")
logger.debug(f"With a timeout of: {timeout=}")
cache.set(
PUBLISHED_CONTENT_CACHE_KEY,
PUBLISHED_POSTS_CACHE_KEY,
queryset,
timeout=timeout,
)

logger.debug(
f"Posts set in cache: {cache.get(PUBLISHED_CONTENT_CACHE_KEY)=}",
)
return queryset

def get_published_post_by_slug(
Expand All @@ -107,33 +96,31 @@ def get_published_post_by_slug(
Must have a date less than or equal to the current date/time based on its slug.
"""
logger.debug(f"Getting post by slug: {slug=}")

# First, try to get the post from the cache
posts = self.get_recent_published_content()
posts = self.get_recent_published_posts()
post = next((post for post in posts if post.slug == slug), None)

# If the post is not found in the cache, fetch it from the database
if not post:
try:
post = self._get_published_content().get(slug=slug)
post = self._get_published_posts().get(slug=slug)
except Post.DoesNotExist as exc:
msg = "Post not found"
raise ValueError(msg) from exc

return post

def get_published_content_by_category(
def get_published_posts_by_category(
self: "PostsManager",
category: Category,
) -> models.QuerySet:
"""Return all published posts.
"""Return all published posts for a given category.
Must have a date less than or equal to the current date/time for a specific
category, ordered by date in descending order.
"""
return (
self._get_published_content()
self._get_published_posts()
.filter(categories=category)
.prefetch_related("categories", "author")
)
Expand All @@ -152,7 +139,7 @@ class Post(models.Model):
date = models.DateTimeField(default=timezone.now)
modified_date = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
content_type = models.CharField(
post_type = models.CharField(
max_length=10,
choices=CONTENT_TYPE_CHOICES,
default="post",
Expand All @@ -164,13 +151,13 @@ class Post(models.Model):
post_objects: "PostsManager" = PostsManager()

class Meta:
"""Meta options for the content model."""
"""Meta options for the Post model."""

verbose_name = "post"
verbose_name_plural = "posts"

def __str__(self: "Post") -> str:
"""Return the string representation of the content."""
"""Return the string representation of the post."""
return self.title

def save(self: "Post", *args, **kwargs) -> None: # noqa: ANN002, ANN003
Expand All @@ -185,7 +172,6 @@ def save(self: "Post", *args, **kwargs) -> None: # noqa: ANN002, ANN003
def render_markdown(self: "Post", markdown_text: str) -> str:
"""Return the markdown text as HTML."""
html = md.convert(markdown_text)
logger.debug(f"Converted markdown to HTML: {html=}")
md.reset()

return html
Expand All @@ -209,3 +195,11 @@ def truncated_content_markdown(self: "Post") -> str:
def is_truncated(self: "Post") -> bool:
"""Return whether the content is truncated."""
return TRUNCATE_TAG in self.content

@property
def author_display_name(self: "Post") -> str:
"""Return the author's display name.
If the author has a first name, return that. Otherwise, return the username.
"""
return self.author.first_name or self.author.username
4 changes: 2 additions & 2 deletions djpress/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
Category,
)
from djpress.models.post import (
PUBLISHED_CONTENT_CACHE_KEY,
PUBLISHED_POSTS_CACHE_KEY,
Post,
)

Expand All @@ -25,4 +25,4 @@ def invalidate_category_cache(**kwargs) -> None: # noqa: ARG001, ANN003
@receiver(post_delete, sender=Post)
def invalidate_published_content_cache(**kwargs) -> None: # noqa: ARG001, ANN003
"""Invalidate the published posts cache."""
cache.delete(PUBLISHED_CONTENT_CACHE_KEY)
cache.delete(PUBLISHED_POSTS_CACHE_KEY)
8 changes: 4 additions & 4 deletions djpress/templates/djpress/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ <h1><a href="{% url "djpress:index" %}">{% get_blog_title %}</a></h1>
<article>
<header>
<h1>{{ post.title }}</h1>
<p>By <span rel="author">{{ post.author.first_name }}</span></p>
<p>By <span rel="author">{{ post.author_display_name }}</span></p>
<p><time datetime="2023-05-01">{{ post.date }}</time></p>
</header>
<section>
Expand Down Expand Up @@ -52,15 +52,15 @@ <h1>{{ category.name }}</h1>

<article>
<header>
<h1><a href="{% url 'djpress:content_detail' post.slug %}">{{ post.title }}</a></h1>
<p>By <span rel="author">{{ post.author.first_name }}</span></p>
<h1><a href="{% url 'djpress:post_detail' post.slug %}">{{ post.title }}</a></h1>
<p>By <span rel="author">{{ post.author_display_name }}</span></p>
<p><time datetime="2023-05-01">{{ post.date }}</time></p>
</header>
<section>
{{ post.truncated_content_markdown|safe }}

{% if post.is_truncated %}
<p><a href="{% url 'djpress:content_detail' post.slug %}">Read more</a></p>
<p><a href="{% url 'djpress:post_detail' post.slug %}">Read more</a></p>
{% endif %}
</section>
<footer>
Expand Down
6 changes: 3 additions & 3 deletions djpress/templatetags/djpress_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ def get_categories() -> models.QuerySet[Category] | None:


@register.simple_tag
def get_recent_published_content() -> models.QuerySet[Category] | None:
def get_recent_published_posts() -> models.QuerySet[Category] | None:
"""Return recent published posts from the cache."""
return Post.post_objects.get_recent_published_content()
return Post.post_objects.get_recent_published_posts()


@register.simple_tag
def get_single_published_content(slug: str) -> Post | None:
def get_single_published_post(slug: str) -> Post | None:
"""Return a single published post by slug."""
return Post.post_objects.get_published_post_by_slug(slug)

Expand Down
Loading

0 comments on commit e3580b6

Please sign in to comment.