Skip to content

Commit

Permalink
#105 select theme view + tests and some refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
ephes committed Sep 1, 2023
1 parent 0b72a7c commit 40920b0
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 16 deletions.
16 changes: 15 additions & 1 deletion cast/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from wagtail.admin.forms.search import SearchForm
from wagtail.models import Collection

from .models import Audio, ChapterMark, Video
from .models import Audio, ChapterMark, Video, get_template_base_dir_choices


class VideoForm(forms.ModelForm):
Expand Down Expand Up @@ -183,3 +183,17 @@ def clean_q(self) -> str:
if not query:
raise forms.ValidationError(_("Please enter a search term"))
return query


class SelectThemeForm(forms.Form):
template_base_dir = forms.ChoiceField(
choices=[],
label=_("Theme"),
help_text=_("Select a theme for this site."),
required=True,
)
next = forms.CharField(widget=forms.HiddenInput, required=False)

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.fields["template_base_dir"].choices = get_template_base_dir_choices()
11 changes: 9 additions & 2 deletions cast/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from .audio import Audio, ChapterMark, sync_chapter_marks
from .file import File
from .gallery import Gallery, get_or_create_gallery
from .index_pages import Blog, Podcast
from .index_pages import Blog, HtmxHttpRequest, Podcast
from .itunes import ItunesArtWork
from .moderation import SpamFilter
from .pages import Episode, HomePage, Post, sync_media_ids
from .snippets import PostCategory
from .theme import TemplateBaseDirectory
from .theme import (
TemplateBaseDirectory,
get_template_base_dir,
get_template_base_dir_choices,
)
from .video import Video, get_video_dimensions

__all__ = [
Expand All @@ -17,7 +21,10 @@
"File",
"Gallery",
"get_or_create_gallery",
"get_template_base_dir",
"get_template_base_dir_choices",
"HomePage",
"HtmxHttpRequest",
"ItunesArtWork",
"Post",
"PostCategory",
Expand Down
29 changes: 16 additions & 13 deletions cast/models/index_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from datetime import datetime
from typing import Any

import django.forms.forms
from django.core.paginator import InvalidPage, Paginator
from django.db import models
from django.http import Http404, HttpRequest
from django.http.request import QueryDict
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_htmx.middleware import HtmxDetails
from wagtail.admin.panels import FieldPanel
from wagtail.api import APIField
from wagtail.fields import RichTextField
Expand All @@ -20,19 +20,16 @@
from cast.filters import PostFilterset
from cast.models.itunes import ItunesArtWork

from ..views import HtmxHttpRequest
from .pages import Post
from .theme import TemplateBaseDirectory, get_template_base_dir_choices
from .theme import get_template_base_dir, get_template_base_dir_choices

logger = logging.getLogger(__name__)


ContextDict = dict[str, Any]


class HtmxHttpRequest(HttpRequest):
htmx: HtmxDetails


class Blog(Page):
"""
This is the index page for a blog. It contains a list of posts.
Expand Down Expand Up @@ -92,13 +89,8 @@ class Blog(Page):
def __str__(self):
return self.title

def get_template_base_dir(self, request: HttpRequest) -> str:
if hasattr(request, "session") and (template_base_dir := request.session.get("template_base_dir")) is not None:
return template_base_dir
if self.template_base_dir is not None:
return self.template_base_dir
else:
return TemplateBaseDirectory.for_request(request).name
def get_template_base_dir(self, request: HtmxHttpRequest) -> str:
return get_template_base_dir(request, self.template_base_dir)

def get_template(self, request: HtmxHttpRequest, *args, **kwargs) -> str:
template_base_dir = self.get_template_base_dir(request)
Expand Down Expand Up @@ -200,6 +192,16 @@ def comment_post_url(self) -> str:
def pagination_page_size(self) -> int:
return appsettings.POST_LIST_PAGINATION

def get_theme_form(self, request: HttpRequest) -> django.forms.forms.Form:
from ..forms import SelectThemeForm

return SelectThemeForm(
initial={
"template_base_dir": self.get_template_base_dir(request),
"next": request.path,
}
)

def get_context(self, request: HttpRequest, *args, **kwargs) -> ContextDict:
context = super().get_context(request, *args, **kwargs)
get_params = request.GET.copy()
Expand All @@ -209,6 +211,7 @@ def get_context(self, request: HttpRequest, *args, **kwargs) -> ContextDict:
context["posts"] = context["object_list"] # convenience
context["blog"] = self
context["use_audio_player"] = any([post.has_audio for post in context["posts"]])
context["theme_form"] = self.get_theme_form(request)
return context


Expand Down
12 changes: 12 additions & 0 deletions cast/models/theme.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pathlib import Path
from typing import Union

from django.conf import settings
from django.db import models
Expand All @@ -7,6 +8,8 @@
from django.utils.translation import gettext_lazy as _
from wagtail.contrib.settings.models import BaseSiteSetting, register_setting

from ..views import HtmxHttpRequest


def get_required_template_names() -> list[str]:
"""
Expand Down Expand Up @@ -106,6 +109,15 @@ def get_template_base_dir_choices() -> list[tuple[str, str]]:
return choices


def get_template_base_dir(request: HtmxHttpRequest, pre_selected: Union[str, None]) -> str:
if hasattr(request, "session") and (template_base_dir := request.session.get("template_base_dir")) is not None:
return template_base_dir
if pre_selected is not None:
return pre_selected
else:
return TemplateBaseDirectory.for_request(request).name


@register_setting
class TemplateBaseDirectory(BaseSiteSetting):
"""
Expand Down
2 changes: 2 additions & 0 deletions cast/templates/cast/plain/blog_list_of_posts.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ <h4>Feed</h4>

{% block navigation %}
<nav>
<hr>
{% include "./select_theme.html" %}
<hr>
{% include "./_filter_form.html" with form=filterset.form %}
<hr>
Expand Down
5 changes: 5 additions & 0 deletions cast/templates/cast/plain/select_theme.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<form action='{% url "cast:select-theme" %}' method="post">
{% csrf_token %}
{{ theme_form.as_p }}
<input role="button" type="submit" />
</form>
3 changes: 3 additions & 0 deletions cast/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from . import feeds
from .views import meta
from .views.theme import select_theme

app_name = "cast"
urlpatterns: list[Any] = [
Expand All @@ -28,4 +29,6 @@
),
# Meta views like twitter player cards etc
path("<slug:blog_slug>/<slug:episode_slug>/twitter-player/", view=meta.twitter_player, name="twitter-player"),
# Store selected theme in session
path("select-theme/", view=select_theme, name="select-theme"),
]
5 changes: 5 additions & 0 deletions cast/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from django.contrib.auth.models import User
from django.http import HttpRequest
from django_htmx.middleware import HtmxDetails


class AuthenticatedHttpRequest(HttpRequest):
user: User


class HtmxHttpRequest(HttpRequest):
htmx: HtmxDetails
43 changes: 43 additions & 0 deletions cast/views/theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Union

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django_htmx.http import HttpResponseLocation

from ..forms import SelectThemeForm
from ..models import HtmxHttpRequest, get_template_base_dir


def select_theme(request: HtmxHttpRequest) -> Union[HttpResponse, HttpResponseLocation]:
"""
Store the selected theme in the session. This is used to
determine the template base directory for rendering the
blog and episode pages.
"""
template_base_dir = get_template_base_dir(request, None)
# use the referer as the next url if it exists because request.path is
# the url of the select theme view, and we want to redirect to the previous page
next_url = request.headers.get("referer", request.path)
if request.method == "POST":
form = SelectThemeForm(request.POST)
if form.is_valid():
request.session["template_base_dir"] = form.cleaned_data["template_base_dir"]
success_url = form.cleaned_data["next"]
if request.htmx:
return HttpResponseLocation(success_url)
else:
return HttpResponseRedirect(success_url)
else:
form = SelectThemeForm(
initial={
"template_base_dir": template_base_dir,
"next": next_url,
}
)
context = {
"theme_form": form,
"template_base_dir": template_base_dir,
"template_base_dir_choices": form.fields["template_base_dir"].choices,
"next_url": next_url,
}
return render(request, f"cast/{template_base_dir}/select_theme.html", context=context)
72 changes: 72 additions & 0 deletions tests/theme_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json
import shutil
from pathlib import Path

import pytest
from django.template import engines
from django.urls import reverse

from cast.context_processors import site_template_base_dir
from cast.models.theme import (
Expand Down Expand Up @@ -93,3 +95,73 @@ def test_context_processors_site_template_base_dir(request_factory):
context = site_template_base_dir(request)
assert context["cast_site_template_base_dir"] == "bootstrap4"
assert context["cast_base_template"] == "cast/bootstrap4/base.html"


@pytest.mark.django_db
def test_get_select_theme_view(client):
url = reverse("cast:select-theme")
response = client.get(url)
assert response.status_code == 200
select_template_name = response.templates[0].name
assert select_template_name == "cast/bootstrap4/select_theme.html"


@pytest.mark.django_db
def test_post_select_theme_view_invalid(client):
# Given an invalid theme and a next url
url = reverse("cast:select-theme")
theme, next_url = "invalid", "/next-url/"
# When we post to the select theme view
response = client.post(
url,
{
"template_base_dir": theme,
"next": next_url,
},
)
# Then we are not redirected to the next url and the theme is not stored in the session
# and the form was invalid and has an error for the invalid field
assert response.status_code == 200
assert "template_base_dir" not in client.session
assert "template_base_dir" in response.context["form"].errors


@pytest.mark.django_db
def test_post_select_theme_view_happy(client):
# Given plain as theme and a next url
url = reverse("cast:select-theme")
theme, next_url = "plain", "/next-url/"
# When we post to the select theme view
response = client.post(
url,
{
"template_base_dir": theme,
"next": next_url,
},
)
# Then we are redirected to the next url and the theme is stored in the session
assert response.status_code == 302
assert next_url == response.url
assert client.session["template_base_dir"] == "plain"


@pytest.mark.django_db
def test_post_select_theme_view_happy_htmx(client):
# Given plain as theme and a next url
url = reverse("cast:select-theme")
theme, next_url = "plain", "/next-url/"
# When we post to the select theme view
headers = {"HTTP_HX-Request": "true"}
response = client.post(
url,
{
"template_base_dir": theme,
"next": next_url,
},
**headers,
)
# Then we are redirected to the next url and the theme is stored in the session
assert response.status_code == 200
actual_next_url = json.loads(response.headers["HX-Location"])["path"]
assert actual_next_url == next_url
assert client.session["template_base_dir"] == "plain"

0 comments on commit 40920b0

Please sign in to comment.