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
|
||||
file at the root of this project.
|
||||
{% endcomment %}
|
||||
{% load version_markup %}
|
||||
{% block title %}{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}{% endblock %}
|
||||
{% block main %}
|
||||
<h1>{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}</h1>
|
||||
{% if has_multiple_versions %}
|
||||
<p><a href="{{ recipe.get_absolute_url }}">Show all versions</a></p>
|
||||
{% endif %}
|
||||
<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 'edit-version' recipe.slug version.slug %}">Edit Version</a></p>
|
||||
<article>
|
||||
<h1>{{ version.recipe.title }}{% if has_multiple_versions %} ({{ version.label }}){% endif %}</h1>
|
||||
<ul>
|
||||
{% for i in ingredients %}
|
||||
<li>{{ i.text }}</li>
|
||||
{% endfor %}
|
||||
</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 %}
|
||||
{% 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