Skip to content

Commit

Permalink
Merge pull request #7 from stuartmaxwell/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartmaxwell authored Jun 3, 2024
2 parents aea007e + e32b6f6 commit f641d3e
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 32 deletions.
1 change: 1 addition & 0 deletions djpress/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
RECENT_PUBLISHED_POSTS_COUNT: int = 20
MARKDOWN_EXTENSIONS: list = ["fenced_code", "codehilite", "tables"]
BLOG_TITLE: str = "My DJ Press Blog"
BLOG_DESCRIPTION: str = ""

# DJPress URL settings
CATEGORY_PATH_ENABLED: bool = True
Expand Down
9 changes: 5 additions & 4 deletions djpress/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
from django.contrib.syndication.views import Feed
from django.urls import reverse

from djpress.conf import settings
from djpress.models import Post

if TYPE_CHECKING:
if TYPE_CHECKING: # pragma: no cover
from django.db import models


class PostFeed(Feed):
"""RSS feed for blog posts."""

title = "stuartm.nz"
link = "/rss"
description = "stuartm.nz updates"
title = settings.BLOG_TITLE
link = f"/{settings.RSS_PATH}/"
description = settings.BLOG_DESCRIPTION

def items(self: "PostFeed") -> "models.QuerySet":
"""Return the most recent posts."""
Expand Down
29 changes: 23 additions & 6 deletions djpress/models/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,7 @@ def _get_cached_recent_published_posts(self: "PostsManager") -> models.QuerySet:
.prefetch_related("categories", "author")
)

future_posts = queryset.filter(date__gt=timezone.now())
if future_posts.exists():
future_post = future_posts[0]
timeout = (future_post.date - timezone.now()).total_seconds()
else:
timeout = None
timeout = self._get_cache_timeout(queryset)

queryset = queryset.filter(date__lte=timezone.now())[
: settings.RECENT_PUBLISHED_POSTS_COUNT
Expand All @@ -85,6 +80,28 @@ def _get_cached_recent_published_posts(self: "PostsManager") -> models.QuerySet:

return queryset

def _get_cache_timeout(
self: "PostsManager",
queryset: models.QuerySet,
) -> int | None:
"""Return the timeout for the cache.
If there are any future posts, we calculate the seconds until that post.
Args:
queryset: The queryset of published posts.
Returns:
int | None: The number of seconds until the next future post, or None if
there are no future posts.
"""
future_posts = queryset.filter(date__gt=timezone.now())
if future_posts.exists():
future_post = future_posts[0]
return (future_post.date - timezone.now()).total_seconds()

return None

def get_published_post_by_slug(
self: "PostsManager",
slug: str,
Expand Down
33 changes: 24 additions & 9 deletions djpress/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging

from django.contrib.auth.models import User
from django.http import Http404, HttpRequest, HttpResponse
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
from django.utils.timezone import datetime

Expand Down Expand Up @@ -40,11 +40,11 @@ def archives_posts(
day (str): The day.
"""
try:
test_dates(year, month, day)
validate_date(year, month, day)

except ValueError as exc:
except ValueError:
msg = "Invalid date"
raise Http404(msg) from exc
return HttpResponseBadRequest(msg)

posts = Post.post_objects._get_published_posts() # noqa: SLF001

Expand Down Expand Up @@ -72,21 +72,36 @@ def archives_posts(
)


def test_dates(year: str, month: str | None, day: str | None) -> None:
def validate_date(year: str, month: str, day: str) -> None:
"""Test the date values.
Convert the date values to integers and test if they are valid dates.
The regex that gets the date values checks for the following:
- year: four digits
- month: two digits
- day: two digits
Args:
year (str): The year.
month (str | None): The month.
day (str | None): The day.
Raises:
ValueError: If the date is invalid.
Returns:
None
"""
try:
int_year = int(year)
int_month = int(month) if month else None
int_day = int(day) if day else None
int_year: int = int(year)
int_month: int | None = int(month) if month else None
int_day: int | None = int(day) if day else None

if int_month == 0 or int_day == 0:
msg = "Invalid date"
raise ValueError(msg)

try:
if int_month and int_day:
datetime(int_year, int_month, int_day)

Expand Down
1 change: 1 addition & 0 deletions example/config/settings_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

# Changing these settings will affect lots of tests!
BLOG_TITLE = "My Test DJ Press Blog"
BLOG_DESCRIPTION = "This is a test blog."
AUTHOR_PATH_ENABLED = True
AUTHOR_PATH = "test-url-author"
CATEGORY_PATH_ENABLED = True
Expand Down
3 changes: 1 addition & 2 deletions tests/test_conf_urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
from importlib import reload

from django.urls import reverse, NoReverseMatch
from django.urls import reverse

from djpress.conf import settings

Expand Down
12 changes: 6 additions & 6 deletions tests/test_feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ def test_latest_posts_feed(client, user):
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"

feed = response.content.decode("utf-8")
assert "<title>stuartm.nz</title>" in feed
assert "<link>http://testserver/rss</link>" in feed
assert "<description>stuartm.nz updates</description>" in feed
assert f"<title>{settings.BLOG_TITLE}</title>" in feed
assert f"<link>http://testserver/{settings.RSS_PATH}/</link>" in feed
assert f"<description>{settings.BLOG_DESCRIPTION}</description>" in feed
assert "<item>" in feed
assert "<title>Post 1</title>" in feed
assert "<description>&lt;p&gt;Content of post 1.&lt;/p&gt;</description>" in feed
Expand Down Expand Up @@ -60,9 +60,9 @@ def test_truncated_posts_feed(client, user):
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"

feed = response.content.decode("utf-8")
assert "<title>stuartm.nz</title>" in feed
assert "<link>http://testserver/rss</link>" in feed
assert "<description>stuartm.nz updates</description>" in feed
assert f"<title>{settings.BLOG_TITLE}</title>" in feed
assert f"<link>http://testserver/{settings.RSS_PATH}/</link>" in feed
assert f"<description>{settings.BLOG_DESCRIPTION}</description>" in feed
assert "<item>" in feed
assert "<title>Post 1</title>" in feed
assert "Truncated content" not in feed
Expand Down
31 changes: 31 additions & 0 deletions tests/test_models_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,34 @@ def test_get_recent_published_posts(user):
# Assert that the correct posts are returned
assert list(recent_posts) == [post3, post2]
assert not post1 in recent_posts


@pytest.mark.django_db
def test_get_published_post_by_path(user):
"""Test that the get_published_post_by_path method returns the correct post."""

# Confirm settings are set according to settings_testing.py
assert settings.POST_PREFIX == "test-posts"

# Create a post
post = Post.objects.create(title="Test Post", status="published", author=user)

# Test case 1: POST_PREFIX is set and path starts with POST_PREFIX
post_path = f"test-posts/{post.slug}"
assert post == Post.post_objects.get_published_post_by_path(post_path)

# Test case 2: POST_PREFIX is set but path does not start with POST_PREFIX
post_path = f"/incorrect-path/{post.slug}"
# Should raise a ValueError
with pytest.raises(ValueError):
Post.post_objects.get_published_post_by_path(post_path)

# Test case 3: POST_PREFIX is not set but path starts with POST_PREFIX
settings.set("POST_PREFIX", "")
post_path = f"test-posts/non-existent-slug"
# Should raise a ValueError
with pytest.raises(ValueError):
Post.post_objects.get_published_post_by_path(post_path)

# Set back to default
settings.set("POST_PREFIX", "test-posts")
25 changes: 20 additions & 5 deletions tests/test_templatetags_djpress_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,7 @@ def test_get_categories(category1, category2, category3):
assert category2 in djpress_categories
assert category3 in djpress_categories

# The following is only here to avoid type errors
if not categories or not djpress_categories:
return None

assert list(djpress_categories) == list(categories)
assert list(djpress_categories) == list(categories) # type: ignore


@pytest.mark.django_db
Expand Down Expand Up @@ -192,6 +188,25 @@ def test_post_author_link(create_test_post):
assert djpress_tags.post_author_link(context) == expected_output


@pytest.mark.django_db
def test_post_author_link_author_path_disabled(create_test_post):
context = Context({"post": create_test_post})

# Confirm settings are set according to settings_testing.py
assert settings.AUTHOR_PATH_ENABLED is True
assert settings.AUTHOR_PATH == "test-url-author"

settings.set("AUTHOR_PATH_ENABLED", False)

author = create_test_post.author

expected_output = f'<span rel="author">{get_author_display_name(author)}</span>'
assert djpress_tags.post_author_link(context) == expected_output

# Set back to defaults
settings.set("AUTHOR_PATH_ENABLED", True)


@pytest.mark.django_db
def test_post_author_link_with_author_path_with_one_link_class(create_test_post):
context = Context({"post": create_test_post})
Expand Down
80 changes: 80 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.utils import timezone

from djpress.models import Category, Post
from djpress.views import validate_date


@pytest.fixture
Expand Down Expand Up @@ -62,6 +63,33 @@ def test_post_detail_not_exist(client):
assert response.status_code == 404


@pytest.mark.django_db
def test_author_with_no_posts_view(client, user):
url = reverse("djpress:author_posts", args=[user.username])
response = client.get(url)
assert response.status_code == 200
assert "author" in response.context
assert "posts" in response.context
assert b"No posts available" in response.content


@pytest.mark.django_db
def test_author_with_posts_view(client, create_test_post):
url = reverse("djpress:author_posts", args=[create_test_post.author.username])
response = client.get(url)
assert response.status_code == 200
assert "author" in response.context
assert "posts" in response.context
assert not b"No posts available" in response.content


@pytest.mark.django_db
def test_author_with_invalid_author(client):
url = reverse("djpress:author_posts", args=["non-existent-author"])
response = client.get(url)
assert response.status_code == 404


@pytest.mark.django_db
def test_category_with_no_posts_view(client, category):
url = reverse("djpress:category_posts", args=[category.slug])
Expand All @@ -82,6 +110,36 @@ def test_category_with_posts_view(client, create_test_post, category):
assert not b"No posts available" in response.content


@pytest.mark.django_db
def test_category_with_invalid_category(client):
url = reverse("djpress:category_posts", args=["non-existent-category"])
response = client.get(url)
assert response.status_code == 404


def test_validate_date():
# Test 1 - invalid year
year = "0000"
with pytest.raises(ValueError):
validate_date(year, "", "")

# Test 2 - invalid months
month = "00"
with pytest.raises(ValueError):
validate_date("2024", month, "")
month = "13"
with pytest.raises(ValueError):
validate_date("2024", month, "")

# Test 3 - invalid days
day = "00"
with pytest.raises(ValueError):
validate_date("2024", "1", day)
day = "32"
with pytest.raises(ValueError):
validate_date("2024", "1", day)


@pytest.mark.django_db
def test_date_archives_year(client, create_test_post):
url = reverse("djpress:archives_posts", kwargs={"year": "2024"})
Expand All @@ -91,6 +149,12 @@ def test_date_archives_year(client, create_test_post):
assert create_test_post.title.encode() in response.content


@pytest.mark.django_db
def test_date_archives_year_invalid_year(client):
response = client.get("/archives/0000/")
assert response.status_code == 400


@pytest.mark.django_db
def test_date_archives_year_no_posts(client, create_test_post):
url = reverse("djpress:archives_posts", kwargs={"year": "2023"})
Expand All @@ -110,6 +174,14 @@ def test_date_archives_month(client, create_test_post):
assert create_test_post.title.encode() in response.content


@pytest.mark.django_db
def test_date_archives_month_invalid_month(client):
response1 = client.get("/archives/2024/00/")
assert response1.status_code == 400
response2 = client.get("/archives/2024/13/")
assert response2.status_code == 400


@pytest.mark.django_db
def test_date_archives_month_no_posts(client, create_test_post):
url = reverse("djpress:archives_posts", kwargs={"year": "2024", "month": "02"})
Expand All @@ -131,6 +203,14 @@ def test_date_archives_day(client, create_test_post):
assert create_test_post.title.encode() in response.content


@pytest.mark.django_db
def test_date_archives_day_invalid_day(client):
response1 = client.get("/archives/2024/01/00/")
assert response1.status_code == 400
response2 = client.get("/archives/2024/01/32/")
assert response2.status_code == 400


@pytest.mark.django_db
def test_date_archives_day_no_posts(client, create_test_post):
url = reverse(
Expand Down

0 comments on commit f641d3e

Please sign in to comment.