mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-27 04:00:48 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7046bc705 | ||
|
|
52946a8e4c | ||
|
|
dd6b77e029 | ||
|
|
396c1f3d5f | ||
|
|
379d5a5177 | ||
|
|
85a4d5d432 | ||
|
|
43eb10e488 | ||
|
|
d702c08a12 |
36
README.md
36
README.md
@@ -3,6 +3,8 @@ Recipes is a Django application to manage, tag and search recipes using either b
|
||||
|
||||

|
||||
|
||||
[More Screenshots](https://imgur.com/a/V01151p)
|
||||
|
||||
### Features
|
||||
|
||||
- :package: **Sync** files with Dropbox and Nextcloud (more can easily be added)
|
||||
@@ -12,13 +14,15 @@ Recipes is a Django application to manage, tag and search recipes using either b
|
||||
- :iphone: Optimized for use on **mobile** devices like phones and tablets
|
||||
- :shopping_cart: Generate **shopping** lists from recipes
|
||||
- :calendar: Create a **Plan** on what to eat when
|
||||
- :person_with_blond_hair: **Share** recipes with friends and comment on them to suggest or remember changes you made
|
||||
- :family: **Share** recipes with friends and comment on them to suggest or remember changes you made
|
||||
- :whale: Easy setup with **Docker**
|
||||
- :art: Customize your interface with **themes**
|
||||
- :envelope: Export and import recipes from other users
|
||||
- :heavy_plus_sign: Many more like recipe scaling, image compression, cookbooks, printing views, ...
|
||||
|
||||
This application is meant for people with a collection of recipes they want to share with family and friends or simply store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as a public page.
|
||||
|
||||
This application is meant for people with a collection of recipes they want to share with family and friends or simply
|
||||
store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as a public page.
|
||||
Some Documentation can be found [here](https://github.com/vabene1111/recipes/wiki)
|
||||
# Installation
|
||||
|
||||
The docker image (`vabene1111/recipes`) simply exposes the application on port `8080`. You may choose any preferred installation method, the following are just examples to make it easier.
|
||||
@@ -38,7 +42,6 @@ Otherwise simply follow the instructions for any django based deployment
|
||||
(for example [this one](http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)).
|
||||
|
||||
## Updating
|
||||
|
||||
While intermediate updates can be skipped when updating please make sure to **read the release notes** in case some special action is required to update.
|
||||
|
||||
0. Before updating it is recommended to **create a backup!**
|
||||
@@ -50,31 +53,6 @@ While intermediate updates can be skipped when updating please make sure to **re
|
||||
|
||||
You can find a basic kubernetes setup [here](docs/k8s/). Please see the README in the folder for more detail.
|
||||
|
||||
# Documentation
|
||||
|
||||
Most things should be straight forward but there are some more complicated things.
|
||||
##### Storage Backends
|
||||
A `Storage Backend` is a remote storage location where PDF files are read from. To add a new backend click on `Storage Data` and then on `Storage Backends`. There click the plus button.
|
||||
|
||||
Enter a name (just a display name for you to identify it) and an API access Token for the account you want to use.
|
||||
Dropboxes API tokens can be found on the [Dropboxes API explorer](https://dropbox.github.io/dropbox-api-v2-explorer/#auth_token/from_oauth1)
|
||||
with the button on the top right. For Nextcloud you can use a App apssword created in the settings.
|
||||
|
||||
##### Adding Synced Paths
|
||||
To add a new path from your Storage backend to the sync list, go to `Storage Data >> Configure Sync` and select the storage backend you want to use.
|
||||
Then enter the path you want to monitor starting at the storage root (e.g. `/Folder/RecipesFolder`) and save it.
|
||||
|
||||
##### Syncing Data
|
||||
To sync the recipes app with the storage backends press `Sync now` under `Storage Data >> Configure Sync`.
|
||||
##### Import Recipes
|
||||
All files found by the sync can be found under `Manage Data >> Import recipes`. There you can either import all at once without modifying them or import one by one, adding tags while importing.
|
||||
##### Batch Edit
|
||||
If you have many untagged recipes, you may want to edit them all at once. To do so, go to
|
||||
`Storage Data >> Batch Edit`. Enter a word which should be contained in the recipe name and select the tags you want to apply.
|
||||
When clicking submit, every recipe containing the word will be updated (tags are added).
|
||||
|
||||
> Currently the only option is word contains, maybe some more SQL like operators will be added later.
|
||||
|
||||
## Contributing
|
||||
Pull Requests and ideas are welcome, feel free to contribute in any way.
|
||||
For any questions on how to work with django please refer to their excellent [documentation](https://www.djangoproject.com/start/).
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.forms import widgets
|
||||
from django.utils.translation import gettext as _
|
||||
@@ -100,6 +99,25 @@ class ShoppingForm(forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class ExportForm(forms.Form):
|
||||
recipe = forms.ModelChoiceField(
|
||||
queryset=Recipe.objects.filter(internal=True).all(),
|
||||
widget=SelectWidget
|
||||
)
|
||||
image = forms.BooleanField(
|
||||
help_text=_('Export Base64 encoded image?'),
|
||||
required=False
|
||||
)
|
||||
download = forms.BooleanField(
|
||||
help_text=_('Download export directly or show on page?'),
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
class ImportForm(forms.Form):
|
||||
recipe = forms.CharField(widget=forms.Textarea, help_text=_('Simply paste a JSON export into this textarea and click import.'))
|
||||
|
||||
|
||||
class UnitMergeForm(forms.Form):
|
||||
prefix = 'unit'
|
||||
|
||||
|
||||
81
cookbook/helper/mdx_urlize.py
Normal file
81
cookbook/helper/mdx_urlize.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""A more liberal autolinker
|
||||
|
||||
Inspired by Django's urlize function.
|
||||
|
||||
Positive examples:
|
||||
|
||||
>>> import markdown
|
||||
>>> md = markdown.Markdown(extensions=['urlize'])
|
||||
|
||||
>>> md.convert('http://example.com/')
|
||||
u'<p><a href="http://example.com/">http://example.com/</a></p>'
|
||||
|
||||
>>> md.convert('go to http://example.com')
|
||||
u'<p>go to <a href="http://example.com">http://example.com</a></p>'
|
||||
|
||||
>>> md.convert('example.com')
|
||||
u'<p><a href="http://example.com">example.com</a></p>'
|
||||
|
||||
>>> md.convert('example.net')
|
||||
u'<p><a href="http://example.net">example.net</a></p>'
|
||||
|
||||
>>> md.convert('www.example.us')
|
||||
u'<p><a href="http://www.example.us">www.example.us</a></p>'
|
||||
|
||||
>>> md.convert('(www.example.us/path/?name=val)')
|
||||
u'<p>(<a href="http://www.example.us/path/?name=val">www.example.us/path/?name=val</a>)</p>'
|
||||
|
||||
>>> md.convert('go to <http://example.com> now!')
|
||||
u'<p>go to <a href="http://example.com">http://example.com</a> now!</p>'
|
||||
|
||||
Negative examples:
|
||||
|
||||
>>> md.convert('del.icio.us')
|
||||
u'<p>del.icio.us</p>'
|
||||
|
||||
"""
|
||||
|
||||
import markdown
|
||||
|
||||
# Global Vars
|
||||
URLIZE_RE = '(%s)' % '|'.join([
|
||||
r'<(?:f|ht)tps?://[^>]*>',
|
||||
r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
|
||||
r'\bwww\.[^)<>\s]+[^.,)<>\s]',
|
||||
r'[^(<\s]+\.(?:com|net|org)\b',
|
||||
])
|
||||
|
||||
class UrlizePattern(markdown.inlinepatterns.Pattern):
|
||||
""" Return a link Element given an autolink (`http://example/com`). """
|
||||
def handleMatch(self, m):
|
||||
url = m.group(2)
|
||||
|
||||
if url.startswith('<'):
|
||||
url = url[1:-1]
|
||||
|
||||
text = url
|
||||
|
||||
if not url.split('://')[0] in ('http','https','ftp'):
|
||||
if '@' in url and not '/' in url:
|
||||
url = 'mailto:' + url
|
||||
else:
|
||||
url = 'http://' + url
|
||||
|
||||
el = markdown.util.etree.Element("a")
|
||||
el.set('href', url)
|
||||
el.text = markdown.util.AtomicString(text)
|
||||
return el
|
||||
|
||||
class UrlizeExtension(markdown.Extension):
|
||||
""" Urlize Extension for Python-Markdown. """
|
||||
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
""" Replace autolink with UrlizePattern """
|
||||
md.inlinePatterns['autolink'] = UrlizePattern(URLIZE_RE, md)
|
||||
|
||||
def makeExtension(*args, **kwargs):
|
||||
return UrlizeExtension(*args, **kwargs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
Binary file not shown.
@@ -7,25 +7,25 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-03-18 12:13+0100\n"
|
||||
"PO-Revision-Date: 2020-03-18 12:19+0100\n"
|
||||
"POT-Creation-Date: 2020-04-25 23:31+0200\n"
|
||||
"PO-Revision-Date: 2020-04-25 23:31+0200\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: de\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"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
|
||||
#: .\cookbook\filters.py:15 .\cookbook\templates\base.html:98
|
||||
#: .\cookbook\filters.py:15 .\cookbook\templates\base.html:99
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:28
|
||||
#: .\cookbook\templates\forms\ingredients.html:34
|
||||
#: .\cookbook\templates\recipe_view.html:104 .\cookbook\views\lists.py:45
|
||||
#: .\cookbook\templates\recipe_view.html:110 .\cookbook\views\lists.py:45
|
||||
msgid "Ingredients"
|
||||
msgstr "Zutaten"
|
||||
|
||||
#: .\cookbook\forms.py:35
|
||||
#: .\cookbook\forms.py:36
|
||||
msgid ""
|
||||
"Color of the top navigation bar. Not all colors work with all themes, just "
|
||||
"try them out!"
|
||||
@@ -33,36 +33,48 @@ msgstr ""
|
||||
"Farbe der oberen Navigationsleiste. Nicht alle Farben passen, daher einfach "
|
||||
"mal ausprobieren!"
|
||||
|
||||
#: .\cookbook\forms.py:49 .\cookbook\forms.py:67 .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:37
|
||||
msgid "Default Unit to be used when inserting a new ingredient into a recipe."
|
||||
msgstr "Standard Einheit für neue Zutaten."
|
||||
|
||||
#: .\cookbook\forms.py:49
|
||||
msgid ""
|
||||
"Both fields are optional. If none are given the username will be displayed "
|
||||
"instead"
|
||||
msgstr ""
|
||||
"Beide Felder sind optional, wenn keins von beiden gegeben ist wird der "
|
||||
"Nutzername angezeigt"
|
||||
|
||||
#: .\cookbook\forms.py:63 .\cookbook\forms.py:81 .\cookbook\forms.py:229
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: .\cookbook\forms.py:50 .\cookbook\forms.py:68 .\cookbook\forms.py:197
|
||||
#: .\cookbook\forms.py:64 .\cookbook\forms.py:82 .\cookbook\forms.py:230
|
||||
#: .\cookbook\templates\stats.html:22
|
||||
msgid "Keywords"
|
||||
msgstr "Schlagwörter"
|
||||
|
||||
#: .\cookbook\forms.py:51 .\cookbook\forms.py:70
|
||||
#: .\cookbook\forms.py:65 .\cookbook\forms.py:84
|
||||
msgid "Preparation time in minutes"
|
||||
msgstr "Zubereitungszeit in Minuten"
|
||||
|
||||
#: .\cookbook\forms.py:52 .\cookbook\forms.py:71
|
||||
#: .\cookbook\forms.py:66 .\cookbook\forms.py:85
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
msgstr "Wartezeit (kochen/backen) in Minuten"
|
||||
|
||||
#: .\cookbook\forms.py:53 .\cookbook\forms.py:198
|
||||
#: .\cookbook\forms.py:67 .\cookbook\forms.py:231
|
||||
msgid "Path"
|
||||
msgstr "Pfad"
|
||||
|
||||
#: .\cookbook\forms.py:54
|
||||
#: .\cookbook\forms.py:68
|
||||
msgid "Storage UID"
|
||||
msgstr "Speicher ID"
|
||||
|
||||
#: .\cookbook\forms.py:69
|
||||
#: .\cookbook\forms.py:83
|
||||
msgid "Instructions"
|
||||
msgstr "Anleitung"
|
||||
|
||||
#: .\cookbook\forms.py:82
|
||||
#: .\cookbook\forms.py:96
|
||||
msgid ""
|
||||
"Include <code>- [ ]</code> in list for easier usage in markdown based "
|
||||
"documents."
|
||||
@@ -70,51 +82,63 @@ msgstr ""
|
||||
"Füge <code>- [ ]</code> vor den Zutaten ein um sie besser in einem Markdown "
|
||||
"Dokument zu verwenden."
|
||||
|
||||
#: .\cookbook\forms.py:94
|
||||
#: .\cookbook\forms.py:108
|
||||
msgid "Export Base64 encoded image?"
|
||||
msgstr "Base64 kodiertes Bild exportieren ?"
|
||||
|
||||
#: .\cookbook\forms.py:112
|
||||
msgid "Download export directly or show on page?"
|
||||
msgstr "Direkter Download oder anzeige auf Seite ?"
|
||||
|
||||
#: .\cookbook\forms.py:118
|
||||
msgid "Simply paste a JSON export into this textarea and click import."
|
||||
msgstr "Einfach JSON in die Textbox einfügen und importieren klicken."
|
||||
|
||||
#: .\cookbook\forms.py:127
|
||||
msgid "New Unit"
|
||||
msgstr "Neue Einheit"
|
||||
|
||||
#: .\cookbook\forms.py:95
|
||||
#: .\cookbook\forms.py:128
|
||||
msgid "New unit that other gets replaced by."
|
||||
msgstr "Neue Einheit die die alte ersetzt."
|
||||
|
||||
#: .\cookbook\forms.py:100
|
||||
#: .\cookbook\forms.py:133
|
||||
msgid "Old Unit"
|
||||
msgstr "Alte Einheit"
|
||||
|
||||
#: .\cookbook\forms.py:101
|
||||
#: .\cookbook\forms.py:134
|
||||
msgid "Unit that should be replaced."
|
||||
msgstr "Einheit die ersetzt werden soll."
|
||||
|
||||
#: .\cookbook\forms.py:111
|
||||
#: .\cookbook\forms.py:144
|
||||
msgid "New Ingredient"
|
||||
msgstr "Neue Zutat"
|
||||
|
||||
#: .\cookbook\forms.py:112
|
||||
#: .\cookbook\forms.py:145
|
||||
msgid "New ingredient that other gets replaced by."
|
||||
msgstr "Neue Zutat die die alte ersetzt."
|
||||
|
||||
#: .\cookbook\forms.py:117
|
||||
#: .\cookbook\forms.py:150
|
||||
msgid "Old Ingredient"
|
||||
msgstr "Alte Zutat"
|
||||
|
||||
#: .\cookbook\forms.py:118
|
||||
#: .\cookbook\forms.py:151
|
||||
msgid "Ingredient that should be replaced."
|
||||
msgstr "Zutat die ersetzt werden soll."
|
||||
|
||||
#: .\cookbook\forms.py:130
|
||||
#: .\cookbook\forms.py:163
|
||||
msgid "Add your comment: "
|
||||
msgstr "Schreibe einen Kommentar:"
|
||||
msgstr "Schreibe einen Kommentar: "
|
||||
|
||||
#: .\cookbook\forms.py:155
|
||||
#: .\cookbook\forms.py:188
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr "Für Dropbox leer lassen, bei Nextcloud App-Passwort eingeben."
|
||||
|
||||
#: .\cookbook\forms.py:158
|
||||
#: .\cookbook\forms.py:191
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr "Bei Nextcloud leer lassen, bei Dropbox API Token eingeben."
|
||||
|
||||
#: .\cookbook\forms.py:166
|
||||
#: .\cookbook\forms.py:199
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -122,119 +146,128 @@ msgstr ""
|
||||
"Bei Dropbox leer lassen, bei Nextcloud Server URL angeben (<code>/remote.php/"
|
||||
"webdav/</code> wird automatisch hinzugefügt)"
|
||||
|
||||
#: .\cookbook\forms.py:185
|
||||
#: .\cookbook\forms.py:218
|
||||
msgid "Search String"
|
||||
msgstr "Such Wort"
|
||||
|
||||
#: .\cookbook\forms.py:199
|
||||
#: .\cookbook\forms.py:232
|
||||
msgid "File ID"
|
||||
msgstr "Datei ID"
|
||||
|
||||
#: .\cookbook\models.py:190
|
||||
#: .\cookbook\models.py:49
|
||||
msgid "Search"
|
||||
msgstr "Suche"
|
||||
|
||||
#: .\cookbook\models.py:49 .\cookbook\templates\base.html:93
|
||||
#: .\cookbook\templates\meal_plan.html:4 .\cookbook\templates\meal_plan.html:32
|
||||
#: .\cookbook\views\delete.py:136 .\cookbook\views\edit.py:286
|
||||
#: .\cookbook\views\new.py:138
|
||||
msgid "Meal-Plan"
|
||||
msgstr "Plan"
|
||||
|
||||
#: .\cookbook\models.py:49 .\cookbook\templates\base.html:90
|
||||
msgid "Books"
|
||||
msgstr "Bücher"
|
||||
|
||||
#: .\cookbook\models.py:210
|
||||
msgid "Breakfast"
|
||||
msgstr "Frühstück"
|
||||
|
||||
#: .\cookbook\models.py:190
|
||||
#: .\cookbook\models.py:210
|
||||
msgid "Lunch"
|
||||
msgstr "Mittagessen"
|
||||
|
||||
#: .\cookbook\models.py:190
|
||||
#: .\cookbook\models.py:210
|
||||
msgid "Dinner"
|
||||
msgstr "Abendessen"
|
||||
|
||||
#: .\cookbook\models.py:190
|
||||
#: .\cookbook\models.py:210
|
||||
msgid "Other"
|
||||
msgstr "Andere"
|
||||
|
||||
#: .\cookbook\tables.py:83
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:49
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:160
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:50
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:161
|
||||
#: .\cookbook\templates\generic\delete_template.html:5
|
||||
#: .\cookbook\templates\generic\delete_template.html:13
|
||||
#: .\cookbook\templates\generic\edit_template.html:25
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: .\cookbook\templates\base.html:70 .\cookbook\templates\base.html:78
|
||||
#: .\cookbook\templates\base.html:70 .\cookbook\templates\base.html:79
|
||||
#: .\cookbook\templates\forms\ingredients.html:7
|
||||
#: .\cookbook\templates\index.html:7 .\cookbook\templates\shopping_list.html:7
|
||||
msgid "Cookbook"
|
||||
msgstr "Kochbuch"
|
||||
|
||||
#: .\cookbook\templates\base.html:85
|
||||
#: .\cookbook\templates\base.html:86
|
||||
msgid "Utensils"
|
||||
msgstr "Utensilien"
|
||||
|
||||
#: .\cookbook\templates\base.html:89
|
||||
msgid "Books"
|
||||
msgstr "Bücher"
|
||||
|
||||
#: .\cookbook\templates\base.html:92 .\cookbook\templates\meal_plan.html:4
|
||||
#: .\cookbook\templates\meal_plan.html:13 .\cookbook\views\delete.py:136
|
||||
#: .\cookbook\views\edit.py:283 .\cookbook\views\new.py:130
|
||||
msgid "Meal-Plan"
|
||||
msgstr "Plan"
|
||||
|
||||
#: .\cookbook\templates\base.html:95
|
||||
#: .\cookbook\templates\base.html:96
|
||||
msgid "Shopping"
|
||||
msgstr "Einkaufsliste"
|
||||
|
||||
#: .\cookbook\templates\base.html:105
|
||||
#: .\cookbook\templates\base.html:106
|
||||
msgid "Tags"
|
||||
msgstr "Schlagwörter"
|
||||
|
||||
#: .\cookbook\templates\base.html:109 .\cookbook\views\delete.py:70
|
||||
#: .\cookbook\views\edit.py:159 .\cookbook\views\lists.py:18
|
||||
#: .\cookbook\views\new.py:46
|
||||
#: .\cookbook\templates\base.html:110 .\cookbook\views\delete.py:70
|
||||
#: .\cookbook\views\edit.py:162 .\cookbook\views\lists.py:18
|
||||
#: .\cookbook\views\new.py:47
|
||||
msgid "Keyword"
|
||||
msgstr "Schlagwort"
|
||||
|
||||
#: .\cookbook\templates\base.html:111
|
||||
#: .\cookbook\templates\base.html:112
|
||||
msgid "Batch Edit"
|
||||
msgstr "Massenbearbeitung"
|
||||
|
||||
#: .\cookbook\templates\base.html:116
|
||||
#: .\cookbook\templates\base.html:117
|
||||
msgid "Storage Data"
|
||||
msgstr "Datenquellen"
|
||||
|
||||
#: .\cookbook\templates\base.html:120
|
||||
#: .\cookbook\templates\base.html:121
|
||||
msgid "Storage Backends"
|
||||
msgstr "Speicher Quellen"
|
||||
|
||||
#: .\cookbook\templates\base.html:122
|
||||
#: .\cookbook\templates\base.html:123
|
||||
msgid "Configure Sync"
|
||||
msgstr "Sync Einstellen"
|
||||
|
||||
#: .\cookbook\templates\base.html:124
|
||||
msgid "Import Recipes"
|
||||
msgstr "Importierte Rezepte"
|
||||
#: .\cookbook\templates\base.html:125
|
||||
msgid "Discovered Recipes"
|
||||
msgstr "Entdeckte Rezepte"
|
||||
|
||||
#: .\cookbook\templates\base.html:126 .\cookbook\views\lists.py:26
|
||||
msgid "Import Log"
|
||||
msgstr "Import Log"
|
||||
#: .\cookbook\templates\base.html:127
|
||||
msgid "Discovery Log"
|
||||
msgstr "Entdeckungs Log"
|
||||
|
||||
#: .\cookbook\templates\base.html:128 .\cookbook\templates\stats.html:10
|
||||
#: .\cookbook\templates\base.html:129 .\cookbook\templates\stats.html:10
|
||||
msgid "Statistics"
|
||||
msgstr "Statistiken"
|
||||
|
||||
#: .\cookbook\templates\base.html:130
|
||||
#: .\cookbook\templates\base.html:131
|
||||
msgid "Units & Ingredients"
|
||||
msgstr "Einheiten & Zutaten"
|
||||
|
||||
#: .\cookbook\templates\base.html:145 .\cookbook\templates\settings.html:6
|
||||
#: .\cookbook\templates\base.html:133
|
||||
msgid "Import Recipe"
|
||||
msgstr "Importier Rezept"
|
||||
|
||||
#: .\cookbook\templates\base.html:149 .\cookbook\templates\settings.html:6
|
||||
#: .\cookbook\templates\settings.html:11
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: .\cookbook\templates\base.html:148
|
||||
#: .\cookbook\templates\base.html:152
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: .\cookbook\templates\base.html:152
|
||||
#: .\cookbook\templates\base.html:156
|
||||
msgid "Logout"
|
||||
msgstr "Ausloggen"
|
||||
|
||||
#: .\cookbook\templates\base.html:157
|
||||
#: .\cookbook\templates\base.html:161
|
||||
#: .\cookbook\templates\registration\login.html:44
|
||||
msgid "Login"
|
||||
msgstr "Einloggen"
|
||||
@@ -253,7 +286,7 @@ msgstr ""
|
||||
"Ausgewählte Schlagwörter zu allen Rezepten die das Suchwort enthalten "
|
||||
"hinzufügen"
|
||||
|
||||
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:143
|
||||
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:146
|
||||
msgid "Sync"
|
||||
msgstr "Synchronisieren"
|
||||
|
||||
@@ -302,17 +335,45 @@ msgstr "Neues Buch"
|
||||
msgid "There are no recipes in this book yet."
|
||||
msgstr "In diesem Buch sind bisher keine Rezepte."
|
||||
|
||||
#: .\cookbook\templates\export.html:6
|
||||
msgid "Export Recipes"
|
||||
msgstr "Exportier Rezepte"
|
||||
|
||||
#: .\cookbook\templates\export.html:19
|
||||
msgid "Export"
|
||||
msgstr "Export"
|
||||
|
||||
#: .\cookbook\templates\export.html:31
|
||||
msgid "Exported Recipe"
|
||||
msgstr "Exportierte Rezepte"
|
||||
|
||||
#: .\cookbook\templates\export.html:42
|
||||
msgid "Copy to clipboard"
|
||||
msgstr "In Zwischenablage kopieren"
|
||||
|
||||
#: .\cookbook\templates\export.html:54
|
||||
#: .\cookbook\templates\shopping_list.html:48
|
||||
msgid "Copied!"
|
||||
msgstr "Kopiert!"
|
||||
|
||||
#: .\cookbook\templates\export.html:61
|
||||
#: .\cookbook\templates\shopping_list.html:37
|
||||
#: .\cookbook\templates\shopping_list.html:55
|
||||
msgid "Copy list to clipboard"
|
||||
msgstr "Kopiere Liste in Zwischenablage"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_import_recipe.html:5
|
||||
#: .\cookbook\templates\forms\edit_import_recipe.html:9
|
||||
msgid "Import new Recipe"
|
||||
msgstr "Rezept Importieren"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_import_recipe.html:14
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:47
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:48
|
||||
#: .\cookbook\templates\generic\edit_template.html:23
|
||||
#: .\cookbook\templates\generic\new_template.html:23
|
||||
#: .\cookbook\templates\recipe_view.html:340
|
||||
#: .\cookbook\templates\settings.html:33 .\cookbook\templates\settings.html:47
|
||||
#: .\cookbook\templates\recipe_view.html:357
|
||||
#: .\cookbook\templates\settings.html:22 .\cookbook\templates\settings.html:28
|
||||
#: .\cookbook\templates\settings.html:50 .\cookbook\templates\settings.html:64
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
@@ -321,7 +382,7 @@ msgstr "Speichern"
|
||||
msgid "Edit Recipe"
|
||||
msgstr "Rezept bearbeiten"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:37
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:38
|
||||
msgid ""
|
||||
"Use <b>Ctrl</b>+<b>Space</b> to insert new Ingredient!<br/>You can also save "
|
||||
"the recipe using <b>Ctrl</b>+<b>Shift</b>+<b>S</b>."
|
||||
@@ -329,36 +390,35 @@ msgstr ""
|
||||
"Benutze <b>Strg</b>+<b>Leertaste</b> um eine neue Zutat einzufügen!<br/"
|
||||
">Rezepte können mit<b>Strg</b>+<b>Shift</b>+<b>S</b> gespeichert werden."
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:51
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:52
|
||||
#: .\cookbook\templates\generic\edit_template.html:27
|
||||
#: .\cookbook\templates\recipe_view.html:7
|
||||
msgid "View"
|
||||
msgstr "Angucken"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:55
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:56
|
||||
#: .\cookbook\templates\generic\edit_template.html:30
|
||||
msgid "Delete original file"
|
||||
msgstr "Original löschen"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:142
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:189
|
||||
#: .\cookbook\views\delete.py:81 .\cookbook\views\edit.py:175
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:143
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:190
|
||||
#: .\cookbook\views\delete.py:81 .\cookbook\views\edit.py:178
|
||||
msgid "Ingredient"
|
||||
msgstr "Zutat"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:147
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:148
|
||||
msgid "Amount"
|
||||
msgstr "Menge"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:149
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:150
|
||||
msgid "Unit"
|
||||
msgstr "Einheit"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:154
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:155
|
||||
msgid "Note"
|
||||
msgstr "Notiz "
|
||||
msgstr "Notiz"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:163
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:164
|
||||
msgid "Are you sure that you want to delete this ingredient?"
|
||||
msgstr "Bist du sicher das du diese Zutat löschen willst?"
|
||||
|
||||
@@ -404,7 +464,7 @@ msgstr "Bist du sicher diese beiden Zutaten zusammengeführt werden sollen ?"
|
||||
#: .\cookbook\templates\generic\delete_template.html:18
|
||||
#, python-format
|
||||
msgid "Are you sure you want to delete the %(title)s: <b>%(object)s</b> "
|
||||
msgstr "Bist du sicher das %(title)s: <b>%(object)s</b> gelöscht werden soll"
|
||||
msgstr "Bist du sicher das %(title)s: <b>%(object)s</b> gelöscht werden soll "
|
||||
|
||||
#: .\cookbook\templates\generic\delete_template.html:21
|
||||
msgid "Confirm"
|
||||
@@ -441,9 +501,18 @@ msgstr "vorherige"
|
||||
msgid "next"
|
||||
msgstr "nächste"
|
||||
|
||||
#: .\cookbook\templates\import.html:6
|
||||
msgid "Import Recipes"
|
||||
msgstr "Importierte Rezepte"
|
||||
|
||||
#: .\cookbook\templates\import.html:14 .\cookbook\views\delete.py:48
|
||||
#: .\cookbook\views\edit.py:254
|
||||
msgid "Import"
|
||||
msgstr "Rezept Importieren"
|
||||
|
||||
#: .\cookbook\templates\include\recipe_open_modal.html:28
|
||||
#: .\cookbook\views\delete.py:21 .\cookbook\views\edit.py:315
|
||||
#: .\cookbook\views\new.py:34
|
||||
#: .\cookbook\views\delete.py:21 .\cookbook\views\edit.py:318
|
||||
#: .\cookbook\views\new.py:35
|
||||
msgid "Recipe"
|
||||
msgstr "Rezept"
|
||||
|
||||
@@ -501,47 +570,47 @@ msgstr "Suche zurücksetzen"
|
||||
msgid "Log in to view Recipies"
|
||||
msgstr "Bitte einloggen um Rezepte zu sehen"
|
||||
|
||||
#: .\cookbook\templates\meal_plan.html:20
|
||||
#: .\cookbook\templates\meal_plan.html:39
|
||||
msgid "Week"
|
||||
msgstr "Woche"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:67
|
||||
#: .\cookbook\templates\recipe_view.html:71
|
||||
msgid "in"
|
||||
msgstr "in"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:72
|
||||
#: .\cookbook\templates\recipe_view.html:293
|
||||
#: .\cookbook\templates\recipe_view.html:76
|
||||
#: .\cookbook\templates\recipe_view.html:310
|
||||
msgid "by"
|
||||
msgstr "von"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:84
|
||||
#: .\cookbook\templates\recipe_view.html:89
|
||||
msgid "Preparation time ca."
|
||||
msgstr "Zubereitungszeit ca."
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:89
|
||||
#: .\cookbook\templates\recipe_view.html:95
|
||||
msgid "Waiting time ca."
|
||||
msgstr "Wartezeit ca."
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:170
|
||||
#: .\cookbook\templates\recipe_view.html:186
|
||||
msgid "Recipe Image"
|
||||
msgstr "Rezept Bild"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:193
|
||||
#: .\cookbook\templates\recipe_view.html:227
|
||||
#: .\cookbook\templates\recipe_view.html:209
|
||||
#: .\cookbook\templates\recipe_view.html:243
|
||||
msgid "View external recipe"
|
||||
msgstr "Externes Rezept ansehen"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:205
|
||||
#: .\cookbook\templates\recipe_view.html:221
|
||||
msgid "Cloud not show a file preview. Maybe its not a PDF ?"
|
||||
msgstr ""
|
||||
"Datei konnte nicht angezeigt werden. Direkte anzeige funktioniert nur mit "
|
||||
"PDF Dateien."
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:212
|
||||
#: .\cookbook\templates\recipe_view.html:228
|
||||
msgid "External recipe"
|
||||
msgstr "Externes Rezept"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:214
|
||||
#: .\cookbook\templates\recipe_view.html:230
|
||||
msgid ""
|
||||
"\n"
|
||||
" This is an external recipe, which means "
|
||||
@@ -562,16 +631,16 @@ msgstr ""
|
||||
"bleibt weiterhin verfügbar.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:225
|
||||
#: .\cookbook\templates\recipe_view.html:241
|
||||
msgid "Convert now!"
|
||||
msgstr "Jetzt umwandeln!"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:289
|
||||
#: .\cookbook\templates\recipe_view.html:305
|
||||
msgid "Comments"
|
||||
msgstr "Kommentare"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:309 .\cookbook\views\delete.py:103
|
||||
#: .\cookbook\views\edit.py:234
|
||||
#: .\cookbook\templates\recipe_view.html:326 .\cookbook\views\delete.py:103
|
||||
#: .\cookbook\views\edit.py:237
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
@@ -580,10 +649,14 @@ msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Nutzername oder Passwort falsch. Bitte versuch es erneut."
|
||||
|
||||
#: .\cookbook\templates\settings.html:17
|
||||
msgid "Account"
|
||||
msgstr "Account"
|
||||
|
||||
#: .\cookbook\templates\settings.html:34
|
||||
msgid "Language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: .\cookbook\templates\settings.html:42
|
||||
#: .\cookbook\templates\settings.html:59
|
||||
msgid "Style"
|
||||
msgstr "Stil"
|
||||
|
||||
@@ -595,15 +668,6 @@ msgstr "Einkaufsliste"
|
||||
msgid "Load"
|
||||
msgstr "Laden"
|
||||
|
||||
#: .\cookbook\templates\shopping_list.html:37
|
||||
#: .\cookbook\templates\shopping_list.html:55
|
||||
msgid "Copy list to clipboard"
|
||||
msgstr "Kopiere Liste in Zwischenablage"
|
||||
|
||||
#: .\cookbook\templates\shopping_list.html:48
|
||||
msgid "Copied!"
|
||||
msgstr "Kopiert!"
|
||||
|
||||
#: .\cookbook\templates\stats.html:4
|
||||
msgid "Stats"
|
||||
msgstr "Statistiken"
|
||||
@@ -644,22 +708,17 @@ msgstr[0] "Massenbearbeitung erfolgreich. %(count)d Rezept wurde aktualisiert."
|
||||
msgstr[1] ""
|
||||
"Massenbearbeitung erfolgreich. %(count)d Rezepte wurden aktualisiert."
|
||||
|
||||
#: .\cookbook\views\delete.py:48 .\cookbook\views\edit.py:251
|
||||
#: .\cookbook\views\lists.py:35
|
||||
msgid "Import"
|
||||
msgstr "Rezept Importieren"
|
||||
|
||||
#: .\cookbook\views\delete.py:59
|
||||
msgid "Monitor"
|
||||
msgstr "Monitor"
|
||||
|
||||
#: .\cookbook\views\delete.py:92 .\cookbook\views\lists.py:53
|
||||
#: .\cookbook\views\new.py:64
|
||||
#: .\cookbook\views\new.py:65
|
||||
msgid "Storage Backend"
|
||||
msgstr "Speicher Quelle"
|
||||
|
||||
#: .\cookbook\views\delete.py:114 .\cookbook\views\edit.py:267
|
||||
#: .\cookbook\views\new.py:112
|
||||
#: .\cookbook\views\delete.py:114 .\cookbook\views\edit.py:270
|
||||
#: .\cookbook\views\new.py:114
|
||||
msgid "Recipe Book"
|
||||
msgstr "Rezeptbuch"
|
||||
|
||||
@@ -667,58 +726,82 @@ msgstr "Rezeptbuch"
|
||||
msgid "Bookmarks"
|
||||
msgstr "Lesezeichen"
|
||||
|
||||
#: .\cookbook\views\edit.py:117
|
||||
#: .\cookbook\views\edit.py:104
|
||||
msgid "There was an error converting your ingredients amount to a number: "
|
||||
msgstr "Es gab einen Fehler beim umwandeln der Menge in eine Zahl: "
|
||||
|
||||
#: .\cookbook\views\edit.py:120
|
||||
msgid "Recipe saved!"
|
||||
msgstr "Rezept gespeichert"
|
||||
msgstr "Rezept gespeichert!"
|
||||
|
||||
#: .\cookbook\views\edit.py:119
|
||||
#: .\cookbook\views\edit.py:122
|
||||
msgid "There was an error saving this recipe!"
|
||||
msgstr "Es gab einen Fehler beim Speichern des Rezepts"
|
||||
msgstr "Es gab einen Fehler beim Speichern des Rezepts!"
|
||||
|
||||
#: .\cookbook\views\edit.py:184
|
||||
#: .\cookbook\views\edit.py:187
|
||||
msgid "You cannot edit this storage!"
|
||||
msgstr "Du kannst diese Speicherquelle nicht bearbeiten!"
|
||||
|
||||
#: .\cookbook\views\edit.py:203
|
||||
#: .\cookbook\views\edit.py:206
|
||||
msgid "Storage saved!"
|
||||
msgstr "Speicherquelle gespeichert"
|
||||
msgstr "Speicherquelle gespeichert!"
|
||||
|
||||
#: .\cookbook\views\edit.py:205
|
||||
msgid "There was an error updating this storage backend.!"
|
||||
msgstr "Es gab einen Fehler beim aktualisierung dieser Speicher Quelle"
|
||||
#: .\cookbook\views\edit.py:208
|
||||
msgid "There was an error updating this storage backend!"
|
||||
msgstr "Es gab einen Fehler beim aktualisierung dieser Speicher Quelle!"
|
||||
|
||||
#: .\cookbook\views\edit.py:225
|
||||
#: .\cookbook\views\edit.py:228
|
||||
msgid "You cannot edit this comment!"
|
||||
msgstr "Du kannst diesen Kommentar nicht bearbeiten!"
|
||||
|
||||
#: .\cookbook\views\edit.py:303
|
||||
#: .\cookbook\views\edit.py:306
|
||||
msgid "Changes saved!"
|
||||
msgstr "Änderungen gespeichert"
|
||||
msgstr "Änderungen gespeichert!"
|
||||
|
||||
#: .\cookbook\views\edit.py:307
|
||||
#: .\cookbook\views\edit.py:310
|
||||
msgid "Error saving changes!"
|
||||
msgstr "Fehler beim Speichern der Daten."
|
||||
msgstr "Fehler beim Speichern der Daten!"
|
||||
|
||||
#: .\cookbook\views\edit.py:337
|
||||
#: .\cookbook\views\edit.py:340
|
||||
msgid "Units merged!"
|
||||
msgstr "Einheiten zusammengeführt"
|
||||
msgstr "Einheiten zusammengeführt!"
|
||||
|
||||
#: .\cookbook\views\edit.py:350
|
||||
#: .\cookbook\views\edit.py:353
|
||||
msgid "Ingredients merged!"
|
||||
msgstr "Zutaten zusammengeführt"
|
||||
msgstr "Zutaten zusammengeführt!"
|
||||
|
||||
#: .\cookbook\views\new.py:86
|
||||
#: .\cookbook\views\import_export.py:57
|
||||
msgid "Recipe imported successfully!"
|
||||
msgstr "Rezept erfolgreich importiert!"
|
||||
|
||||
#: .\cookbook\views\import_export.py:103
|
||||
msgid ""
|
||||
"External recipes cannot be exported, please share the file directly or "
|
||||
"select an internal recipe."
|
||||
msgstr ""
|
||||
"Externe Rezepte können nicht exportiert werden, bitte Datei direkt teilen "
|
||||
"oder ein Internes Rezept auswählen."
|
||||
|
||||
#: .\cookbook\views\lists.py:26
|
||||
msgid "Import Log"
|
||||
msgstr "Import Log"
|
||||
|
||||
#: .\cookbook\views\lists.py:35
|
||||
msgid "Discovery"
|
||||
msgstr "Entdeckung"
|
||||
|
||||
#: .\cookbook\views\new.py:88
|
||||
msgid "Imported new recipe!"
|
||||
msgstr "Importier neue Rezepte"
|
||||
msgstr "Importier neue Rezepte!"
|
||||
|
||||
#: .\cookbook\views\new.py:89
|
||||
#: .\cookbook\views\new.py:91
|
||||
msgid "There was an error importing this recipe!"
|
||||
msgstr "Beim importieren des Rezeptes ist ein Fehler aufgetreten"
|
||||
msgstr "Beim importieren des Rezeptes ist ein Fehler aufgetreten!"
|
||||
|
||||
#: .\cookbook\views\views.py:44
|
||||
#: .\cookbook\views\views.py:63
|
||||
msgid "Comment saved!"
|
||||
msgstr "Kommentar gespeichert"
|
||||
msgstr "Kommentar gespeichert!"
|
||||
|
||||
#: .\cookbook\views\views.py:54
|
||||
#: .\cookbook\views\views.py:73
|
||||
msgid "Bookmark saved!"
|
||||
msgstr "Lesezeichen gespeichert"
|
||||
msgstr "Lesezeichen gespeichert!"
|
||||
|
||||
@@ -154,8 +154,8 @@ class Ingredient(models.Model):
|
||||
|
||||
|
||||
class RecipeIngredient(models.Model):
|
||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.PROTECT)
|
||||
amount = models.DecimalField(default=0, decimal_places=2, max_digits=16)
|
||||
note = models.CharField(max_length=64, null=True, blank=True)
|
||||
|
||||
@@ -75,7 +75,8 @@
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {% if request.resolver_match.url_name in 'view_search,edit_recipe,edit_internal_recipe,edit_external_recipe,view_recipe' %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_search' %}"><i class="fas fa-book"></i> {% trans 'Cookbook' %}<span
|
||||
<a class="nav-link" href="{% url 'view_search' %}"><i
|
||||
class="fas fa-book"></i> {% trans 'Cookbook' %}<span
|
||||
class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
|
||||
@@ -121,13 +122,15 @@
|
||||
<a class="dropdown-item" href="{% url 'data_sync' %}"><i
|
||||
class="fas fa-sync-alt fa-fw"></i> {% trans 'Configure Sync' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_recipe_import' %}"><i
|
||||
class="far fa-file-alt fa-fw"></i> {% trans 'Import Recipes' %}</a>
|
||||
class="far fa-file-alt fa-fw"></i> {% trans 'Discovered Recipes' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_sync_log' %}"><i
|
||||
class="fas fa-history fa-fw"></i> {% trans 'Import Log' %}</a>
|
||||
class="fas fa-history fa-fw"></i> {% trans 'Discovery Log' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_stats' %}"><i
|
||||
class="fas fa-chart-line fa-fw"></i> {% trans 'Statistics' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'edit_ingredient' %}"><i
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units & Ingredients' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'view_import' %}"><i
|
||||
class="fas fa-file-import"></i> {% trans 'Import Recipe' %}</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
@@ -137,7 +140,8 @@
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_settings' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fas fa-user-alt"></i> {{ user.get_user_name }}
|
||||
aria-haspopup="true" aria-expanded="false"><i
|
||||
class="fas fa-user-alt"></i> {{ user.get_user_name }}
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
|
||||
|
||||
70
cookbook/templates/export.html
Normal file
70
cookbook/templates/export.html
Normal file
@@ -0,0 +1,70 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans 'Export Recipes' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-file-export"></i> {% trans 'Export' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if export %}
|
||||
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<label for="id_export">
|
||||
{% trans 'Exported Recipe' %}</label>
|
||||
<textarea id="id_export" class="form-control" rows="12">
|
||||
{{ export }}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<button class="btn btn-success" onclick="copy()" style="width: 15vw" data-toggle="tooltip"
|
||||
data-placement="right" title="{% trans 'Copy to clipboard' %}" id="id_btn_copy"
|
||||
onmouseout="resetTooltip()"><i
|
||||
class="far fa-copy"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function copy() {
|
||||
let json = $('#id_export');
|
||||
|
||||
json.select();
|
||||
|
||||
$('#id_btn_copy').attr('data-original-title', '{% trans 'Copied!' %}').tooltip('show');
|
||||
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
function resetTooltip() {
|
||||
setTimeout(function () {
|
||||
$('#id_btn_copy').attr('data-original-title', '{% trans 'Copy list to clipboard' %}');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
19
cookbook/templates/import.html
Normal file
19
cookbook/templates/import.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans 'Import Recipes' %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-file-import"></i> {% trans 'Import' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -52,16 +52,24 @@
|
||||
class="fas fa-pencil-alt"></i></a></h3>
|
||||
</div>
|
||||
<div class="col col-md-3 d-print-none" style="text-align: right">
|
||||
<button class="btn btn-success" onclick="$('#bookmarkModal').modal({'show':true})"><i
|
||||
<button class="btn btn-success" onclick="$('#bookmarkModal').modal({'show':true})" data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Add to Book' %}"><i
|
||||
class="fas fa-bookmark"></i></button>
|
||||
{% if ingredients %}
|
||||
<a class="btn btn-warning" href="{% url 'view_shopping' %}?r={{ recipe.pk }}"><i
|
||||
<a class="btn btn-warning" href="{% url 'view_shopping' %}?r={{ recipe.pk }}" data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Generate shopping list' %}"><i
|
||||
class="fas fa-shopping-cart"></i></a>
|
||||
{% endif %}
|
||||
<a class="btn btn-info" href="{% url 'new_meal_plan' %}?recipe={{ recipe.pk }}"><i
|
||||
<a class="btn btn-info" href="{% url 'new_meal_plan' %}?recipe={{ recipe.pk }}" data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Add to Mealplan' %}"><i
|
||||
class="fas fa-calendar"></i></a>
|
||||
<a class="btn btn-light" onclick="window.print()"><i
|
||||
<a class="btn btn-light" onclick="window.print()" data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Print' %}"><i
|
||||
class="fas fa-print"></i></a>
|
||||
<a class="btn btn-primary" href="{% url 'view_export' %}?r={{ recipe.pk }}" target="_blank"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Export recipe' %}"><i
|
||||
class="fas fa-file-export"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +202,7 @@
|
||||
|
||||
<div style="font-size: large">
|
||||
{% if recipe.instructions %}
|
||||
{{ recipe.instructions | markdown | safe | urlize }}
|
||||
{{ recipe.instructions | markdown | safe }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -382,5 +390,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="row">
|
||||
<div class="col col-md-12 text-center">
|
||||
<button class="btn btn-success" onclick="copy()" style="width: 15vw" data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Copy list to clipboard' %}" id="id_btn_copy" onmouseout="resetTooltip()"><i
|
||||
data-placement="right" title="{% trans 'Copy list to clipboard' %}" id="id_btn_copy" onmouseout="resetTooltip()"><i
|
||||
class="far fa-copy"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ from bleach_whitelist import markdown_tags, markdown_attrs, all_styles, print_at
|
||||
from django.urls import reverse
|
||||
|
||||
from cookbook.helper.mdx_attributes import MarkdownFormatExtension
|
||||
from cookbook.helper.mdx_urlize import UrlizeExtension
|
||||
from cookbook.models import get_model_name
|
||||
|
||||
register = template.Library()
|
||||
@@ -28,5 +29,5 @@ def delete_url(model, pk):
|
||||
@register.filter()
|
||||
def markdown(value):
|
||||
tags = markdown_tags + ['pre', 'table', 'td', 'tr', 'th', 'tbody', 'style', 'thead']
|
||||
parsed_md = md.markdown(value, extensions=['markdown.extensions.fenced_code', 'tables', MarkdownFormatExtension()])
|
||||
parsed_md = md.markdown(value, extensions=['markdown.extensions.fenced_code', 'tables', UrlizeExtension(), MarkdownFormatExtension()])
|
||||
return bleach.clean(parsed_md, tags, markdown_attrs)
|
||||
|
||||
@@ -3,7 +3,7 @@ from pydoc import locate
|
||||
from django.urls import path
|
||||
|
||||
from .views import *
|
||||
from cookbook.views import api
|
||||
from cookbook.views import api, import_export
|
||||
from cookbook.helper import dal
|
||||
|
||||
urlpatterns = [
|
||||
@@ -14,6 +14,9 @@ urlpatterns = [
|
||||
path('shopping/', views.shopping_list, name='view_shopping'),
|
||||
path('settings/', views.settings, name='view_settings'),
|
||||
|
||||
path('import/', import_export.import_recipe, name='view_import'),
|
||||
path('export/', import_export.export_recipe, name='view_export'),
|
||||
|
||||
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
|
||||
|
||||
path('new/recipe_import/<int:import_id>/', new.create_new_external_recipe, name='new_recipe_import'),
|
||||
|
||||
@@ -205,7 +205,7 @@ def edit_storage(request, pk):
|
||||
|
||||
messages.add_message(request, messages.SUCCESS, _('Storage saved!'))
|
||||
else:
|
||||
messages.add_message(request, messages.ERROR, _('There was an error updating this storage backend.!'))
|
||||
messages.add_message(request, messages.ERROR, _('There was an error updating this storage backend!'))
|
||||
else:
|
||||
pseudo_instance = instance
|
||||
pseudo_instance.password = '__NO__CHANGE__'
|
||||
|
||||
114
cookbook/views/import_export.py
Normal file
114
cookbook/views/import_export.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import base64
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import IntegrityError
|
||||
from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
from cookbook.forms import ExportForm, ImportForm
|
||||
from cookbook.models import RecipeIngredient, Recipe, Unit, Ingredient, Keyword
|
||||
|
||||
|
||||
def import_recipe(request):
|
||||
if request.method == "POST":
|
||||
form = ImportForm(request.POST)
|
||||
if form.is_valid():
|
||||
data = json.loads(form.cleaned_data['recipe'])
|
||||
|
||||
recipe = Recipe.objects.create(name=data['recipe']['name'], instructions=data['recipe']['instructions'],
|
||||
working_time=data['recipe']['working_time'], waiting_time=data['recipe']['waiting_time'],
|
||||
created_by=request.user, internal=True)
|
||||
|
||||
for k in data['keywords']:
|
||||
try:
|
||||
Keyword.objects.create(name=k['name'], icon=k['icon'], description=k['description']).save()
|
||||
except IntegrityError:
|
||||
pass
|
||||
|
||||
recipe.keywords.add(Keyword.objects.get(name=k['name']))
|
||||
|
||||
for u in data['units']:
|
||||
try:
|
||||
Unit.objects.create(name=u['name'], description=u['description']).save()
|
||||
except IntegrityError:
|
||||
pass
|
||||
|
||||
for i in data['ingredients']:
|
||||
try:
|
||||
Ingredient.objects.create(name=i['name']).save()
|
||||
except IntegrityError:
|
||||
pass
|
||||
|
||||
for ri in data['recipe_ingredients']:
|
||||
RecipeIngredient.objects.create(recipe=recipe, ingredient=Ingredient.objects.get(name=ri['ingredient']),
|
||||
unit=Unit.objects.get(name=ri['unit']), amount=ri['amount'], note=ri['note'])
|
||||
|
||||
if data['image']:
|
||||
fmt, img = data['image'].split(';base64,')
|
||||
ext = fmt.split('/')[-1]
|
||||
recipe.image = ContentFile(base64.b64decode(img), name=f'{recipe.pk}.{ext}')
|
||||
recipe.save()
|
||||
|
||||
messages.add_message(request, messages.SUCCESS, _('Recipe imported successfully!'))
|
||||
return HttpResponseRedirect(reverse_lazy('view_recipe', args=[recipe.pk]))
|
||||
else:
|
||||
form = ImportForm()
|
||||
|
||||
return render(request, 'import.html', {'form': form})
|
||||
|
||||
|
||||
def export_recipe(request):
|
||||
context = {}
|
||||
if request.method == "POST":
|
||||
form = ExportForm(request.POST)
|
||||
if form.is_valid():
|
||||
recipe = form.cleaned_data['recipe']
|
||||
if recipe.internal:
|
||||
export = {
|
||||
'recipe': {'name': recipe.name, 'instructions': recipe.instructions, 'working_time': recipe.working_time, 'waiting_time': recipe.working_time},
|
||||
'units': [],
|
||||
'ingredients': [],
|
||||
'recipe_ingredients': [],
|
||||
'keywords': [],
|
||||
'image': None
|
||||
}
|
||||
|
||||
for k in recipe.keywords.all():
|
||||
export['keywords'].append({'name': k.name, 'icon': k.icon, 'description': k.description})
|
||||
|
||||
for ri in RecipeIngredient.objects.filter(recipe=recipe).all():
|
||||
if ri.unit not in export['units']:
|
||||
export['units'].append({'name': ri.unit.name, 'description': ri.unit.description})
|
||||
if ri.ingredient not in export['ingredients']:
|
||||
export['ingredients'].append({'name': ri.ingredient.name})
|
||||
|
||||
export['recipe_ingredients'].append({'ingredient': ri.ingredient.name, 'unit': ri.unit.name, 'amount': float(ri.amount), 'note': ri.note})
|
||||
|
||||
if recipe.image and form.cleaned_data['image']:
|
||||
with open(recipe.image.path, 'rb') as img_f:
|
||||
export['image'] = f'data:image/png;base64,{base64.b64encode(img_f.read()).decode("utf-8")}'
|
||||
|
||||
if form.cleaned_data['download']:
|
||||
response = HttpResponse(json.dumps(export), content_type='text/plain')
|
||||
response['Content-Disposition'] = f'attachment; filename={recipe.name}.json'
|
||||
return response
|
||||
|
||||
context['export'] = json.dumps(export, indent=4)
|
||||
else:
|
||||
form.add_error('recipe', _('External recipes cannot be exported, please share the file directly or select an internal recipe.'))
|
||||
else:
|
||||
form = ExportForm()
|
||||
recipe = request.GET.get('r')
|
||||
if recipe:
|
||||
if re.match(r'^([0-9])+$', recipe):
|
||||
if recipe := Recipe.objects.filter(pk=int(recipe)).first():
|
||||
form = ExportForm(initial={'recipe': recipe})
|
||||
|
||||
context['form'] = form
|
||||
|
||||
return render(request, 'export.html', context)
|
||||
@@ -32,7 +32,7 @@ def recipe_import(request):
|
||||
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
|
||||
return render(request, 'generic/list_template.html', {'title': _("Import"), 'table': table, 'import_btn': True})
|
||||
return render(request, 'generic/list_template.html', {'title': _("Discovery"), 'table': table, 'import_btn': True})
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
BIN
docs/preview.png
BIN
docs/preview.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.5 MiB |
BIN
docs/preview.xcf
BIN
docs/preview.xcf
Binary file not shown.
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-03-18 12:13+0100\n"
|
||||
"POT-Creation-Date: 2020-04-25 23:31+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -18,10 +18,10 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: .\recipes\settings.py:136
|
||||
#: .\recipes\settings.py:137
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
#: .\recipes\settings.py:137
|
||||
#: .\recipes\settings.py:138
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
|
||||
Reference in New Issue
Block a user