Skip to content

Commit

Permalink
Add preview --drafts
Browse files Browse the repository at this point in the history
The site will appear as if all drafts are published.
  • Loading branch information
Siecje committed Feb 4, 2024
1 parent a5daa27 commit 6e66ec1
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 30 deletions.
10 changes: 10 additions & 0 deletions htmd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,19 @@ def build(
default=True,
help='If JavaScript should be minified',
)
@click.option(
'--drafts',
default=False,
help='Show draft posts in the preview.',
is_flag=True,
)
def preview(
_ctx: click.Context,
host: str,
port: int,
css_minify: bool, # noqa: FBT001
js_minify: bool, # noqa: FBT001
drafts: bool, # noqa: FBT001
) -> None:
from . import site
# reload for tests to refresh app.static_folder
Expand All @@ -219,6 +226,9 @@ def preview(
assert app.static_folder is not None
combine_and_minify_js(Path(app.static_folder))

if drafts:
site.preview_drafts()

# reload when static files change
# werkzeug will re-run the terminal command
# Which causes the above combine_and_minify_*() to run
Expand Down
51 changes: 45 additions & 6 deletions htmd/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from feedwerk.atom import AtomFeed
from flask import abort, Blueprint, Flask, render_template, Response, url_for
from flask.typing import ResponseReturnValue
from flask_flatpages import FlatPages, pygments_style_defs
from flask_flatpages import FlatPages, Page, pygments_style_defs
from flask_frozen import Freezer
from htmlmin import minify
from jinja2 import ChoiceLoader, FileSystemLoader
Expand Down Expand Up @@ -101,6 +101,13 @@ def get_project_dir() -> Path:
freezer = Freezer(app)


SHOW_DRAFTS = False
def preview_drafts() -> None:
global published_posts, SHOW_DRAFTS # noqa: PLW0603
SHOW_DRAFTS = True
published_posts = [p for p in posts if 'published' in p.meta]


# Allow config settings (even new user created ones) to be used in templates
for key in app.config:
app.jinja_env.globals[key] = app.config[key]
Expand Down Expand Up @@ -222,12 +229,19 @@ def all_posts() -> ResponseReturnValue:
return render_template('all_posts.html', active='posts', posts=latest)


def draft_and_not_shown(post: Page) -> bool:
is_draft = 'draft' in post.meta
return is_draft and not SHOW_DRAFTS and 'build' not in str(post.meta['draft'])


# If month and day are ints then Flask removes leading zeros
@app.route('/<year>/<month>/<day>/<path:path>/')
def post(year: str, month: str, day: str, path: str) -> ResponseReturnValue:
if len(year) != 4 or len(month) != 2 or len(day) != 2: # noqa: PLR2004
abort(404)
post = posts.get_or_404(path)
if draft_and_not_shown(post):
abort(404)
date_str = f'{year}-{month}-{day}'
if post.meta.get('published').strftime('%Y-%m-%d') != date_str:
abort(404)
Expand All @@ -253,11 +267,25 @@ def all_tags() -> ResponseReturnValue:
return render_template('all_tags.html', active='tags', tags=tag_counts)


def no_posts_shown(post_list: list[Page]) -> bool:
return all(
'draft' in p.meta and 'build' not in str(p.meta['draft'])
for p in post_list
)

@app.route('/tags/<string:tag>/')
def tag(tag: str) -> ResponseReturnValue:
tagged = [p for p in published_posts if tag in p.meta.get('tags', [])]
tagged = [p for p in posts if tag in p.meta.get('tags', [])]
if not tagged:
abort(404)
if not SHOW_DRAFTS and no_posts_shown(tagged):
abort(404)
if SHOW_DRAFTS:
tagged_published = tagged
else:
tagged_published = [p for p in tagged if 'draft' not in p.meta]
sorted_posts = sorted(
tagged,
tagged_published,
reverse=True,
key=lambda p: p.meta.get('published'),
)
Expand All @@ -266,10 +294,21 @@ def tag(tag: str) -> ResponseReturnValue:

@app.route('/author/<author>/')
def author(author: str) -> ResponseReturnValue:
# if the author has a draft build
# page is served without displaying posts
# so no 404 when for the link from the draft
posts_author = [p for p in posts if author == p.meta.get('author', '')]

if not posts_author:
abort(404)
posts_author_published = [p for p in posts_author if not p.meta.get('draft', False)]

if not SHOW_DRAFTS and no_posts_shown(posts_author):
abort(404)
if SHOW_DRAFTS:
posts_author_published = posts_author
else:
posts_author_published = [p for p in posts_author if 'draft' not in p.meta]

posts_sorted = sorted(
posts_author_published,
reverse=True,
Expand Down Expand Up @@ -309,7 +348,7 @@ def year_view(year: int) -> ResponseReturnValue:
@app.route('/<year>/<month>/')
def month_view(year: str, month: str) -> ResponseReturnValue:
month_posts = [
p for p in posts if year == p.meta.get('published').strftime('%Y')
p for p in published_posts if year == p.meta.get('published').strftime('%Y')
and month == p.meta.get('published').strftime('%m')
]
if not month_posts:
Expand All @@ -331,7 +370,7 @@ def month_view(year: str, month: str) -> ResponseReturnValue:
@app.route('/<year>/<month>/<day>/')
def day_view(year: str, month: str, day: str) -> ResponseReturnValue:
day_posts = [
p for p in posts if year == p.meta.get('published').strftime('%Y')
p for p in published_posts if year == p.meta.get('published').strftime('%Y')
and month == p.meta.get('published').strftime('%m')
and day == p.meta.get('published').strftime('%d')
]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ section-order = ["future", "standard-library", "third-party", "first-party", "lo

[tool.ruff.lint.per-file-ignores]
"htmd/utils.py" = ["I001"]
"tests/test_app.py" = ["ARG001"]
"tests/test_app.py" = ["I001", "ARG001"]
"tests/test_build.py" = ["I001"]
"tests/test_drafts.py" = ["ARG001", "I001"]
"tests/test_post_dates.py" = ["I001"]
Expand Down
27 changes: 27 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from collections.abc import Generator
import importlib

from click.testing import CliRunner
from flask import Flask
from flask.testing import FlaskClient
from htmd.cli import start
import pytest

from utils import set_example_to_draft


@pytest.fixture(scope='module')
def run_start() -> Generator[CliRunner, None, None]:
Expand Down Expand Up @@ -51,3 +54,27 @@ def test_draft_does_not_exist(client: FlaskClient) -> None:
# before this change pages.page was serving templates
response = client.get('/draft/dne/')
assert response.status_code == 404 # noqa: PLR2004


def test_tag_does_not_exist(client: FlaskClient) -> None:
found = 200
not_found = 404
# If the author doesn't exist it will be a 404
response = client.get('/tags/dne/')
assert response.status_code == not_found

set_example_to_draft()
from htmd import site
importlib.reload(site)
response = client.get('/tags/first/')
assert response.status_code == not_found
response = client.get('/author/Taylor/')
assert response.status_code == not_found
response = client.get('/2014/10/30/example/')
assert response.status_code == not_found

site.preview_drafts()
response = client.get('/tags/first/')
assert response.status_code == found
response = client.get('/author/Taylor/')
assert response.status_code == found
30 changes: 10 additions & 20 deletions tests/test_drafts.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,12 @@
from htmd.cli import build, start
import pytest

from utils import remove_fields_from_post, SUCCESS_REGEX


def set_example_as_draft() -> None:
remove_fields_from_post('example', ('draft',))
post_path = Path('posts') / 'example.md'
with post_path.open('r') as post_file:
lines = post_file.readlines()
with post_path.open('w') as post_file:
for line in lines:
if line == '...\n':
post_file.write('draft: true\n')
post_file.write(line)
from utils import (
remove_fields_from_post,
set_example_to_draft,
set_example_to_draft_build,
SUCCESS_REGEX,
)


def copy_example_as_draft_build() -> None:
Expand Down Expand Up @@ -48,7 +41,7 @@ def build_draft() -> Generator[CliRunner, None, None]:
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(start)
set_example_as_draft()
set_example_to_draft()
copy_example_as_draft_build()
result = runner.invoke(build)
assert result.exit_code == 0
Expand Down Expand Up @@ -122,14 +115,11 @@ def test_no_drafts_for_day(build_draft: CliRunner) -> None:


def test_draft_without_published(run_start: CliRunner) -> None:
set_example_as_draft()
copy_example_as_draft_build()
example_path = Path('posts') / 'example.md'
example_path.unlink()
remove_fields_from_post('copy', ('published', 'updated'))
set_example_to_draft_build()
remove_fields_from_post('example', ('published', 'updated'))
result = run_start.invoke(build)
assert result.exit_code == 0
assert re.search(SUCCESS_REGEX, result.output)
draft_uuid = get_draft_uuid('copy')
draft_uuid = get_draft_uuid('example')
draft_path = Path('build') / 'draft' / draft_uuid / 'index.html'
assert draft_path.is_file() is True
72 changes: 70 additions & 2 deletions tests/test_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import subprocess
import sys

from utils import set_example_to_draft, set_example_to_draft_build


def invoke_preview(run_start: CliRunner, args: list[str]) -> None:
"""
Expand Down Expand Up @@ -149,8 +151,9 @@ def test_preview_reload_css(run_start: CliRunner) -> None: # noqa: ARG001
try:
response = requests.get(url, timeout=0.1)
except (
requests.exceptions.ReadTimeout,
requests.exceptions.ChunkedEncodingError,
requests.exceptions.ConnectionError,
requests.exceptions.ReadTimeout,
):
# happens during restart
read_timeout = True
Expand Down Expand Up @@ -186,8 +189,9 @@ def test_preview_reload_js(run_start: CliRunner) -> None: # noqa: ARG001
try:
response = requests.get(url, timeout=0.1)
except (
requests.exceptions.ReadTimeout,
requests.exceptions.ChunkedEncodingError,
requests.exceptions.ConnectionError,
requests.exceptions.ReadTimeout,
):
# happens during restart
read_timeout = True
Expand All @@ -197,3 +201,67 @@ def test_preview_reload_js(run_start: CliRunner) -> None: # noqa: ARG001
assert read_timeout
assert before != after
assert expected in after


def test_preview_drafts(run_start: CliRunner) -> None:
args = ['--drafts']
invoke_preview(run_start, args)
set_example_to_draft()
success = 200

urls = (
(404, '/2014/'),
(404, '/2014/10/'),
(404, '/2014/10/30/'),
(404, '/2014/10/30/example/'),
(404, '/tags/first/'),
(404, '/author/Taylor/'),
)
not_in = (
'/',
'/all/',
)
# drafts should not appear
with run_preview():
for status, url in urls:
response = requests.get('http://localhost:9090' + url, timeout=1)
assert response.status_code == status

for url in not_in:
response = requests.get('http://localhost:9090' + url, timeout=0.01)
assert response.status_code == success
assert 'Example Post' not in response.text

# drafts should appear
with run_preview(args):
for _status, url in urls:
response = requests.get('http://localhost:9090' + url, timeout=1)
assert response.status_code == success

for url in not_in:
response = requests.get('http://localhost:9090' + url, timeout=0.01)
assert response.status_code == success
assert 'Example Post' in response.text

set_example_to_draft_build()
urls = (
(404, '/2014/'),
(404, '/2014/10/'),
(404, '/2014/10/30/'),
(200, '/2014/10/30/example/'),
(200, '/tags/first/'),
(200, '/author/Taylor/'),
)
not_in = (
'/',
'/all/',
)
with run_preview():
for status, url in urls:
response = requests.get('http://localhost:9090' + url, timeout=1)
assert response.status_code == status

for url in not_in:
response = requests.get('http://localhost:9090' + url, timeout=0.01)
assert response.status_code == success
assert 'Example Post' not in response.text
23 changes: 23 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,26 @@ def remove_fields_from_post(path: str, field_names: tuple[str, ...]) -> None:
break
else:
post.write(line)


def set_example_draft_status(draft_status: str) -> None:
remove_fields_from_post('example', ('draft',))
post_path = Path('posts') / 'example.md'

with post_path.open('r') as post_file:
lines = post_file.readlines()

with post_path.open('w') as post_file:
for line in lines:
if line == '...\n':
draft_line = f'draft: {draft_status}\n'
post_file.write(draft_line)
post_file.write(line)


def set_example_to_draft() -> None:
set_example_draft_status('true')


def set_example_to_draft_build() -> None:
set_example_draft_status('build')
2 changes: 1 addition & 1 deletion typehints/flask_flatpages/page.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ class Page:
body: Incomplete
html_renderer: Incomplete
folder: Incomplete
meta: dict
def __init__(self, path, meta, body, html_renderer, folder) -> None: ...
def __getitem__(self, name): ...
def __html__(self): ...
def html(self): ...
def meta(self): ...

0 comments on commit 6e66ec1

Please sign in to comment.