Загрузка глобальных переменных [closes #29]
Загрузка меню по-умолчанию через события [closes #27]master
parent
95c18e3e3f
commit
9e5af50a4b
|
@ -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()
|
||||
|
|
|
@ -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'] = []
|
||||
|
|
|
@ -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('/')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
base_url: https://flatfilecms.test/
|
|
@ -2,7 +2,7 @@
|
|||
title: Блоговая запись 1 в формате markdown
|
||||
description: Тест записей блогов
|
||||
template: blogpost
|
||||
published: 2019-02-01
|
||||
published: 2019-02-01T12:00:00+00:00
|
||||
---
|
||||
|
||||
Hello World!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
title: Блоговая запись 1 в формате markdown
|
||||
description: Тест записей блогов
|
||||
template: blogpost
|
||||
published: 3019-02-01
|
||||
published: 3019-02-01T23:59:59+0:00
|
||||
---
|
||||
|
||||
Hello World!
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
template: tests:templates/test_menu
|
||||
menu: 'Menu - OK'
|
|
@ -3,4 +3,4 @@ view:
|
|||
ignore:
|
||||
- blog/.*$
|
||||
data:
|
||||
base_url: https://flatfilecms.test
|
||||
base_url: https://flatfilecms.test/
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{{menu}}
|
|
@ -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/'
|
||||
|
|
|
@ -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'
|
||||
|
|
Reference in New Issue