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

Themes-v1 #39

Merged
merged 6 commits into from
Oct 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
50 changes: 50 additions & 0 deletions docs/themes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Themes

A DJ Press theme is a collection of one or more Django template files. and any static files required to style those
templates.

## Template Files

Template files should be copied to: `./templates/djpress/{{ your_theme_name }}/`. At a minimum, the theme must include
an `index.html` file. You can build an entire theme with just this one file, and an example can be seen in the DJ Press
included theme called `default`.

Other template files that can be included are as follows:

- `home.html` - used on the home page view, i.e. `https://yourblog.com/`
- `archives.html` - used to display the date-based archives views, e.g. `https://yourblog.com/2024/`
- `category.html` - used to display blog posts in a particular category, e.g. `https://yourblog.com/category/news/`
- `author.html` - used to display blog posts by a particular author, e.g. `https://yourblog.com/author/sam/`
- `single.html` - used to display a single blog post, e.g. `https://yourblog.com/2024/09/30/my-interesting-blog-post/`
- `page.html` - used to display a single page, e.g. `https://yourblog.com/colophon/`

In all cases, if the above file is not found, the `index.html` page will be displayed instead.

## Static Files

Static files should be copied to: `./static/djpress/{{ your_theme_name }}/`. Static files are typically grouped into
sub-directories for CSS or JavaScript or image files, e.g. `./static/djpress/{{ your_theme_name }}/css/` or
`./static/djpress/{{ your_theme_name }}/js/` or `./static/djpress/{{ your_theme_name }}/img`, but these
sub-directories are optional and up to the theme developer how or if to use them.

From within the template files, static assets can be referenced using standard Django template tags, e.g.
`{% static 'djpress/{{ your_theme_name }}/css/style.css' %}`, depending on the aforementioned directory structure.

## Configure a Theme

In your Django project settings file, configure the `THEME` setting in the `DJPRESS_SETTINGS` object, e.g.

```python
DJPRESS_SETTINGS = {
"THEME": "your_theme_name",
}
```

In the example configuration, `your_theme_name` must match exactly the directory name that the theme's template files
are copied to. If this directory cannot be found, or if there is no matching template file in it, your site will crash
with a `TemplateDoesNotExist` error.

## Examples

There are currently two themes included in DJ Press: `default` and `simple`. The `default` theme demonstrates how to
build a theme with just a single `index.html` file, whereas the `simple` theme uses all available template types.
1 change: 1 addition & 0 deletions src/djpress/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
"RSS_ENABLED": (True, bool),
"RSS_PATH": ("rss", str),
"MICROFORMATS_ENABLED": (True, bool),
"THEME": ("default", str),
}
1 change: 1 addition & 0 deletions src/djpress/static/simple/css/simple.min.css

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

30 changes: 30 additions & 0 deletions src/djpress/static/simple/css/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
body {
display: grid;
grid-template-columns: 0.6fr 1.4fr;
grid-template-rows: min-content 1.8fr min-content;
gap: 0px 0px;
grid-auto-flow: row;
grid-template-areas:
"Header Header"
"Sidebar Content"
"Footer Footer";
width: 80%;
height: 100%;
margin: 0 auto;
}

header {
grid-area: Header;
}

nav {
grid-area: Sidebar;
}

main {
grid-area: Content;
}

footer {
grid-area: Footer;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% blog_page_title post_text="| " %}{% blog_title %}</title>
<link rel="stylesheet" href="{% static 'djpress/css/style.css' %}">
<link rel="stylesheet" href="{% static 'default/css/style.css' %}">
<link rel="alternate" type="application/rss+xml" title="Latest Posts" href="{% rss_url %}">
</head>
<body>
Expand Down
86 changes: 86 additions & 0 deletions src/djpress/templates/djpress/simple/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{% load static %}
{% load djpress_tags %}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% blog_page_title post_text="| " %}{% blog_title %}</title>
<link rel="stylesheet" href="{% static "simple/css/simple.min.css" %}">
<link rel="stylesheet" href="{% static "simple/css/style.css" %}">
</head>
<body>
<header>
<h1>{% blog_title_link %}</h1>
<nav>
{% blog_pages %}
</nav>
</header>

<nav>
<h3>Recent Posts:</h3>
<ul>
{% get_recent_posts as posts %}
{% for post in posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
</nav>

<main>

{% if post %}

{% post_wrap %}

<header>
{% post_title outer_tag="h1" %}
<p>By {% post_author %}. {% post_date %}</p>
</header>

{% post_content outer_tag="section" %}

<footer>
<p>Categories: {% post_categories "span" "badge" %}</p>
</footer>

{% end_post_wrap %}

{% else %}

<h1>Latest Posts</h1>

{% for post in posts %}

{% post_wrap %}

<header>
{% post_title outer_tag="h2" %}
<p>By {% post_author %}. {% post_date %}</p>
</header>

{% post_content outer_tag="section" %}

<footer>
<p>Categories: {% post_categories "span" "badge" %}</p>
</footer>

{% end_post_wrap %}

{% empty %}

<p>No posts available.</p>

{% endfor %}

{% endif %}

{% pagination_links %}
</main>

<footer>
<p>{% blog_title_link %}</p>
</footer>
</body>
</html>
42 changes: 40 additions & 2 deletions src/djpress/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,53 @@ def validate_date_parts(year: str | None, month: str | None, day: str | None) ->
return result


def get_template_name(templates: list[str]) -> str:
def get_templates(view_name: str) -> list[str]:
"""Get the template names for a view.

Args:
view_name (str): The view name.

Returns:
list[str]: The list of template names.
"""
theme = djpress_settings.THEME

template = ""

if view_name == "index":
template = f"djpress/{theme}/home.html"

if view_name == "archive_posts":
template = f"djpress/{theme}/archives.html"

if view_name == "category_posts":
template = f"djpress/{theme}/category.html"

if view_name == "author_posts":
template = f"djpress/{theme}/author.html"

if view_name == "single_post":
template = f"djpress/{theme}/single.html"

if view_name == "single_page":
template = f"djpress/{theme}/page.html"

default_template = f"djpress/{theme}/index.html"

return [template, default_template] if template else [default_template]


def get_template_name(view_name: str) -> str:
"""Return the first template that exists.

Args:
templates (list[str]): The list of template names.
view_name (str): The view name

Returns:
str: The template name.
"""
templates = get_templates(view_name)

try:
template = str(select_template(templates).template.name)
except TemplateDoesNotExist as exc:
Expand Down
40 changes: 7 additions & 33 deletions src/djpress/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,7 @@ def index(
Context:
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)
template: str = get_template_name("index")

posts = Paginator(
Post.post_objects.get_published_posts(),
Expand Down Expand Up @@ -129,11 +124,7 @@ def archive_posts(
Context:
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)
template: str = get_template_name(view_name="archive_posts")

try:
date_parts = validate_date_parts(year=year, month=month, day=day)
Expand Down Expand Up @@ -172,11 +163,7 @@ def category_posts(request: HttpRequest, slug: str) -> HttpResponse:
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)
template: str = get_template_name(view_name="category_posts")

try:
category: Category = Category.objects.get_category_by_slug(slug=slug)
Expand Down Expand Up @@ -212,11 +199,7 @@ def author_posts(request: HttpRequest, author: str) -> HttpResponse:
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)
template: str = get_template_name(view_name="author_posts")

try:
user: User = User.objects.get(username=author)
Expand Down Expand Up @@ -260,11 +243,6 @@ def single_post(
Context:
post (Post): The post object.
"""
template_names: list[str] = [
"djpress/single.html",
"djpress/index.html",
]

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)
Expand All @@ -275,7 +253,8 @@ def single_post(
msg = "Post not found"
raise Http404(msg) from exc

template: str = get_template_name(templates=template_names)
template: str = get_template_name(view_name="single_post")

return render(
request=request,
context=context,
Expand All @@ -299,19 +278,14 @@ def single_page(request: HttpRequest, path: str) -> HttpResponse:
Raises:
Http404: If the page is not found.
"""
template_names: list[str] = [
"djpress/single.html",
"djpress/index.html",
]

try:
post = Post.page_objects.get_published_page_by_path(path)
context: dict = {"post": post}
except (PageNotFoundError, ValueError) as exc:
msg = "Page not found"
raise Http404(msg) from exc

template: str = get_template_name(templates=template_names)
template: str = get_template_name(view_name="single_page")

return render(
request=request,
Expand Down
14 changes: 10 additions & 4 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,25 @@ def test_render_markdown_image_with_title():
)


def test_get_template_name():
def test_get_template_name(settings):
# Test case 1 - template exists
templates = [
"djpress/not-exists.html",
"djpress/index.html",
"djpress/default/index.html",
]
template_name = get_template_name(templates)
assert template_name == "djpress/index.html"
assert template_name == "djpress/default/index.html"

# Test case 2 - template does not exist
# Test case 2 - template does not exist - fall back to default
templates = [
"djpress/not-exists.html",
"djpress/not-exists-2.html",
]
template_name = get_template_name(templates)
assert template_name == "djpress/default/index.html"

# Test case 3 - default template file does not exist
settings.DJPRESS_SETTINGS["THEME"] = "not-exists"

with pytest.raises(TemplateDoesNotExist):
get_template_name(templates)