Compare commits

..

No commits in common. "b3dfab2611d294f8c1d25b1d4acef62122ba75cf" and "0f19fcb174586c3d516f2203fb5253447f1c5494" have entirely different histories.

14 changed files with 76 additions and 158 deletions

View File

@ -40,9 +40,8 @@ def create_app(instance_path=None, test_config=None):
logger.info("RESPONSE: %s %s: %s", request.method, request.path, response.status)
return response
from . import error_pages, pages, static
from . import error_pages, pages
app.register_blueprint(pages.bp)
app.register_blueprint(static.bp)
app.register_error_handler(400, error_pages.bad_request)
app.register_error_handler(404, error_pages.page_not_found)
app.register_error_handler(500, error_pages.internal_server_error)

View File

@ -50,12 +50,6 @@ class Config(object):
MEDIA_DIR = 'media'
# customizations
PAGE_STYLES = {
'dark': '/static/css/dark.css',
'light': '/static/css/light.css',
'plain': '/static/css/plain.css',
}
DEFAULT_PAGE_STYLE = 'light'
TITLE_SUFFIX = 'example.com'
CONTACT_EMAIL = 'admin@example.com'

View File

@ -31,15 +31,19 @@ def render(template_name_or_list, **context):
* Determine the proper site theme to use in the template and provide it.
"""
page_styles = app.config['PAGE_STYLES']
PAGE_STYLES = {
'dark': 'css/dark.css',
'light': 'css/light.css',
'plain': 'css/plain.css',
}
selected_style = request.args.get('style', None)
if selected_style:
user_style = selected_style
else:
user_style = request.cookies.get('user-style')
logger.debug("user style cookie: %s", user_style)
context['user_style'] = page_styles.get(user_style, page_styles.get(app.config['DEFAULT_PAGE_STYLE']))
context['page_styles'] = page_styles
context['user_style'] = PAGE_STYLES.get(user_style, PAGE_STYLES.get(app.config['DEFAULT_PAGE_STYLE']))
resp = make_response(render_template(template_name_or_list, **context))
if selected_style:

View File

@ -1,14 +0,0 @@
"""Serve static files from the instance directory."""
import os
from flask import Blueprint
from flask import current_app as app
from flask import send_from_directory
bp = Blueprint('static', __name__, url_prefix='/custom-static')
@bp.route('/<path:name>')
def serve_instance_static_file(name):
"""Serve a static file from the instance directory, used for customization."""
return send_from_directory(os.path.join(app.instance_path, 'custom-static'), name)

View File

@ -28,8 +28,7 @@ body {
a {
font-weight: bold;
text-decoration-line: underline;
text-decoration-thickness: 1px;
text-decoration: none;
}
div.header {
@ -40,6 +39,10 @@ div.header {
padding-bottom: 0;
}
div.header a {
border-bottom: none;
}
div.content {
font-size: 11pt;
padding: 0 1rem;
@ -167,5 +170,6 @@ figcaption {
}
.footnote-ref:link, .footnote-ref:visited, .footnote-ref:hover, .footnote-ref:active {
border-bottom: none;
font-weight: normal;
}

View File

@ -15,14 +15,17 @@ h1, h2, h3, h4, h5, h6 {
p a, ul a, ol a {
color: #DDD;
border-bottom: 1px solid #DDD;
}
footer a {
color: #999;
border-bottom: 1px solid #999;
}
p a:hover, ul a:hover, ol a:hover, footer a:hover {
color: #B31D15;
border-bottom: 1px solid #B31D15;
}
div.site-wrap {
@ -31,7 +34,10 @@ div.site-wrap {
div.header, div.header a {
color: #555;
text-decoration: none;
}
div.header a:hover, div.header a:active {
border-bottom: 1px solid #555;
}
table, th, td {

View File

@ -15,14 +15,17 @@ h1, h2, h3, h4, h5, h6 {
p a, ul a, ol a {
color: #222;
border-bottom: 1px solid #222;
}
footer a {
color: #999;
border-bottom: 1px solid #999;
}
p a:hover, ul a:hover, ol a:hover, footer a:hover {
color: #811610;
border-bottom: 1px solid #811610;
}
div.site-wrap {
@ -31,7 +34,10 @@ div.site-wrap {
div.header, div.header a {
color: #AAA;
text-decoration: none;
}
div.header a:hover, div.header a:active {
border-bottom: 1px solid #AAA;
}
table, th, td {

View File

@ -7,7 +7,7 @@
<meta property="og:url" content="{{ base_url }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ user_style }}">
<link rel="stylesheet" href="{{ url_for('static', filename=user_style) }}">
<link rel="icon" href="{% if config.FAVICON %}{{ config.FAVICON }}{% else %}{{ url_for('static', filename='img/favicon.png') }}{% endif %}">
<div {% block site_class %}class="site-wrap site-wrap-normal-width"{% endblock %}>
@ -20,9 +20,9 @@
{% endfor %}
</div>
<div class="styles">
{% for style in page_styles %}
<a href="?style={{ style }}">[{{ style }}]</a>
{% endfor %}
<a href="?style=dark">[dark]</a>
<a href="?style=light">[light]</a>
<a href="?style=plain">[plain]</a>
</div>
</div>
{% endblock %}

View File

@ -21,6 +21,6 @@ safety # check requirements file for issues
# maintenance utilities and tox
pip-tools # pip-compile
tox<4 # CI stuff, pinned for now to avoid packaging conflict w/safety
tox # CI stuff
tox-wheel # build wheels in tox
versioneer # automatic version numbering

View File

@ -1,16 +1,16 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in
#
attrs==22.2.0
attrs==22.1.0
# via pytest
bandit==1.7.4
# via -r requirements/requirements-dev.in
build==0.9.0
build==0.8.0
# via pip-tools
certifi==2022.12.7
certifi==2022.9.14
# via requests
charset-normalizer==2.1.1
# via requests
@ -19,17 +19,15 @@ click==8.1.3
# flask
# pip-tools
# safety
coverage[toml]==7.0.1
coverage[toml]==6.4.4
# via pytest-cov
distlib==0.3.6
# via virtualenv
dlint==0.13.0
# via -r requirements/requirements-dev.in
dparse==0.6.2
dparse==0.6.0
# via safety
exceptiongroup==1.1.0
# via pytest
filelock==3.9.0
filelock==3.8.0
# via
# tox
# virtualenv
@ -44,31 +42,31 @@ flake8==5.0.4
# flake8-mutable
flake8-blind-except==0.2.1
# via -r requirements/requirements-dev.in
flake8-builtins==2.1.0
flake8-builtins==1.5.3
# via -r requirements/requirements-dev.in
flake8-docstrings==1.6.0
# via -r requirements/requirements-dev.in
flake8-executable==2.1.2
flake8-executable==2.1.1
# via -r requirements/requirements-dev.in
flake8-fixme==1.1.1
# via -r requirements/requirements-dev.in
flake8-isort==6.0.0
flake8-isort==4.2.0
# via -r requirements/requirements-dev.in
flake8-logging-format==0.9.0
flake8-logging-format==0.7.5
# via -r requirements/requirements-dev.in
flake8-mutable==1.2.0
# via -r requirements/requirements-dev.in
flask==2.2.2
# via -r requirements/requirements.in
gitdb==4.0.10
gitdb==4.0.9
# via gitpython
gitpython==3.1.30
gitpython==3.1.27
# via bandit
idna==3.4
# via requests
iniconfig==1.1.1
# via pytest
isort==5.11.4
isort==5.10.1
# via flake8-isort
itsdangerous==2.1.2
# via flask
@ -89,20 +87,22 @@ packaging==21.3
# pytest
# safety
# tox
pbr==5.11.0
pbr==5.10.0
# via stevedore
pep517==0.13.0
# via build
pip-tools==6.12.1
pip-tools==6.8.0
# via -r requirements/requirements-dev.in
platformdirs==2.6.2
platformdirs==2.5.2
# via virtualenv
pluggy==1.0.0
# via
# pytest
# tox
py==1.11.0
# via tox
# via
# pytest
# tox
pycodestyle==2.9.1
# via flake8
pydocstyle==6.1.1
@ -115,11 +115,11 @@ pyparsing==3.0.9
# via
# packaging
# pydot
pytest==7.2.0
pytest==7.1.3
# via
# -r requirements/requirements-dev.in
# pytest-cov
pytest-cov==4.0.0
pytest-cov==3.0.0
# via -r requirements/requirements-dev.in
pyyaml==6.0
# via bandit
@ -127,9 +127,9 @@ requests==2.28.1
# via safety
ruamel-yaml==0.17.21
# via safety
ruamel-yaml-clib==0.2.7
ruamel-yaml-clib==0.2.6
# via ruamel-yaml
safety==2.3.5
safety==2.1.1
# via -r requirements/requirements-dev.in
six==1.16.0
# via tox
@ -137,7 +137,7 @@ smmap==5.0.0
# via gitdb
snowballstemmer==2.2.0
# via pydocstyle
stevedore==4.1.1
stevedore==4.0.0
# via bandit
toml==0.10.2
# via dparse
@ -148,21 +148,21 @@ tomli==2.0.1
# pep517
# pytest
# tox
tox==3.28.0
tox==3.26.0
# via
# -r requirements/requirements-dev.in
# tox-wheel
tox-wheel==1.0.0
tox-wheel==0.7.0
# via -r requirements/requirements-dev.in
urllib3==1.26.13
urllib3==1.26.12
# via requests
versioneer==0.28
versioneer==0.26
# via -r requirements/requirements-dev.in
virtualenv==20.17.1
virtualenv==20.16.5
# via tox
werkzeug==2.2.2
# via flask
wheel==0.38.4
wheel==0.37.1
# via
# pip-tools
# tox-wheel

View File

@ -190,15 +190,15 @@ def test_setting_selected_style_includes_cookie(client):
response = client.get('/?style=light')
style_cookie = next((cookie for cookie in client.cookie_jar if cookie.name == 'user-style'), None)
assert response.status_code == 200
assert b'/static/css/light.css' in response.data
assert b'/static/css/dark.css' not in response.data
assert b'light.css' in response.data
assert b'dark.css' not in response.data
assert style_cookie.value == 'light'
response = client.get('/?style=dark')
style_cookie = next((cookie for cookie in client.cookie_jar if cookie.name == 'user-style'), None)
assert response.status_code == 200
assert b'/static/css/dark.css' in response.data
assert b'/static/css/light.css' not in response.data
assert b'dark.css' in response.data
assert b'light.css' not in response.data
assert style_cookie.value == 'dark'
@ -218,23 +218,3 @@ def test_extra_footer_per_page(client):
assert b'<div class="extra-footer">' not in response.data
response = client.get('/index-but-with-footer')
assert b'<div class="extra-footer"><i>ooo <a href="a">a</a></i>' in response.data
def test_serving_static_files(client):
"""Test the usage of send_from_directory to serve extra static files."""
response = client.get('/custom-static/css/warm.css')
assert response.status_code == 200
# can't serve directories, just files
response = client.get('/custom-static/')
assert response.status_code == 404
response = client.get('/custom-static/css/')
assert response.status_code == 404
response = client.get('/custom-static/css')
assert response.status_code == 404
# can't serve files that don't exist or bad paths
response = client.get('/custom-static/css/cold.css')
assert response.status_code == 404
response = client.get('/custom-static/css/../../unreachable.md')
assert response.status_code == 404

View File

@ -1,3 +0,0 @@
* {
color: red;
}

View File

@ -1,15 +1,10 @@
"""Unit test helper methods."""
import os
import pytest
from werkzeug.http import dump_cookie
from incorporealcms import create_app
from incorporealcms.pages import (generate_parent_navs, instance_resource_path_to_request_path, render,
request_path_to_breadcrumb_display, request_path_to_instance_resource_path)
HERE = os.path.dirname(os.path.abspath(__file__))
def test_generate_page_navs_index(app):
"""Test that the index page has navs to the root (itself)."""
@ -54,74 +49,22 @@ def test_render_with_user_dark_theme(app):
"""Test that a request with the dark theme selected renders the dark theme."""
cookie = dump_cookie("user-style", 'dark')
with app.test_request_context(headers={'COOKIE': cookie}):
assert b'/static/css/dark.css' in render('base.html').data
assert b'/static/css/light.css' not in render('base.html').data
assert b'dark.css' in render('base.html').data
assert b'light.css' not in render('base.html').data
def test_render_with_user_light_theme(app):
"""Test that a request with the light theme selected renders the light theme."""
with app.test_request_context():
assert b'/static/css/light.css' in render('base.html').data
assert b'/static/css/dark.css' not in render('base.html').data
assert b'light.css' in render('base.html').data
assert b'dark.css' not in render('base.html').data
def test_render_with_no_user_theme(app):
"""Test that a request with no theme set renders the light theme."""
with app.test_request_context():
assert b'/static/css/light.css' in render('base.html').data
assert b'/static/css/dark.css' not in render('base.html').data
def test_render_with_theme_defaults_affects_html(app):
"""Test that the base themes are all that's presented in the HTML."""
# test we can remove stuff from the default
with app.test_request_context():
assert b'?style=light' in render('base.html').data
assert b'?style=dark' in render('base.html').data
assert b'?style=plain' in render('base.html').data
def test_render_with_theme_overrides_affects_html(app):
"""Test that the overridden themes are presented in the HTML."""
# test we can remove stuff from the default
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'PAGE_STYLES': {'light': '/static/css/light.css'}})
with restyled_app.test_request_context():
assert b'?style=light' in render('base.html').data
assert b'?style=dark' not in render('base.html').data
assert b'?style=plain' not in render('base.html').data
# test that we can add new stuff too/instead
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'PAGE_STYLES': {'cool': '/static/css/cool.css',
'warm': '/static/css/warm.css'},
'DEFAULT_PAGE_STYLE': 'warm'})
with restyled_app.test_request_context():
assert b'?style=cool' in render('base.html').data
assert b'?style=warm' in render('base.html').data
def test_render_with_theme_overrides(app):
"""Test that the loaded themes can be overridden from the default."""
cookie = dump_cookie("user-style", 'cool')
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'PAGE_STYLES': {'cool': '/static/css/cool.css',
'warm': '/static/css/warm.css'}})
with restyled_app.test_request_context(headers={'COOKIE': cookie}):
assert b'/static/css/cool.css' in render('base.html').data
assert b'/static/css/warm.css' not in render('base.html').data
def test_render_with_theme_overrides_not_found_is_default(app):
"""Test that theme overrides work, and if a requested theme doesn't exist, the default is loaded."""
cookie = dump_cookie("user-style", 'nonexistent')
restyled_app = create_app(instance_path=os.path.join(HERE, 'instance'),
test_config={'PAGE_STYLES': {'cool': '/static/css/cool.css',
'warm': '/static/css/warm.css'},
'DEFAULT_PAGE_STYLE': 'warm'})
with restyled_app.test_request_context(headers={'COOKIE': cookie}):
assert b'/static/css/warm.css' in render('base.html').data
assert b'/static/css/nonexistent.css' not in render('base.html').data
assert b'light.css' in render('base.html').data
assert b'dark.css' not in render('base.html').data
def test_request_path_to_instance_resource_path(app):

View File

@ -56,10 +56,9 @@ commands =
# run security checks
#
# again it seems the most valuable here to run against the packaged code
# 51457 is nearly a red herring that I'm stuck with because tox is pinned, try removing occasionally
commands =
bandit {envsitepackagesdir}/incorporealcms/ -r
safety check -r requirements/requirements-dev.txt -i 51457
safety check -r requirements/requirements-dev.txt
[testenv:lint]
# run style checks