Compare commits

..

16 Commits

Author SHA1 Message Date
vabene1111
f5312496e3 fixed url image import 2021-06-20 13:35:05 +02:00
vabene1111
7f57e7ab56 Fixed recipe create space 2021-06-19 14:44:49 +02:00
vabene1111
7725665aa4 django needs two files 2021-06-17 16:05:18 +02:00
vabene1111
74e3d09065 moved to new migration 2021-06-17 15:23:39 +02:00
vabene1111
0522fa0236 formatting 2021-06-17 15:15:42 +02:00
vabene1111
38aeb285c5 fixes space migration fo I/N/S 2021-06-17 15:02:06 +02:00
vabene1111
a9a0716c45 import response improvements 2021-06-17 14:08:03 +02:00
vabene1111
afc31b313f Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-06-17 13:38:59 +02:00
vabene1111
151f43b0d5 lots of importer adjustments 2021-06-17 13:38:57 +02:00
Kaibu
a695261b9c share intend 2021-06-17 03:01:22 +02:00
Sander
1229a37d74 Translated using Weblate (Dutch)
Currently translated at 80.8% (381 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/nl/
2021-06-16 20:12:34 +00:00
Kaibu
ef200a4283 ui fixes 2021-06-16 19:13:03 +02:00
vabene1111
018fcf27ea migrated space to ingredient, step and nutritional value 2021-06-15 22:56:25 +02:00
vabene1111
b3504699b1 allow disable sharing for spaces 2021-06-15 21:12:43 +02:00
vabene1111
cdf4476345 recipe share link in modal 2021-06-15 20:57:25 +02:00
vabene1111
0edc9f48c9 fixed invite link signup 2021-06-15 20:13:25 +02:00
46 changed files with 504 additions and 184 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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))

View File

@@ -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"):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View 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),
),
]

View 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'),
),
]

View 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),
),
]

View 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),
]

View 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'),
),
]

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,
)
)

View File

@@ -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'),

View File

@@ -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):

View File

@@ -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]))

View File

@@ -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):

View File

@@ -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
})
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -44,8 +44,10 @@
"Date": "Date",
"Share": "Share",
"Export": "Export",
"Copy": "Copy",
"Rating": "Rating",
"Close": "Close",
"Link": "Link",
"Add": "Add",
"New": "New",
"Success": "Success",

View File

@@ -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