diff --git a/djpress/migrations/0003_post_menu_order.py b/djpress/migrations/0003_post_menu_order.py new file mode 100644 index 0000000..d490c68 --- /dev/null +++ b/djpress/migrations/0003_post_menu_order.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2024-06-12 11:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("djpress", "0002_rename_content_type_post_post_type"), + ] + + operations = [ + migrations.AddField( + model_name="post", + name="menu_order", + field=models.IntegerField(default=0), + ), + ] diff --git a/djpress/models/post.py b/djpress/models/post.py index f1986b0..b632f09 100644 --- a/djpress/models/post.py +++ b/djpress/models/post.py @@ -19,13 +19,80 @@ PUBLISHED_POSTS_CACHE_KEY = "published_posts" +class PagesManager(models.Manager): + """Page custom manager.""" + + def get_queryset(self: "PagesManager") -> models.QuerySet: + """Return the queryset for pages.""" + return super().get_queryset().filter(post_type="page").order_by("-date") + + def get_published_pages(self: "PagesManager") -> models.QuerySet: + """Return all published pages. + + For a page to be considered published, it must meet the following requirements: + - The status must be "published". + - The date must be less than or equal to the current date/time. + """ + return self.get_queryset().filter( + status="published", + date__lte=timezone.now(), + ) + + def get_published_page_by_slug( + self: "PagesManager", + slug: str, + ) -> "Post": + """Return a single published page. + + Args: + slug (str): The slug of the page. + + Returns: + Post: The published page. + """ + try: + page: Post = self.get_published_pages().get(slug=slug) + except Post.DoesNotExist as exc: + msg = "Page not found" + raise ValueError(msg) from exc + + return page + + def get_published_page_by_path( + self: "PagesManager", + path: str, + ) -> "Post": + """Return a single published post from a path. + + For now, we'll only allow a top level path. + """ + # Check for a single item in the path + if path.count("/") > 0: + msg = "Invalid path" + raise ValueError(msg) + + return self.get_published_page_by_slug(path) + + class PostsManager(models.Manager): """Post custom manager.""" def get_queryset(self: "PostsManager") -> models.QuerySet: - """Return the queryset for published posts.""" + """Return the queryset for posts.""" return super().get_queryset().filter(post_type="post").order_by("-date") + 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: + - The status must be "published". + - The date must be less than or equal to the current date/time. + """ + return self.get_queryset().filter( + status="published", + date__lte=timezone.now(), + ) + def get_recent_published_posts(self: "PostsManager") -> models.QuerySet: """Return recent published posts. @@ -41,18 +108,6 @@ def get_recent_published_posts(self: "PostsManager") -> models.QuerySet: : settings.RECENT_PUBLISHED_POSTS_COUNT ] - 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: - - The status must be "published". - - The date must be less than or equal to the current date/time. - """ - return self.get_queryset().filter( - status="published", - date__lte=timezone.now(), - ) - def _get_cached_recent_published_posts(self: "PostsManager") -> models.QuerySet: """Return the cached recent published posts queryset. @@ -221,10 +276,12 @@ class Post(models.Model): default="post", ) categories = models.ManyToManyField(Category, blank=True) + menu_order = models.IntegerField(default=0) # Managers objects = models.Manager() post_objects: "PostsManager" = PostsManager() + page_objects: "PagesManager" = PagesManager() class Meta: """Meta options for the Post model.""" diff --git a/djpress/static/djpress/css/style.css b/djpress/static/djpress/css/style.css index a5dc501..de1f5c5 100644 --- a/djpress/static/djpress/css/style.css +++ b/djpress/static/djpress/css/style.css @@ -81,7 +81,7 @@ body > header ul li a:hover { main { padding: 2em; max-width: 992px; - margin: 0 auto; + margin: 0 auto 3em; } main > h1 { diff --git a/djpress/static/djpress/css/style.min.css b/djpress/static/djpress/css/style.min.css index 4989e2b..1ab6a79 100644 --- a/djpress/static/djpress/css/style.min.css +++ b/djpress/static/djpress/css/style.min.css @@ -1 +1 @@ -:root{--dark0:#2e3440;--dark1:#3b4252;--dark3:#4c566a;--light4:#d8dee9;--light5:#e5e9f0;--light6:#eceff4;--primary7:#8fbcbb;--primary8:#88c0d0;--secondary9:#81a1c1;--secondary10:#5e81ac;--danger11:#bf616a;--error12:#d08770;--warning13:#ebcb8b;--success14:#a3be8c;--info15:#b48ead}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Ubuntu,sans-serif;color:var(--dark0);margin:0;padding:0;box-sizing:border-box;background-color:var(--light5)}body>header{background-color:var(--dark1);padding:1rem;padding-left:calc((100% - 992px)/ 2);padding-right:calc((100% - 992px)/ 2);display:flex;align-items:center;justify-content:space-between}body>header h1{margin:0;font-size:1.5rem}body>header h1 a{color:var(--light6);text-decoration:none;padding:.5rem 1rem}body>header ul{list-style:none;margin:0;padding:0;display:flex}body>header ul li{margin-left:1rem}body>header ul li a{color:var(--light6);text-decoration:none;padding:.5rem 1rem;border-radius:.25rem;transition:background-color .2s}body>header ul li a:hover{background-color:var(--dark3);color:var(--primary8)}main{padding:2em;max-width:992px;margin:0 auto}main>h1{font-size:2rem;margin:0 .5em 1em;color:var(--dark3)}article{margin:0;margin-bottom:2em;padding:2em;border:1px solid var(--light4);border-radius:.5em;background-color:var(--light6);box-shadow:0 2px 4px hsl(0deg 0% 0% / .1)}article>header{margin-bottom:1rem;border-bottom:1px solid var(--light4);padding-bottom:.5rem}article>header h1{font-size:1.75rem;margin:0}article>header h1 a{text-decoration:none;color:var(--secondary10);transition:color .2s}article>header h1 a:hover{color:var(--primary8)}article>header p{margin:.5em 0;font-size:.875rem;color:var(--dark3)}article>section{margin-bottom:1em}article>footer{border-top:1px solid var(--light4);padding-top:.5em;margin-top:1em}article>footer p{margin:0;color:#555}.badge{background-color:var(--primary8);color:var(--dark0);padding:.375em .5em;border-radius:.75em;font-size:.875rem;margin-right:.5em;font-weight:700;text-decoration:none}.badge:hover{background-color:var(--secondary10);color:var(--light4)}body>footer{background-color:var(--dark1);padding:1rem;padding-left:calc((100% - 992px)/ 2);padding-right:calc((100% - 992px)/ 2);display:flex;align-items:center;justify-content:space-between;position:fixed;bottom:0;width:100%;color:var(--light6)}body>footer p{font-size:1rem;margin:0}body>footer a{color:var(--light6)}body>footer a:hover{color:var(--primary8)} \ No newline at end of file +:root{--dark0:#2e3440;--dark1:#3b4252;--dark2:#434c5e;--dark3:#4c566a;--light4:#d8dee9;--light5:#e5e9f0;--light6:#eceff4;--primary7:#8fbcbb;--primary8:#88c0d0;--secondary9:#81a1c1;--tertiary10:#5e81ac;--error11:#bf616a;--danger12:#d08770;--warning13:#ebcb8b;--success14:#a3be8c;--info15:#b48ead}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Ubuntu,sans-serif;color:var(--dark0);margin:0;padding:0;box-sizing:border-box;background-color:var(--light5)}body>header{background-color:var(--dark1);padding:1rem;padding-left:calc((100% - 992px)/ 2);padding-right:calc((100% - 992px)/ 2);display:flex;align-items:center;justify-content:space-between}body>header h1{margin:0;font-size:1.5rem}body>header h1 a{color:var(--light6);text-decoration:none;padding:.5rem 1rem}body>header ul{list-style:none;margin:0;padding:0;display:flex}body>header ul li{margin-left:1rem}body>header ul li a{color:var(--light6);text-decoration:none;padding:.5rem 1rem;border-radius:.25rem;transition:background-color .2s}body>header ul li a:hover{background-color:var(--dark3);color:var(--primary8)}main{padding:2em;max-width:992px;margin:0 auto 3em}main>h1{font-size:2rem;margin:0 .5em 1em;color:var(--dark3)}article{margin:0;margin-bottom:2em;padding:2em;border:1px solid var(--light4);border-radius:.5em;background-color:var(--light6);box-shadow:0 2px 4px hsl(0deg 0% 0% / .1)}article>header{margin-bottom:1rem;border-bottom:1px solid var(--light4);padding-bottom:.5rem}article>header h1{font-size:1.75rem;margin:0}article>header h1 a{text-decoration:none;color:var(--tertiary10);transition:color .2s}article>header h1 a:hover{color:var(--primary8)}article>header p{margin:.5em 0;font-size:.875rem;color:var(--dark3)}article>section{margin-bottom:1em}article>footer{border-top:1px solid var(--light4);padding-top:.5em;margin-top:1em}article>footer p{margin:0;color:#555}.badge{background-color:var(--primary8);color:var(--dark0);padding:.375em .5em;border-radius:.75em;font-size:.875rem;margin-right:.5em;font-weight:700;text-decoration:none}.badge:hover{background-color:var(--tertiary10);color:var(--light4)}body>footer{background-color:var(--dark1);padding:1rem;padding-left:calc((100% - 992px)/ 2);padding-right:calc((100% - 992px)/ 2);display:flex;align-items:center;justify-content:space-between;position:fixed;bottom:0;width:100%;color:var(--light6)}body>footer p{font-size:1rem;margin:0}body>footer a{color:var(--light6)}body>footer a:hover{color:var(--primary8)} \ No newline at end of file diff --git a/djpress/templates/djpress/base.html b/djpress/templates/djpress/base.html new file mode 100644 index 0000000..b3a81da --- /dev/null +++ b/djpress/templates/djpress/base.html @@ -0,0 +1,32 @@ +{% load static %} +{% load djpress_tags %} + + + + + + + {% blog_page_title post_text="| " %}{% blog_title %} + + + +
+

{% blog_title_link %}

+ {% blog_pages %} +
+ +
+ + {% block main %}{% endblock main %} + +
+ + + + diff --git a/djpress/templates/djpress/index.html b/djpress/templates/djpress/index.html index 4e59b47..f8924ad 100644 --- a/djpress/templates/djpress/index.html +++ b/djpress/templates/djpress/index.html @@ -1,60 +1,49 @@ -{% load static %} +{% extends "djpress/base.html" %} {% load djpress_tags %} - - - - - - {% blog_title %} - - - -
-

{% blog_title_link %}

- {% blog_categories %} -
- -
- - {% have_posts as posts %} - - {% category_name "h1" pre_text="View Posts in the " post_text=" Category" %} - {% author_name "h1" pre_text="View Posts by " %} - - {% for post in posts %} - -
-
-

{% post_title_link %}

-

By {% post_author_link %}. {% post_date_link %}

-
-
- {% post_content %} -
-
-

Categories: {% post_categories_link "span" "badge" %}

-
-
- - {% empty %} - -

No posts available.

- - {% endfor %} - - {% posts_nav_links %} - -


- -
- - - - +{% block main %} + + {% category_name "h1" pre_text="View Posts in the " post_text=" Category" %} + {% author_name "h1" pre_text="View Posts by " %} + + {% if post %} + +
+
+

{% post_title_link %}

+

By {% post_author_link %}. {% post_date_link %}

+
+
+ {% post_content %} +
+ +
+ + {% else %} + + {% for post in posts %} + +
+
+

{% post_title_link %}

+

By {% post_author_link %}. {% post_date_link %}

+
+
+ {% post_content %} +
+
+ + {% empty %} + +

No posts available.

+ + {% endfor %} + + {% endif %} + + + {% posts_nav_links %} + +{% endblock main %} diff --git a/djpress/templates/djpress/single.html b/djpress/templates/djpress/single.html new file mode 100644 index 0000000..66572d8 --- /dev/null +++ b/djpress/templates/djpress/single.html @@ -0,0 +1,21 @@ +{% extends "djpress/base.html" %} +{% load djpress_tags %} + +{% block main %} + +
+
+

{% post_title_link %}

+

By {% post_author_link %}. {% post_date_link %}

+
+
+ {% post_content %} +
+ +
+ + {% posts_nav_links %} + +{% endblock main %} diff --git a/djpress/templatetags/djpress_tags.py b/djpress/templatetags/djpress_tags.py index 26f5715..7fd67ff 100644 --- a/djpress/templatetags/djpress_tags.py +++ b/djpress/templatetags/djpress_tags.py @@ -13,6 +13,7 @@ from djpress.templatetags.helpers import ( categories_html, category_link, + get_page_link, post_read_more_link, ) from djpress.utils import get_author_display_name @@ -50,6 +51,18 @@ def blog_title_link(link_class: str = "") -> str: return mark_safe(ouptut) +@register.simple_tag +def get_pages() -> models.QuerySet[Post]: + """Return all pages as a queryset. + + Returns: + models.QuerySet[Post]: All pages. + """ + return ( + Post.page_objects.get_published_pages().order_by("menu_order").order_by("title") + ) + + @register.simple_tag def get_categories() -> models.QuerySet[Category] | None: """Return all categories as a queryset. @@ -83,13 +96,98 @@ def blog_categories( return mark_safe(categories_html(categories, outer, outer_class, link_class)) +@register.simple_tag +def blog_pages( + outer: str = "ul", + outer_class: str = "", + link_class: str = "", +) -> str: + """Return the pages of the blog. + + Args: + outer: The outer HTML tag for the pages. + outer_class: The CSS class(es) for the outer tag. + link_class: The CSS class(es) for the link. + + Returns: + str: The pages of the blog. + """ + pages: models.QuerySet[Post] = get_pages() + + if not pages: + return "" + + output = "" + + outer_class_html = f' class="{outer_class}"' if outer_class else "" + + if outer == "ul": + output += f"" + for page in pages: + output += f"
  • {get_page_link(page=page, link_class=link_class)}
  • " + output += "" + + if outer == "div": + output += f"" + for page in pages: + output += f"{get_page_link(page=page, link_class=link_class)}, " + output = output[:-2] # Remove the trailing comma and space + output += "" + + if outer == "span": + output += f"" + for page in pages: + output += f"{get_page_link(page=page, link_class=link_class)}, " + output = output[:-2] # Remove the trailing comma and space + output += "" + + return mark_safe(output) + + +@register.simple_tag(takes_context=True) +def blog_page_title( + context: Context, + pre_text: str = "", + post_text: str = "", +) -> str: + """Return the page title. + + Args: + context: The context. + pre_text: The text to prepend to the page title. + post_text: The text to append to the page title. + + Returns: + str: The page title. + """ + category: Category | None = context.get("category") + author: User | None = context.get("author") + post: Post | None = context.get("post") + + if category: + page_title = category.name + + elif author: + page_title = get_author_display_name(author) + + elif post: + page_title = post.title + else: + page_title = "" + + if page_title: + page_title = f"{pre_text}{page_title}{post_text}" + + return page_title + + @register.simple_tag(takes_context=True) def have_posts(context: Context) -> list[Post | None] | Page: """Return the posts in the context. - If there's a `_post` in the context, then we return a list with that post. + If there's a `post` in the context, then we return a list with that post. - If there's a `_posts` in the context, then we return the posts. The `_posts` should + If there's a `posts` in the context, then we return the posts. The `posts` should be a Page object. Args: @@ -98,8 +196,8 @@ def have_posts(context: Context) -> list[Post | None] | Page: Returns: list[Post]: The posts in the context. """ - post: Post | None = context.get("_post") - posts: Page | None = context.get("_posts") + post: Post | None = context.get("post") + posts: Page | None = context.get("posts") if post: return [post] @@ -130,6 +228,13 @@ def post_title(context: Context) -> str: def post_title_link(context: Context, link_class: str = "") -> str: """Return the title link for a post. + If the post is part of a posts collection, then return the title and a link to the + post. + + If the post is a single post, then return just the title of the post with no link. + + Otherwise return and empty string. + Args: context: The context. link_class: The CSS class(es) for the link. @@ -138,23 +243,24 @@ def post_title_link(context: Context, link_class: str = "") -> str: str: The title link for the post. """ post: Post | None = context.get("post") - if not post: - return "" + posts: Page | None = context.get("posts") - _post: Post | None = context.get("_post") - if _post: - return post_title(context) + if posts and post: + post_url = reverse("djpress:post_detail", args=[post.permalink]) - post_url = reverse("djpress:post_detail", args=[post.permalink]) + link_class_html = f' class="{link_class}"' if link_class else "" - link_class_html = f' class="{link_class}"' if link_class else "" + output = ( + f'' + f"{post.title}" + ) - output = ( - f'' - f"{post.title}" - ) + return mark_safe(output) - return mark_safe(output) + if post: + return post_title(context) + + return "" @register.simple_tag(takes_context=True) @@ -312,9 +418,12 @@ def post_content( ) -> str: """Return the content of a post. - If there's no post in the context, then we return an empty string. If there are - multiple posts in the context, then we return the truncated content of the post with - the read more link. Otherwise, we return the full content of the post. + If the post is part of a posts collection, then we return the truncated content of + the post with the read more link. + + If the post is a single post, then return the full content of the post. + + Otherwise return and empty string. Args: context: The context. @@ -324,25 +433,22 @@ def post_content( Returns: str: The content of the post. """ - content: str = "" - - # Check if there's a post or _posts in the context. + # Check if there's a post or posts in the context. post: Post | None = context.get("post") - _posts: models.QuerySet[Post] | None = context.get("_posts") + posts: Page | None = context.get("posts") - # If there's no post, then we return an empty string. - if not post: - return content + content: str = "" - # If there are _posts, then we return the truncated content of the post. - if _posts: + if posts and post: content = mark_safe(post.truncated_content_markdown) if post.is_truncated: content += post_read_more_link(post, read_more_link_class, read_more_text) return mark_safe(content) - # If there - return mark_safe(post.content_markdown) + if post: + return mark_safe(post.content_markdown) + + return "" @register.simple_tag(takes_context=True) @@ -521,3 +627,40 @@ def posts_nav_links( f"{previous_output} {current_output} {next_output}" "", ) + + +@register.simple_tag() +def page_link( + page_slug: str, + outer: str = "div", + outer_class: str = "", + link_class: str = "", +) -> str: + """Return the link to a page. + + Args: + page_slug: The slug of the page. + outer: The outer HTML tag for the page link. + outer_class: The CSS class(es) for the outer tag. + link_class: The CSS class(es) for the link. + + Returns: + str: The link to the page. + """ + try: + page: Post | None = Post.page_objects.get_published_page_by_slug(page_slug) + except ValueError: + return "" + + output = get_page_link(page, link_class=link_class) + + if outer == "li": + return mark_safe(f"{output}") + + if outer == "span": + return mark_safe(f"{output}") + + if outer == "div": + return mark_safe(f"{output}") + + return mark_safe(output) diff --git a/djpress/templatetags/helpers.py b/djpress/templatetags/helpers.py index 86d46be..dc21489 100644 --- a/djpress/templatetags/helpers.py +++ b/djpress/templatetags/helpers.py @@ -54,13 +54,13 @@ def categories_html( def category_link(category: Category, link_class: str = "") -> str: - """Return the category link for a post. + """Return the category link. This is not intded to be used as a template tag. It is used by the other template tags in this module to generate the category links. Args: - category: The category of the post. + category: The category. link_class: The CSS class(es) for the link. """ category_url = reverse("djpress:category_posts", args=[category.slug]) @@ -73,6 +73,26 @@ def category_link(category: Category, link_class: str = "") -> str: ) +def get_page_link(page: Post, link_class: str = "") -> str: + """Return the page link. + + This is not intded to be used as a template tag. It is used by the other + template tags in this module to generate the page links. + + Args: + page: The page. + link_class: The CSS class(es) for the link. + """ + page_url = reverse("djpress:post_detail", args=[page.slug]) + + link_class_html = f' class="{link_class}"' if link_class else "" + + return ( + f'{ page.title }" + ) + + def post_read_more_link( post: Post, link_class: str = "", diff --git a/djpress/utils.py b/djpress/utils.py index d557b4c..fba13f8 100644 --- a/djpress/utils.py +++ b/djpress/utils.py @@ -2,6 +2,7 @@ import markdown from django.contrib.auth.models import User +from django.template.loader import TemplateDoesNotExist, select_template from django.utils.timezone import datetime from djpress.conf import settings @@ -80,3 +81,21 @@ def validate_date(year: str, month: str, day: str) -> None: except ValueError as exc: msg = "Invalid date" raise ValueError(msg) from exc + + +def get_template_name(templates: list[str]) -> str: + """Return the first template that exists. + + Args: + templates (list[str]): The list of template names. + + Returns: + str: The template name. + """ + try: + template = str(select_template(templates).template.name) + except TemplateDoesNotExist as exc: + msg = "No template found" + raise TemplateDoesNotExist(msg) from exc + + return template diff --git a/djpress/views.py b/djpress/views.py index f110f38..4ab7dfe 100644 --- a/djpress/views.py +++ b/djpress/views.py @@ -1,4 +1,8 @@ -"""djpress views file.""" +"""DJ Press views file. + +There are two type of views - those that return a collection of posts, and then a view +that returns a single post. +""" import logging @@ -9,7 +13,7 @@ from djpress.conf import settings from djpress.models import Category, Post -from djpress.utils import validate_date +from djpress.utils import get_template_name, validate_date logger = logging.getLogger(__name__) @@ -26,8 +30,15 @@ def index( HttpResponse: The response. Context: - _posts (Page): The published posts as a Page object. + posts (Page): The published posts as a Page object. """ + template_names: list[str] = [ + "djpress/home.html", + "djpress/index.html", + ] + + template: str = get_template_name(templates=template_names) + posts = Paginator( Post.post_objects.get_published_posts(), settings.RECENT_PUBLISHED_POSTS_COUNT, @@ -36,9 +47,9 @@ def index( page = posts.get_page(page_number) return render( - request, - "djpress/index.html", - {"_posts": page}, + request=request, + template_name=template, + context={"posts": page}, ) @@ -60,11 +71,16 @@ def archives_posts( HttpResponse: The response. Context: - _posts (Page): The published posts for the date as a Page object. + posts (Page): The published posts for the date as a Page object. """ + template_names: list[str] = [ + "djpress/archives.html", + "djpress/index.html", + ] + template: str = get_template_name(templates=template_names) + try: validate_date(year, month, day) - except ValueError: msg = "Invalid date" return HttpResponseBadRequest(msg) @@ -95,9 +111,9 @@ def archives_posts( page = posts.get_page(page_number) return render( - request, - "djpress/index.html", - {"_posts": page}, + request=request, + template_name=template, + context={"posts": page}, ) @@ -112,9 +128,15 @@ def category_posts(request: HttpRequest, slug: str) -> HttpResponse: HttpResponse: The response. Context: - _posts (Page): The published posts for the category as a Page object. + posts (Page): The published posts for the category as a Page object. category (Category): The category object. """ + template_names: list[str] = [ + "djpress/category.html", + "djpress/index.html", + ] + template: str = get_template_name(templates=template_names) + try: category: Category = Category.objects.get_category_by_slug(slug=slug) except ValueError as exc: @@ -129,9 +151,9 @@ def category_posts(request: HttpRequest, slug: str) -> HttpResponse: page = posts.get_page(page_number) return render( - request, - "djpress/index.html", - {"_posts": page, "category": category}, + request=request, + template_name=template, + context={"posts": page, "category": category}, ) @@ -146,9 +168,15 @@ def author_posts(request: HttpRequest, author: str) -> HttpResponse: HttpResponse: The response. Context: - _posts (Page): The published posts by the author as a Page object. + posts (Page): The published posts by the author as a Page object. author (User): The author as a User object. """ + template_names: list[str] = [ + "djpress/author.html", + "djpress/index.html", + ] + template: str = get_template_name(templates=template_names) + try: user: User = User.objects.get(username=author) except User.DoesNotExist as exc: @@ -163,9 +191,9 @@ def author_posts(request: HttpRequest, author: str) -> HttpResponse: page = posts.get_page(page_number) return render( - request, - "djpress/index.html", - {"_posts": page, "author": user}, + request=request, + template_name=template, + context={"posts": page, "author": user}, ) @@ -180,16 +208,27 @@ def post_detail(request: HttpRequest, path: str) -> HttpResponse: HttpResponse: The response. Context: - _post (Post): The post object. + post (Post): The post object. """ + template_names: list[str] = [ + "djpress/single.html", + "djpress/index.html", + ] + template: str = get_template_name(templates=template_names) + try: - post = Post.post_objects.get_published_post_by_path(path) - except ValueError as exc: - msg = "Post not found" - raise Http404(msg) from exc + page: Post = Post.page_objects.get_published_page_by_path(path) + context: dict = {"post": page} + except ValueError: + try: + post = Post.post_objects.get_published_post_by_path(path) + context: dict = {"post": post} + except ValueError as exc: + msg = "Post not found" + raise Http404(msg) from exc return render( - request, - "djpress/index.html", - {"_post": post}, + request=request, + context=context, + template_name=template, ) diff --git a/pyproject.toml b/pyproject.toml index 2280d67..bc9f48b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "djpress" -version = "0.5.1" +version = "0.6.0" authors = [{ name = "Stuart Maxwell", email = "stuart@amanzi.nz" }] description = "A blog application for Django sites, inspired by classic WordPress." readme = "README.md" diff --git a/requirements.txt b/requirements.txt index 70981d3..2d35d0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ pytest pytest-django +coverage build setuptools -r example/requirements.txt diff --git a/tests/test_models_post.py b/tests/test_models_post.py index 7383dfb..0ae1b7b 100644 --- a/tests/test_models_post.py +++ b/tests/test_models_post.py @@ -13,54 +13,87 @@ def user(): return User.objects.create_user(username="testuser", password="testpass") -@pytest.mark.django_db -def test_post_model(user): - category = Category.objects.create(name="Test Category", slug="test-category") - post = Post.post_objects.create( - title="Test Content", - slug="test-content", - content="This is a test content.", +@pytest.fixture +def category1(): + return Category.objects.create(name="Test Category1", slug="test-category1") + + +@pytest.fixture +def category2(): + return Category.objects.create(name="Test Category2", slug="test-category2") + + +@pytest.fixture +def test_post1(user, category1): + post = Post.objects.create( + title="Test Post1", + slug="test-post1", + content="This is test post 1.", author=user, status="published", post_type="post", ) - post.categories.add(category) - assert post.title == "Test Content" - assert post.slug == "test-content" - assert post.author == user - assert post.status == "published" - assert post.post_type == "post" - assert post.categories.count() == 1 - assert str(post) == "Test Content" + return post -@pytest.mark.django_db -def test_post_methods(user): - category1 = Category.objects.create(name="Category 1", slug="category-1") - category2 = Category.objects.create(name="Category 2", slug="category-2") - Post.post_objects.create( - title="Test Post 1", - slug="test-post-1", - content="This is test post 1.", +@pytest.fixture +def test_post2(user, category1): + post = Post.objects.create( + title="Test Post2", + slug="test-post2", + content="This is test post 2.", author=user, status="published", post_type="post", - ).categories.add(category1) + ) - Post.post_objects.create( - title="Test Post 2", - slug="test-post-2", - content="This is test post 2.", + return post + + +@pytest.fixture +def test_page1(user): + return Post.objects.create( + title="Test Page1", + slug="test-page1", + content="This is test page 1.", author=user, - status="draft", - post_type="post", + status="published", + post_type="page", + ) + + +@pytest.fixture +def test_page2(user): + return Post.objects.create( + title="Test Page2", + slug="test-page2", + content="This is test page 2.", + author=user, + status="published", + post_type="page", ) + +@pytest.mark.django_db +def test_post_model(test_post1, user, category1): + test_post1.categories.add(category1) + assert test_post1.title == "Test Post1" + assert test_post1.slug == "test-post1" + assert test_post1.author == user + assert test_post1.status == "published" + assert test_post1.post_type == "post" + assert test_post1.categories.count() == 1 + assert str(test_post1) == "Test Post1" + + +@pytest.mark.django_db +def test_post_methods(test_post1, test_post2, category1, category2): + test_post1.categories.add(category1) + assert Post.post_objects.all().count() == 2 assert ( - Post.post_objects.get_published_post_by_slug("test-post-1").title - == "Test Post 1" + Post.post_objects.get_published_post_by_slug("test-post1").title == "Test Post1" ) assert Post.post_objects.get_published_posts_by_category(category1).count() == 1 assert Post.post_objects.get_published_posts_by_category(category2).count() == 0 @@ -423,6 +456,10 @@ def test_get_recent_published_posts(user): assert list(recent_posts) == [post3, post2] assert not post1 in recent_posts + # Set back to defaults + settings.set("RECENT_PUBLISHED_POSTS_COUNT", 3) + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 3 + @pytest.mark.django_db def test_get_published_post_by_path(user): @@ -453,3 +490,37 @@ def test_get_published_post_by_path(user): # Set back to default settings.set("POST_PREFIX", "test-posts") + + +@pytest.mark.django_db +def test_get_published_page_by_slug(test_page1): + """Test that the get_published_page_by_slug method returns the correct page.""" + assert test_page1 == Post.page_objects.get_published_page_by_slug("test-page1") + + with pytest.raises(ValueError): + Post.page_objects.get_published_page_by_slug("non-existent-page") + + +@pytest.mark.django_db +def test_get_published_pages(test_page1, test_page2): + """Test that the get_published_pages method returns the correct pages.""" + assert list(Post.page_objects.get_published_pages()) == [test_page2, test_page1] + + +@pytest.mark.django_db +def test_get_published_page_by_path(test_page1: Post): + """Test that the get_published_page_by_path method returns the correct page.""" + + # Test case 1: pages can only be at the top level + page_path = f"test-pages/{test_page1.slug}" + with pytest.raises(expected_exception=ValueError): + Post.page_objects.get_published_page_by_path(page_path) + + # Test case 2: pages at the top level + page_path: str = test_page1.slug + assert test_page1 == Post.page_objects.get_published_page_by_path(page_path) + + # Test case 3: pages doesn't exist + page_path = "non-existent-page" + with pytest.raises(expected_exception=ValueError): + Post.page_objects.get_published_page_by_path(page_path) diff --git a/tests/test_templatetags_djpress_tags.py b/tests/test_templatetags_djpress_tags.py index 2014bb7..0663db2 100644 --- a/tests/test_templatetags_djpress_tags.py +++ b/tests/test_templatetags_djpress_tags.py @@ -3,11 +3,16 @@ from django.template import Context from django.urls import reverse from django.utils import timezone +from django.core.paginator import Paginator from djpress.conf import settings from djpress.models import Category, Post from djpress.templatetags import djpress_tags -from djpress.templatetags.helpers import post_read_more_link +from djpress.templatetags.helpers import ( + post_read_more_link, + categories_html, + get_page_link, +) from djpress.utils import get_author_display_name @@ -63,6 +68,20 @@ def test_post1(user, category1): return post +@pytest.fixture +def test_post2(user, category2): + post = Post.post_objects.create( + title="Test Post2", + slug="test-post2", + content="This is a test post.", + author=user, + status="published", + post_type="post", + ) + post.categories.set([category2]) + return post + + @pytest.fixture def test_long_post1(user, category1): post = Post.post_objects.create( @@ -77,10 +96,36 @@ def test_long_post1(user, category1): return post +@pytest.fixture +def test_page1(user): + post = Post.post_objects.create( + title="Test Page1", + slug="test-page1", + content="This is a test page.", + author=user, + status="published", + post_type="page", + ) + return post + + +@pytest.fixture +def test_page2(user): + post = Post.post_objects.create( + title="Test Page2", + slug="test-page2", + content="This is a test page.", + author=user, + status="published", + post_type="page", + ) + return post + + @pytest.mark.django_db def test_have_posts_single_post(test_post1): """Return a list of posts in the context.""" - context = Context({"_post": test_post1}) + context = Context({"post": test_post1}) assert djpress_tags.have_posts(context) == [test_post1] @@ -96,7 +141,7 @@ def test_have_posts_no_posts(): @pytest.mark.django_db def test_have_posts_multiple_posts(test_post1, test_long_post1): """Return a list of posts in the context.""" - context = Context({"_posts": [test_post1, test_long_post1]}) + context = Context({"posts": [test_post1, test_long_post1]}) assert djpress_tags.have_posts(context) == [test_post1, test_long_post1] @@ -136,23 +181,24 @@ def test_get_categories(category1, category2, category3): @pytest.mark.django_db -def test_post_title(test_post1): +def test_post_title_single_post(test_post1): context = Context({"post": test_post1}) assert djpress_tags.post_title(context) == test_post1.title -def test_post_title_no_post(): +def test_post_title_no_post_context(): context = Context({"foo": "bar"}) assert djpress_tags.post_title(context) == "" assert type(djpress_tags.post_title(context)) == str @pytest.mark.django_db -def test_post_title_link(test_post1): +def test_post_title_posts(test_post1): """Test the post_title_link template tag. This uses the post.permalink property to generate the link.""" - context = Context({"post": test_post1}) + # Context should have both a posts and a post to simulate the for post in posts loop + context = Context({"posts": [test_post1], "post": test_post1}) # Confirm settings in settings_testing.py assert settings.POST_PREFIX == "test-posts" @@ -179,7 +225,9 @@ def test_post_title_link_with_prefix(test_post1): # Confirm settings in settings_testing.py assert settings.POST_PREFIX == "test-posts" - context = Context({"post": test_post1}) + # Context should have both a posts and a post to simulate the for post in posts loop + context = Context({"posts": [test_post1], "post": test_post1}) + post_url = reverse("djpress:post_detail", args=[test_post1.slug]) expected_output = f'{test_post1.title}' @@ -518,9 +566,8 @@ def test_post_content_with_post(test_post1): @pytest.mark.django_db def test_post_content_with_posts(test_long_post1): """If there's a posts in the context, return the truncated post content.""" - context = Context( - {"post": test_long_post1, "_posts": [test_long_post1]}, - ) + # Context should have both a posts and a post to simulate the for post in posts loop + context = Context({"posts": [test_long_post1], "post": test_long_post1}) expected_output = ( f"{test_long_post1.truncated_content_markdown}" @@ -759,3 +806,406 @@ def test_post_categories_span_class1_class2(test_post1): ) == expected_output ) + + +@pytest.mark.django_db +def test_blog_categories(category1, category2): + categories = Category.objects.all() + + assert category1 in categories + assert category2 in categories + + assert djpress_tags.blog_categories() == categories_html( + categories=categories, outer="ul", outer_class="", link_class="" + ) + + +@pytest.mark.django_db +def test_blog_categories_no_categories(): + assert djpress_tags.blog_categories() == "" + + +@pytest.mark.django_db +def test_blog_pages_no_pages(): + assert djpress_tags.blog_pages() == "" + + +@pytest.mark.django_db +def test_blog_pages(test_page1, test_page2): + pages = Post.page_objects.all() + + assert test_page1 in pages + assert test_page2 in pages + + expected_output_ul = ( + f"
    • {get_page_link(page=test_page1)}
    • " + f"
    • {get_page_link(page=test_page2)}
    " + ) + + expected_output_div = ( + f"
    {get_page_link(page=test_page1)}, {get_page_link(page=test_page2)}
    " + ) + + expected_output_span = f"{get_page_link(page=test_page1)}, {get_page_link(page=test_page2)}" + + assert djpress_tags.blog_pages() == expected_output_ul + assert djpress_tags.blog_pages(outer="div") == expected_output_div + assert djpress_tags.blog_pages(outer="span") == expected_output_span + + +@pytest.mark.django_db +def test_blog_page_title(test_post1, test_page1): + # Test case 1 - category page + context = Context({"category": test_post1.categories.first()}) + assert test_post1.categories.first().name == djpress_tags.blog_page_title(context) + + # Test case 2 - author page + context = Context({"author": test_post1.author}) + assert get_author_display_name(test_post1.author) == djpress_tags.blog_page_title( + context + ) + + # Test case 3 - single post + context = Context({"post": test_post1}) + assert test_post1.title == djpress_tags.blog_page_title(context) + + # Test case 4 - single page + context = Context({"post": test_page1}) + assert test_page1.title == djpress_tags.blog_page_title(context) + + # Test case 5 - no context + context = Context() + assert "" == djpress_tags.blog_page_title(context) + + +def test_posts_nav_links_no_posts(): + context = Context() + + assert djpress_tags.posts_nav_links(context) == "" + assert type(djpress_tags.posts_nav_links(context)) == str + + +@pytest.mark.django_db +def test_posts_nav_links_one_page(test_post1, test_post2, test_long_post1): + # Confirm settings are set according to settings_testing.py + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 3 + + posts = Paginator( + Post.post_objects.get_published_posts(), + settings.RECENT_PUBLISHED_POSTS_COUNT, + ) + page = posts.get_page(number=None) + + context = Context({"posts": page}) + + previous_output = "" + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = "" + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + +@pytest.mark.django_db +def test_posts_nav_links_two_pages(test_post1, test_post2, test_long_post1): + # Confirm settings are set according to settings_testing.py + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 3 + + settings.set("RECENT_PUBLISHED_POSTS_COUNT", 2) + + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 2 + + posts = Paginator( + Post.post_objects.get_published_posts(), + settings.RECENT_PUBLISHED_POSTS_COUNT, + ) + + # Test case 1 - first page with no page page + page = posts.get_page(number=None) + + context = Context({"posts": page}) + + previous_output = "" + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = ( + f'' + f'next ' + f'last »' + f"" + ) + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Test case 2 - first page with page number 1 + page = posts.get_page(number=1) + + context = Context({"posts": page}) + + previous_output = "" + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = ( + f'' + f'next ' + f'last »' + f"" + ) + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Test case 3 - second page with page number 2 + page = posts.get_page(number=2) + + context = Context({"posts": page}) + + previous_output = ( + f'' + f'« first ' + f'previous' + f"" + ) + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = "" + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Set back to defaults + settings.set("RECENT_PUBLISHED_POSTS_COUNT", 3) + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 3 + + +@pytest.mark.django_db +def test_posts_nav_links_three_pages(test_post1, test_post2, test_long_post1): + # Confirm settings are set according to settings_testing.py + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 3 + + settings.set("RECENT_PUBLISHED_POSTS_COUNT", 1) + + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 1 + + posts = Paginator( + Post.post_objects.get_published_posts(), + settings.RECENT_PUBLISHED_POSTS_COUNT, + ) + + # Test case 1 - first page with no page number + page = posts.get_page(number=None) + + context = Context({"posts": page}) + + previous_output = "" + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = ( + f'' + f'next ' + f'last »' + f"" + ) + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Test case 2 - first page with page number 1 + page = posts.get_page(number=1) + + context = Context({"posts": page}) + + previous_output = "" + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = ( + f'' + f'next ' + f'last »' + f"" + ) + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Test case 3 - first page with page number 2 + page = posts.get_page(number=2) + + context = Context({"posts": page}) + + previous_output = ( + f'' + f'« first ' + f'previous' + f"" + ) + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = ( + f'' + f'next ' + f'last »' + f"" + ) + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Test case 4 - first page with page number 3 + page = posts.get_page(number=3) + + context = Context({"posts": page}) + + previous_output = ( + f'' + f'« first ' + f'previous' + f"" + ) + current_output = ( + f'' + f"Page {page.number} of {page.paginator.num_pages}" + f"" + ) + next_output = "" + + expected_output = f'' + + assert djpress_tags.posts_nav_links(context) == expected_output + + # Set back to defaults + settings.set("RECENT_PUBLISHED_POSTS_COUNT", 3) + assert settings.RECENT_PUBLISHED_POSTS_COUNT == 3 + + +@pytest.mark.django_db +def test_page_link(test_page1): + # Test 1 - page with a non existent page_slug + page_slug = "" + assert djpress_tags.page_link(page_slug=page_slug) == "" + + # Test 2 - page with a page_slug and no options + page_slug = test_page1.slug + outer = "" + outer_class = "" + link_class = "" + + output = get_page_link(page=test_page1, link_class=link_class) + expected_output = f"{output}" + assert djpress_tags.page_link(page_slug=page_slug) == expected_output + + # Test 3 - page with a page_slug and div and no options + page_slug = test_page1.slug + outer = "div" + outer_class = "" + link_class = "" + + output = get_page_link(page=test_page1, link_class=link_class) + expected_output = f"{output}" + assert djpress_tags.page_link(page_slug=page_slug) == expected_output + + # Test 4 - page with a page_slug and div and all options + page_slug = test_page1.slug + outer = "div" + outer_class = "outerclass" + link_class = "linkclass" + + outer_class = f' class="{outer_class}"' if outer_class else "" + output = get_page_link(page=test_page1, link_class=link_class) + expected_output = f"{output}" + assert ( + djpress_tags.page_link( + page_slug=page_slug, + outer=outer, + outer_class=outer_class, + link_class=link_class, + ) + == expected_output + ) + + # Test 5 - page with a page_slug and span and all options + page_slug = test_page1.slug + outer = "span" + outer_class = "outerclass" + link_class = "linkclass" + + outer_class = f' class="{outer_class}"' if outer_class else "" + output = get_page_link(page=test_page1, link_class=link_class) + expected_output = f"{output}" + assert ( + djpress_tags.page_link( + page_slug=page_slug, + outer=outer, + outer_class=outer_class, + link_class=link_class, + ) + == expected_output + ) + + # Test 6 - page with a page_slug and li and no options + page_slug = test_page1.slug + outer = "li" + outer_class = "" + link_class = "" + + outer_class = f' class="{outer_class}"' if outer_class else "" + output = get_page_link(page=test_page1, link_class=link_class) + expected_output = f"{output}" + assert ( + djpress_tags.page_link( + page_slug=page_slug, + outer=outer, + outer_class=outer_class, + link_class=link_class, + ) + == expected_output + ) + + # Test 6 - page with a page_slug and a wrong outer and no options + page_slug = test_page1.slug + outer = "foobar" + outer_class = "" + link_class = "" + + outer_class = f' class="{outer_class}"' if outer_class else "" + output = get_page_link(page=test_page1, link_class=link_class) + expected_output = f"{output}" + assert ( + djpress_tags.page_link( + page_slug=page_slug, + outer=outer, + outer_class=outer_class, + link_class=link_class, + ) + == expected_output + ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 4fa824f..676d07d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,8 @@ import pytest -from djpress.utils import get_author_display_name, render_markdown + +from djpress.utils import get_author_display_name, render_markdown, get_template_name from django.contrib.auth.models import User +from django.template.loader import TemplateDoesNotExist # create a parameterized fixture for a test user with first name, last name, and username @@ -120,3 +122,21 @@ def test_render_markdown_python_codehilite(): ) assert output in html + + +def test_get_template_name(): + # Test case 1 - template exists + templates = [ + "djpress/not-exists.html", + "djpress/index.html", + ] + template_name = get_template_name(templates) + assert template_name == "djpress/index.html" + + # Test case 2 - template does not exist + templates = [ + "djpress/not-exists.html", + "djpress/not-exists-2.html", + ] + with pytest.raises(TemplateDoesNotExist): + get_template_name(templates) diff --git a/tests/test_views.py b/tests/test_views.py index 3fabe47..af334c7 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,7 +1,11 @@ +from django.template import TemplateDoesNotExist import pytest +from collections.abc import Iterable + from django.contrib.auth.models import User from django.urls import reverse from django.utils import timezone +from django.test.utils import override_settings from djpress.models import Category, Post from djpress.utils import validate_date @@ -26,7 +30,7 @@ def category(): @pytest.fixture -def create_test_post(user, category): +def test_post1(user, category): post = Post.post_objects.create( title="Test Post", slug="test-post", @@ -40,20 +44,44 @@ def create_test_post(user, category): return post +@pytest.fixture +def test_page1(user): + page = Post.post_objects.create( + title="Test Page", + slug="test-page", + content="This is a test page.", + author=user, + status="published", + post_type="page", + ) + return page + + @pytest.mark.django_db def test_index_view(client): url = reverse("djpress:index") response = client.get(url) assert response.status_code == 200 assert b"No posts available" in response.content + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db -def test_post_detail_view(client, create_test_post): - url = reverse("djpress:post_detail", args=[create_test_post.permalink]) +def test_post_detail_view(client, test_post1, test_page1): + # Test 1 - post + url = reverse("djpress:post_detail", args=[test_post1.permalink]) + response = client.get(url) + assert response.status_code == 200 + assert "post" in response.context + assert not isinstance(response.context["post"], Iterable) + + # Test 2 - page + url = reverse("djpress:post_detail", args=[test_page1.permalink]) response = client.get(url) assert response.status_code == 200 - assert "_post" in response.context + assert "post" in response.context + assert not isinstance(response.context["post"], Iterable) @pytest.mark.django_db @@ -69,18 +97,20 @@ def test_author_with_no_posts_view(client, user): 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 + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @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]) +def test_author_with_posts_view(client, test_post1): + url = reverse("djpress:author_posts", args=[test_post1.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 + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db @@ -96,18 +126,20 @@ def test_category_with_no_posts_view(client, category): response = client.get(url) assert response.status_code == 200 assert "category" in response.context - assert "posts" in response.context assert b"No posts available" in response.content + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db -def test_category_with_posts_view(client, create_test_post, category): +def test_category_with_posts_view(client, test_post1, category): url = reverse("djpress:category_posts", args=[category.slug]) response = client.get(url) assert response.status_code == 200 assert "category" in response.context - assert "posts" in response.context assert not b"No posts available" in response.content + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db @@ -141,12 +173,13 @@ def test_validate_date(): @pytest.mark.django_db -def test_date_archives_year(client, create_test_post): +def test_date_archives_year(client, test_post1): url = reverse("djpress:archives_posts", kwargs={"year": "2024"}) response = client.get(url) assert response.status_code == 200 + assert test_post1.title.encode() in response.content assert "posts" in response.context - assert create_test_post.title.encode() in response.content + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db @@ -156,22 +189,24 @@ def test_date_archives_year_invalid_year(client): @pytest.mark.django_db -def test_date_archives_year_no_posts(client, create_test_post): +def test_date_archives_year_no_posts(client, test_post1): url = reverse("djpress:archives_posts", kwargs={"year": "2023"}) response = client.get(url) assert response.status_code == 200 - assert "posts" in response.context - assert not create_test_post.title.encode() in response.content + assert not test_post1.title.encode() in response.content assert b"No posts available" in response.content + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db -def test_date_archives_month(client, create_test_post): +def test_date_archives_month(client, test_post1): url = reverse("djpress:archives_posts", kwargs={"year": "2024", "month": "01"}) response = client.get(url) assert response.status_code == 200 + assert test_post1.title.encode() in response.content assert "posts" in response.context - assert create_test_post.title.encode() in response.content + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db @@ -183,24 +218,25 @@ def test_date_archives_month_invalid_month(client): @pytest.mark.django_db -def test_date_archives_month_no_posts(client, create_test_post): +def test_date_archives_month_no_posts(client, test_post1): url = reverse("djpress:archives_posts", kwargs={"year": "2024", "month": "02"}) response = client.get(url) assert response.status_code == 200 - assert "posts" in response.context - assert not create_test_post.title.encode() in response.content + assert not test_post1.title.encode() in response.content assert b"No posts available" in response.content + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable) @pytest.mark.django_db -def test_date_archives_day(client, create_test_post): +def test_date_archives_day(client, test_post1): url = reverse( "djpress:archives_posts", kwargs={"year": "2024", "month": "01", "day": "01"} ) response = client.get(url) assert response.status_code == 200 assert "posts" in response.context - assert create_test_post.title.encode() in response.content + assert test_post1.title.encode() in response.content @pytest.mark.django_db @@ -212,12 +248,13 @@ def test_date_archives_day_invalid_day(client): @pytest.mark.django_db -def test_date_archives_day_no_posts(client, create_test_post): +def test_date_archives_day_no_posts(client, test_post1): url = reverse( "djpress:archives_posts", kwargs={"year": "2024", "month": "01", "day": "02"} ) response = client.get(url) assert response.status_code == 200 - assert "posts" in response.context - assert not create_test_post.title.encode() in response.content + assert not test_post1.title.encode() in response.content assert b"No posts available" in response.content + assert "posts" in response.context + assert isinstance(response.context["posts"], Iterable)