├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── manage.py ├── markdown_view ├── __init__.py ├── apps.py ├── checks.py ├── constants.py ├── loaders.py ├── markdown_extensions.py ├── models.py ├── templates │ └── markdown_view │ │ └── markdown.html └── views.py ├── setup.cfg ├── setup.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = 4 | markdown-view 5 | 6 | [report] 7 | show_missing = True 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /media 2 | 3 | # Python bytecode: 4 | *.py[co] 5 | 6 | # Packaging files: 7 | *.egg* 8 | 9 | # Sphinx docs: 10 | build 11 | 12 | # SQLite3 database files: 13 | *.db 14 | 15 | # Logs: 16 | *.log 17 | 18 | # Linux Editors 19 | *~ 20 | \#*\# 21 | /.emacs.desktop 22 | /.emacs.desktop.lock 23 | .elc 24 | auto-save-list 25 | tramp 26 | .\#* 27 | *.swp 28 | *.swo 29 | 30 | # Mac 31 | .DS_Store 32 | ._* 33 | 34 | # Windows 35 | Thumbs.db 36 | Desktop.ini 37 | 38 | # Dev tools and config files 39 | .idea 40 | .vagrant 41 | .vscode 42 | .editorconfig 43 | .ftpconfig 44 | .ftpignore 45 | .jshintignore 46 | .lintignore 47 | project.cson 48 | .env 49 | .project 50 | 51 | # Ignore local configurations 52 | config/settings/user.py 53 | config/settings/local.py 54 | db.sqlite3 55 | 56 | # Node modules 57 | node_modules/ 58 | *node_modules/ 59 | 60 | #sass cache 61 | *sass-cache/ 62 | 63 | # PostgreSQL Backups 64 | db_backup/ 65 | 66 | # Coverage/Tests 67 | htmlcov/ 68 | .coverage 69 | django-queries-results.html 70 | .pytest-queries 71 | 72 | # other files 73 | db.json 74 | /static 75 | /venv 76 | /uninstall* 77 | /dist 78 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | install: 4 | - pip install tox 5 | - pip install coveralls 6 | 7 | matrix: 8 | include: 9 | - python: "3.5" 10 | env: TOXENV=django111-py35 11 | - python: "3.5" 12 | env: TOXENV=django2-py35 13 | - python: "3.5" 14 | env: TOXENV=django21-py35 15 | - python: "3.5" 16 | env: TOXENV=django22-py35 17 | 18 | - python: "3.6" 19 | env: TOXENV=django111-py36 20 | - python: "3.6" 21 | env: TOXENV=django2-py36 22 | - python: "3.6" 23 | env: TOXENV=django21-py36 24 | - python: "3.6" 25 | env: TOXENV=django22-py36 26 | - python: "3.6" 27 | env: TOXENV=django3-py36 28 | 29 | - python: "3.7" 30 | sudo: required 31 | dist: xenial 32 | env: TOXENV=django111-py37 33 | - python: "3.7" 34 | sudo: required 35 | dist: xenial 36 | env: TOXENV=django2-py37 37 | - python: "3.7" 38 | sudo: required 39 | dist: xenial 40 | env: TOXENV=django21-py37 41 | - python: "3.7" 42 | sudo: required 43 | dist: xenial 44 | env: TOXENV=django22-py37 45 | - python: "3.7" 46 | sudo: required 47 | dist: xenial 48 | env: TOXENV=django3-py37 49 | - python: "3.7" 50 | sudo: required 51 | dist: xenial 52 | env: TOXENV=pycodestyle-py37 53 | 54 | - python: "3.8" 55 | sudo: required 56 | dist: xenial 57 | env: TOXENV=django3-py38 58 | - python: "3.8" 59 | sudo: required 60 | dist: xenial 61 | env: TOXENV=pycodestyle-py38 62 | 63 | cache: pip 64 | 65 | script: 66 | - tox 67 | - coveralls 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 28 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django Markdown View 2 | ==================== 3 | 4 | .. image:: https://travis-ci.org/rgs258/django-markdown-view.svg?branch=master 5 | :target: https://travis-ci.org/rgs258/django-markdown-view 6 | 7 | .. image:: https://coveralls.io/repos/github/rgs258/django-markdown-view/badge.svg?branch=master 8 | :target: https://coveralls.io/github/rgs258/django-markdown-view?branch=master 9 | 10 | 11 | **Serve .md pages as Django views.** 12 | 13 | This package aims to make it easy to serve .md files on Django sites. 14 | 15 | 16 | .. contents:: Contents 17 | :depth: 5 18 | 19 | .. note:: 20 | * This package needs tests, and to have Travis and Coveralls properly configured. 21 | 22 | Installation 23 | ------------ 24 | 25 | #. Install with ``pip install django-markdown-view``. 26 | 27 | #. Add ``'markdown_view'`` to your ``INSTALLED_APPS`` settings. 28 | 29 | .. code-block:: python 30 | 31 | INSTALLED_APPS = [ 32 | ..., 33 | 'markdown_view', 34 | ... 35 | ] 36 | 37 | #. (OPTIONAL) Add ``MARKDOWN_VIEW_BASE_DIR`` or ``BASE_DIR`` to settings 38 | The dictionary of the application's base. See Settings_ below 39 | 40 | For example, if settings are in config/settings/base.py, then: 41 | 42 | .. code-block:: python 43 | 44 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 45 | 46 | 47 | Usage 48 | ----- 49 | 50 | Views 51 | ~~~~~ 52 | 53 | Use one of ``MarkdownView``, ``LoggedInMarkdownView``, or ``StaffMarkdownView`` 54 | from ``markdown_view.views`` to serve a .md file 55 | 56 | .. code-block:: python 57 | 58 | from markdown_view.views import StaffMarkdownView 59 | 60 | path('readme/', 61 | StaffMarkdownView.as_view(file_name='my_app/README.md'), 62 | name="readme"), 63 | 64 | Settings 65 | ~~~~~~~~ 66 | 67 | All settings are optional. See ``_ for the defaults. 68 | 69 | * `MARKDOWN_VIEW_BASE_DIR` and `BASE_DIR` 70 | 71 | When present, the value is taken as a location to append to the list of dirs that 72 | Django's `django.template.utils.get_app_template_dirs` will return when passed 73 | `dirname=""`. This is used to locate .md files in the root of the project, e.g., 74 | a README.md file. Looks for `BASE_DIR` if `MARKDOWN_VIEW_BASE_DIR` is not found. 75 | 76 | * `MARKDOWN_VIEW_LOADERS` 77 | 78 | A list of loaders that locate .md files. The default list includes only 79 | `markdown_view.loaders.MarkdownLoader` which will by default try to load .md files 80 | from root directories in INSTALLED_APPS packages much the same as Django's 81 | `django.template.loaders.app_directories.Loader` looks to load from "templates". 82 | 83 | * `MARKDOWN_VIEW_LOADER_TEMPLATES_DIR` 84 | 85 | The name of the directories in INSTALLED_APPS packages in which to locate .md 86 | files. Defaults to "" in order to locate .md filed in the root directories. 87 | 88 | * `MARKDOWN_VIEW_EXTENSIONS` 89 | 90 | The extensions to enable. These extensions are enabled be default: 91 | 92 | * `toc`: 93 | generates a Table of Contents. If `toc` is removed from extensions, then 94 | `MARKDOWN_VIEW_TEMPLATE_USE_TOC` must be set to False. 95 | 96 | * `tables`: 97 | enables tables. 98 | 99 | * `fenced_code`: 100 | enables code blocks. If `fenced_code` is removed from extensions, then 101 | `MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS`, which provides the highlighting for 102 | code blocks, can be disabled. 103 | 104 | * `markdown_view.markdown_extensions.ImageExtension`: 105 | makes images responsive in bootstrap4. 106 | 107 | See https://python-markdown.github.io/extensions/ and 108 | https://github.com/Python-Markdown/markdown/wiki/Third-Party-Extensions for more 109 | extensions. 110 | 111 | You can create your own extensions by following 112 | https://github.com/Python-Markdown/markdown/wiki/Tutorial-1---Writing-Extensions-for-Python-Markdown 113 | 114 | * `MARKDOWN_VIEW_TEMPLATE` 115 | 116 | The Django template that'll be used to render the HTML that is generated from the 117 | Markdown. Set your own template to style your pages. Context includes: 118 | 119 | * `markdown_content`: 120 | The HTML produced from the Markdown. 121 | 122 | * `use_highlight_js`: 123 | If highlight.js is enabled. 124 | 125 | * `use_toc`: 126 | If the table of contents should be rendered. 127 | 128 | * `markdown_toc`: 129 | A table of contents from the headers of the Markdown. Not set when `use_toc` 130 | is False. 131 | 132 | * `page_title`: 133 | A guess at a page title, for now it's the first row of the TOC. Not set when 134 | `use_toc` is False. 135 | 136 | * `MARKDOWN_VIEW_TEMPLATE_USE_TOC` 137 | 138 | Whether to render the TOC. If false, in the template context, `use_toc` is False 139 | and `markdown_toc` and `page_title` are not present. 140 | 141 | * `MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS` 142 | 143 | Whether to load and activate the highlight.js library in the template. 144 | 145 | Experimental Settings 146 | ~~~~~~~~~~~~~~~~~~~~~ 147 | 148 | * `MARKDOWN_VIEW_USE_REQUEST_CONTEXT` 149 | 150 | If the request context should be used as a base when creating the context with 151 | which to render the Markdown internally. This is because the Markdown is rendered 152 | once first in order to prepend it with `{% load static %}`. 153 | This is not well tested; YMMV. 154 | 155 | * `MARKDOWN_VIEW_EXTRA_CONTEXT` 156 | 157 | Any extra context to send to the internal render of the Markdown. Can be used 158 | to expose context to template tags embedded in the Markdown. 159 | This is not well tested; YMMV. 160 | 161 | 162 | Implementation 163 | -------------- 164 | 165 | At a high level, `MarkdownView` will: 166 | 167 | #. Use a template loader to locate `.md` given as `file_name` 168 | 169 | #. Render as a template, the contents of the `.md` file prepended with 170 | `{% load static %}`, into several context variables 171 | 172 | #. Serve the `MARKDOWN_VIEW_TEMPLATE` with the context variables 173 | 174 | Release Notes and Contributors 175 | ------------------------------ 176 | 177 | * `Release notes `_ 178 | * `Our wonderful contributors `_ 179 | 180 | Contributing 181 | ------------ 182 | 183 | All contributions are very welcomed. Propositions, problems, bugs, and 184 | enhancement are tracked with `GitHub issues`_ and patches are submitted 185 | via `pull requests`_. 186 | 187 | We use `Travis`_ coupled with `Coveralls`_ as continious integration tools. 188 | 189 | .. _`GitHub issues`: https://github.com/rgs258/django-markdown-view/issues 190 | .. _`pull requests`: https://github.com/rgs258/django-markdown-view/pulls 191 | .. _Travis: https://travis-ci.org/github/rgs258/django-markdown-view 192 | .. _Coveralls: https://coveralls.io/github/rgs258/django-markdown-view 193 | 194 | Requirements 195 | ------------ 196 | 197 | We aspire to support the currently supported versions of Django. 198 | 199 | **The Tested With section describes aspirational goals.** 200 | 201 | Tested with: 202 | 203 | * Python: 3.6, 3.7, 3.8, 3.9, 3.10 204 | * Django: 2.2, 3.2, 4.0 205 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "markdown_view.tests.settings.coveralls_settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /markdown_view/__init__.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import ImproperlyConfigured 3 | 4 | SETTINGS_TYPES = { 5 | "MARKDOWN_VIEW_LOADERS": list, 6 | "MARKDOWN_VIEW_LOADER_TEMPLATES_DIR": str, 7 | "MARKDOWN_VIEW_EXTENSIONS": list, 8 | "MARKDOWN_VIEW_BASE_DIR": str, 9 | "MARKDOWN_VIEW_TEMPLATE": str, 10 | "MARKDOWN_VIEW_TEMPLATE_USE_TOC": bool, 11 | "MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS": bool, 12 | "MARKDOWN_VIEW_USE_REQUEST_CONTEXT": bool, 13 | "MARKDOWN_VIEW_EXTRA_CONTEXT": dict 14 | } 15 | 16 | # Validate settings types. 17 | for variable, instance_type in SETTINGS_TYPES.items(): 18 | if ( 19 | hasattr(settings, variable) 20 | and not isinstance(getattr(settings, variable), instance_type) 21 | ): 22 | raise ImproperlyConfigured( 23 | "Setting %s is not of type" % variable, instance_type 24 | ) 25 | 26 | default_app_config = "markdown_view.apps.MarkdownViewConfig" 27 | -------------------------------------------------------------------------------- /markdown_view/apps.py: -------------------------------------------------------------------------------- 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) 13 | -------------------------------------------------------------------------------- /markdown_view/checks.py: -------------------------------------------------------------------------------- 1 | from django.core import checks 2 | 3 | 4 | def markdown_view_check(app_configs, **kwargs): 5 | errors = [] 6 | 7 | if False: 8 | errors.extend([checks.Error( 9 | "Example of a check error", 10 | hint="Example of a check error hint", 11 | id="markdown_view.markdown_view_check" 12 | )]) 13 | return errors 14 | -------------------------------------------------------------------------------- /markdown_view/constants.py: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /markdown_view/loaders.py: -------------------------------------------------------------------------------- 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 | ) 62 | -------------------------------------------------------------------------------- /markdown_view/markdown_extensions.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from markdown.treeprocessors import Treeprocessor 4 | from markdown.extensions import Extension 5 | 6 | 7 | class InlineImageProcessor(Treeprocessor): 8 | def run(self, root): 9 | for element in root.iter("img"): 10 | src_orig = element.attrib.get('src', '') 11 | src_re = re.sub("[/\\\]*static[/\\\]*", "", src_orig) 12 | src = "{{% static '{}' %}}".format(src_re) 13 | element.attrib["src"] = src 14 | element.attrib["class"] = "img-fluid" 15 | 16 | 17 | class ImageExtension(Extension): 18 | def extendMarkdown(self, md): 19 | md.treeprocessors.register(InlineImageProcessor(md), 'inlineimageprocessor', 15) 20 | -------------------------------------------------------------------------------- /markdown_view/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgs258/django-markdown-view/d3fca56ca755144793de75007d2c8c1826c8afc4/markdown_view/models.py -------------------------------------------------------------------------------- /markdown_view/templates/markdown_view/markdown.html: -------------------------------------------------------------------------------- 1 | {% if use_highlight_js %} 2 | 3 | 4 | {% endif %} 5 | 6 | {% comment %}{{ page_title|safe }}{% endcomment %} 7 | 8 | {% if use_toc %} 9 |
10 |

Table of Contents

11 | {{ markdown_toc }} 12 |
13 | {% endif %} 14 | 15 |
16 | {{ markdown_content }} 17 |
18 | 19 | {% if use_highlight_js %} 20 | 28 | {% endif %} -------------------------------------------------------------------------------- /markdown_view/views.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import markdown 4 | from django.conf import settings 5 | from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin 6 | from django.template import Engine, Template, Context 7 | from django.utils.safestring import mark_safe 8 | from django.views.generic import TemplateView 9 | 10 | from markdown_view.constants import ( 11 | DEFAULT_MARKDOWN_VIEW_LOADERS, 12 | DEFAULT_MARKDOWN_VIEW_EXTENSIONS, DEFAULT_MARKDOWN_VIEW_TEMPLATE, 13 | DEFAULT_MARKDOWN_VIEW_USE_REQUEST_CONTEXT, DEFAULT_MARKDOWN_VIEW_EXTRA_CONTEXT, 14 | DEFAULT_MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS, DEFAULT_MARKDOWN_VIEW_TEMPLATE_USE_TOC, 15 | ) 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class MarkdownView(TemplateView): 21 | file_name = None 22 | 23 | def get_context_data(self, *args, **kwargs): 24 | context = super().get_context_data(*args, **kwargs) 25 | if self.file_name: 26 | engine = Engine(loaders=getattr( 27 | settings, "MARKDOWN_VIEW_LOADERS", DEFAULT_MARKDOWN_VIEW_LOADERS) 28 | ) 29 | template = engine.get_template(self.file_name) 30 | md = markdown.Markdown(extensions=getattr( 31 | settings, 32 | "MARKDOWN_VIEW_EXTENSIONS", 33 | DEFAULT_MARKDOWN_VIEW_EXTENSIONS 34 | )) 35 | template = Template( 36 | "{{% load static %}}{}".format(md.convert(template.source)) 37 | ) 38 | render_context_base = {} 39 | if getattr( 40 | settings, 41 | "MARKDOWN_VIEW_USE_REQUEST_CONTEXT", 42 | DEFAULT_MARKDOWN_VIEW_USE_REQUEST_CONTEXT 43 | ): 44 | render_context_base = context 45 | render_context = Context({ 46 | **render_context_base, 47 | **(getattr( 48 | settings, 49 | "MARKDOWN_VIEW_EXTRA_CONTEXT", 50 | DEFAULT_MARKDOWN_VIEW_EXTRA_CONTEXT 51 | )) 52 | }) 53 | context.update({ 54 | "markdown_content": mark_safe(template.render(render_context)), 55 | "use_highlight_js": getattr( 56 | settings, 57 | "MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS", 58 | DEFAULT_MARKDOWN_VIEW_TEMPLATE_USE_HIGHLIGHT_JS 59 | ), 60 | "use_toc": False, 61 | }) 62 | 63 | if getattr( 64 | settings, 65 | "MARKDOWN_VIEW_TEMPLATE_USE_TOC", 66 | DEFAULT_MARKDOWN_VIEW_TEMPLATE_USE_TOC 67 | ): 68 | context.update({ 69 | "markdown_toc": mark_safe(md.toc), 70 | "page_title": mark_safe(md.toc_tokens[0]['name']), 71 | "use_toc": True, 72 | }) 73 | 74 | return context 75 | 76 | template_name = getattr( 77 | settings, 78 | "MARKDOWN_VIEW_TEMPLATE", 79 | DEFAULT_MARKDOWN_VIEW_TEMPLATE 80 | ) 81 | 82 | 83 | class LoggedInMarkdownView(LoginRequiredMixin, MarkdownView): 84 | pass 85 | 86 | 87 | class StaffMarkdownView(UserPassesTestMixin, MarkdownView): 88 | def test_func(self): 89 | return self.request.user.is_active and self.request.user.is_staff 90 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | long_desc = open('README.rst', 'rb').read().decode('utf-8') 5 | 6 | setup( 7 | name='django-markdown-view', 8 | description='Serve .md pages as Django views.', 9 | long_description=long_desc, 10 | author='Ryan J. Sullivan', 11 | author_email='ryanj@ryanjsullivan.com', 12 | license='BSD', 13 | url='http://github.com/rgs258/django-markdown-view', 14 | packages=find_packages(), 15 | install_requires=[ 16 | 'django>=2.2', 17 | 'markdown>=3.2', 18 | ], 19 | tests_require=[ 20 | 'tox', 21 | ], 22 | keywords=['django', 'markdown', 'markdown view', 'md'], 23 | include_package_data=True, 24 | setup_requires=["setuptools_scm"], 25 | use_scm_version=True, 26 | classifiers=[ 27 | 'Development Status :: 3 - Alpha', 28 | 'Framework :: Django :: 2', 29 | 'Framework :: Django :: 3', 30 | 'Framework :: Django :: 4', 31 | 'Framework :: Django', 32 | 'Intended Audience :: Developers', 33 | 'License :: OSI Approved :: BSD License', 34 | 'Operating System :: OS Independent', 35 | 'Programming Language :: Python', 36 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 37 | 'Programming Language :: Python :: 3', 38 | 'Programming Language :: Python :: 3.7', 39 | 'Programming Language :: Python :: 3.8', 40 | 'Programming Language :: Python :: 3.9', 41 | 'Programming Language :: Python :: 3.10', 42 | ], 43 | zip_safe=False, 44 | ) 45 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | pycodestyle-{py37,py38} 4 | django{22}-{py35,py36,py37} 5 | django{3}-{py36,py37,py38} 6 | 7 | [testenv] 8 | deps = 9 | django{22,3}: coverage 10 | django22: Django<3.0 11 | django3: Django<4.0 12 | pycodestyle: pycodestyle 13 | commands = 14 | django{22,3}: coverage run manage.py test 15 | pycodestyle: pycodestyle markdown_view/ 16 | --------------------------------------------------------------------------------