Загрузка глобальных переменных [closes #29]

Загрузка меню по-умолчанию через события [closes #27]
master
Антон Касимов 2019-05-21 20:31:11 +03:00
parent 95c18e3e3f
commit 9e5af50a4b
19 changed files with 132 additions and 112 deletions

View File

@ -5,26 +5,26 @@ from pyramid.path import AssetResolver
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from flatfilecms.resources import Root
from flatfilecms.resources import Root, root_factory, roots
class PagesEventHandler(FileSystemEventHandler):
def __init__(self, root, root_path):
def __init__(self, root_path):
super(PagesEventHandler, self).__init__()
self.root = root
self.root_path = root_path
self.abspath = AssetResolver().resolve(root_path).abspath()
def find_dir(self, path):
folder = self.root
path = path.relative_to(self.root_path)
folder = roots.setdefault(self.root_path, Root(self.root_path))
path = path.relative_to(self.abspath)
for name in path.parts[:-1]:
folder = folder[name]
return folder
def on_created(self, event):
super(PagesEventHandler, self).on_created(event)
path = Path(event.dest_path if hasattr(event, 'dest_path')
else event.src_path)
path = Path(
event.dest_path if hasattr(event, 'dest_path') else event.src_path)
folder = self.find_dir(path)
if event.is_directory:
folder.create_dir(path)
@ -51,30 +51,24 @@ def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
from pyramid.settings import asbool
watchdog = asbool(settings.get(
'watchdog', 'false'))
watchdog = asbool(settings.get('watchdog', 'false'))
settings['watchdog'] = watchdog
settings.setdefault('pages_path', 'pages')
settings.setdefault('data_path', 'data')
default_jinja2_filters = """markdown = flatfilecms.filters:markdown2html
fileglob = flatfilecms.filters:fileglob
merge_dict = flatfilecms.filters.merge_dict
join_url = flatfilecms.filters.join_url"""
settings['jinja2.filters'] = default_jinja2_filters + settings.get(
'jinja2.filters', '')
pages = Root(settings['pages_path'], settings['data_path'])
def factory(request):
return pages
config = Configurator(settings=settings,
root_factory=factory)
config = Configurator(settings=settings, root_factory=root_factory)
config.include('pyramid_jinja2')
config.include('.routes')
config.scan()
if watchdog:
path = AssetResolver().resolve(
settings['pages_path']).abspath()
observer = Observer()
event_handler = PagesEventHandler(pages, path)
observer.schedule(
event_handler,
path,
recursive=True)
event_handler = PagesEventHandler(settings['pages_path'])
observer.schedule(event_handler, event_handler.abspath, recursive=True)
observer.start()
return config.make_wsgi_app()

View File

@ -4,6 +4,8 @@ from datetime import date
from pyramid.events import subscriber
from pyramid.events import BeforeRender
from .models import load_yaml
@subscriber(BeforeRender)
def add_global(event):
@ -13,3 +15,19 @@ def add_global(event):
'format_datetime': dates.format_datetime,
'today': date.today(),
}
@subscriber(BeforeRender)
def add_data(event):
try:
event['data'] = load_yaml('globals.yaml', True)
except FileNotFoundError:
event['data'] = {}
@subscriber(BeforeRender)
def add_menu(event):
try:
event['menu'] = load_yaml('menu/default.yaml', True)
except FileNotFoundError:
event['menu'] = []

View File

@ -27,19 +27,17 @@ class Jinja2Processor(BlockProcessor):
class IgnoreExtension(Extension):
def extendMarkdown(self, md, md_globals):
md.parser.blockprocessors.add(
'jinja2',
Jinja2Processor(md.parser),
">hashheader")
md.parser.blockprocessors.add('jinja2', Jinja2Processor(md.parser),
">hashheader")
@contextfilter
def markdown2html(context, text, render=True):
result = markdown.markdown(
text,
extensions=[IgnoreExtension(), 'markdown.extensions.extra'],
output_format='html5',
tab_length=2)
text,
extensions=[IgnoreExtension(), 'markdown.extensions.extra'],
output_format='html5',
tab_length=2)
if render:
result = context.environment.from_string(result).render(context)
if context.eval_ctx.autoescape:
@ -55,3 +53,7 @@ def merge_dict(a, b):
def fileglob(path, root):
return [p.relative_to(root) for p in Path(root).glob(path)]
def join_url(a, b):
return a.rstrip('/') + '/' + b.lstrip('/')

View File

@ -5,6 +5,7 @@ import markdown
from datetime import datetime
from pyramid.path import AssetResolver
from zope.interface import (Interface, implementer)
from pyramid.threadlocal import get_current_registry
from flatfilecms.filters import IgnoreExtension
@ -67,6 +68,27 @@ class CustomYAMLHandler(frontmatter.YAMLHandler):
return yaml.load(fm, self.loader)
def load_yaml(path, data=False):
registry = get_current_registry()
settings = registry.settings
data_path = settings['data_path']
if data:
path = str(PurePath(data_path) / path)
return yaml.load(
AssetResolver().resolve(path).stream(),
LoaderFactory(data_path))
def load_frontmatter(path):
registry = get_current_registry()
settings = registry.settings
data_path = settings['data_path']
return frontmatter.load(
AssetResolver().resolve(path).stream(),
handler=CustomYAMLHandler(data_path),
).to_dict()
class HiddenFile(Exception):
pass
@ -76,8 +98,6 @@ class Folder(dict):
self.path = path
self.name = name
self.__parent__ = parent
if parent is not None:
self.data_path = parent.data_path
for entry in AssetResolver().resolve(path).listdir():
asset = f"{path}/{entry}"
if AssetResolver().resolve(asset).isdir():
@ -124,7 +144,6 @@ class Document(object):
self.name = name
self.__parent__ = parent
self.path = path
self.data_path = parent.data_path
class IMarkdown(Interface):
@ -135,25 +154,19 @@ class IMarkdown(Interface):
class Markdown(Document):
def __init__(self, name, parent, path):
super(Markdown, self).__init__('', parent, path)
self.page = frontmatter.load(
AssetResolver().resolve(self.path).stream(),
handler=CustomYAMLHandler(self.data_path),
).to_dict()
self.page = load_frontmatter(path)
if 'published' in self.page and \
str(datetime.now()) < str(self.page['published']):
self.page['published'] > datetime.now():
raise HiddenFile
@implementer(IMarkdown)
class YAML(Document):
def __init__(self, name, parent, path):
super(YAML, self).__init__('', parent, path)
self.page = yaml.load(
AssetResolver().resolve(self.path).stream(),
LoaderFactory(self.data_path))
self.page = load_yaml(path)
if 'published' in self.page and \
str(datetime.now()) < str(self.page['published']):
self.page['published'] > datetime.now():
raise HiddenFile

View File

@ -1,23 +1,14 @@
from flatfilecms.models import Folder
def flat(d, path):
structure = []
for k, v in d.items():
if isinstance(v, dict):
structure.extend(flat(v, f"{path}/{k}"))
else:
if k == 'index':
structure.append(f"{path}/")
else:
structure.append(f"{path}/{k}")
return structure
class Root(Folder):
def __init__(self, path, data_path):
self.data_path = data_path
def __init__(self, path):
super(Root, self).__init__('', None, path)
def structure(self, base_dir=''):
return flat(self, base_dir)
roots = {}
def root_factory(request):
path = request.registry.settings['pages_path']
return roots.setdefault(path, Root(path))

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{data.base_url}}">
<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text">{{title}}</title>
{%- if subtitle %}
<subtitle type="html">{{subtitle}}</subtitle>
{%- endif %}
<link rel="self" href="{{request.path_qs}}"/>
<link rel="self" href="{{data.base_url|join_url(request.path_qs)}}"/>
{%- if 'links' in feed and feed['index'] %}
<link rel="alternate" type="text/html" hreflang="{{request.locale_name}}" href="{{feed['links']['index']}}"/>
<link rel="alternate" type="text/html" hreflang="{{request.locale_name}}" href="{{data.base_url|join_url(feed['links']['index'])}}"/>
{%- endif %}
<updated>{{(pages[0][1].page['updated'] or pages[0][1].page['published']).isoformat()}}+00:00</updated>
<id>urn:uuid:{{feed['id']}}</id>
@ -20,7 +20,7 @@
{%- for item in pages %}
<entry>
<title>{{item[1].page['title']}}</title>
<link href="{{item[0]}}"/>
<link href="{{data.base_url|join_url(item[0])}}"/>
<id>urn:uuid:{{item[1].page['id']}}</id>
<updated>{{(item[1].page['updated'] or item[1].page['published']).isoformat()}}+00:00</updated>
<published>{{item[1].page['published'].isoformat()}}+00:00</published>

View File

@ -2,7 +2,7 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for item, lastmod in pages %}
<url>
<loc>{{data.base_url}}/{{item}}</loc>
<loc>{{data['base_url']|join_url(item)}}</loc>
{%- if lastmod %}
<lastmod>{{lastmod}}</lastmod>
{%- endif %}

View File

@ -1,9 +1,5 @@
import re
import yaml
from pyramid.renderers import render_to_response
from pyramid.path import AssetResolver
from pathlib import PurePath
from flatfilecms.models import LoaderFactory
from pyramid.httpexceptions import HTTPFound
from pyramid.traversal import (find_root, find_resource)
@ -30,12 +26,6 @@ def blog(self, options={}):
post = self.context.page.copy()
post['pages'] = sorted(
postlist, key=lambda t: t[1].page['published'], reverse=True)
if 'menu' not in post:
post['menu'] = yaml.load(
AssetResolver().resolve(
str(PurePath(self.context.data_path) /
'menu/default.yaml')).stream(),
LoaderFactory(self.context.data_path))
if format == 'atom':
set_content_type(self, 'application/atom+xml')
return render_to_response(

View File

@ -6,11 +6,8 @@ from pyramid.renderers import render_to_response
from pyramid.httpexceptions import HTTPNotFound
from pyramid.response import FileResponse
from pyramid.path import (DottedNameResolver, AssetResolver)
from pathlib import PurePath
import yaml
from flatfilecms.models import (Folder, Document, IMarkdown, Jinja2,
LoaderFactory)
from flatfilecms.models import (Folder, Document, IMarkdown, Jinja2)
class PagesView:
@ -55,13 +52,6 @@ class PagesView:
if response:
return response
post = self.context.page
if 'menu' not in post:
post['menu'] = yaml.load(
AssetResolver().resolve(
str(
PurePath(self.context.data_path) /
'menu/default.yaml')).stream(),
LoaderFactory(self.context.data_path))
return render_to_response(
'{0}.jinja2'.format(post.get('template', 'default')),
post,

View File

@ -1,9 +1,27 @@
import pytest
@pytest.fixture
def current_directory():
from pathlib import Path
return Path(__file__).parent
@pytest.fixture
def config(current_directory):
from pyramid.testing import testConfig
settings = {
'data_path': current_directory / 'data',
'jinja2.filters': 'join_url = flatfilecms.filters.join_url'
}
with testConfig(settings=settings) as config:
config.include('pyramid_jinja2')
config.add_jinja2_search_path('tests:templates')
config.add_jinja2_search_path('flatfilecms:templates')
yield config
@pytest.fixture
def fake_request():
from pyramid.testing import DummyRequest
return DummyRequest()

1
tests/data/globals.yaml Normal file
View File

@ -0,0 +1 @@
base_url: https://flatfilecms.test/

View File

@ -2,7 +2,7 @@
title: Блоговая запись 1 в формате markdown
description: Тест записей блогов
template: blogpost
published: 2019-02-01
published: 2019-02-01T12:00:00+00:00
---
Hello World!

View File

@ -1,4 +1,4 @@
title: Блоговая запись 2 в формате YAML
template: blogpost
published: 2019-02-02
published: 2019-02-02T11:00:00+3:00
content: Hello World! 2

View File

@ -2,7 +2,7 @@
title: Блоговая запись 1 в формате markdown
description: Тест записей блогов
template: blogpost
published: 3019-02-01
published: 3019-02-01T23:59:59+0:00
---
Hello World!

2
tests/pages/menu.yaml Normal file
View File

@ -0,0 +1,2 @@
template: tests:templates/test_menu
menu: 'Menu - OK'

View File

@ -3,4 +3,4 @@ view:
ignore:
- blog/.*$
data:
base_url: https://flatfilecms.test
base_url: https://flatfilecms.test/

View File

@ -0,0 +1 @@
{{menu}}

View File

@ -5,3 +5,11 @@ def test_globals(fake_request):
assert 'globals' in event
assert 'format_date' in event['globals']
assert 'format_datetime' in event['globals']
def test_data(config):
from flatfilecms.events import add_data
event = {}
add_data(event)
assert 'data' in event
assert event['data']['base_url'] == 'https://flatfilecms.test/'

View File

@ -2,27 +2,10 @@ import pytest
@pytest.fixture
def current_directory():
from pathlib import Path
return Path(__file__).parent
@pytest.fixture
def pages(current_directory):
def pages(current_directory, config):
from flatfilecms.resources import Root
return Root(
str(current_directory / 'pages'),
str(current_directory / 'data'))
@pytest.fixture
def config(current_directory):
from pyramid.testing import testConfig
with testConfig() as config:
config.include('pyramid_jinja2')
config.add_jinja2_search_path('tests:templates')
config.add_jinja2_search_path('flatfilecms:templates')
yield config
str(current_directory / 'pages'))
def test_loading(pages):
@ -83,5 +66,14 @@ def test_sitemap(pages, fake_request, config):
<url>
<loc>https://flatfilecms.test/view-with-options</loc>
</url>
<url>
<loc>https://flatfilecms.test/menu</loc>
</url>
</urlset>"""
assert response.content_type == 'text/xml'
def test_menu(pages, fake_request, config):
from flatfilecms.views.pages import PagesView
response = PagesView(pages['menu'], fake_request).process_yaml()
assert response.text == 'Menu - OK'