Format body of recipe
This commit is contained in:
parent
41d346d56a
commit
4461a81ae6
|
|
@ -17,20 +17,23 @@
|
||||||
This program comes with a copy of the GNU Affero General Public License
|
This program comes with a copy of the GNU Affero General Public License
|
||||||
file at the root of this project.
|
file at the root of this project.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
{% load version_markup %}
|
||||||
{% block title %}{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}{% endblock %}
|
{% block title %}{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}{% endblock %}
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1>{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}</h1>
|
|
||||||
{% if has_multiple_versions %}
|
{% if has_multiple_versions %}
|
||||||
<p><a href="{{ recipe.get_absolute_url }}">Show all versions</a></p>
|
<p><a href="{{ recipe.get_absolute_url }}">Show all versions</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p><a href="{% url 'edit-recipe' recipe.slug %}">Edit recipe name</a></p>
|
<p><a href="{% url 'edit-recipe' recipe.slug %}">Edit recipe name</a></p>
|
||||||
<p><a href="{% url 'add-version' recipe.slug %}">Add version</a></p>
|
<p><a href="{% url 'add-version' recipe.slug %}">Add version</a></p>
|
||||||
<p><a href="{% url 'edit-version' recipe.slug version.slug %}">Edit Version</a></p>
|
<p><a href="{% url 'edit-version' recipe.slug version.slug %}">Edit Version</a></p>
|
||||||
|
<article>
|
||||||
|
<h1>{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for i in ingredients %}
|
{% for i in ingredients %}
|
||||||
<li>{{ i.text }}</li>
|
<li>{{ i.text }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
<p>{{ version.body }}</p>
|
{% if version.body %}{{ version.body|ndash_for_ranges|nbsp_for_amounts|unify_units|vulgar_fractions|to_sections }}{% endif %}
|
||||||
|
</article>
|
||||||
{% if version.img %}<img src="{{ version.img.url }}" alt="">{% endif %}
|
{% if version.img %}<img src="{{ version.img.url }}" alt="">{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
0
recipes/templatetags/__init__.py
Normal file
0
recipes/templatetags/__init__.py
Normal file
175
recipes/templatetags/version_markup.py
Normal file
175
recipes/templatetags/version_markup.py
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
"""
|
||||||
|
Barn Web App - A collection of web-apps for my family's personal use,
|
||||||
|
including a recipe database.
|
||||||
|
Copyright © 2023 Benjamin Stadlbauer
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify it
|
||||||
|
under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
This program comes with a copy of the GNU Affero General Public License
|
||||||
|
file at the root of this project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
import re
|
||||||
|
from fractions import Fraction
|
||||||
|
from django.template.defaultfilters import stringfilter
|
||||||
|
from django.utils.html import conditional_escape, linebreaks
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.text import normalize_newlines
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter(name='unify_units', is_safe=True, needs_autoescape=True)
|
||||||
|
@stringfilter
|
||||||
|
def unify_units_filter(value, autoescape=True):
|
||||||
|
if autoescape:
|
||||||
|
value = conditional_escape(value)
|
||||||
|
|
||||||
|
value = re.sub(r"\b[lL](iter)?\b", "Liter", value)
|
||||||
|
value = re.sub(r"\b[eE](ss|ß)?[lL]?(öffel)?\b", "Esslöffel", value)
|
||||||
|
value = re.sub(r"\b[tT](ee)?[lL]\.?(öffel)?\b", "Teelöffel", value)
|
||||||
|
value = re.sub(r"\b[dD]a?[gG]\b", "dag", value)
|
||||||
|
value = re.sub(r"\b([kK][iI][lL][oO]|[kK](ilo)?[gG]\.?(ramm)?)\b", "kg", value)
|
||||||
|
value = re.sub(r"(\b[Gg](rad)? ?|\u00B0 ?)?[Cc](elsius)?\b", "°C", value)
|
||||||
|
value = re.sub(r"\b[Mm](in)?\.?(uten)?\b", "min", value)
|
||||||
|
value = re.sub(r"\b[sS]\.?(ek)?\.?(unden)?\b", "Sekunden", value)
|
||||||
|
|
||||||
|
return mark_safe(value)
|
||||||
|
|
||||||
|
@register.filter(name='vulgar_fractions', is_safe=True, needs_autoescape=True)
|
||||||
|
@stringfilter
|
||||||
|
def fractions_to_vuglar_fractions_filter(value, autoescape=True):
|
||||||
|
fraction_re = re.compile(r"(\d{1,2})( ?\/ ?)(\d{1,2})")
|
||||||
|
if autoescape:
|
||||||
|
value = conditional_escape(value)
|
||||||
|
|
||||||
|
match = fraction_re.search(value)
|
||||||
|
|
||||||
|
while (match):
|
||||||
|
f = Fraction(int(match.group(1)), int(match.group(3)))
|
||||||
|
fraction_string = ""
|
||||||
|
|
||||||
|
if 1 == f.numerator:
|
||||||
|
if 2 == f.denominator:
|
||||||
|
fraction_string = "½" # 1/2
|
||||||
|
elif 3 == f.denominator:
|
||||||
|
fraction_string = "⅓" # 1/3
|
||||||
|
elif 4 == f.denominator:
|
||||||
|
fraction_string = "¼" # 1/4
|
||||||
|
elif 5 == f.denominator:
|
||||||
|
fraction_string = "⅕" # 1/5
|
||||||
|
elif 6 == f.denominator:
|
||||||
|
fraction_string = "⅙" # 1/6
|
||||||
|
elif 7 == f.denominator:
|
||||||
|
fraction_string = "⅐" # 1/7
|
||||||
|
elif 8 == f.denominator:
|
||||||
|
fraction_string = "⅛" # 1/8
|
||||||
|
elif 9 == f.denominator:
|
||||||
|
fraction_string = "⅑" # 1/9
|
||||||
|
elif 10 == f.denominator:
|
||||||
|
fraction_string = "⅒" # 1/10
|
||||||
|
elif 2 == f.numerator:
|
||||||
|
if 3 == f.denominator:
|
||||||
|
fraction_string = "⅔" # 2/3
|
||||||
|
elif 5 == f.denominator:
|
||||||
|
fraction_string = "⅖" # 2/5
|
||||||
|
elif 3 == f.numerator:
|
||||||
|
if 4 == f.denominator:
|
||||||
|
fraction_string = "¾" # 3/4
|
||||||
|
elif 5 == f.denominator:
|
||||||
|
fraction_string = "⅗" # 3/5
|
||||||
|
elif 8 == f.denominator:
|
||||||
|
fraction_string = "⅜" # 3/8
|
||||||
|
elif 4 == f.numerator:
|
||||||
|
if 5 == f.denominator:
|
||||||
|
fraction_string = "⅘" # 4/5
|
||||||
|
elif 5 == f.numerator:
|
||||||
|
if 6 == f.denominator:
|
||||||
|
fraction_string = "⅚" # 5/6
|
||||||
|
elif 8 == f.denominator:
|
||||||
|
fraction_string = "⅝" # 5/8
|
||||||
|
elif 7 == f.numerator:
|
||||||
|
if 8 == f.denominator:
|
||||||
|
fraction_string = "⅞" # 7/8
|
||||||
|
if "" == fraction_string:
|
||||||
|
fraction_string = "%i⁄%i" % (f.numerator, f.denominator)
|
||||||
|
|
||||||
|
prefix = value[:match.start()]
|
||||||
|
suffix = value[match.end():]
|
||||||
|
value = prefix + fraction_string + suffix
|
||||||
|
match = fraction_re.search(value)
|
||||||
|
|
||||||
|
return mark_safe(value)
|
||||||
|
|
||||||
|
@register.filter(name='nbsp_for_amounts', is_safe=True, needs_autoescape=True)
|
||||||
|
@stringfilter
|
||||||
|
def nbsp_for_amounts_filter(value, autoescape=True):
|
||||||
|
if autoescape:
|
||||||
|
value = conditional_escape(value)
|
||||||
|
|
||||||
|
return mark_safe(re.sub(r"(?<=\d) *(?=\b[^\d\W]+\b)", ' ', value)) # replaces optional spaces between digits and non-digit word characters with non-breaking spaces
|
||||||
|
|
||||||
|
@register.filter(name='ndash_for_ranges', is_safe=True, needs_autoescape=True)
|
||||||
|
@stringfilter
|
||||||
|
def ndash_for_ranges_filter(value, autoescape=True):
|
||||||
|
if autoescape:
|
||||||
|
value = conditional_escape(value)
|
||||||
|
|
||||||
|
en_dash = '⁠–⁠' # word joiner (U+2060) en dash (U+2013) word joiner (U+2060)
|
||||||
|
return mark_safe(re.sub(r"(?<=\d) ?-+ ?(?=\d)", en_dash, value)) # replaces dashes between digits with en-dashes (and omits optional spaces and adds word joiner)
|
||||||
|
|
||||||
|
def line_is_section_heading(text):
|
||||||
|
return len(text) < 30 and not re.search(r"\. *$", text)
|
||||||
|
|
||||||
|
@register.filter(name='to_sections', is_safe=True, needs_autoescape=True)
|
||||||
|
@stringfilter
|
||||||
|
def body_to_sections_filter(value, autoescape=True):
|
||||||
|
if autoescape:
|
||||||
|
value = conditional_escape(value)
|
||||||
|
|
||||||
|
value = normalize_newlines(value)
|
||||||
|
|
||||||
|
section_objects = []
|
||||||
|
section_object = {
|
||||||
|
'title': '',
|
||||||
|
'body': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in re.split('\n{2,}', value):
|
||||||
|
if line_is_section_heading(line):
|
||||||
|
if section_object['body']:
|
||||||
|
section_objects += [section_object]
|
||||||
|
|
||||||
|
section_object = {
|
||||||
|
'title': line,
|
||||||
|
'body': '',
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
if section_object['body']:
|
||||||
|
section_object['body'] += '\n\n' + line
|
||||||
|
else:
|
||||||
|
section_object['body'] = line
|
||||||
|
|
||||||
|
section_objects += [section_object]
|
||||||
|
|
||||||
|
sections = ''
|
||||||
|
|
||||||
|
for section in section_objects:
|
||||||
|
sections += '<section>\n'
|
||||||
|
|
||||||
|
if section['title']:
|
||||||
|
sections += '<h2>%s</h2>\n' % section['title']
|
||||||
|
|
||||||
|
sections += linebreaks(section['body'], autoescape=False)
|
||||||
|
sections += '\n</section>\n'
|
||||||
|
|
||||||
|
|
||||||
|
return mark_safe(sections)
|
||||||
Loading…
Reference in a new issue