Merge branch 'develop' of https://github.com/vabene1111/recipes into develop
@@ -135,8 +135,14 @@ class ImportExportBase(forms.Form):
|
||||
DEFAULT = 'DEFAULT'
|
||||
PAPRIKA = 'PAPRIKA'
|
||||
NEXTCLOUD = 'NEXTCLOUD'
|
||||
MEALIE = 'MEALIE'
|
||||
CHOWDOWN = 'CHOWDOWN'
|
||||
SAFRON = 'SAFRON'
|
||||
|
||||
type = forms.ChoiceField(choices=((DEFAULT, _('Default')), (PAPRIKA, _('Paprika')), (NEXTCLOUD, _('Nextcloud Cookbook')),))
|
||||
type = forms.ChoiceField(choices=(
|
||||
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
|
||||
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'),
|
||||
))
|
||||
|
||||
|
||||
class ImportForm(ImportExportBase):
|
||||
|
||||
@@ -28,7 +28,7 @@ def parse_amount(x):
|
||||
and (
|
||||
x[end] in string.digits
|
||||
or (
|
||||
(x[end] == '.' or x[end] == ',')
|
||||
(x[end] == '.' or x[end] == ',' or x[end] == '/')
|
||||
and end + 1 < len(x)
|
||||
and x[end + 1] in string.digits
|
||||
)
|
||||
@@ -36,7 +36,10 @@ def parse_amount(x):
|
||||
):
|
||||
end += 1
|
||||
if end > 0:
|
||||
amount = float(x[:end].replace(',', '.'))
|
||||
if "/" in x[:end]:
|
||||
amount = parse_fraction(x[:end])
|
||||
else:
|
||||
amount = float(x[:end].replace(',', '.'))
|
||||
else:
|
||||
amount = parse_fraction(x[0])
|
||||
end += 1
|
||||
|
||||
@@ -224,7 +224,7 @@ def find_recipe_json(ld_json, url):
|
||||
ld_json['servings'] = int(re.findall(r'\b\d+\b', ld_json['recipeYield'])[0])
|
||||
except Exception as e:
|
||||
print(e)
|
||||
ld_json['servings'] = 0
|
||||
ld_json['servings'] = 1
|
||||
|
||||
for key in list(ld_json):
|
||||
if key not in [
|
||||
|
||||
79
cookbook/integration/chowdown.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||
|
||||
|
||||
class Chowdown(Integration):
|
||||
|
||||
def import_file_name_filter(self, zip_info_object):
|
||||
print("testing", zip_info_object.filename)
|
||||
return re.match(r'^_recipes/([A-Za-z\d\s-])+.md$', zip_info_object.filename)
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
ingredient_mode = False
|
||||
direction_mode = False
|
||||
description_mode = False
|
||||
|
||||
ingredients = []
|
||||
directions = []
|
||||
descriptions = []
|
||||
for fl in file.readlines():
|
||||
line = fl.decode("utf-8")
|
||||
if 'title:' in line:
|
||||
title = line.replace('title:', '').replace('"', '').strip()
|
||||
if 'image:' in line:
|
||||
image = line.replace('image:', '').strip()
|
||||
if 'tags:' in line:
|
||||
tags = line.replace('tags:', '').strip()
|
||||
if ingredient_mode:
|
||||
if len(line) > 2 and 'directions:' not in line:
|
||||
ingredients.append(line[2:])
|
||||
if '---' in line and direction_mode:
|
||||
direction_mode = False
|
||||
description_mode = True
|
||||
if direction_mode:
|
||||
if len(line) > 2:
|
||||
directions.append(line[2:])
|
||||
if 'ingredients:' in line:
|
||||
ingredient_mode = True
|
||||
if 'directions:' in line:
|
||||
ingredient_mode = False
|
||||
direction_mode = True
|
||||
if description_mode and len(line) > 3 and '---' not in line:
|
||||
descriptions.append(line)
|
||||
|
||||
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, )
|
||||
|
||||
for k in tags.split(','):
|
||||
keyword, created = Keyword.objects.get_or_create(name=k.strip())
|
||||
recipe.keywords.add(keyword)
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions)
|
||||
)
|
||||
|
||||
for ingredient in ingredients:
|
||||
amount, unit, ingredient, note = parse(ingredient)
|
||||
f, created = Food.objects.get_or_create(name=ingredient)
|
||||
u, created = Unit.objects.get_or_create(name=unit)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
for f in self.files:
|
||||
if '.zip' in f.name:
|
||||
import_zip = ZipFile(f.file)
|
||||
for z in import_zip.filelist:
|
||||
if re.match(f'^images/{image}$', z.filename):
|
||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
||||
|
||||
return recipe
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
52
cookbook/integration/mealie.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
|
||||
|
||||
class Mealie(Integration):
|
||||
|
||||
def import_file_name_filter(self, zip_info_object):
|
||||
return re.match(r'^recipes/([A-Za-z\d-])+.json$', zip_info_object.filename)
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
recipe_json = json.loads(file.getvalue().decode("utf-8"))
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
|
||||
created_by=self.request.user, internal=True)
|
||||
|
||||
# TODO parse times (given in PT2H3M )
|
||||
|
||||
ingredients_added = False
|
||||
for s in recipe_json['recipeInstructions']:
|
||||
step = Step.objects.create(
|
||||
instruction=s['text']
|
||||
)
|
||||
if not ingredients_added:
|
||||
ingredients_added = True
|
||||
|
||||
for ingredient in recipe_json['recipeIngredient']:
|
||||
amount, unit, ingredient, note = parse(ingredient)
|
||||
f, created = Food.objects.get_or_create(name=ingredient)
|
||||
u, created = Unit.objects.get_or_create(name=unit)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
for f in self.files:
|
||||
if '.zip' in f.name:
|
||||
import_zip = ZipFile(f.file)
|
||||
for z in import_zip.filelist:
|
||||
if re.match(f'^images/{recipe_json["slug"]}.jpg$', z.filename):
|
||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
||||
|
||||
return recipe
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
@@ -3,18 +3,14 @@ import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
from cookbook.serializer import RecipeExportSerializer
|
||||
|
||||
|
||||
class NextcloudCookbook(Integration):
|
||||
|
||||
def import_file_name_filter(self, zip_info_object):
|
||||
print("testing", zip_info_object.filename)
|
||||
return re.match(r'^Recipes/([A-Za-z\d\s])+/recipe.json$', zip_info_object.filename)
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
@@ -26,6 +22,7 @@ class NextcloudCookbook(Integration):
|
||||
servings=recipe_json['recipeYield'])
|
||||
|
||||
# TODO parse times (given in PT2H3M )
|
||||
# TODO parse keywords
|
||||
|
||||
ingredients_added = False
|
||||
for s in recipe_json['recipeInstructions']:
|
||||
@@ -54,6 +51,4 @@ class NextcloudCookbook(Integration):
|
||||
return recipe
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
export = RecipeExportSerializer(recipe).data
|
||||
|
||||
return 'recipe.json', JSONRenderer().render(export).decode("utf-8")
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import json
|
||||
import re
|
||||
from io import BytesIO
|
||||
from zipfile import ZipFile
|
||||
|
||||
import microdata
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from cookbook.helper.recipe_url_import import find_recipe_json
|
||||
from cookbook.integration.integration import Integration
|
||||
@@ -9,6 +13,10 @@ from cookbook.models import Recipe, Step, Food, Ingredient, Unit
|
||||
|
||||
class Paprika(Integration):
|
||||
|
||||
def import_file_name_filter(self, zip_info_object):
|
||||
print("testing", zip_info_object.filename)
|
||||
return re.match(r'^Recipes/([A-Za-z\s])+.html$', zip_info_object.filename)
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
|
||||
@@ -33,4 +41,16 @@ class Paprika(Integration):
|
||||
))
|
||||
|
||||
recipe.steps.add(step)
|
||||
|
||||
soup = BeautifulSoup(html_text, "html.parser")
|
||||
image = soup.find('img')
|
||||
image_name = image.attrs['src'].strip().replace('Images/', '')
|
||||
|
||||
for f in self.files:
|
||||
if '.zip' in f.name:
|
||||
import_zip = ZipFile(f.file)
|
||||
for z in import_zip.filelist:
|
||||
if re.match(f'^Recipes/Images/{image_name}$', z.filename):
|
||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
||||
|
||||
return recipe
|
||||
|
||||
60
cookbook/integration/safron.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.helper.ingredient_parser import parse
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
|
||||
|
||||
|
||||
class Safron(Integration):
|
||||
|
||||
def get_recipe_from_file(self, file):
|
||||
ingredient_mode = False
|
||||
direction_mode = False
|
||||
|
||||
ingredients = []
|
||||
directions = []
|
||||
for fl in file.readlines():
|
||||
line = fl.decode("utf-8")
|
||||
if 'Title:' in line:
|
||||
title = line.replace('Title:', '').strip()
|
||||
if 'Description:' in line:
|
||||
description = line.replace('Description:', '').strip()
|
||||
if 'Yield:' in line:
|
||||
directions.append(_('Servings') + ' ' + line.replace('Yield:', '').strip() + '\n')
|
||||
if 'Cook:' in line:
|
||||
directions.append(_('Waiting time') + ' ' + line.replace('Cook:', '').strip() + '\n')
|
||||
if 'Prep:' in line:
|
||||
directions.append(_('Preparation Time') + ' ' + line.replace('Prep:', '').strip() + '\n')
|
||||
if 'Cookbook:' in line:
|
||||
directions.append(_('Cookbook') + ' ' + line.replace('Cookbook:', '').strip() + '\n')
|
||||
if 'Section:' in line:
|
||||
directions.append(_('Section') + ' ' + line.replace('Section:', '').strip() + '\n')
|
||||
if ingredient_mode:
|
||||
if len(line) > 2 and 'Instructions:' not in line:
|
||||
ingredients.append(line.strip())
|
||||
if direction_mode:
|
||||
if len(line) > 2:
|
||||
directions.append(line.strip())
|
||||
if 'Ingredients:' in line:
|
||||
ingredient_mode = True
|
||||
if 'Instructions:' in line:
|
||||
ingredient_mode = False
|
||||
direction_mode = True
|
||||
|
||||
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, )
|
||||
|
||||
step = Step.objects.create(instruction='\n'.join(directions))
|
||||
|
||||
for ingredient in ingredients:
|
||||
amount, unit, ingredient, note = parse(ingredient)
|
||||
f, created = Food.objects.get_or_create(name=ingredient)
|
||||
u, created = Unit.objects.get_or_create(name=unit)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
return recipe
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
BIN
cookbook/locale/cs/LC_MESSAGES/django.mo
Normal file
1871
cookbook/locale/cs/LC_MESSAGES/django.po
Normal file
43
cookbook/static/assets/favicon.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" 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" serif: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>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
43
cookbook/static/assets/logo_color.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" 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" serif: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>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
BIN
cookbook/static/assets/logo_color144.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
cookbook/static/assets/logo_color512.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1 +0,0 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="book" class="svg-inline--fa fa-book fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M128 152v-32c0-4.4 3.6-8 8-8h208c4.4 0 8 3.6 8 8v32c0 4.4-3.6 8-8 8H136c-4.4 0-8-3.6-8-8zm8 88h208c4.4 0 8-3.6 8-8v-32c0-4.4-3.6-8-8-8H136c-4.4 0-8 3.6-8 8v32c0 4.4 3.6 8 8 8zm299.1 159.7c-4.2 13-4.2 51.6 0 64.6 7.3 1.4 12.9 7.9 12.9 15.7v16c0 8.8-7.2 16-16 16H80c-44.2 0-80-35.8-80-80V80C0 35.8 35.8 0 80 0h352c8.8 0 16 7.2 16 16v368c0 7.8-5.5 14.2-12.9 15.7zm-41.1.3H80c-17.6 0-32 14.4-32 32 0 17.7 14.3 32 32 32h314c-2.7-17.3-2.7-46.7 0-64zm6-352H80c-17.7 0-32 14.3-32 32v278.7c9.8-4.3 20.6-6.7 32-6.7h320V48z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 740 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -11,17 +11,17 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{% static 'favicon.png' %}">
|
||||
<link rel="shortcut icon" href="{% static 'favicon.png' %}">
|
||||
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="{% static 'favicon.png' %}" sizes="96x96">
|
||||
<link rel="apple-touch-icon" href="{% static 'favicon.png' %}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'favicon.png' %}">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{% static 'favicon.png' %}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'favicon.png' %}">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="{% static 'favicon.png' %}">
|
||||
<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.svg' %}" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="{% static 'assets/favicon.svg' %}" sizes="96x96">
|
||||
<link rel="apple-touch-icon" href="{% static 'assets/favicon.svg' %}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'assets/favicon.svg' %}">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="{% static 'assets/favicon.svg' %}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'assets/favicon.svg' %}">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="{% static 'assets/favicon.svg' %}">
|
||||
|
||||
<link rel="manifest" href="{% static 'manifest/webmanifest' %}">
|
||||
<link rel="manifest" href="{% url 'web_manifest' %}">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
|
||||
|
||||
@@ -1,51 +1,48 @@
|
||||
{
|
||||
"name": "Recipes",
|
||||
"name": "Tandoor Recipes",
|
||||
"short_name" : "Tandoor",
|
||||
"description": "Application to manage, tag and search recipes.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/manifest/icon-192.png",
|
||||
"src": "/static/assets/logo_color144.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
"sizes": "144x144"
|
||||
},
|
||||
{
|
||||
"src": "/static/manifest/icon-512.png",
|
||||
"src": "/static/assets/logo_color512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": "/search",
|
||||
"background_color": "#18BC9C",
|
||||
"background_color": "#ffcb76",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#18BC9C",
|
||||
"theme_color": "#ffcb76",
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Plan",
|
||||
"short_name": "Plan",
|
||||
"description": "View your meal Plan",
|
||||
"url": "/plan",
|
||||
"icons": [{ "src": "/static/manifest/icon-192.png", "sizes": "192x192" }]
|
||||
"url": "/plan"
|
||||
},
|
||||
{
|
||||
"name": "Books",
|
||||
"short_name": "Cookbooks",
|
||||
"description": "View your cookbooks",
|
||||
"url": "/books",
|
||||
"icons": [{ "src": "/static/manifest/icon-192.png", "sizes": "192x192" }]
|
||||
"url": "/books"
|
||||
},
|
||||
{
|
||||
"name": "Shopping",
|
||||
"short_name": "Shopping",
|
||||
"description": "View your shopping lists",
|
||||
"url": "/list/shopping-list/",
|
||||
"icons": [{ "src": "/static/manifest/shopping-cart-192.png", "sizes": "192x192" }]
|
||||
"url": "/list/shopping-list/"
|
||||
},
|
||||
{
|
||||
"name": "Latest Shopping List",
|
||||
"short_name": "Shopping List",
|
||||
"description": "View the latest shopping list",
|
||||
"url": "/shopping/latest/",
|
||||
"icons": [{ "src": "/static/manifest/shopping-cart-192.png", "sizes": "192x192" }]
|
||||
"url": "/shopping/latest/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -129,7 +129,7 @@
|
||||
[](https://github.com/vabene1111/recipes)
|
||||
[GitHub](https://github.com/vabene1111/recipes)
|
||||
|
||||

|
||||

|
||||
</code></pre>
|
||||
|
||||
<div style="text-align: center">
|
||||
@@ -142,7 +142,7 @@
|
||||
<div class="card-body">
|
||||
<a href="https://github.com/vabene1111/recipes">https://github.com/vabene1111/recipes</a> <br/>
|
||||
<a href="https://github.com/vabene1111/recipes">GitHub</a> <br/>
|
||||
<img src="{% static 'favicon.png' %}" class="img-fluid" alt="{% trans 'This will become an image' %}"
|
||||
<img src="{% static 'assets/favicon.svg' %}" class="img-fluid" alt="{% trans 'This will become an image' %}"
|
||||
style="height: 3vw">
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<img src=" {{ row.cells.image }}" alt="{% trans 'Recipe Image' %}"
|
||||
class="card-img" style="object-fit: cover;height: 130px">
|
||||
{% else %}
|
||||
<img src="{% static 'recipe_no_image.svg' %}"
|
||||
<img src="{% static 'assets/recipe_no_image.svg' %}"
|
||||
alt="{% trans 'Recipe Image' %}"
|
||||
class="card-img d-none d-md-block"
|
||||
style="object-fit: cover; height: 130px">
|
||||
|
||||
@@ -681,7 +681,7 @@
|
||||
Promise.allSettled(recipe_promises).then(() => {
|
||||
console.log("proceeding to update shopping list", this.shopping_list)
|
||||
|
||||
if (this.shopping_list_id === null) {
|
||||
if (this.shopping_list.id === undefined) {
|
||||
return this.$http.post("{% url 'api:shoppinglist-list' %}", this.shopping_list, {}).then((response) => {
|
||||
console.log(response)
|
||||
this.makeToast(gettext('Updated'), gettext('Object created successfully!'), 'success')
|
||||
@@ -697,7 +697,7 @@
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
return this.$http.put("{% url 'api:shoppinglist-detail' shopping_list_id %}", this.shopping_list, {}).then((response) => {
|
||||
return this.$http.put("{% url 'api:shoppinglist-detail' 123456 %}".replace('123456', this.shopping_list.id), this.shopping_list, {}).then((response) => {
|
||||
console.log(response)
|
||||
this.shopping_list = response.body
|
||||
this.makeToast(gettext('Updated'), gettext('Changes saved successfully!'), 'success')
|
||||
|
||||
@@ -86,6 +86,8 @@ def page_help(page_name):
|
||||
help_pages = {
|
||||
'edit_storage': 'https://vabene1111.github.io/recipes/features/external_recipes/',
|
||||
'view_shopping': 'https://vabene1111.github.io/recipes/features/shopping/',
|
||||
'view_import': 'https://vabene1111.github.io/recipes/features/import_export/',
|
||||
'view_export': 'https://vabene1111.github.io/recipes/features/import_export/',
|
||||
}
|
||||
|
||||
link = help_pages.get(page_name, '')
|
||||
|
||||
@@ -33,6 +33,7 @@ class TestEditsRecipe(TestBase):
|
||||
expectations = {
|
||||
"2¼ l Wasser": (2.25, "l", "Wasser", ""),
|
||||
"2¼l Wasser": (2.25, "l", "Wasser", ""),
|
||||
"¼ l Wasser": (0.25, "l", "Wasser", ""),
|
||||
"3l Wasser": (3, "l", "Wasser", ""),
|
||||
"4 l Wasser": (4, "l", "Wasser", ""),
|
||||
"½l Wasser": (0.5, "l", "Wasser", ""),
|
||||
@@ -43,6 +44,10 @@ class TestEditsRecipe(TestBase):
|
||||
"1 Zwiebel(n)": (1, "", "Zwiebel(n)", ""),
|
||||
"4 1/2 Zwiebeln": (4.5, "", "Zwiebeln", ""),
|
||||
"4 ½ Zwiebeln": (4.5, "", "Zwiebeln", ""),
|
||||
"1/2 EL Mehl": (0.5, "EL", "Mehl", ""),
|
||||
"1/2 Zwiebel": (0.5, "", "Zwiebel", ""),
|
||||
"1/5g Mehl, gesiebt": (0.2, "g", "Mehl", "gesiebt"),
|
||||
"1/2 Zitrone, ausgepresst": (0.5, "", "Zitrone", "ausgepresst"),
|
||||
"etwas Mehl": (0, "", "etwas Mehl", ""),
|
||||
"Öl zum Anbraten": (0, "", "Öl zum Anbraten", ""),
|
||||
"n. B. Knoblauch, zerdrückt": (0, "", "n. B. Knoblauch", "zerdrückt"),
|
||||
|
||||
@@ -105,8 +105,10 @@ urlpatterns = [
|
||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
|
||||
path('offline/', views.offline, name='view_offline'),
|
||||
path('service-worker.js', (TemplateView.as_view(template_name="sw.js", content_type='application/javascript', )), name='service_worker'),
|
||||
|
||||
|
||||
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'),
|
||||
]
|
||||
|
||||
generic_models = (
|
||||
|
||||
@@ -6,9 +6,12 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
|
||||
from cookbook.helper.permission_helper import group_required
|
||||
from cookbook.integration.chowdown import Chowdown
|
||||
from cookbook.integration.default import Default
|
||||
from cookbook.integration.mealie import Mealie
|
||||
from cookbook.integration.nextcloud_cookbook import NextcloudCookbook
|
||||
from cookbook.integration.paprika import Paprika
|
||||
from cookbook.integration.safron import Safron
|
||||
from cookbook.models import Recipe
|
||||
|
||||
|
||||
@@ -19,6 +22,12 @@ def get_integration(request, export_type):
|
||||
return Paprika(request)
|
||||
if export_type == ImportExportBase.NEXTCLOUD:
|
||||
return NextcloudCookbook(request)
|
||||
if export_type == ImportExportBase.MEALIE:
|
||||
return Mealie(request)
|
||||
if export_type == ImportExportBase.CHOWDOWN:
|
||||
return Chowdown(request)
|
||||
if export_type == ImportExportBase.SAFRON:
|
||||
return Safron(request)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
|
||||
@@ -30,9 +30,7 @@ def keyword(request):
|
||||
@group_required('admin')
|
||||
def sync_log(request):
|
||||
table = ImportLogTable(
|
||||
SyncLog.objects.all().order_by(
|
||||
Lower('created_at').desc()
|
||||
)
|
||||
SyncLog.objects.all().order_by('-created_at')
|
||||
)
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
|
||||
|
||||