mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-28 20:49:22 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5312496e3 | ||
|
|
7f57e7ab56 | ||
|
|
7725665aa4 | ||
|
|
74e3d09065 | ||
|
|
0522fa0236 | ||
|
|
38aeb285c5 | ||
|
|
a9a0716c45 | ||
|
|
afc31b313f | ||
|
|
151f43b0d5 | ||
|
|
a695261b9c | ||
|
|
1229a37d74 | ||
|
|
ef200a4283 | ||
|
|
018fcf27ea | ||
|
|
b3504699b1 | ||
|
|
cdf4476345 | ||
|
|
0edc9f48c9 |
@@ -7,6 +7,8 @@ from django.contrib import messages
|
||||
from django.core.cache import caches
|
||||
from gettext import gettext as _
|
||||
|
||||
from cookbook.models import InviteLink
|
||||
|
||||
|
||||
class AllAuthCustomAdapter(DefaultAccountAdapter):
|
||||
|
||||
@@ -14,7 +16,11 @@ class AllAuthCustomAdapter(DefaultAccountAdapter):
|
||||
"""
|
||||
Whether to allow sign ups.
|
||||
"""
|
||||
if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP:
|
||||
signup_token = False
|
||||
if 'signup_token' in request.session and InviteLink.objects.filter(valid_until__gte=datetime.datetime.today(), used_by=None, uuid=request.session['signup_token']).exists():
|
||||
signup_token = True
|
||||
|
||||
if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP and not signup_token:
|
||||
return False
|
||||
else:
|
||||
return super(AllAuthCustomAdapter, self).is_open_for_signup(request)
|
||||
|
||||
@@ -40,7 +40,7 @@ class Pepperplate(Integration):
|
||||
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space)
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction='\n'.join(directions) + '\n\n'
|
||||
instruction='\n'.join(directions) + '\n\n', space=self.request.space,
|
||||
)
|
||||
|
||||
for ingredient in ingredients:
|
||||
@@ -49,7 +49,7 @@ class Pepperplate(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class ChefTap(Integration):
|
||||
|
||||
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, space=self.request.space, )
|
||||
|
||||
step = Step.objects.create(instruction='\n'.join(directions))
|
||||
step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space,)
|
||||
|
||||
if source_url != '':
|
||||
step.instruction += '\n' + source_url
|
||||
@@ -50,7 +50,7 @@ class ChefTap(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class Chowdown(Integration):
|
||||
recipe.keywords.add(keyword)
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions)
|
||||
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions), space=self.request.space,
|
||||
)
|
||||
|
||||
for ingredient in ingredients:
|
||||
@@ -63,7 +63,7 @@ class Chowdown(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class Domestica(Integration):
|
||||
recipe.save()
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction=file['directions']
|
||||
instruction=file['directions'], space=self.request.space,
|
||||
)
|
||||
|
||||
if file['source'] != '':
|
||||
@@ -40,7 +40,7 @@ class Domestica(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -109,35 +109,52 @@ class Integration:
|
||||
for f in files:
|
||||
if 'RecipeKeeper' in f['name']:
|
||||
import_zip = ZipFile(f['file'])
|
||||
file_list = []
|
||||
for z in import_zip.filelist:
|
||||
if self.import_file_name_filter(z):
|
||||
data_list = self.split_recipe_file(import_zip.read(z.filename).decode('utf-8'))
|
||||
for d in data_list:
|
||||
recipe = self.get_recipe_from_file(d)
|
||||
recipe.keywords.add(self.keyword)
|
||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||
self.handle_duplicates(recipe, import_duplicates)
|
||||
file_list.append(z)
|
||||
il.total_recipes += len(file_list)
|
||||
|
||||
for z in file_list:
|
||||
data_list = self.split_recipe_file(import_zip.read(z.filename).decode('utf-8'))
|
||||
for d in data_list:
|
||||
recipe = self.get_recipe_from_file(d)
|
||||
recipe.keywords.add(self.keyword)
|
||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||
self.handle_duplicates(recipe, import_duplicates)
|
||||
il.imported_recipes += 1
|
||||
il.save()
|
||||
import_zip.close()
|
||||
elif '.zip' in f['name'] or '.paprikarecipes' in f['name']:
|
||||
import_zip = ZipFile(f['file'])
|
||||
file_list = []
|
||||
for z in import_zip.filelist:
|
||||
if self.import_file_name_filter(z):
|
||||
try:
|
||||
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
|
||||
recipe.keywords.add(self.keyword)
|
||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||
self.handle_duplicates(recipe, import_duplicates)
|
||||
except Exception as e:
|
||||
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
|
||||
file_list.append(z)
|
||||
il.total_recipes += len(file_list)
|
||||
|
||||
for z in file_list:
|
||||
try:
|
||||
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
|
||||
recipe.keywords.add(self.keyword)
|
||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||
self.handle_duplicates(recipe, import_duplicates)
|
||||
il.imported_recipes += 1
|
||||
il.save()
|
||||
except Exception as e:
|
||||
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
|
||||
import_zip.close()
|
||||
elif '.json' in f['name'] or '.txt' in f['name']:
|
||||
data_list = self.split_recipe_file(f['file'])
|
||||
il.total_recipes += len(data_list)
|
||||
for d in data_list:
|
||||
try:
|
||||
recipe = self.get_recipe_from_file(d)
|
||||
recipe.keywords.add(self.keyword)
|
||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||
self.handle_duplicates(recipe, import_duplicates)
|
||||
il.imported_recipes += 1
|
||||
il.save()
|
||||
except Exception as e:
|
||||
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
|
||||
elif '.rtk' in f['name']:
|
||||
@@ -145,12 +162,16 @@ class Integration:
|
||||
for z in import_zip.filelist:
|
||||
if self.import_file_name_filter(z):
|
||||
data_list = self.split_recipe_file(import_zip.read(z.filename).decode('utf-8'))
|
||||
il.total_recipes += len(data_list)
|
||||
|
||||
for d in data_list:
|
||||
try:
|
||||
recipe = self.get_recipe_from_file(d)
|
||||
recipe.keywords.add(self.keyword)
|
||||
il.msg += f'{recipe.pk} - {recipe.name} \n'
|
||||
self.handle_duplicates(recipe, import_duplicates)
|
||||
il.imported_recipes += 1
|
||||
il.save()
|
||||
except Exception as e:
|
||||
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
|
||||
import_zip.close()
|
||||
|
||||
@@ -12,7 +12,7 @@ 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)
|
||||
return re.match(r'^recipes/([A-Za-z\d-])+/([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"))
|
||||
@@ -26,9 +26,9 @@ class Mealie(Integration):
|
||||
# TODO parse times (given in PT2H3M )
|
||||
|
||||
ingredients_added = False
|
||||
for s in recipe_json['recipeInstructions']:
|
||||
for s in recipe_json['recipe_instructions']:
|
||||
step = Step.objects.create(
|
||||
instruction=s['text']
|
||||
instruction=s['text'], space=self.request.space,
|
||||
)
|
||||
if not ingredients_added:
|
||||
ingredients_added = True
|
||||
@@ -36,21 +36,31 @@ class Mealie(Integration):
|
||||
if len(recipe_json['description'].strip()) > 500:
|
||||
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
|
||||
|
||||
for ingredient in recipe_json['recipeIngredient']:
|
||||
amount, unit, ingredient, note = parse(ingredient)
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
))
|
||||
for ingredient in recipe_json['recipe_ingredient']:
|
||||
try:
|
||||
if ingredient['food']:
|
||||
f = get_food(ingredient['food'], self.request.space)
|
||||
u = get_unit(ingredient['unit'], self.request.space)
|
||||
amount = ingredient['quantity']
|
||||
note = ingredient['note']
|
||||
else:
|
||||
amount, unit, ingredient, note = parse(ingredient['note'])
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
except:
|
||||
pass
|
||||
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)), filetype=get_filetype(z.filename))
|
||||
try:
|
||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(f'recipes/{recipe_json["slug"]}/images/min-original.webp')), filetype=get_filetype(f'recipes/{recipe_json["slug"]}/images/original'))
|
||||
except:
|
||||
pass
|
||||
|
||||
return recipe
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class MealMaster(Integration):
|
||||
recipe.keywords.add(keyword)
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction='\n'.join(directions) + '\n\n'
|
||||
instruction='\n'.join(directions) + '\n\n', space=self.request.space,
|
||||
)
|
||||
|
||||
for ingredient in ingredients:
|
||||
@@ -53,7 +53,7 @@ class MealMaster(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class NextcloudCookbook(Integration):
|
||||
ingredients_added = False
|
||||
for s in recipe_json['recipeInstructions']:
|
||||
step = Step.objects.create(
|
||||
instruction=s
|
||||
instruction=s, space=self.request.space,
|
||||
)
|
||||
if not ingredients_added:
|
||||
if len(recipe_json['description'].strip()) > 500:
|
||||
@@ -43,7 +43,7 @@ class NextcloudCookbook(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ class OpenEats(Integration):
|
||||
if file["source"] != '':
|
||||
instructions += file["source"]
|
||||
|
||||
step = Step.objects.create(instruction=instructions)
|
||||
step = Step.objects.create(instruction=instructions, space=self.request.space,)
|
||||
|
||||
for ingredient in file['ingredients']:
|
||||
f = get_food(ingredient['food'], self.request.space)
|
||||
u = get_unit(ingredient['unit'], self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=ingredient['amount']
|
||||
food=f, unit=u, amount=ingredient['amount'], space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class Paprika(Integration):
|
||||
pass
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction=instructions
|
||||
instruction=instructions, space=self.request.space,
|
||||
)
|
||||
|
||||
if len(recipe_json['description'].strip()) > 500:
|
||||
@@ -73,7 +73,7 @@ class Paprika(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
@@ -45,7 +45,7 @@ class RecetteTek(Integration):
|
||||
if not instructions:
|
||||
instructions = ''
|
||||
|
||||
step = Step.objects.create(instruction=instructions)
|
||||
step = Step.objects.create(instruction=instructions, space=self.request.space,)
|
||||
|
||||
# Append the original import url to the step (if it exists)
|
||||
try:
|
||||
@@ -63,7 +63,7 @@ class RecetteTek(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
except Exception as e:
|
||||
print(recipe.name, ': failed to parse recipe ingredients ', str(e))
|
||||
|
||||
@@ -41,7 +41,7 @@ class RecipeKeeper(Integration):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
step = Step.objects.create(instruction='')
|
||||
step = Step.objects.create(instruction='', space=self.request.space,)
|
||||
|
||||
for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"):
|
||||
if ingredient.text == "":
|
||||
@@ -50,7 +50,7 @@ class RecipeKeeper(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
|
||||
for s in file.find("div", {"itemprop": "recipeDirections"}).find_all("p"):
|
||||
|
||||
@@ -36,7 +36,7 @@ class RecipeSage(Integration):
|
||||
ingredients_added = False
|
||||
for s in file['recipeInstructions']:
|
||||
step = Step.objects.create(
|
||||
instruction=s['text']
|
||||
instruction=s['text'], space=self.request.space,
|
||||
)
|
||||
if not ingredients_added:
|
||||
ingredients_added = True
|
||||
@@ -46,7 +46,7 @@ class RecipeSage(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class RezKonv(Integration):
|
||||
recipe.keywords.add(keyword)
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction='\n'.join(directions) + '\n\n'
|
||||
instruction='\n'.join(directions) + '\n\n', space=self.request.space,
|
||||
)
|
||||
|
||||
for ingredient in ingredients:
|
||||
@@ -52,7 +52,7 @@ class RezKonv(Integration):
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -43,14 +43,14 @@ class Safron(Integration):
|
||||
|
||||
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space, )
|
||||
|
||||
step = Step.objects.create(instruction='\n'.join(directions))
|
||||
step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space,)
|
||||
|
||||
for ingredient in ingredients:
|
||||
amount, unit, ingredient, note = parse(ingredient)
|
||||
f = get_food(ingredient, self.request.space)
|
||||
u = get_unit(unit, self.request.space)
|
||||
step.ingredients.add(Ingredient.objects.create(
|
||||
food=f, unit=u, amount=amount, note=note
|
||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
||||
))
|
||||
recipe.steps.add(step)
|
||||
|
||||
|
||||
@@ -13,16 +13,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-06-12 20:30+0200\n"
|
||||
"PO-Revision-Date: 2021-05-04 09:02+0000\n"
|
||||
"Last-Translator: Jesse <jesse.kamps@pm.me>\n"
|
||||
"Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/recipes-"
|
||||
"backend/nl/>\n"
|
||||
"PO-Revision-Date: 2021-06-16 20:12+0000\n"
|
||||
"Last-Translator: Sander <dev.tandoor.translate_1623785130@binaryletter.com>\n"
|
||||
"Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/nl/>\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.5.3\n"
|
||||
"X-Generator: Weblate 4.6.2\n"
|
||||
|
||||
#: .\cookbook\filters.py:23 .\cookbook\templates\base.html:98
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:246
|
||||
@@ -223,21 +223,23 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:409
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr ""
|
||||
msgstr "Maximum aantal gebruikers voor deze ruimte bereikt."
|
||||
|
||||
#: .\cookbook\forms.py:415
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
msgstr "E-mailadres reeds in gebruik!"
|
||||
|
||||
#: .\cookbook\forms.py:423
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be send "
|
||||
"to the user."
|
||||
msgstr ""
|
||||
"Een e-mailadres is niet vereist, maar indien aanwezig zal de "
|
||||
"uitnodigingslink naar de gebruiker worden gestuurd."
|
||||
|
||||
#: .\cookbook\forms.py:438
|
||||
msgid "Name already taken."
|
||||
msgstr ""
|
||||
msgstr "Naam reeds in gebruik."
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
msgid "Accept Terms and Privacy"
|
||||
@@ -248,6 +250,8 @@ msgid ""
|
||||
"In order to prevent spam, the requested email was not send. Please wait a "
|
||||
"few minutes and try again."
|
||||
msgstr ""
|
||||
"Om spam te voorkomen werd de gevraagde e-mail niet verzonden. Wacht een paar "
|
||||
"minuten en probeer het opnieuw."
|
||||
|
||||
#: .\cookbook\helper\permission_helper.py:124
|
||||
#: .\cookbook\helper\permission_helper.py:144 .\cookbook\views\views.py:147
|
||||
@@ -303,6 +307,8 @@ msgid ""
|
||||
"An unexpected error occurred during the import. Please make sure you have "
|
||||
"uploaded a valid file."
|
||||
msgstr ""
|
||||
"Er is een onverwachte fout opgetreden tijdens het importeren. Controleer of "
|
||||
"u een geldig bestand hebt geüpload."
|
||||
|
||||
#: .\cookbook\integration\integration.py:169
|
||||
msgid "The following recipes were ignored because they already existed:"
|
||||
@@ -373,6 +379,8 @@ msgid ""
|
||||
"Maximum file storage for space in MB. 0 for unlimited, -1 to disable file "
|
||||
"upload."
|
||||
msgstr ""
|
||||
"Maximale bestandsopslag voor ruimte in MB. 0 voor onbeperkt, -1 om uploaden "
|
||||
"van bestanden uit te schakelen."
|
||||
|
||||
#: .\cookbook\models.py:121 .\cookbook\templates\search.html:7
|
||||
#: .\cookbook\templates\shopping_list.html:52
|
||||
@@ -416,18 +424,16 @@ msgstr "Tijd"
|
||||
#: .\cookbook\models.py:340
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:204
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:218
|
||||
#, fuzzy
|
||||
#| msgid "File ID"
|
||||
msgid "File"
|
||||
msgstr "Bestands ID"
|
||||
msgstr "Bestand"
|
||||
|
||||
#: .\cookbook\serializer.py:109
|
||||
msgid "File uploads are not enabled for this Space."
|
||||
msgstr ""
|
||||
msgstr "Bestandsuploads zijn niet ingeschakeld voor deze Ruimte."
|
||||
|
||||
#: .\cookbook\serializer.py:117
|
||||
msgid "You have reached your file upload limit."
|
||||
msgstr ""
|
||||
msgstr "U heeft de uploadlimiet bereikt."
|
||||
|
||||
#: .\cookbook\tables.py:35 .\cookbook\templates\books.html:36
|
||||
#: .\cookbook\templates\generic\edit_template.html:6
|
||||
@@ -468,23 +474,23 @@ msgstr "Rapporteer een bug"
|
||||
#: .\cookbook\templates\account\email.html:6
|
||||
#: .\cookbook\templates\account\email.html:9
|
||||
msgid "E-mail Addresses"
|
||||
msgstr ""
|
||||
msgstr "E-mailadressen"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:11
|
||||
msgid "The following e-mail addresses are associated with your account:"
|
||||
msgstr ""
|
||||
msgstr "De volgende e-mailadressen zijn aan uw account gekoppeld:"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:28
|
||||
msgid "Verified"
|
||||
msgstr ""
|
||||
msgstr "Geverifieerd"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:30
|
||||
msgid "Unverified"
|
||||
msgstr ""
|
||||
msgstr "Ongeverifieerd"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:32
|
||||
msgid "Primary"
|
||||
msgstr ""
|
||||
msgstr "Primair"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:39
|
||||
#, fuzzy
|
||||
@@ -494,7 +500,7 @@ msgstr "Stel in als kop"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:41
|
||||
msgid "Re-send Verification"
|
||||
msgstr ""
|
||||
msgstr "Verificatie opnieuw verzenden"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:42
|
||||
#: .\cookbook\templates\socialaccount\connections.html:36
|
||||
@@ -502,33 +508,33 @@ msgid "Remove"
|
||||
msgstr "Verwijder"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:50
|
||||
#, fuzzy
|
||||
#| msgid "Warning"
|
||||
msgid "Warning:"
|
||||
msgstr "Waarschuwing"
|
||||
msgstr "Waarschuwing:"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:50
|
||||
msgid ""
|
||||
"You currently do not have any e-mail address set up. You should really add "
|
||||
"an e-mail address so you can receive notifications, reset your password, etc."
|
||||
msgstr ""
|
||||
"U hebt momenteel geen e-mailadres ingesteld. U zou een e-mailadres moeten "
|
||||
"toevoegen zodat u meldingen kunt ontvangen, uw wachtwoord kunt resetten, enz."
|
||||
|
||||
#: .\cookbook\templates\account\email.html:56
|
||||
msgid "Add E-mail Address"
|
||||
msgstr ""
|
||||
msgstr "E-mailadres toevoegen"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:61
|
||||
msgid "Add E-mail"
|
||||
msgstr ""
|
||||
msgstr "E-mail toevoegen"
|
||||
|
||||
#: .\cookbook\templates\account\email.html:71
|
||||
msgid "Do you really want to remove the selected e-mail address?"
|
||||
msgstr ""
|
||||
msgstr "Wilt u het geselecteerde e-mailadres echt verwijderen?"
|
||||
|
||||
#: .\cookbook\templates\account\email_confirm.html:6
|
||||
#: .\cookbook\templates\account\email_confirm.html:10
|
||||
msgid "Confirm E-mail Address"
|
||||
msgstr ""
|
||||
msgstr "Bevestig e-mailadres"
|
||||
|
||||
#: .\cookbook\templates\account\email_confirm.html:16
|
||||
#, python-format
|
||||
@@ -538,6 +544,10 @@ msgid ""
|
||||
"for user %(user_display)s\n"
|
||||
" ."
|
||||
msgstr ""
|
||||
"Bevestig dat\n"
|
||||
" <a href=\"mailto:%(email)s\">%(email)s</a> een e-mailadres is voor "
|
||||
"gebruiker %(user_display)s\n"
|
||||
" ."
|
||||
|
||||
#: .\cookbook\templates\account\email_confirm.html:22
|
||||
#: .\cookbook\templates\generic\delete_template.html:21
|
||||
|
||||
18
cookbook/migrations/0134_space_allow_sharing.py
Normal file
18
cookbook/migrations/0134_space_allow_sharing.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-15 19:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0133_sharelink_abuse_blocked'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='space',
|
||||
name='allow_sharing',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
29
cookbook/migrations/0135_auto_20210615_2210.py
Normal file
29
cookbook/migrations/0135_auto_20210615_2210.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-15 20:10
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0134_space_allow_sharing'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ingredient',
|
||||
name='space',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='nutritioninformation',
|
||||
name='space',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='step',
|
||||
name='space',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
|
||||
]
|
||||
23
cookbook/migrations/0136_auto_20210617_1343.py
Normal file
23
cookbook/migrations/0136_auto_20210617_1343.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-17 11:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0135_auto_20210615_2210'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='importlog',
|
||||
name='imported_recipes',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='importlog',
|
||||
name='total_recipes',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
34
cookbook/migrations/0137_auto_20210617_1501.py
Normal file
34
cookbook/migrations/0137_auto_20210617_1501.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-17 13:01
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import Subquery, OuterRef
|
||||
from django_scopes import scopes_disabled
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_spaces(apps, schema_editor):
|
||||
with scopes_disabled():
|
||||
Recipe = apps.get_model('cookbook', 'Recipe')
|
||||
Step = apps.get_model('cookbook', 'Step')
|
||||
Ingredient = apps.get_model('cookbook', 'Ingredient')
|
||||
NutritionInformation = apps.get_model('cookbook', 'NutritionInformation')
|
||||
|
||||
Step.objects.filter(recipe__isnull=True).delete()
|
||||
Ingredient.objects.filter(step__recipe__isnull=True).delete()
|
||||
NutritionInformation.objects.filter(recipe__isnull=True).delete()
|
||||
|
||||
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
||||
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
|
||||
NutritionInformation.objects.update(space=Subquery(NutritionInformation.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0136_auto_20210617_1343'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_spaces),
|
||||
|
||||
]
|
||||
31
cookbook/migrations/0138_auto_20210617_1602.py
Normal file
31
cookbook/migrations/0138_auto_20210617_1602.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 3.2.4 on 2021-06-17 14:02
|
||||
|
||||
from django.db import migrations
|
||||
from django.db.models import Subquery, OuterRef
|
||||
from django_scopes import scopes_disabled
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('cookbook', '0137_auto_20210617_1501'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ingredient',
|
||||
name='space',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='nutritioninformation',
|
||||
name='space',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='step',
|
||||
name='space',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
|
||||
),
|
||||
]
|
||||
@@ -70,6 +70,7 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
max_recipes = models.IntegerField(default=0)
|
||||
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
|
||||
max_users = models.IntegerField(default=0)
|
||||
allow_sharing = models.BooleanField(default=True)
|
||||
demo = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
@@ -314,14 +315,8 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
|
||||
no_amount = models.BooleanField(default=False)
|
||||
order = models.IntegerField(default=0)
|
||||
|
||||
objects = ScopedManager(space='step__recipe__space')
|
||||
|
||||
@staticmethod
|
||||
def get_space_key():
|
||||
return 'step', 'recipe', 'space'
|
||||
|
||||
def get_space(self):
|
||||
return self.step_set.first().recipe_set.first().space
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
def __str__(self):
|
||||
return str(self.amount) + ' ' + str(self.unit) + ' ' + str(self.food)
|
||||
@@ -348,14 +343,8 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True)
|
||||
show_as_header = models.BooleanField(default=True)
|
||||
|
||||
objects = ScopedManager(space='recipe__space')
|
||||
|
||||
@staticmethod
|
||||
def get_space_key():
|
||||
return 'recipe', 'space'
|
||||
|
||||
def get_space(self):
|
||||
return self.recipe_set.first().space
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
def get_instruction_render(self):
|
||||
from cookbook.helper.template_helper import render_instructions
|
||||
@@ -376,17 +365,11 @@ class NutritionInformation(models.Model, PermissionModelMixin):
|
||||
max_length=512, default="", null=True, blank=True
|
||||
)
|
||||
|
||||
objects = ScopedManager(space='recipe__space')
|
||||
|
||||
@staticmethod
|
||||
def get_space_key():
|
||||
return 'recipe', 'space'
|
||||
|
||||
def get_space(self):
|
||||
return self.recipe_set.first().space
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
def __str__(self):
|
||||
return 'Nutrition'
|
||||
return f'Nutrition {self.pk}'
|
||||
|
||||
|
||||
class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModelMixin):
|
||||
@@ -682,6 +665,10 @@ class ImportLog(models.Model, PermissionModelMixin):
|
||||
running = models.BooleanField(default=True)
|
||||
msg = models.TextField(default="")
|
||||
keyword = models.ForeignKey(Keyword, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
total_recipes = models.IntegerField(default=0)
|
||||
imported_recipes = models.IntegerField(default=0)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
|
||||
@@ -283,6 +283,10 @@ class IngredientSerializer(WritableNestedModelSerializer):
|
||||
unit = UnitSerializer(allow_null=True)
|
||||
amount = CustomDecimalField()
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = (
|
||||
@@ -297,6 +301,10 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue')
|
||||
file = UserFileViewSerializer(allow_null=True, required=False)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
def get_ingredients_vue(self, obj):
|
||||
return obj.get_instruction_render()
|
||||
|
||||
@@ -312,6 +320,11 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
|
||||
|
||||
class NutritionInformationSerializer(serializers.ModelSerializer):
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = NutritionInformation
|
||||
fields = ('id', 'carbohydrates', 'fats', 'proteins', 'calories', 'source')
|
||||
@@ -520,7 +533,7 @@ class ImportLogSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ImportLog
|
||||
fields = ('id', 'type', 'msg', 'running', 'keyword', 'created_by', 'created_at')
|
||||
fields = ('id', 'type', 'msg', 'running', 'keyword', 'total_recipes', 'imported_recipes', 'created_by', 'created_at')
|
||||
read_only_fields = ('created_by',)
|
||||
|
||||
|
||||
@@ -578,6 +591,10 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
|
||||
unit = UnitExportSerializer(allow_null=True)
|
||||
amount = CustomDecimalField()
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount')
|
||||
@@ -586,6 +603,10 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
|
||||
class StepExportSerializer(WritableNestedModelSerializer):
|
||||
ingredients = IngredientExportSerializer(many=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = ('name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header')
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,10 +1,11 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from django.db.models import Subquery, OuterRef
|
||||
from django.urls import reverse
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.models import Ingredient
|
||||
from cookbook.models import Ingredient, Step
|
||||
|
||||
LIST_URL = 'api:ingredient-list'
|
||||
DETAIL_URL = 'api:ingredient-detail'
|
||||
@@ -28,6 +29,8 @@ def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
||||
with scopes_disabled():
|
||||
recipe_1_s1.space = space_2
|
||||
recipe_1_s1.save()
|
||||
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
||||
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
|
||||
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 0
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 10
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from django.db.models import Subquery, OuterRef
|
||||
from django.urls import reverse
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.models import Step
|
||||
from cookbook.models import Step, Ingredient
|
||||
|
||||
LIST_URL = 'api:step-list'
|
||||
DETAIL_URL = 'api:step-detail'
|
||||
@@ -28,6 +29,8 @@ def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
||||
with scopes_disabled():
|
||||
recipe_1_s1.space = space_2
|
||||
recipe_1_s1.save()
|
||||
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
||||
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
|
||||
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 0
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 2
|
||||
|
||||
@@ -51,8 +51,8 @@ def get_random_recipe(space_1, u1_s1):
|
||||
internal=True,
|
||||
)
|
||||
|
||||
s1 = Step.objects.create(name=uuid.uuid4(), instruction=uuid.uuid4(), )
|
||||
s2 = Step.objects.create(name=uuid.uuid4(), instruction=uuid.uuid4(), )
|
||||
s1 = Step.objects.create(name=uuid.uuid4(), instruction=uuid.uuid4(), space=space_1, )
|
||||
s2 = Step.objects.create(name=uuid.uuid4(), instruction=uuid.uuid4(), space=space_1, )
|
||||
|
||||
r.steps.add(s1)
|
||||
r.steps.add(s2)
|
||||
@@ -64,6 +64,7 @@ def get_random_recipe(space_1, u1_s1):
|
||||
food=Food.objects.create(name=uuid.uuid4(), space=space_1, ),
|
||||
unit=Unit.objects.create(name=uuid.uuid4(), space=space_1, ),
|
||||
note=uuid.uuid4(),
|
||||
space=space_1,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -73,6 +74,7 @@ def get_random_recipe(space_1, u1_s1):
|
||||
food=Food.objects.create(name=uuid.uuid4(), space=space_1, ),
|
||||
unit=Unit.objects.create(name=uuid.uuid4(), space=space_1, ),
|
||||
note=uuid.uuid4(),
|
||||
space=space_1,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ urlpatterns = [
|
||||
path('api/recipe-from-source/', api.recipe_from_source, name='api_recipe_from_source'),
|
||||
path('api/backup/', api.get_backup, name='api_backup'),
|
||||
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),
|
||||
path('api/share-link/<int:pk>', api.share_link, name='api_share_link'),
|
||||
|
||||
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
||||
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'),
|
||||
|
||||
@@ -14,6 +14,7 @@ from django.core.files import File
|
||||
from django.db.models import Q
|
||||
from django.http import FileResponse, HttpResponse, JsonResponse
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from icalendar import Calendar, Event
|
||||
from recipe_scrapers import scrape_me, WebsiteNotImplementedError, NoSchemaFoundInWildMode
|
||||
@@ -40,7 +41,7 @@ from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
|
||||
MealType, Recipe, RecipeBook, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Step,
|
||||
Storage, Sync, SyncLog, Unit, UserPreference,
|
||||
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile)
|
||||
ViewLog, RecipeBookEntry, Supermarket, ImportLog, BookmarkletImport, SupermarketCategory, UserFile, ShareLink)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@@ -576,6 +577,16 @@ def sync_all(request):
|
||||
return redirect('list_recipe_import')
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def share_link(request, pk):
|
||||
if request.space.allow_sharing:
|
||||
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
|
||||
link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space)
|
||||
return JsonResponse({'pk': pk, 'share': link.uuid, 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))})
|
||||
else:
|
||||
return JsonResponse({'error': 'sharing_disabled'}, status=403)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
@ajax_request
|
||||
def log_cooking(request, recipe_id):
|
||||
|
||||
@@ -143,7 +143,7 @@ def import_url(request):
|
||||
)
|
||||
|
||||
step = Step.objects.create(
|
||||
instruction=data['recipeInstructions'],
|
||||
instruction=data['recipeInstructions'], space=request.space,
|
||||
)
|
||||
|
||||
recipe.steps.add(step)
|
||||
@@ -156,7 +156,7 @@ def import_url(request):
|
||||
recipe.keywords.add(k)
|
||||
|
||||
for ing in data['recipeIngredient']:
|
||||
ingredient = Ingredient()
|
||||
ingredient = Ingredient(space=request.space,)
|
||||
|
||||
if ing['ingredient']['text'] != '':
|
||||
ingredient.food, f_created = Food.objects.get_or_create(
|
||||
@@ -188,16 +188,19 @@ def import_url(request):
|
||||
try:
|
||||
response = requests.get(data['image'])
|
||||
|
||||
img, filetype = handle_image(request, response.content)
|
||||
img, filetype = handle_image(request, BytesIO(response.content))
|
||||
recipe.image = File(
|
||||
img, name=f'{uuid.uuid4()}_{recipe.pk}{filetype}'
|
||||
)
|
||||
recipe.save()
|
||||
except UnidentifiedImageError:
|
||||
except UnidentifiedImageError as e:
|
||||
print(e)
|
||||
pass
|
||||
except MissingSchema:
|
||||
except MissingSchema as e:
|
||||
print(e)
|
||||
pass
|
||||
except:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
return HttpResponse(reverse('view_recipe', args=[recipe.pk]))
|
||||
|
||||
@@ -41,7 +41,7 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
|
||||
obj.space = self.request.space
|
||||
obj.internal = True
|
||||
obj.save()
|
||||
obj.steps.add(Step.objects.create())
|
||||
obj.steps.add(Step.objects.create(space=self.request.space))
|
||||
return HttpResponseRedirect(reverse('edit_recipe', kwargs={'pk': obj.pk}))
|
||||
|
||||
def get_success_url(self):
|
||||
|
||||
@@ -1,54 +1,42 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<h2>{{ $t('Import') }}</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<template v-if="import_info !== undefined">
|
||||
|
||||
<template v-if="import_info.running" style="text-align: center;">
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<loading-spinner></loading-spinner>
|
||||
<br/>
|
||||
<br/>
|
||||
<template v-if="import_info.running">
|
||||
<h5 style="text-align: center">{{ $t('Importing') }}...</h5>
|
||||
|
||||
<b-progress :max="import_info.total_recipes">
|
||||
<b-progress-bar :value="import_info.imported_recipes" :label="`${import_info.imported_recipes}/${import_info.total_recipes}`"></b-progress-bar>
|
||||
</b-progress>
|
||||
|
||||
<loading-spinner :size="25"></loading-spinner>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<span>{{ $t('Import_finished') }}! </span>
|
||||
<a :href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
|
||||
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col col-md-12" v-if="!import_info.running">
|
||||
<span>{{ $t('Import_finished') }}! </span>
|
||||
<a :href="`${resolveDjangoUrl('view_search') }?keyword=${import_info.keyword.id}`"
|
||||
v-if="import_info.keyword !== null">{{ $t('View_Recipes') }}</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label for="id_textarea">{{ $t('Information') }}</label>
|
||||
<textarea id="id_textarea" class="form-control" style="height: 50vh" v-html="import_info.msg"
|
||||
disabled></textarea>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label for="id_textarea">{{ $t('Information') }}</label>
|
||||
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh"
|
||||
v-html="import_info.msg"
|
||||
disabled></textarea>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -90,6 +78,8 @@ export default {
|
||||
setInterval(() => {
|
||||
if ((this.import_id !== null) && window.navigator.onLine && this.import_info.running) {
|
||||
this.refreshData()
|
||||
let el = this.$refs.output_text
|
||||
el.scrollTop = el.scrollHeight;
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
@@ -100,6 +90,7 @@ export default {
|
||||
|
||||
apiClient.retrieveImportLog(this.import_id).then(result => {
|
||||
this.import_info = result.data
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<b-modal class="modal" id="id_modal_add_book" :title="$t('Add_to_Book')" :ok-title="$t('Add')"
|
||||
<b-modal class="modal" :id="`id_modal_add_book_${modal_id}`" :title="$t('Add_to_Book')" :ok-title="$t('Add')"
|
||||
:cancel-title="$t('Close')" @ok="addToBook()">
|
||||
|
||||
<multiselect
|
||||
@@ -42,6 +42,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
recipe: Object,
|
||||
modal_id: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<b-modal class="modal" id="id_modal_cook_log" :title="$t('Log_Recipe_Cooking')" :ok-title="$t('Save')"
|
||||
<b-modal class="modal" :id="`id_modal_cook_log_${modal_id}`" :title="$t('Log_Recipe_Cooking')" :ok-title="$t('Save')"
|
||||
:cancel-title="$t('Close')" @ok="logCook()">
|
||||
|
||||
<p>{{ $t('all_fields_optional') }}</p>
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
name: 'CookLog',
|
||||
props: {
|
||||
recipe: Object,
|
||||
modal_id: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: center">
|
||||
<img class="spinner-tandoor" alt="loading spinner" src="" style="height: 30vh"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col" style="text-align: center">
|
||||
<img class="spinner-tandoor" alt="loading spinner" src="" v-bind:style="{ height: size + 'vh' }"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -12,6 +12,10 @@ export default {
|
||||
name: 'LoadingSpinner',
|
||||
props: {
|
||||
recipe: Object,
|
||||
size: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
<a :href="clickUrl()">
|
||||
<b-card-img-lazy style="height: 15vh; object-fit: cover" :src=recipe_image v-bind:alt="$t('Recipe_Image')"
|
||||
top></b-card-img-lazy>
|
||||
|
||||
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right"
|
||||
style="float:right; text-align: right; padding-top: 10px; padding-right: 5px">
|
||||
<recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu>
|
||||
<a><recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu></a>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
<b-card-body>
|
||||
<h5><a :href="clickUrl()">
|
||||
<template v-if="recipe !== null">{{ recipe.name }}</template>
|
||||
@@ -23,7 +23,10 @@
|
||||
{{ recipe.description }}
|
||||
<keywords :recipe="recipe" style="margin-top: 4px"></keywords>
|
||||
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t('External') }}</b-badge>
|
||||
<b-badge pill variant="success" v-if="Date.parse(recipe.created_at) > new Date(Date.now() - (7 * (1000 * 60 * 60 * 24)))">{{ $t('New') }}</b-badge>
|
||||
<b-badge pill variant="success"
|
||||
v-if="Date.parse(recipe.created_at) > new Date(Date.now() - (7 * (1000 * 60 * 60 * 24)))">
|
||||
{{ $t('New') }}
|
||||
</b-badge>
|
||||
</template>
|
||||
<template v-else>{{ meal_plan.note }}</template>
|
||||
</b-card-text>
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)" v-if="!recipe.internal"><i
|
||||
class="fas fa-exchange-alt fa-fw"></i> {{ $t('convert_internal') }}</a>
|
||||
|
||||
<button class="dropdown-item" @click="$bvModal.show('id_modal_add_book')">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Add_to_Book') }}
|
||||
</button>
|
||||
<a href="#">
|
||||
<button class="dropdown-item" @click="$bvModal.show(`id_modal_add_book_${modal_id}`)">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {{ $t('Add_to_Book') }}
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" :href="`${resolveDjangoUrl('view_shopping') }?r=[${recipe.id},${servings_value}]`"
|
||||
v-if="recipe.internal" target="_blank" rel="noopener noreferrer">
|
||||
@@ -29,10 +31,11 @@
|
||||
class="fas fa-calendar fa-fw"></i> {{ $t('Add_to_Plan') }}
|
||||
</a>
|
||||
|
||||
|
||||
<button class="dropdown-item" @click="$bvModal.show('id_modal_cook_log')"><i
|
||||
class="fas fa-clipboard-list fa-fw"></i> {{ $t('Log_Cooking') }}
|
||||
</button>
|
||||
<a href="#">
|
||||
<button class="dropdown-item" @click="$bvModal.show(`id_modal_cook_log_${modal_id}`)"><i
|
||||
class="fas fa-clipboard-list fa-fw"></i> {{ $t('Log_Cooking') }}
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<button class="dropdown-item" onclick="window.print()"><i
|
||||
class="fas fa-print fa-fw"></i> {{ $t('Print') }}
|
||||
@@ -41,21 +44,42 @@
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('view_export') + '?r=' + recipe.id" target="_blank"
|
||||
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {{ $t('Export') }}</a>
|
||||
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('new_share_link', recipe.id)" target="_blank"
|
||||
rel="noopener noreferrer" v-if="recipe.internal"><i class="fas fa-share-alt fa-fw"></i> {{ $t('Share') }}</a>
|
||||
<a href="#">
|
||||
<button class="dropdown-item" @click="createShareLink()" v-if="recipe.internal"><i
|
||||
class="fas fa-share-alt fa-fw"></i> {{ $t('Share') }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<cook-log :recipe="recipe"></cook-log>
|
||||
<cook-log :recipe="recipe" :modal_id="modal_id"></cook-log>
|
||||
<add-recipe-to-book :recipe="recipe" :modal_id="modal_id"></add-recipe-to-book>
|
||||
|
||||
<b-modal :id="`modal-share-link_${modal_id}`" v-bind:title="$t('Share')" hide-footer>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label v-if="recipe_share_link !== undefined">{{ $t('Link') }}</label>
|
||||
<input ref="share_link_ref" class="form-control" v-model="recipe_share_link"/>
|
||||
<b-button class="mt-2 mb-3" variant="secondary" @click="$bvModal.hide(`modal-share-link_${modal_id}`)">{{ $t('Close') }}</b-button>
|
||||
<b-button class="mt-2 mb-3 ml-2" variant="primary" @click="copyShareLink()">{{ $t('Copy') }}</b-button>
|
||||
<b-button class="mt-2 mb-3 ml-2 float-right" variant="success" @click="shareIntend()">{{ $t('Share') }} <i class="fa fa-share-alt"></i></b-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</b-modal>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
import {makeToast, resolveDjangoUrl, ResolveUrlMixin} from "@/utils/utils";
|
||||
import CookLog from "@/components/CookLog";
|
||||
import axios from "axios";
|
||||
import AddRecipeToBook from "./AddRecipeToBook";
|
||||
|
||||
export default {
|
||||
name: 'RecipeContextMenu',
|
||||
@@ -63,11 +87,14 @@ export default {
|
||||
ResolveUrlMixin
|
||||
],
|
||||
components: {
|
||||
AddRecipeToBook,
|
||||
CookLog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
servings_value: 0
|
||||
servings_value: 0,
|
||||
recipe_share_link: undefined,
|
||||
modal_id: this.recipe.id + Math.round(Math.random() * 100000)
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@@ -79,6 +106,33 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.servings_value = ((this.servings === -1) ? this.recipe.servings : this.servings)
|
||||
},
|
||||
methods: {
|
||||
createShareLink: function () {
|
||||
axios.get(resolveDjangoUrl('api_share_link', this.recipe.id)).then(result => {
|
||||
this.$bvModal.show(`modal-share-link_${this.modal_id}`)
|
||||
this.recipe_share_link = result.data.link
|
||||
}).catch(err => {
|
||||
|
||||
if (err.response.status === 403) {
|
||||
makeToast(this.$t('Share'), this.$t('Sharing is not enabled for this space.'), 'danger')
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
copyShareLink: function () {
|
||||
let share_input = this.$refs.share_link_ref;
|
||||
share_input.select();
|
||||
document.execCommand("copy");
|
||||
},
|
||||
shareIntend: function () {
|
||||
let shareData = {
|
||||
title: this.recipe.name,
|
||||
text: this.$t('Check out this recipe!'),
|
||||
url: this.recipe_share_link
|
||||
}
|
||||
navigator.share(shareData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -44,8 +44,10 @@
|
||||
"Date": "Date",
|
||||
"Share": "Share",
|
||||
"Export": "Export",
|
||||
"Copy": "Copy",
|
||||
"Rating": "Rating",
|
||||
"Close": "Close",
|
||||
"Link": "Link",
|
||||
"Add": "Add",
|
||||
"New": "New",
|
||||
"Success": "Success",
|
||||
|
||||
@@ -193,6 +193,18 @@ export interface ImportLog {
|
||||
* @memberof ImportLog
|
||||
*/
|
||||
keyword?: ImportLogKeyword;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ImportLog
|
||||
*/
|
||||
total_recipes?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ImportLog
|
||||
*/
|
||||
imported_recipes?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@@ -1031,6 +1043,12 @@ export interface RecipeSteps {
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
show_as_header?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {StepFile}
|
||||
* @memberof RecipeSteps
|
||||
*/
|
||||
file?: StepFile | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1039,7 +1057,8 @@ export interface RecipeSteps {
|
||||
*/
|
||||
export enum RecipeStepsTypeEnum {
|
||||
Text = 'TEXT',
|
||||
Time = 'TIME'
|
||||
Time = 'TIME',
|
||||
File = 'FILE'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1429,6 +1448,12 @@ export interface Step {
|
||||
* @memberof Step
|
||||
*/
|
||||
show_as_header?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {StepFile}
|
||||
* @memberof Step
|
||||
*/
|
||||
file?: StepFile | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1437,9 +1462,35 @@ export interface Step {
|
||||
*/
|
||||
export enum StepTypeEnum {
|
||||
Text = 'TEXT',
|
||||
Time = 'TIME'
|
||||
Time = 'TIME',
|
||||
File = 'FILE'
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface StepFile
|
||||
*/
|
||||
export interface StepFile {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StepFile
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
*
|
||||
* @type {any}
|
||||
* @memberof StepFile
|
||||
*/
|
||||
file?: any;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof StepFile
|
||||
*/
|
||||
id?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
|
||||
Reference in New Issue
Block a user