Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test-paths #90

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "djpress"
version = "0.14.0"
version = "0.14.1"
description = "A blog application for Django sites, inspired by classic WordPress."
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -97,7 +97,7 @@ include = ["src/djpress/*"]
omit = ["*/tests/*", "*/migrations/*"]

[tool.bumpver]
current_version = "0.14.0"
current_version = "0.14.1"
version_pattern = "MAJOR.MINOR.PATCH"
commit_message = "👍 bump version {old_version} -> {new_version}"
commit = true
Expand Down
2 changes: 1 addition & 1 deletion src/djpress/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""djpress module."""

__version__ = "0.14.0"
__version__ = "0.14.1"
58 changes: 41 additions & 17 deletions src/djpress/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,24 @@ def dispatcher(request: HttpRequest, path: str) -> HttpResponse | None:
if djpress_settings.RSS_ENABLED and (path in (djpress_settings.RSS_PATH, f"{djpress_settings.RSS_PATH}/")):
return PostFeed()(request)

# 2. Check if it matches the single post regex
# 2. Check if it matches the single post or the archives regex
post_match = re.fullmatch(get_path_regex("post"), path)
archives_match = re.fullmatch(get_path_regex("archives"), path)
if post_match:
# Does the post exist?
post_groups = post_match.groupdict()
return single_post(request, **post_groups)
post = get_post(**post_groups)

# If so, return the single post view
if post:
return single_post(request, post)

# If it doesn't look like an archive post, then it's a 404 - otherwise continue to the next step
if not archives_match:
msg = "Post not found"
raise Http404(msg)

# 3. Check if it matches the archives regex
archives_match = re.fullmatch(get_path_regex("archives"), path)
if archives_match:
archives_groups = archives_match.groupdict()
return archive_posts(request, **archives_groups)
Expand Down Expand Up @@ -221,38 +231,52 @@ def author_posts(request: HttpRequest, author: str) -> HttpResponse:
)


def single_post(
request: HttpRequest,
def get_post(
slug: str,
year: str | None = None,
month: str | None = None,
day: str | None = None,
) -> HttpResponse:
"""View for a single post.
) -> None | Post:
"""Try to get a post by slug and date parts.

Args:
request (HttpRequest): The request object.
slug (str): The post slug.
year (str | None): The year.
month (str | None): The month.
day (str | None): The day.

Returns:
HttpResponse: The response.

Context:
post (Post): The post object.
None | Post: The post object, or None if not found.
"""
try:
date_parts = validate_date_parts(year=year, month=month, day=day)
post = Post.post_objects.get_published_post_by_slug(slug=slug, **date_parts)
context: dict = {"post": post}
except (PostNotFoundError, ValueError) as exc:
return Post.post_objects.get_published_post_by_slug(slug=slug, **date_parts)
except (PostNotFoundError, ValueError):
# A PageNotFoundError means we were able to parse the path, but the page was not found
# A ValueError means we were not able to parse the path
msg = "Post not found"
raise Http404(msg) from exc
return None


def single_post(
request: HttpRequest,
post: Post,
) -> HttpResponse:
"""View for a single post.

The single_post view is different to the others in that it doesn't look for a post since this is done in the
dispatcher function. This view is only called if a post is found.

Args:
request (HttpRequest): The request object.
post (Post): The post object.

Returns:
HttpResponse: The response.

Context:
post (Post): The post object.
"""
context: dict = {"post": post}
template: str = get_template_name(view_name="single_post")

return render(
Expand Down
133 changes: 130 additions & 3 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.urls import reverse
from django.utils import timezone

from djpress.models import Post
from djpress.url_utils import get_archives_url, get_author_url, get_category_url


Expand Down Expand Up @@ -167,8 +168,21 @@ def test_category_with_category_enabled_false(client, settings):


@pytest.mark.django_db
def test_date_archives_year(client, test_post1):
def test_date_archives_year(client, settings, test_post1):
assert settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] == "test-url-archives"
url = get_archives_url(test_post1.date.year)
assert url == f"/test-url-archives/{test_post1.date.year}/"
response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)
assert "Test Post1" in response.content.decode()

settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] = ""
url = get_archives_url(test_post1.date.year)
assert url == f"/{test_post1.date.year}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
Expand Down Expand Up @@ -196,8 +210,21 @@ def test_date_archives_year_no_posts(client, test_post1):


@pytest.mark.django_db
def test_date_archives_month(client, test_post1):
def test_date_archives_month(client, settings, test_post1):
assert settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] == "test-url-archives"
url = get_archives_url(test_post1.date.year, test_post1.date.month)
assert url == f"/test-url-archives/{test_post1.date.year}/{test_post1.date.month}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)

settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] = ""
url = get_archives_url(test_post1.date.year, test_post1.date.month)
assert url == f"/{test_post1.date.year}/{test_post1.date.month}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
Expand Down Expand Up @@ -226,12 +253,112 @@ def test_date_archives_month_no_posts(client, test_post1):


@pytest.mark.django_db
def test_date_archives_day(client, test_post1):
def test_date_archives_day(client, settings, test_post1):
assert settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] == "test-url-archives"
url = get_archives_url(test_post1.date.year, test_post1.date.month, test_post1.date.day)
assert url == f"/test-url-archives/{test_post1.date.year}/{test_post1.date.month}/{test_post1.date.day}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)

settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] = ""
url = get_archives_url(test_post1.date.year, test_post1.date.month, test_post1.date.day)
assert url == f"/{test_post1.date.year}/{test_post1.date.month}/{test_post1.date.day}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)

assert settings.DJPRESS_SETTINGS["POST_PREFIX"] == "test-posts"
settings.DJPRESS_SETTINGS["POST_PREFIX"] = "{{ year }}/{{ month }}/{{ day }}"
url = get_archives_url(test_post1.date.year, test_post1.date.month, test_post1.date.day)
assert url == f"/{test_post1.date.year}/{test_post1.date.month}/{test_post1.date.day}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)


@pytest.mark.django_db
def test_conflict_day_archives_and_single_post(client, settings, test_post1):
"""Change the POST_PREFIX to match the same format as the day archives.

For example, with POST_PREFIX = "{{ year }}/{{ month }}", either of the following two URLs could be valid posts:

- /2024/01/31/ - This could be a day archive, or a post with the slug "31" in the year 2024 and month 01.
- /2024/01/test-post1/

The first URL will try to get a post and if that doesn't exist, it will try to get a day archive, but only if the
POST_PREFIX matches the day archive format.
"""
settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] = ""
settings.DJPRESS_SETTINGS["POST_PREFIX"] = "{{ year }}/{{ month }}"
url = get_archives_url(test_post1.date.year, test_post1.date.month, test_post1.date.day)
assert url == f"/{test_post1.date.year}/{test_post1.date.month}/{test_post1.date.day}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)


@pytest.mark.django_db
def test_conflict_month_archives_and_single_post(client, settings, test_post1):
"""Similar concept to test_conflict_day_archives_and_single_post.

With POST_PREFIX = "{{ year }}", either of the following two URLs could be valid posts:

- /2024/12/ - This could be a month archive, or a post with the slug "12" in the year 2024.
- /2024/test-post1/
"""
settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] = ""
settings.DJPRESS_SETTINGS["POST_PREFIX"] = "{{ year }}"
url = get_archives_url(test_post1.date.year, test_post1.date.month)
assert url == f"/{test_post1.date.year}/{test_post1.date.month}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)


@pytest.mark.django_db
def test_conflict_year_archives_and_single_post(client, settings, test_post1):
"""Similar concept to test_conflict_day_archives_and_single_post and test_conflict_month_archives_and_single_post.

With POST_PREFIX = "", either of the following two URLs could be valid posts:

- /2024/ - This could be a year archive, or a post with the slug "2024".
- /test-post1/
"""
settings.DJPRESS_SETTINGS["ARCHIVE_PREFIX"] = ""
settings.DJPRESS_SETTINGS["POST_PREFIX"] = ""
url = get_archives_url(test_post1.date.year)
assert url == f"/{test_post1.date.year}/"

response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "posts" in response.context
assert isinstance(response.context["posts"], Iterable)

# If the post slug is 2024, then the post will be returned.
test_post1.slug = str(test_post1.date.year)
test_post1.save()
response = client.get(url)
assert response.status_code == 200
assert test_post1.title.encode() in response.content
assert "post" in response.context
assert isinstance(response.context["post"], Post)


@pytest.mark.django_db
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.