basics of custom icons
@ -0,0 +1,49 @@
|
||||
# Generated by Django 4.2.7 on 2024-01-06 15:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0206_rename_sticky_navbar_userpreference_nav_sticky_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_128',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_128', to='cookbook.userfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_144',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_144', to='cookbook.userfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_180',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_180', to='cookbook.userfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_192',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_192', to='cookbook.userfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_32',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_32', to='cookbook.userfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_512',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_512', to='cookbook.userfile'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='logo_color_svg',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='space_logo_color_svg', to='cookbook.userfile'),
|
||||
),
|
||||
]
|
@ -289,6 +289,14 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
nav_bg_color = models.CharField(max_length=8, default='', blank=True, )
|
||||
nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=BLANK)
|
||||
|
||||
logo_color_32 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_32')
|
||||
logo_color_128 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_128')
|
||||
logo_color_144 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_144')
|
||||
logo_color_180 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_180')
|
||||
logo_color_192 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_192')
|
||||
logo_color_512 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_512')
|
||||
logo_color_svg = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_svg')
|
||||
|
||||
created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
message = models.CharField(max_length=512, default='', blank=True)
|
||||
@ -1344,6 +1352,9 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio
|
||||
self.file_size_kb = round(self.file.size / 1000)
|
||||
super(UserFile, self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} (#{self.id})'
|
||||
|
||||
|
||||
class Automation(ExportModelOperationsMixin('automations'), models.Model, PermissionModelMixin):
|
||||
FOOD_ALIAS = 'FOOD_ALIAS'
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,41 +0,0 @@
|
||||
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Logo" transform="matrix(0.637323,0,0,0.637323,-243.095,-716.725)">
|
||||
<g id="Kreis" transform="matrix(1.44936,0,0,1.50279,387.258,1039.34)">
|
||||
<ellipse cx="273.123" cy="324.015" rx="259.822" ry="250.584" style="fill:url(#_Linear1);"/>
|
||||
<clipPath id="_clip2">
|
||||
<ellipse cx="273.123" cy="324.015" rx="259.822" ry="250.584"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip2)">
|
||||
<g id="Shadow" transform="matrix(1.10322,0,0,1.064,-5.58287,50.5786)">
|
||||
<path d="M156.285,427.208L389.554,660.477L668.803,495.551L374.012,200.761L156.285,427.208Z" style="fill:rgb(22,22,22);"/>
|
||||
<g transform="matrix(1,0,0,1,-4.22105,0.775864)">
|
||||
<path d="M208.628,178.613L485.935,455.919L590.027,364.63L296.923,71.526L294.175,138.989L208.628,178.613Z" style="fill:rgb(22,22,22);"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,-85.3876,27.8512)">
|
||||
<path d="M310.385,145.641L587.692,422.948L590.392,361.357L297.288,68.253L294.175,138.989L310.385,145.641Z" style="fill:rgb(22,22,22);"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(1.471,0,0,1.471,406.537,1149.69)">
|
||||
<path d="M256.049,220C286.222,219.994 312.656,207.31 329.388,194.134C346.35,180.754 370.899,183.406 384.611,200.1C407.129,227.376 420.598,261.944 420.598,299.53C420.598,361.08 382.604,437.101 329.764,463.706C307.035,475.15 283.466,480.586 256.098,480.599L256.098,480.599L256.049,480.599L256,480.599L256,480.599C228.632,480.586 205.063,475.15 182.334,463.706C129.494,437.101 91.5,361.08 91.5,299.53C91.5,261.944 104.969,227.376 127.487,200.1C141.199,183.406 165.748,180.754 182.71,194.134C199.442,207.31 225.876,219.994 256.049,220Z" style="fill:rgb(255,203,118);"/>
|
||||
</g>
|
||||
<g id="Flame-2" transform="matrix(0.965725,0,0,0.89175,164.497,436.391)">
|
||||
<path d="M604.408,844.314C601.981,840.845 601.962,836.056 604.362,832.565C606.763,829.074 611.005,827.721 614.769,829.246C633.87,836.869 658.833,848.629 678.207,864.452C718.526,897.381 729.55,919.407 738.552,942.091C749.208,968.943 750.785,996.68 748.515,1016.08C742.018,1071.61 700.355,1117.5 641.034,1117.5C581.713,1117.5 534.493,1072.05 533.553,1016.08C532.986,982.372 543.985,955.443 555.988,936.22C558.982,931.437 564.594,929.469 569.609,931.444C574.623,933.419 577.757,938.831 577.215,944.58C575.493,956.716 574.362,969.372 574.932,979.484C576.863,1013.7 597.171,1022.5 618.083,1022.29C640.371,1022.08 662.925,1003.17 654.797,954.895C647.69,912.681 622.362,870.194 604.408,844.314Z" style="fill:rgb(255,111,0);"/>
|
||||
<clipPath id="_clip3">
|
||||
<path d="M604.408,844.314C601.981,840.845 601.962,836.056 604.362,832.565C606.763,829.074 611.005,827.721 614.769,829.246C633.87,836.869 658.833,848.629 678.207,864.452C718.526,897.381 729.55,919.407 738.552,942.091C749.208,968.943 750.785,996.68 748.515,1016.08C742.018,1071.61 700.355,1117.5 641.034,1117.5C581.713,1117.5 534.493,1072.05 533.553,1016.08C532.986,982.372 543.985,955.443 555.988,936.22C558.982,931.437 564.594,929.469 569.609,931.444C574.623,933.419 577.757,938.831 577.215,944.58C575.493,956.716 574.362,969.372 574.932,979.484C576.863,1013.7 597.171,1022.5 618.083,1022.29C640.371,1022.08 662.925,1003.17 654.797,954.895C647.69,912.681 622.362,870.194 604.408,844.314Z"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip3)">
|
||||
<g transform="matrix(1.28784,-0.270602,0.285942,1.59598,247.349,825.209)">
|
||||
<path d="M255.004,46.957C279.547,58.545 306,85.447 313.307,120.161C325.437,177.791 291.571,193.789 262.496,192.403C215.889,190.181 200.194,153.246 231.326,108.9C250.631,81.401 232.663,36.408 255.004,46.957Z" style="fill:rgb(255,209,0);"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Hut" transform="matrix(1.521,0,0,1.521,393.566,1149.06)">
|
||||
<path d="M228.197,408.524C222.698,408.524 217.813,406.688 214.024,403.619C211.776,401.794 210.92,398.752 211.888,396.024C212.856,393.295 215.437,391.472 218.332,391.472C232.214,391.4 256.112,391.396 256.112,391.396C256.112,391.396 280.009,391.4 293.891,391.472C296.786,391.472 299.367,393.295 300.335,396.024C301.303,398.752 300.447,401.794 298.199,403.619C294.41,406.688 289.526,408.524 284.027,408.524L228.197,408.524ZM217.24,378.877C214.208,378.877 211.3,377.671 209.158,375.525C207.015,373.379 205.814,370.469 205.82,367.436C205.831,361.119 205.842,354.539 205.842,354.539C205.842,350.423 203.097,346.814 199.131,345.714C185.313,341.841 175.2,329.468 175.2,314.823C175.2,297.07 190.059,282.657 208.362,282.657C208.362,282.657 208.362,282.657 208.362,282.657C215.401,282.657 221.675,278.218 224.017,271.581C227.243,262.39 236.411,252.015 256,251.998L256,251.998L256.223,251.998L256.223,251.998C275.812,252.015 284.98,262.39 288.206,271.581C290.549,278.218 296.822,282.657 303.861,282.657C303.861,282.657 303.861,282.657 303.861,282.657C322.164,282.657 337.023,297.07 337.023,314.823C337.023,329.468 326.911,341.841 313.093,345.714C309.127,346.814 306.382,350.423 306.381,354.539C306.381,354.539 306.386,361.127 306.391,367.447C306.394,370.478 305.191,373.385 303.049,375.529C300.907,377.672 298.001,378.877 294.971,378.877C275.615,378.877 236.604,378.877 217.24,378.877Z" style="fill:rgb(22,22,22);"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2e-06,0,0,2e-06,3755.77,81.7179)"><stop offset="0" style="stop-color:rgb(39,39,39);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(108,108,108);stop-opacity:1"/></linearGradient>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
cookbook/static/assets/logo_color_128.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
BIN
cookbook/static/assets/logo_color_192.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
@ -3,6 +3,8 @@
|
||||
{% load theming_tags %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% theme_values request as theme_values %}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %}
|
||||
@ -11,23 +13,18 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="robots" content="noindex,nofollow"/>
|
||||
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/favicon.svg' %}">
|
||||
<link rel="shortcut icon" href="{% static 'assets/favicon.svg' %}">
|
||||
<link rel="icon" type="image/png" href="{% static 'assets/favicon-32x32.png' %}" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="{% static 'assets/favicon-16x16.png' %}" sizes="16x16">
|
||||
|
||||
<link rel="mask-icon" href="{% static 'assets/safari-pinned-tab.svg' %}" color="#161616">
|
||||
<link rel="apple-touch-icon" href="{% static 'assets/apple-touch-icon.png' %}" sizes="180x180">
|
||||
<link rel="icon" href="{{ theme_values.logo_color_svg }}">
|
||||
<link rel="icon" href="{{ theme_values.logo_color_32 }}" sizes="32x32">
|
||||
<link rel="icon" href="{{ theme_values.logo_color_128 }}" sizes="128x128">
|
||||
<link rel="icon" href="{{ theme_values.logo_color_192 }}" sizes="192x192">
|
||||
<link rel="apple-touch-icon" href="{{ theme_values.logo_color_180 }}" sizes="180x180">
|
||||
|
||||
<link rel="manifest" crossorigin="use-credentials" href="{% url 'web_manifest' %}">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
|
||||
<meta name="msapplication-TileColor" content="{% nav_bg_color request %}">
|
||||
<meta name="msapplication-TileImage" content="{{ theme_values.logo_color_144 }}">
|
||||
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#161616">
|
||||
<meta name="msapplication-TileColor" content="#161616">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta name="theme-color" content="{% nav_bg_color request %}">
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
|
||||
|
@ -8,6 +8,33 @@ from recipes.settings import STICKY_NAV_PREF_DEFAULT, UNAUTHENTICATED_THEME_FROM
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def theme_values(request):
|
||||
# TODO move all theming values to this tag to prevent double queries
|
||||
tv = {
|
||||
'logo_color_32': static('assets/logo_color_32.png'),
|
||||
'logo_color_128': static('assets/logo_color_128.png'),
|
||||
'logo_color_144': static('assets/logo_color_144.png'),
|
||||
'logo_color_180': static('assets/logo_color_180.png'),
|
||||
'logo_color_192': static('assets/logo_color_192.png'),
|
||||
'logo_color_512': static('assets/logo_color_512.png'),
|
||||
'logo_color_svg': static('assets/logo_color_svg.svg'),
|
||||
}
|
||||
space = None
|
||||
if request.space:
|
||||
space = request.space
|
||||
if UNAUTHENTICATED_THEME_FROM_SPACE > 0: # TODO load unauth space setting on boot in settings.py and use them here
|
||||
with scopes_disabled():
|
||||
space = Space.objects.filter(id=UNAUTHENTICATED_THEME_FROM_SPACE).first()
|
||||
|
||||
for logo in list(tv.keys()):
|
||||
print(f'looking for {logo} in {space} has logo {getattr(space, logo, None)}')
|
||||
if logo.startswith('logo_color_') and getattr(space, logo, None):
|
||||
tv[logo] = getattr(space, logo).file.url
|
||||
|
||||
return tv
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def theme_url(request):
|
||||
themes = {
|
||||
@ -20,7 +47,7 @@ def theme_url(request):
|
||||
}
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
if UNAUTHENTICATED_THEME_FROM_SPACE > 0: # TODO load unauth space setting on boot in settings.py and use them here
|
||||
if UNAUTHENTICATED_THEME_FROM_SPACE > 0: # TODO load unauth space setting on boot in settings.py and use them here
|
||||
with scopes_disabled():
|
||||
return static(themes[Space.objects.filter(id=UNAUTHENTICATED_THEME_FROM_SPACE).first().space_theme])
|
||||
else:
|
||||
|
@ -162,8 +162,7 @@ urlpatterns = [
|
||||
|
||||
path('service-worker.js', (TemplateView.as_view(template_name="sw.js", content_type='application/javascript', )),
|
||||
name='service_worker'),
|
||||
path('manifest.json', (TemplateView.as_view(template_name="manifest.json", content_type='application/json', )),
|
||||
name='web_manifest'),
|
||||
path('manifest.json', views.web_manifest, name='web_manifest'),
|
||||
]
|
||||
|
||||
generic_models = (
|
||||
|
@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
@ -14,8 +15,9 @@ from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.management import call_command
|
||||
from django.db import models
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
@ -335,13 +337,16 @@ def system(request):
|
||||
database_message = _('Everything is fine!')
|
||||
elif postgres_ver < postgres_current - 2:
|
||||
database_status = 'danger'
|
||||
database_message = _('PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!') % {'v': postgres_ver}
|
||||
database_message = _('PostgreSQL %(v)s is deprecated. Upgrade to a fully supported version!') % {
|
||||
'v': postgres_ver}
|
||||
else:
|
||||
database_status = 'info'
|
||||
database_message = _('You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended') % {'v1': postgres_ver, 'v2': postgres_current}
|
||||
database_message = _('You are running PostgreSQL %(v1)s. PostgreSQL %(v2)s is recommended') % {
|
||||
'v1': postgres_ver, 'v2': postgres_current}
|
||||
else:
|
||||
database_status = 'info'
|
||||
database_message = _('This application is not running with a Postgres database backend. This is ok but not recommended as some features only work with postgres databases.')
|
||||
database_message = _(
|
||||
'This application is not running with a Postgres database backend. This is ok but not recommended as some features only work with postgres databases.')
|
||||
|
||||
secret_key = False if os.getenv('SECRET_KEY') else True
|
||||
|
||||
@ -366,10 +371,12 @@ def system(request):
|
||||
pass
|
||||
else:
|
||||
current_app = row
|
||||
migration_info[current_app] = {'app': current_app, 'unapplied_migrations': [], 'applied_migrations': [], 'total': 0}
|
||||
migration_info[current_app] = {'app': current_app, 'unapplied_migrations': [], 'applied_migrations': [],
|
||||
'total': 0}
|
||||
|
||||
for key in migration_info.keys():
|
||||
migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len(migration_info[key]['applied_migrations'])
|
||||
migration_info[key]['total'] = len(migration_info[key]['unapplied_migrations']) + len(
|
||||
migration_info[key]['applied_migrations'])
|
||||
|
||||
return render(request, 'system.html', {
|
||||
'gunicorn_media': settings.GUNICORN_MEDIA,
|
||||
@ -431,7 +438,8 @@ def invite_link(request, token):
|
||||
link.used_by = request.user
|
||||
link.save()
|
||||
|
||||
user_space = UserSpace.objects.create(user=request.user, space=link.space, internal_note=link.internal_note, invite_link=link, active=False)
|
||||
user_space = UserSpace.objects.create(user=request.user, space=link.space,
|
||||
internal_note=link.internal_note, invite_link=link, active=False)
|
||||
|
||||
if request.user.userspace_set.count() == 1:
|
||||
user_space.active = True
|
||||
@ -472,6 +480,65 @@ def report_share_abuse(request, token):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
|
||||
def web_manifest(request):
|
||||
icons = [
|
||||
{"src": static("/assets/logo_color.svg"), "sizes": "any"},
|
||||
{"src": static("/assets/logo_color144.png"), "type": "image/png", "sizes": "144x144"},
|
||||
{"src": static("/assets/logo_color512.png"), "type": "image/png", "sizes": "512x512"}
|
||||
]
|
||||
|
||||
if request.user.is_authenticated and getattr(request.space, 'logo_color_svg') and getattr(request.space, 'logo_color_144') and getattr(request.space, 'logo_color_512'):
|
||||
icons = [
|
||||
{"src": request.space.logo_color_svg.file.url, "sizes": "any"},
|
||||
{"src": request.space.logo_color_144.file.url, "type": "image/png", "sizes": "144x144"},
|
||||
{"src": request.space.logo_color_512.file.url, "type": "image/png", "sizes": "512x512"}
|
||||
]
|
||||
|
||||
manifest_info = {
|
||||
"name": "Tandoor Recipes",
|
||||
"short_name": "Tandoor",
|
||||
"description": _("Manage recipes, shopping list, meal plans and more."),
|
||||
"icons": icons,
|
||||
"start_url": "./search",
|
||||
"background_color": "#ffcb76",
|
||||
"display": "standalone",
|
||||
"scope": ".",
|
||||
"theme_color": "#ffcb76",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": _("Plan"),
|
||||
"short_name": _("Plan"),
|
||||
"description": _("View your meal Plan"),
|
||||
"url": "./plan"
|
||||
},
|
||||
{
|
||||
"name": _("Books"),
|
||||
"short_name": _("Books"),
|
||||
"description": _("View your cookbooks"),
|
||||
"url": "./books"
|
||||
},
|
||||
{
|
||||
"name": _("Shopping"),
|
||||
"short_name": _("Shopping"),
|
||||
"description": _("View your shopping lists"),
|
||||
"url": "./list/shopping-list/"
|
||||
}
|
||||
],
|
||||
"share_target": {
|
||||
"action": "/data/import/url",
|
||||
"method": "GET",
|
||||
"params": {
|
||||
"title": "title",
|
||||
"url": "url",
|
||||
"text": "text"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JsonResponse(manifest_info, json_dumps_params={'indent': 4})
|
||||
|
||||
|
||||
def markdown_info(request):
|
||||
return render(request, 'markdown_info.html', {})
|
||||
|
||||
|