diff --git a/django-markdown/README.md b/django-markdown/README.md new file mode 100644 index 0000000000..3fa4319d1c --- /dev/null +++ b/django-markdown/README.md @@ -0,0 +1,3 @@ +# How to Render Markdown in a Django Application + +This folder provides the code examples for the Real Python tutorial [How to Render Markdown in a Django Application](https://realpython.com/django-markdown/). diff --git a/django-markdown/django_markdown/django_markdown/__init__.py b/django-markdown/django_markdown/django_markdown/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django-markdown/django_markdown/django_markdown/asgi.py b/django-markdown/django_markdown/django_markdown/asgi.py new file mode 100644 index 0000000000..b8ddddeae1 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for django_markdown project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_markdown.settings") + +application = get_asgi_application() diff --git a/django-markdown/django_markdown/django_markdown/settings.py b/django-markdown/django_markdown/django_markdown/settings.py new file mode 100644 index 0000000000..e6d103d634 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown/settings.py @@ -0,0 +1,126 @@ +""" +Django settings for django_markdown project. + +Generated by 'django-admin startproject' using Django 4.1.3. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = ( + "django-insecure-6ynx$w58$^0g8)y5)kv&bp0ryw9l%eq*x8lmv5+^@p6n54eu@$" +) + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django_markdown_app", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "django_markdown.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "django_markdown.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.1/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/django-markdown/django_markdown/django_markdown/urls.py b/django-markdown/django_markdown/django_markdown/urls.py new file mode 100644 index 0000000000..b69d0327bc --- /dev/null +++ b/django-markdown/django_markdown/django_markdown/urls.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from django.urls import path + +from django_markdown_app.views import markdown_content_view + +urlpatterns = [ + path("admin/", admin.site.urls), + path( + "markdown-content//", + markdown_content_view, + name="markdown-content", + ), +] diff --git a/django-markdown/django_markdown/django_markdown/wsgi.py b/django-markdown/django_markdown/django_markdown/wsgi.py new file mode 100644 index 0000000000..59f495e045 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for django_markdown project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_markdown.settings") + +application = get_wsgi_application() diff --git a/django-markdown/django_markdown/django_markdown_app/__init__.py b/django-markdown/django_markdown/django_markdown_app/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django-markdown/django_markdown/django_markdown_app/admin.py b/django-markdown/django_markdown/django_markdown_app/admin.py new file mode 100644 index 0000000000..f6c7e5be71 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from .models import MarkdownContent + + +class MarkdownContentAdmin(admin.ModelAdmin): + prepopulated_fields = {"slug": ["title"]} + + +admin.site.register(MarkdownContent, MarkdownContentAdmin) diff --git a/django-markdown/django_markdown/django_markdown_app/apps.py b/django-markdown/django_markdown/django_markdown_app/apps.py new file mode 100644 index 0000000000..e7161b6f09 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DjangoMarkdownAppConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "django_markdown_app" diff --git a/django-markdown/django_markdown/django_markdown_app/markdown_extensions.py b/django-markdown/django_markdown/django_markdown_app/markdown_extensions.py new file mode 100644 index 0000000000..3350b78a30 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/markdown_extensions.py @@ -0,0 +1,20 @@ +import markdown +from django.urls import reverse +from markdown.inlinepatterns import LINK_RE, LinkInlineProcessor + + +class SlugFieldLinkInlineProcessor(LinkInlineProcessor): + def getLink(self, data, index): + href, title, index, handled = super().getLink(data, index) + if href.startswith("slug"): + slug = href.split(":")[1] + relative_url = reverse("markdown-content", args=[slug]) + href = relative_url + return href, title, index, handled + + +class SlugFieldExtension(markdown.Extension): + def extendMarkdown(self, md, *args, **kwargs): + md.inlinePatterns.register( + SlugFieldLinkInlineProcessor(LINK_RE, md), "link", 160 + ) diff --git a/django-markdown/django_markdown/django_markdown_app/migrations/0001_initial.py b/django-markdown/django_markdown/django_markdown_app/migrations/0001_initial.py new file mode 100644 index 0000000000..c520d9b828 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.6 on 2023-10-23 20:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="MarkdownContent", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=100)), + ("content", models.TextField()), + ], + options={ + "verbose_name_plural": "Markdown content", + }, + ), + ] diff --git a/django-markdown/django_markdown/django_markdown_app/migrations/0002_markdowncontent_slug.py b/django-markdown/django_markdown/django_markdown_app/migrations/0002_markdowncontent_slug.py new file mode 100644 index 0000000000..a863421098 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/migrations/0002_markdowncontent_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-10-23 20:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("django_markdown_app", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="markdowncontent", + name="slug", + field=models.SlugField(blank=True), + ), + ] diff --git a/django-markdown/django_markdown/django_markdown_app/migrations/__init__.py b/django-markdown/django_markdown/django_markdown_app/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django-markdown/django_markdown/django_markdown_app/models.py b/django-markdown/django_markdown/django_markdown_app/models.py new file mode 100644 index 0000000000..7992e7feb4 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/models.py @@ -0,0 +1,13 @@ +from django.db import models + + +class MarkdownContent(models.Model): + title = models.CharField(max_length=100) + content = models.TextField() + slug = models.SlugField(blank=True) + + class Meta: + verbose_name_plural = "Markdown content" + + def __str__(self): + return self.title diff --git a/django-markdown/django_markdown/django_markdown_app/static/django_markdown_app/styles.css b/django-markdown/django_markdown/django_markdown_app/static/django_markdown_app/styles.css new file mode 100644 index 0000000000..9a05b3ad69 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/static/django_markdown_app/styles.css @@ -0,0 +1,74 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.codehilite .hll { background-color: #ffffcc } +.codehilite { background: #f8f8f8; } +.codehilite .c { color: #3D7B7B; font-style: italic } /* Comment */ +.codehilite .err { border: 1px solid #FF0000 } /* Error */ +.codehilite .k { color: #008000; font-weight: bold } /* Keyword */ +.codehilite .o { color: #666666 } /* Operator */ +.codehilite .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.codehilite .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.codehilite .cp { color: #9C6500 } /* Comment.Preproc */ +.codehilite .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.codehilite .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.codehilite .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.codehilite .gd { color: #A00000 } /* Generic.Deleted */ +.codehilite .ge { font-style: italic } /* Generic.Emph */ +.codehilite .gr { color: #E40000 } /* Generic.Error */ +.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.codehilite .gi { color: #008400 } /* Generic.Inserted */ +.codehilite .go { color: #717171 } /* Generic.Output */ +.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.codehilite .gs { font-weight: bold } /* Generic.Strong */ +.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.codehilite .gt { color: #0044DD } /* Generic.Traceback */ +.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.codehilite .kp { color: #008000 } /* Keyword.Pseudo */ +.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.codehilite .kt { color: #B00040 } /* Keyword.Type */ +.codehilite .m { color: #666666 } /* Literal.Number */ +.codehilite .s { color: #BA2121 } /* Literal.String */ +.codehilite .na { color: #687822 } /* Name.Attribute */ +.codehilite .nb { color: #008000 } /* Name.Builtin */ +.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.codehilite .no { color: #880000 } /* Name.Constant */ +.codehilite .nd { color: #AA22FF } /* Name.Decorator */ +.codehilite .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.codehilite .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.codehilite .nf { color: #0000FF } /* Name.Function */ +.codehilite .nl { color: #767600 } /* Name.Label */ +.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.codehilite .nv { color: #19177C } /* Name.Variable */ +.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.codehilite .w { color: #bbbbbb } /* Text.Whitespace */ +.codehilite .mb { color: #666666 } /* Literal.Number.Bin */ +.codehilite .mf { color: #666666 } /* Literal.Number.Float */ +.codehilite .mh { color: #666666 } /* Literal.Number.Hex */ +.codehilite .mi { color: #666666 } /* Literal.Number.Integer */ +.codehilite .mo { color: #666666 } /* Literal.Number.Oct */ +.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */ +.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */ +.codehilite .sc { color: #BA2121 } /* Literal.String.Char */ +.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */ +.codehilite .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.codehilite .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.codehilite .sx { color: #008000 } /* Literal.String.Other */ +.codehilite .sr { color: #A45A77 } /* Literal.String.Regex */ +.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */ +.codehilite .ss { color: #19177C } /* Literal.String.Symbol */ +.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.codehilite .fm { color: #0000FF } /* Name.Function.Magic */ +.codehilite .vc { color: #19177C } /* Name.Variable.Class */ +.codehilite .vg { color: #19177C } /* Name.Variable.Global */ +.codehilite .vi { color: #19177C } /* Name.Variable.Instance */ +.codehilite .vm { color: #19177C } /* Name.Variable.Magic */ +.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/django-markdown/django_markdown/django_markdown_app/templates/django_markdown_app/markdown_content.html b/django-markdown/django_markdown/django_markdown_app/templates/django_markdown_app/markdown_content.html new file mode 100644 index 0000000000..11ed6e23eb --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/templates/django_markdown_app/markdown_content.html @@ -0,0 +1,15 @@ +{% load static %} +{% load django_markdown_extras %} + + + + + + Django Markdown + + + +

{{ markdown_content.title }}

+

{{ markdown_content.content|render_markdown }}

+ + diff --git a/django-markdown/django_markdown/django_markdown_app/templatetags/__init__.py b/django-markdown/django_markdown/django_markdown_app/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django-markdown/django_markdown/django_markdown_app/templatetags/django_markdown_extras.py b/django-markdown/django_markdown/django_markdown_app/templatetags/django_markdown_extras.py new file mode 100644 index 0000000000..359f3b40a8 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/templatetags/django_markdown_extras.py @@ -0,0 +1,17 @@ +import markdown +from django import template +from django.template.defaultfilters import stringfilter +from django.utils.safestring import mark_safe + +from django_markdown_app.markdown_extensions import SlugFieldExtension + +register = template.Library() + + +@register.filter +@stringfilter +def render_markdown(value): + md = markdown.Markdown( + extensions=["fenced_code", "codehilite", SlugFieldExtension()] + ) + return mark_safe(md.convert(value)) diff --git a/django-markdown/django_markdown/django_markdown_app/tests.py b/django-markdown/django_markdown/django_markdown_app/tests.py new file mode 100644 index 0000000000..a79ca8be56 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/tests.py @@ -0,0 +1,3 @@ +# from django.test import TestCase + +# Create your tests here. diff --git a/django-markdown/django_markdown/django_markdown_app/views.py b/django-markdown/django_markdown/django_markdown_app/views.py new file mode 100644 index 0000000000..3acde11db4 --- /dev/null +++ b/django-markdown/django_markdown/django_markdown_app/views.py @@ -0,0 +1,11 @@ +from django.shortcuts import get_object_or_404, render + +from .models import MarkdownContent + + +def markdown_content_view(request, slug): + markdown_content = get_object_or_404(MarkdownContent, slug=slug) + context = {"markdown_content": markdown_content} + return render( + request, "django_markdown_app/markdown_content.html", context=context + ) diff --git a/django-markdown/django_markdown/manage.py b/django-markdown/django_markdown/manage.py new file mode 100755 index 0000000000..8146cf994d --- /dev/null +++ b/django-markdown/django_markdown/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_markdown.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/django-markdown/django_markdown/requirements.txt b/django-markdown/django_markdown/requirements.txt new file mode 100644 index 0000000000..66b573043b --- /dev/null +++ b/django-markdown/django_markdown/requirements.txt @@ -0,0 +1,8 @@ +asgiref==3.7.2 +Django==4.2.6 +importlib-metadata==6.8.0 +Markdown==3.5 +Pygments==2.16.1 +sqlparse==0.4.4 +typing_extensions==4.8.0 +zipp==3.17.0