django-markdown-view 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_markdown_view-0.0.6.dist-info/METADATA +371 -0
- django_markdown_view-0.0.6.dist-info/RECORD +42 -0
- django_markdown_view-0.0.6.dist-info/WHEEL +5 -0
- django_markdown_view-0.0.6.dist-info/licenses/LICENSE +27 -0
- django_markdown_view-0.0.6.dist-info/top_level.txt +1 -0
- markdown_view/__init__.py +1 -0
- markdown_view/apps.py +12 -0
- markdown_view/checks.py +46 -0
- markdown_view/constants.py +13 -0
- markdown_view/escaping.py +82 -0
- markdown_view/loaders.py +61 -0
- markdown_view/loading.py +57 -0
- markdown_view/markdown_extensions.py +19 -0
- markdown_view/models.py +0 -0
- markdown_view/registry.py +387 -0
- markdown_view/templates/markdown_view/markdown.html +28 -0
- markdown_view/tests/__init__.py +0 -0
- markdown_view/tests/fixtures/loaders_a/readme.md +4 -0
- markdown_view/tests/fixtures/loaders_b/readme.md +4 -0
- markdown_view/tests/settings/__init__.py +0 -0
- markdown_view/tests/settings/coveralls_settings.py +74 -0
- markdown_view/tests/test_checks.py +38 -0
- markdown_view/tests/test_escaping.py +75 -0
- markdown_view/tests/test_loaders.py +68 -0
- markdown_view/tests/test_loading.py +37 -0
- markdown_view/tests/test_markdown_extensions.py +36 -0
- markdown_view/tests/test_registry.py +467 -0
- markdown_view/tests/test_settings_validation.py +76 -0
- markdown_view/tests/test_views.py +280 -0
- markdown_view/tests/testapp/CONTEXT_TEST.md +3 -0
- markdown_view/tests/testapp/ESCAPING_TEST.md +14 -0
- markdown_view/tests/testapp/LOGGED_IN.md +3 -0
- markdown_view/tests/testapp/NESTED.md +5 -0
- markdown_view/tests/testapp/NO_HEADERS.md +4 -0
- markdown_view/tests/testapp/README.md +35 -0
- markdown_view/tests/testapp/STAFF.md +3 -0
- markdown_view/tests/testapp/UNROUTED.md +4 -0
- markdown_view/tests/testapp/__init__.py +0 -0
- markdown_view/tests/testapp/apps.py +6 -0
- markdown_view/tests/testapp/docs/OTHER.md +8 -0
- markdown_view/tests/urls.py +118 -0
- markdown_view/views.py +145 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-markdown-view
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Serve .md pages as Django views.
|
|
5
|
+
Author-email: "Ryan J. Sullivan" <ryanj@ryanjsullivan.com>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, http://github.com/rgs258/django-markdown-view
|
|
8
|
+
Keywords: django,markdown,markdown view,md
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Framework :: Django :: 5.2
|
|
11
|
+
Classifier: Framework :: Django :: 6.0
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/x-rst
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: django>=5.2
|
|
26
|
+
Requires-Dist: markdown>=3.2
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
Django Markdown View
|
|
30
|
+
====================
|
|
31
|
+
|
|
32
|
+
.. image:: https://github.com/rgs258/django-markdown-view/actions/workflows/test.yml/badge.svg
|
|
33
|
+
:target: https://github.com/rgs258/django-markdown-view/actions/workflows/test.yml
|
|
34
|
+
|
|
35
|
+
.. image:: https://coveralls.io/repos/github/rgs258/django-markdown-view/badge.svg?branch=master
|
|
36
|
+
:target: https://coveralls.io/github/rgs258/django-markdown-view?branch=master
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
**Serve .md pages as Django views.**
|
|
40
|
+
|
|
41
|
+
This package aims to make it easy to serve .md files on Django sites.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
.. contents:: Contents
|
|
45
|
+
:depth: 5
|
|
46
|
+
|
|
47
|
+
Installation
|
|
48
|
+
------------
|
|
49
|
+
|
|
50
|
+
#. Install with ``pip install django-markdown-view``.
|
|
51
|
+
|
|
52
|
+
#. Add ``'markdown_view'`` to your ``INSTALLED_APPS`` settings.
|
|
53
|
+
|
|
54
|
+
.. code-block:: python
|
|
55
|
+
|
|
56
|
+
INSTALLED_APPS = [
|
|
57
|
+
...,
|
|
58
|
+
'markdown_view',
|
|
59
|
+
...
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
#. (OPTIONAL) Add ``MARKDOWN_VIEW_BASE_DIR`` or ``BASE_DIR`` to settings
|
|
63
|
+
The dictionary of the application's base. See Settings_ below
|
|
64
|
+
|
|
65
|
+
For example, if settings are in config/settings/base.py, then:
|
|
66
|
+
|
|
67
|
+
.. code-block:: python
|
|
68
|
+
|
|
69
|
+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Usage
|
|
73
|
+
-----
|
|
74
|
+
|
|
75
|
+
Views
|
|
76
|
+
~~~~~
|
|
77
|
+
|
|
78
|
+
Use one of ``MarkdownView``, ``LoggedInMarkdownView``, or ``StaffMarkdownView``
|
|
79
|
+
from ``markdown_view.views`` to serve a .md file
|
|
80
|
+
|
|
81
|
+
.. code-block:: python
|
|
82
|
+
|
|
83
|
+
from markdown_view.views import StaffMarkdownView
|
|
84
|
+
|
|
85
|
+
path('readme/',
|
|
86
|
+
StaffMarkdownView.as_view(file_name='my_app/README.md'),
|
|
87
|
+
name="readme"),
|
|
88
|
+
|
|
89
|
+
Settings
|
|
90
|
+
~~~~~~~~
|
|
91
|
+
|
|
92
|
+
All settings are optional. See `<markdown_view/constants.py>`_ for the defaults.
|
|
93
|
+
|
|
94
|
+
* `MARKDOWN_VIEW_BASE_DIR` and `BASE_DIR`
|
|
95
|
+
|
|
96
|
+
When present, the value is taken as a location to append to the list of dirs that
|
|
97
|
+
Django's `django.template.utils.get_app_template_dirs` will return when passed
|
|
98
|
+
`dirname=""`. This is used to locate .md files in the root of the project, e.g.,
|
|
99
|
+
a README.md file. Looks for `BASE_DIR` if `MARKDOWN_VIEW_BASE_DIR` is not found.
|
|
100
|
+
|
|
101
|
+
* `MARKDOWN_VIEW_LOADERS`
|
|
102
|
+
|
|
103
|
+
A list of loaders that locate .md files. The default list includes only
|
|
104
|
+
`markdown_view.loaders.MarkdownLoader` which will by default try to load .md files
|
|
105
|
+
from root directories in INSTALLED_APPS packages much the same as Django's
|
|
106
|
+
`django.template.loaders.app_directories.Loader` looks to load from "templates".
|
|
107
|
+
|
|
108
|
+
* `MARKDOWN_VIEW_LOADER_TEMPLATES_DIR`
|
|
109
|
+
|
|
110
|
+
The name of the directories in INSTALLED_APPS packages in which to locate .md
|
|
111
|
+
files. Defaults to "" in order to locate .md filed in the root directories.
|
|
112
|
+
|
|
113
|
+
* `MARKDOWN_VIEW_EXTENSIONS`
|
|
114
|
+
|
|
115
|
+
The extensions to enable. These extensions are enabled be default:
|
|
116
|
+
|
|
117
|
+
* `toc`:
|
|
118
|
+
generates a Table of Contents. If `toc` is removed from extensions, then
|
|
119
|
+
`MARKDOWN_VIEW_TEMPLATE_USE_TOC` must be set to False.
|
|
120
|
+
|
|
121
|
+
* `tables`:
|
|
122
|
+
enables tables.
|
|
123
|
+
|
|
124
|
+
* `fenced_code`:
|
|
125
|
+
enables code blocks. If `fenced_code` is removed from extensions, then
|
|
126
|
+
`MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS`, which provides the highlighting for
|
|
127
|
+
code blocks, can be disabled.
|
|
128
|
+
|
|
129
|
+
* `markdown_view.markdown_extensions.ImageExtension`:
|
|
130
|
+
makes images responsive in bootstrap4.
|
|
131
|
+
|
|
132
|
+
See https://python-markdown.github.io/extensions/ and
|
|
133
|
+
https://github.com/Python-Markdown/markdown/wiki/Third-Party-Extensions for more
|
|
134
|
+
extensions.
|
|
135
|
+
|
|
136
|
+
You can create your own extensions by following
|
|
137
|
+
https://github.com/Python-Markdown/markdown/wiki/Tutorial-1---Writing-Extensions-for-Python-Markdown
|
|
138
|
+
|
|
139
|
+
* `MARKDOWN_VIEW_TEMPLATE`
|
|
140
|
+
|
|
141
|
+
The Django template that'll be used to render the HTML that is generated from the
|
|
142
|
+
Markdown. Set your own template to style your pages. Context includes:
|
|
143
|
+
|
|
144
|
+
* `markdown_content`:
|
|
145
|
+
The HTML produced from the Markdown.
|
|
146
|
+
|
|
147
|
+
* `use_highlight_js`:
|
|
148
|
+
If highlight.js is enabled.
|
|
149
|
+
|
|
150
|
+
* `use_toc`:
|
|
151
|
+
If the table of contents should be rendered.
|
|
152
|
+
|
|
153
|
+
* `markdown_toc`:
|
|
154
|
+
A table of contents from the headers of the Markdown. Not set when `use_toc`
|
|
155
|
+
is False.
|
|
156
|
+
|
|
157
|
+
* `page_title`:
|
|
158
|
+
A guess at a page title, for now it's the first row of the TOC. Not set when
|
|
159
|
+
`use_toc` is False, or when the Markdown has no headers for the TOC to be
|
|
160
|
+
built from.
|
|
161
|
+
|
|
162
|
+
* `MARKDOWN_VIEW_TEMPLATE_USE_TOC`
|
|
163
|
+
|
|
164
|
+
Whether to render the TOC. If false, in the template context, `use_toc` is False
|
|
165
|
+
and `markdown_toc` and `page_title` are not present.
|
|
166
|
+
|
|
167
|
+
* `MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS`
|
|
168
|
+
|
|
169
|
+
Whether to load and activate the highlight.js library in the template.
|
|
170
|
+
|
|
171
|
+
* `MARKDOWN_VIEW_REWRITE_INTERNAL_LINKS`
|
|
172
|
+
|
|
173
|
+
Defaults to `True`. Markdown source files commonly link to other `.md`
|
|
174
|
+
files using plain, filesystem-relative links (e.g.
|
|
175
|
+
``[See also](../README-cron.md)``), exactly as they'd resolve on GitHub
|
|
176
|
+
or in a local editor. Since a rendered page is served at its *view's*
|
|
177
|
+
URL rather than at the source file's location on disk, such a link
|
|
178
|
+
would otherwise 404 in the browser.
|
|
179
|
+
|
|
180
|
+
When enabled, `MarkdownView` looks up whether any URL pattern in the
|
|
181
|
+
project serves the linked `.md` file via a `MarkdownView` (or
|
|
182
|
+
subclass), and if so, rewrites the link to that view's actual URL
|
|
183
|
+
instead of leaving the raw relative file path in place. See
|
|
184
|
+
`markdown_view/registry.py <markdown_view/registry.py>`_ for the
|
|
185
|
+
resolution logic. This is best-effort: links to files that aren't
|
|
186
|
+
routed anywhere, or whose route is behind a required URL argument, are
|
|
187
|
+
left exactly as Markdown would otherwise have rendered them -- nothing
|
|
188
|
+
breaks if a linked file simply isn't routed, unless
|
|
189
|
+
`MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT` is also configured (see below).
|
|
190
|
+
Set to `False` to disable this rewriting entirely, including the
|
|
191
|
+
`MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT` fallback.
|
|
192
|
+
|
|
193
|
+
* `MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT`
|
|
194
|
+
|
|
195
|
+
Defaults to `None` (disabled). Relative links that `MARKDOWN_VIEW_REWRITE_INTERNAL_LINKS`
|
|
196
|
+
can't resolve to a registered `MarkdownView` route -- including links to
|
|
197
|
+
files other than `.md`, such as repository-relative links to source
|
|
198
|
+
code (e.g. ``[`example.module.helper`](src/example/module.py)``) --
|
|
199
|
+
are left unchanged by default. Setting this to a root URL instead
|
|
200
|
+
resolves such links against it, e.g.:
|
|
201
|
+
|
|
202
|
+
.. code-block:: python
|
|
203
|
+
|
|
204
|
+
MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT = (
|
|
205
|
+
"https://git.example.com/example/project/-/blob/main/"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
With the setting above, ``[`example.module.helper`](src/example/module.py)``
|
|
209
|
+
would render as a link to
|
|
210
|
+
``https://git.example.com/example/project/-/blob/main/src/example/module.py``.
|
|
211
|
+
Registered-route rewriting always takes precedence over this fallback;
|
|
212
|
+
it only applies once a link has already failed to resolve to a route.
|
|
213
|
+
`./` and `../` path segments, and any query string or `#fragment`, are
|
|
214
|
+
resolved/preserved correctly. Absolute URLs, root-relative URLs,
|
|
215
|
+
fragment-only links, and URLs with an explicit scheme are never
|
|
216
|
+
affected by this setting.
|
|
217
|
+
|
|
218
|
+
* `MARKDOWN_VIEW_ESCAPE_UNSAFE_TEMPLATE_SYNTAX`
|
|
219
|
+
|
|
220
|
+
Defaults to `True`. `MarkdownView` re-parses the HTML converted from
|
|
221
|
+
Markdown as a Django template, so that the `{% static %}` tags
|
|
222
|
+
`ImageExtension` injects for image sources (and, when
|
|
223
|
+
`MARKDOWN_VIEW_USE_REQUEST_CONTEXT`/`MARKDOWN_VIEW_EXTRA_CONTEXT` are
|
|
224
|
+
used, any `{{ variable }}` reference to a real context key) actually
|
|
225
|
+
resolve. Markdown source files are prose, though, and prose about
|
|
226
|
+
Django itself routinely contains literal `{% ... %}`/`{{ ... }}` text
|
|
227
|
+
that was never meant to be executed as a template -- left unescaped,
|
|
228
|
+
an unrecognized `{% ... %}` block raises `TemplateSyntaxError` (a hard
|
|
229
|
+
500 for the whole page), and an unrecognized `{{ ... }}` variable is
|
|
230
|
+
silently rendered as an empty string (silent content loss, with no
|
|
231
|
+
error at all).
|
|
232
|
+
|
|
233
|
+
When enabled (the default), every `{% ... %}`/`{{ ... }}` span in the
|
|
234
|
+
converted HTML is escaped to inert literal text *except* the exact
|
|
235
|
+
`{% static '...' %}` tags `ImageExtension` injects, and bare
|
|
236
|
+
`{{ identifier }}` references to a name actually present in the
|
|
237
|
+
context the page renders with. See
|
|
238
|
+
`markdown_view/escaping.py <markdown_view/escaping.py>`_ for the exact
|
|
239
|
+
rules. Set to `False` to restore the previous, fully-permissive
|
|
240
|
+
behavior (the whole converted document is parsed as a real Django
|
|
241
|
+
template, with no escaping at all).
|
|
242
|
+
|
|
243
|
+
Experimental Settings
|
|
244
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
245
|
+
|
|
246
|
+
* `MARKDOWN_VIEW_USE_REQUEST_CONTEXT`
|
|
247
|
+
|
|
248
|
+
If the request context should be used as a base when creating the context with
|
|
249
|
+
which to render the Markdown internally. This is because the Markdown is rendered
|
|
250
|
+
once first in order to prepend it with `{% load static %}`.
|
|
251
|
+
This is not well tested; YMMV. Note that a `{{ name }}` reference only
|
|
252
|
+
resolves against this context if `MARKDOWN_VIEW_ESCAPE_UNSAFE_TEMPLATE_SYNTAX`
|
|
253
|
+
(see above) recognizes `name` as a safe context key -- otherwise it's
|
|
254
|
+
escaped to literal text rather than looked up.
|
|
255
|
+
|
|
256
|
+
* `MARKDOWN_VIEW_EXTRA_CONTEXT`
|
|
257
|
+
|
|
258
|
+
Any extra context to send to the internal render of the Markdown. Can be used
|
|
259
|
+
to expose context to template tags embedded in the Markdown.
|
|
260
|
+
This is not well tested; YMMV. As with `MARKDOWN_VIEW_USE_REQUEST_CONTEXT`
|
|
261
|
+
above, a `{{ name }}` reference to one of these keys is only left live
|
|
262
|
+
when `MARKDOWN_VIEW_ESCAPE_UNSAFE_TEMPLATE_SYNTAX` recognizes it as safe.
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
Implementation
|
|
266
|
+
--------------
|
|
267
|
+
|
|
268
|
+
At a high level, `MarkdownView` will:
|
|
269
|
+
|
|
270
|
+
#. Use a template loader to locate `.md` given as `file_name`
|
|
271
|
+
|
|
272
|
+
#. Convert the Markdown to HTML
|
|
273
|
+
|
|
274
|
+
#. If `MARKDOWN_VIEW_REWRITE_INTERNAL_LINKS` is enabled, rewrite any
|
|
275
|
+
relative links to other `.md` files that resolve to a file served by a
|
|
276
|
+
registered `MarkdownView` route, to that route's actual URL; any
|
|
277
|
+
relative link left unresolved by that step is instead resolved against
|
|
278
|
+
`MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT`, if configured
|
|
279
|
+
|
|
280
|
+
#. Render as a template, the resulting HTML prepended with
|
|
281
|
+
`{% load static %}`, into several context variables
|
|
282
|
+
|
|
283
|
+
#. Serve the `MARKDOWN_VIEW_TEMPLATE` with the context variables
|
|
284
|
+
|
|
285
|
+
Release Notes and Contributors
|
|
286
|
+
------------------------------
|
|
287
|
+
|
|
288
|
+
* `Release notes <https://github.com/rgs258/django-markdown-view/releases>`_
|
|
289
|
+
* `Our wonderful contributors <https://github.com/rgs258/django-markdown-view/graphs/contributors>`_
|
|
290
|
+
|
|
291
|
+
Releasing
|
|
292
|
+
---------
|
|
293
|
+
|
|
294
|
+
Versions are derived from git tags via `setuptools_scm`_, so there is no
|
|
295
|
+
hardcoded ``version`` to bump.
|
|
296
|
+
|
|
297
|
+
To publish a new release to PyPI:
|
|
298
|
+
|
|
299
|
+
#. Merge the changes for the release into ``master``.
|
|
300
|
+
|
|
301
|
+
#. `Create a GitHub Release`_ with a tag matching the desired version
|
|
302
|
+
(e.g. ``0.0.6``).
|
|
303
|
+
|
|
304
|
+
#. Publishing the release triggers the `Publish to PyPI`_ GitHub Actions
|
|
305
|
+
workflow, which builds the sdist/wheel and uploads them to PyPI using
|
|
306
|
+
`PyPI Trusted Publishing`_ (no stored token required).
|
|
307
|
+
|
|
308
|
+
Publishing beta / pre-releases
|
|
309
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
310
|
+
|
|
311
|
+
Beta and other pre-releases can be published to PyPI the same way as a
|
|
312
|
+
regular release, and this is standard practice: PyPI understands
|
|
313
|
+
`PEP 440`_ pre-release version identifiers and will not let a plain
|
|
314
|
+
``pip install django-markdown-view`` pick them up by default, so betas are
|
|
315
|
+
safe to publish without risking accidental adoption.
|
|
316
|
+
|
|
317
|
+
#. Tag the release commit with an explicit PEP 440 pre-release suffix
|
|
318
|
+
instead of a plain version, e.g. ``0.0.6b1`` for a beta, or ``0.0.6rc1``
|
|
319
|
+
for a release candidate. ``setuptools_scm`` will pick up the tag as-is.
|
|
320
|
+
|
|
321
|
+
#. `Create a GitHub Release`_ for that tag and check the **"Set as a
|
|
322
|
+
pre-release"** box. This only affects how the release is labeled on
|
|
323
|
+
GitHub; PyPI determines pre-release status purely from the version
|
|
324
|
+
string.
|
|
325
|
+
|
|
326
|
+
#. Publishing the release triggers the same `Publish to PyPI`_ workflow,
|
|
327
|
+
which uploads the pre-release to PyPI. It will appear in the release
|
|
328
|
+
history but will not be installed by ``pip install
|
|
329
|
+
django-markdown-view``.
|
|
330
|
+
|
|
331
|
+
#. Users who want to try it must opt in, e.g. ``pip install --pre
|
|
332
|
+
django-markdown-view`` or by pinning the exact version, such as
|
|
333
|
+
``pip install django-markdown-view==0.0.6b1``.
|
|
334
|
+
|
|
335
|
+
#. If further fixes are needed, bump the pre-release number (``0.0.6b2``,
|
|
336
|
+
``0.0.6b3``, ...) rather than reusing one, since PyPI does not allow
|
|
337
|
+
re-uploading files for a version that has already been published.
|
|
338
|
+
Once the release is stable, tag and publish the final version without
|
|
339
|
+
a suffix (e.g. ``0.0.6``), which then becomes installable by default.
|
|
340
|
+
|
|
341
|
+
.. _setuptools_scm: https://github.com/pypa/setuptools_scm
|
|
342
|
+
.. _`Create a GitHub Release`: https://github.com/rgs258/django-markdown-view/releases/new
|
|
343
|
+
.. _`Publish to PyPI`: https://github.com/rgs258/django-markdown-view/actions/workflows/publish.yml
|
|
344
|
+
.. _`PyPI Trusted Publishing`: https://docs.pypi.org/trusted-publishers/
|
|
345
|
+
.. _`PEP 440`: https://peps.python.org/pep-0440/#pre-releases
|
|
346
|
+
|
|
347
|
+
Contributing
|
|
348
|
+
------------
|
|
349
|
+
|
|
350
|
+
All contributions are very welcomed. Propositions, problems, bugs, and
|
|
351
|
+
enhancement are tracked with `GitHub issues`_ and patches are submitted
|
|
352
|
+
via `pull requests`_.
|
|
353
|
+
|
|
354
|
+
We use `GitHub Actions`_ coupled with `Coveralls`_ as continuous integration tools.
|
|
355
|
+
|
|
356
|
+
.. _`GitHub issues`: https://github.com/rgs258/django-markdown-view/issues
|
|
357
|
+
.. _`pull requests`: https://github.com/rgs258/django-markdown-view/pulls
|
|
358
|
+
.. _`GitHub Actions`: https://github.com/rgs258/django-markdown-view/actions
|
|
359
|
+
.. _Coveralls: https://coveralls.io/github/rgs258/django-markdown-view
|
|
360
|
+
|
|
361
|
+
Requirements
|
|
362
|
+
------------
|
|
363
|
+
|
|
364
|
+
We aspire to support the currently supported versions of Django.
|
|
365
|
+
|
|
366
|
+
**The Tested With section describes aspirational goals.**
|
|
367
|
+
|
|
368
|
+
Tested with:
|
|
369
|
+
|
|
370
|
+
* Python: 3.10, 3.11, 3.12, 3.13
|
|
371
|
+
* Django: 5.2, 6.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
django_markdown_view-0.0.6.dist-info/licenses/LICENSE,sha256=Xun9tu6GoUnr8qy5C3MBQeqJZeuzkw67SQiFzexknls,1542
|
|
2
|
+
markdown_view/__init__.py,sha256=EXOCt-R6NOvU0yLhQcmwQT_U-DVK0TPZYpErkJ6tbko,61
|
|
3
|
+
markdown_view/apps.py,sha256=QwgUCdY8AupkXpT7AOqqigSaPY45bpp7KHeAYvDlMds,332
|
|
4
|
+
markdown_view/checks.py,sha256=z5YHRcwFF-_1vS6citYfLwQA4wFoXp_d7TQg8S_YtiI,1790
|
|
5
|
+
markdown_view/constants.py,sha256=ao2aD9_rgQrCWJRn3sQ7VMHUHOcGcAktW60bmfZJY_k,689
|
|
6
|
+
markdown_view/escaping.py,sha256=zofhvJzLc5qTII_9_IGh5731AWyQ3fCg8CbP-J8tTms,4097
|
|
7
|
+
markdown_view/loaders.py,sha256=dtZ2X01MGfW5cnGMH78jSQ08SATqsv_amPmBM9jOA14,2221
|
|
8
|
+
markdown_view/loading.py,sha256=I-H_d2pwHCmot5Ol9YZ9uDvcMm7qIx6R3yGd74wain0,2749
|
|
9
|
+
markdown_view/markdown_extensions.py,sha256=T_9cRg99vnXyhBA-evqVRRE0kHITx1YitRGJV9IghX8,635
|
|
10
|
+
markdown_view/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
markdown_view/registry.py,sha256=tHOKuCX3fYLauRShxK21hibLLleUVRotlFWUXgJCGqg,18244
|
|
12
|
+
markdown_view/views.py,sha256=qNQlhihNz-bs46aicoApDkn8R6Uowy99WiOu8jMvKvQ,6197
|
|
13
|
+
markdown_view/templates/markdown_view/markdown.html,sha256=QyddBcicEe0CnGQFN66BRCNcUr3SUVXBEqwzLm9NCWQ,1232
|
|
14
|
+
markdown_view/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
markdown_view/tests/test_checks.py,sha256=dh4LYFj6kD_r7rEoPtsqfbqpJ5sOQmpnoQEi2-zkaq4,1584
|
|
16
|
+
markdown_view/tests/test_escaping.py,sha256=JGYO6TG5_-GTSCwUlbqDB5DsmOFgiaq1XRkSCkOKzhU,3282
|
|
17
|
+
markdown_view/tests/test_loaders.py,sha256=w1-WBFcRjTnYjZWFRFcJVWqbT5B8CYvU0oCwJiz7sZY,2583
|
|
18
|
+
markdown_view/tests/test_loading.py,sha256=or4xpgWRo5NztC1GKk4Dgix2DTq5mlsEWFFZNoKDf4Q,1534
|
|
19
|
+
markdown_view/tests/test_markdown_extensions.py,sha256=GHgcI3zeUFi-BisdrHJ8gx_NOXOv10q-vAKjuDuQ_kk,1271
|
|
20
|
+
markdown_view/tests/test_registry.py,sha256=5eRczxgaOEzkkBEA7DDKNUGcSj6A7tp4up7lJVAlL4M,20015
|
|
21
|
+
markdown_view/tests/test_settings_validation.py,sha256=dbelvQ2Z5j5Xu0GrOSQB3bsPI_fyVcpn8LyaNHRFM5w,3645
|
|
22
|
+
markdown_view/tests/test_views.py,sha256=lMWQgwHKAO7U2Hqnv6FZacII_cyjdV-Yf_95xxbdPwg,12058
|
|
23
|
+
markdown_view/tests/urls.py,sha256=Sgidy3YVxHGOsvfIjdB0lrRZBAmBVDgn-7m1TLaqcGA,4040
|
|
24
|
+
markdown_view/tests/fixtures/loaders_a/readme.md,sha256=3ZLpASU8bfOp6GY2ExZPrzlnHfCb3LR8qGNpVC_9iLU,227
|
|
25
|
+
markdown_view/tests/fixtures/loaders_b/readme.md,sha256=NwuNR4ZsdXlmp2wE5GvfVNrkYoRzXcHwtqdvU_t72sA,227
|
|
26
|
+
markdown_view/tests/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
markdown_view/tests/settings/coveralls_settings.py,sha256=JG-heiLhtoiGwxKL0sb77yIFqPeZGMAKaM4FcDxMgTs,2263
|
|
28
|
+
markdown_view/tests/testapp/CONTEXT_TEST.md,sha256=25kLvqZC8pHZ2DUuEUVABV28to3aMzUKsN-e5rDZPGY,72
|
|
29
|
+
markdown_view/tests/testapp/ESCAPING_TEST.md,sha256=rH2gp79OE-7CTtH5eGr77Sjc_NbTAcyaYxaKhi74B9w,418
|
|
30
|
+
markdown_view/tests/testapp/LOGGED_IN.md,sha256=_ycP4KN2-LCpdapSnskA-6VLs6f7nG3WTdmAGn3VDsw,73
|
|
31
|
+
markdown_view/tests/testapp/NESTED.md,sha256=hXAcDFiEwtG0aZatjSKRzoDiZIOOH3voYMZqfFGBYjc,192
|
|
32
|
+
markdown_view/tests/testapp/NO_HEADERS.md,sha256=YbL0KxChybEQauZ46zltWsQbzKRcvfGxTHzIZ174vkg,237
|
|
33
|
+
markdown_view/tests/testapp/README.md,sha256=xk_cwqo8kS6qNHCMTmmFsgohoMvzrs_tgIpC8e010S4,896
|
|
34
|
+
markdown_view/tests/testapp/STAFF.md,sha256=kJyaIF4JFSjc4QBcX_jAmlJ32_HHzpmWaygPbLeYqGw,68
|
|
35
|
+
markdown_view/tests/testapp/UNROUTED.md,sha256=6xbM35PNQ2e1FJWmihJ8_aTXSN96vYG_6RUrQIibl0A,157
|
|
36
|
+
markdown_view/tests/testapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
markdown_view/tests/testapp/apps.py,sha256=my2snU00q5vjhr0BK_fs9tPVnDTLnk6PM8ezS3lV-ZY,173
|
|
38
|
+
markdown_view/tests/testapp/docs/OTHER.md,sha256=xRf-5jaJCnJiaz_bQaVMI5OVpVcuhWlNVejSedyY57k,231
|
|
39
|
+
django_markdown_view-0.0.6.dist-info/METADATA,sha256=5eaR6Bl6OcZEY6JprrDDRA6FS0lKJ7NDu8bYMDM9yYM,14716
|
|
40
|
+
django_markdown_view-0.0.6.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
41
|
+
django_markdown_view-0.0.6.dist-info/top_level.txt,sha256=MqRRE0ldzEe17rDjvoESYz22dyeBcPAG9ELXUR5FD7w,14
|
|
42
|
+
django_markdown_view-0.0.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) Ryan J. Sullivan and individual contributors.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
5
|
+
are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
3. Neither the name of Django nor the names of its contributors may be used
|
|
15
|
+
to endorse or promote products derived from this software without
|
|
16
|
+
specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
markdown_view
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default_app_config = "markdown_view.apps.MarkdownViewConfig"
|
markdown_view/apps.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
from django.core.checks import register, Tags
|
|
3
|
+
|
|
4
|
+
from markdown_view.checks import markdown_view_check
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MarkdownViewConfig(AppConfig):
|
|
8
|
+
name = "markdown_view"
|
|
9
|
+
verbose_name = "Serve .md pages as Django templates"
|
|
10
|
+
|
|
11
|
+
def ready(self):
|
|
12
|
+
register(markdown_view_check, Tags.security)
|
markdown_view/checks.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.core import checks
|
|
3
|
+
|
|
4
|
+
# Expected type for each optional markdown_view setting. Validated by
|
|
5
|
+
# `markdown_view_check()` below (registered as a Django system check in
|
|
6
|
+
# `apps.py`), rather than at import time, so `manage.py check` -- and
|
|
7
|
+
# `override_settings` in tests -- can exercise this validation directly.
|
|
8
|
+
SETTINGS_TYPES = {
|
|
9
|
+
"MARKDOWN_VIEW_LOADERS": list,
|
|
10
|
+
"MARKDOWN_VIEW_LOADER_TEMPLATES_DIR": str,
|
|
11
|
+
"MARKDOWN_VIEW_EXTENSIONS": list,
|
|
12
|
+
"MARKDOWN_VIEW_BASE_DIR": str,
|
|
13
|
+
"MARKDOWN_VIEW_TEMPLATE": str,
|
|
14
|
+
"MARKDOWN_VIEW_TEMPLATE_USE_TOC": bool,
|
|
15
|
+
"MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS": bool,
|
|
16
|
+
"MARKDOWN_VIEW_USE_REQUEST_CONTEXT": bool,
|
|
17
|
+
"MARKDOWN_VIEW_EXTRA_CONTEXT": dict,
|
|
18
|
+
"MARKDOWN_VIEW_REWRITE_INTERNAL_LINKS": bool,
|
|
19
|
+
# `None` is an explicitly valid value here (meaning "no fallback root
|
|
20
|
+
# configured"), unlike every other setting in this dict.
|
|
21
|
+
"MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT": (str, type(None)),
|
|
22
|
+
"MARKDOWN_VIEW_ESCAPE_UNSAFE_TEMPLATE_SYNTAX": bool,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def markdown_view_check(app_configs, **kwargs):
|
|
27
|
+
"""
|
|
28
|
+
Validate that any of markdown_view's optional settings the project has
|
|
29
|
+
defined are of the expected type.
|
|
30
|
+
"""
|
|
31
|
+
errors = []
|
|
32
|
+
|
|
33
|
+
for variable, instance_type in SETTINGS_TYPES.items():
|
|
34
|
+
if hasattr(settings, variable) and not isinstance(
|
|
35
|
+
getattr(settings, variable), instance_type
|
|
36
|
+
):
|
|
37
|
+
errors.append(
|
|
38
|
+
checks.Error(
|
|
39
|
+
"Setting %s is not of type %s" % (variable, instance_type),
|
|
40
|
+
hint="Update %s in your settings module to be of type %s."
|
|
41
|
+
% (variable, instance_type),
|
|
42
|
+
id="markdown_view.E001",
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return errors
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from markdown_view.markdown_extensions import ImageExtension
|
|
2
|
+
|
|
3
|
+
DEFAULT_MARKDOWN_VIEW_LOADERS = ["markdown_view.loaders.MarkdownLoader", ]
|
|
4
|
+
DEFAULT_MARKDOWN_VIEW_LOADER_TEMPLATES_DIR = ""
|
|
5
|
+
DEFAULT_MARKDOWN_VIEW_EXTENSIONS = ["tables", "fenced_code", "toc", ImageExtension(), ]
|
|
6
|
+
DEFAULT_MARKDOWN_VIEW_TEMPLATE = "markdown_view/markdown.html"
|
|
7
|
+
DEFAULT_MARKDOWN_VIEW_TEMPLATE_USE_TOC = True
|
|
8
|
+
DEFAULT_MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS = True
|
|
9
|
+
DEFAULT_MARKDOWN_VIEW_USE_REQUEST_CONTEXT = False
|
|
10
|
+
DEFAULT_MARKDOWN_VIEW_EXTRA_CONTEXT = {}
|
|
11
|
+
DEFAULT_MARKDOWN_VIEW_REWRITE_INTERNAL_LINKS = True
|
|
12
|
+
DEFAULT_MARKDOWN_VIEW_UNRESOLVED_LINK_ROOT = None
|
|
13
|
+
DEFAULT_MARKDOWN_VIEW_ESCAPE_UNSAFE_TEMPLATE_SYNTAX = True
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Escaping for literal Django template syntax found inside rendered Markdown.
|
|
3
|
+
|
|
4
|
+
`MarkdownView` re-parses the HTML produced from a `.md` file as a Django
|
|
5
|
+
template, so that the `{% static %}` tags `ImageExtension` injects for
|
|
6
|
+
image sources (and, when a project opts in via
|
|
7
|
+
`MARKDOWN_VIEW_EXTRA_CONTEXT`/`MARKDOWN_VIEW_USE_REQUEST_CONTEXT`, any
|
|
8
|
+
`{{ variable }}` references to those context values) actually resolve.
|
|
9
|
+
|
|
10
|
+
Markdown source files are prose, though, and prose about Django itself
|
|
11
|
+
routinely contains literal `{% ... %}`/`{{ ... }}` text that was never
|
|
12
|
+
meant to be executed as a template -- e.g. a paragraph explaining how a
|
|
13
|
+
template tag works. Left unescaped, that literal text is a genuine
|
|
14
|
+
production hazard: an unrecognized `{% ... %}` block raises
|
|
15
|
+
`TemplateSyntaxError` (a hard 500 for the whole page), and an unrecognized
|
|
16
|
+
`{{ ... }}` variable is silently rendered as an empty string by Django's
|
|
17
|
+
default `Context` behavior (silent content loss, with no error at all).
|
|
18
|
+
|
|
19
|
+
`escape_unsafe_template_syntax()` walks the converted HTML and leaves only
|
|
20
|
+
the two genuinely-intended constructs alone -- the exact `{% static '...'
|
|
21
|
+
%}` tags `ImageExtension` injects, and `{{ identifier }}` references to a
|
|
22
|
+
name actually present in the context the page will render with -- while
|
|
23
|
+
converting every other `{% ... %}`/`{{ ... }}` span into inert literal
|
|
24
|
+
text, so a documentation author can safely write about template syntax
|
|
25
|
+
without needing to know anything about this rendering pipeline.
|
|
26
|
+
"""
|
|
27
|
+
import re
|
|
28
|
+
|
|
29
|
+
# The exact shape `ImageExtension.InlineImageProcessor` emits: always
|
|
30
|
+
# single-quoted, no filters, no whitespace variation beyond a single space
|
|
31
|
+
# after `static`.
|
|
32
|
+
_STATIC_TAG_RE = re.compile(r"^\{%\s*static\s+'[^']*'\s*%\}$")
|
|
33
|
+
|
|
34
|
+
# A bare `{{ identifier }}` reference -- no dotted lookups, no filters. This
|
|
35
|
+
# intentionally covers only the simple case `MarkdownView`'s own context-
|
|
36
|
+
# variable support is documented/tested against; anything more elaborate
|
|
37
|
+
# appearing in a `.md` file is far more likely to be prose describing
|
|
38
|
+
# template syntax than a real, intended lookup.
|
|
39
|
+
_SIMPLE_VARIABLE_RE = re.compile(r"^\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}$")
|
|
40
|
+
|
|
41
|
+
# Matches one `{% ... %}` or `{{ ... }}` span at a time, restricted to a
|
|
42
|
+
# single line (`.` does not match `\n`) since genuine template tags and
|
|
43
|
+
# variable references are not expected to span multiple lines, and
|
|
44
|
+
# allowing them to would risk one stray, unmatched delimiter swallowing an
|
|
45
|
+
# entire unrelated section of a document into a single "match".
|
|
46
|
+
_TEMPLATE_SYNTAX_RE = re.compile(r"(\{%.*?%\})|(\{\{.*?\}\})")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _escape_braces(text):
|
|
50
|
+
"""Render literal ``{``/``}`` characters inert to Django's template lexer."""
|
|
51
|
+
return text.replace("{", "{").replace("}", "}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def escape_unsafe_template_syntax(html, safe_context_keys=()):
|
|
55
|
+
"""
|
|
56
|
+
Escape any ``{% ... %}``/``{{ ... }}`` span in `html` that is not one
|
|
57
|
+
of the two constructs `MarkdownView` genuinely intends to execute,
|
|
58
|
+
rendering it as inert literal text instead.
|
|
59
|
+
|
|
60
|
+
:param html: Rendered HTML markup, already converted from Markdown.
|
|
61
|
+
:param safe_context_keys: Top-level variable names that will actually
|
|
62
|
+
be present in the context `html` renders with (e.g. from
|
|
63
|
+
`MARKDOWN_VIEW_EXTRA_CONTEXT` or, when
|
|
64
|
+
`MARKDOWN_VIEW_USE_REQUEST_CONTEXT` is enabled, the view's own
|
|
65
|
+
context). A `{{ name }}` reference to one of these is left alone;
|
|
66
|
+
anything else is treated as literal text.
|
|
67
|
+
:return: `html` with every unsafe template-syntax-shaped span escaped.
|
|
68
|
+
"""
|
|
69
|
+
safe_context_keys = set(safe_context_keys)
|
|
70
|
+
|
|
71
|
+
def _replace(match):
|
|
72
|
+
block_tag, variable_tag = match.group(1), match.group(2)
|
|
73
|
+
if block_tag is not None:
|
|
74
|
+
if _STATIC_TAG_RE.match(block_tag):
|
|
75
|
+
return block_tag
|
|
76
|
+
return _escape_braces(block_tag)
|
|
77
|
+
variable_match = _SIMPLE_VARIABLE_RE.match(variable_tag)
|
|
78
|
+
if variable_match and variable_match.group(1) in safe_context_keys:
|
|
79
|
+
return variable_tag
|
|
80
|
+
return _escape_braces(variable_tag)
|
|
81
|
+
|
|
82
|
+
return _TEMPLATE_SYNTAX_RE.sub(_replace, html)
|
markdown_view/loaders.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.core.exceptions import SuspiciousFileOperation
|
|
3
|
+
from django.template import Origin
|
|
4
|
+
from django.template.loaders.filesystem import Loader as FilesystemLoader
|
|
5
|
+
from django.template.utils import get_app_template_dirs
|
|
6
|
+
from django.utils._os import safe_join
|
|
7
|
+
|
|
8
|
+
from markdown_view.constants import DEFAULT_MARKDOWN_VIEW_LOADER_TEMPLATES_DIR
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MarkdownLoader(FilesystemLoader):
|
|
12
|
+
"""
|
|
13
|
+
A loader that will find a .md file in the context of any installed app or
|
|
14
|
+
application root.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def get_dirs(self):
|
|
18
|
+
base_dir = getattr(
|
|
19
|
+
settings,
|
|
20
|
+
"MARKDOWN_VIEW_BASE_DIR",
|
|
21
|
+
getattr(
|
|
22
|
+
settings,
|
|
23
|
+
"BASE_DIR",
|
|
24
|
+
None)
|
|
25
|
+
)
|
|
26
|
+
dirs = [*get_app_template_dirs(
|
|
27
|
+
getattr(
|
|
28
|
+
settings,
|
|
29
|
+
"MARKDOWN_VIEW_LOADER_TEMPLATES_DIR",
|
|
30
|
+
DEFAULT_MARKDOWN_VIEW_LOADER_TEMPLATES_DIR
|
|
31
|
+
)
|
|
32
|
+
)]
|
|
33
|
+
if base_dir:
|
|
34
|
+
dirs.extend([base_dir])
|
|
35
|
+
return dirs
|
|
36
|
+
|
|
37
|
+
def get_template_sources(self, template_name):
|
|
38
|
+
"""
|
|
39
|
+
Return an Origin object pointing to an absolute path in each directory
|
|
40
|
+
in template_dirs. For security reasons, if a path doesn't lie inside
|
|
41
|
+
one of the template_dirs it is excluded from the result set.
|
|
42
|
+
"""
|
|
43
|
+
if template_name.endswith('.md'):
|
|
44
|
+
template_split = template_name.split("/")
|
|
45
|
+
template_split.reverse()
|
|
46
|
+
template_app_dir = template_split.pop()
|
|
47
|
+
template_split.reverse()
|
|
48
|
+
for template_dir in self.get_dirs():
|
|
49
|
+
if str(template_dir).endswith(template_app_dir):
|
|
50
|
+
try:
|
|
51
|
+
name = safe_join(template_dir, *template_split)
|
|
52
|
+
except SuspiciousFileOperation:
|
|
53
|
+
# The joined path was located outside of this template_dir
|
|
54
|
+
# (it might be inside another one, so this isn't fatal).
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
yield Origin(
|
|
58
|
+
name=name,
|
|
59
|
+
template_name=template_name,
|
|
60
|
+
loader=self,
|
|
61
|
+
)
|