mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-26 19:59:15 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00539b9d1b | ||
|
|
1cadb1e85e | ||
|
|
9e524a8f22 | ||
|
|
a8a7d4e0f4 | ||
|
|
d0cf396f68 | ||
|
|
e45f3f3343 | ||
|
|
13ea2ecd7d | ||
|
|
25ba62e87c | ||
|
|
48107b918d | ||
|
|
0b56e22af9 | ||
|
|
47128fbb79 | ||
|
|
12f6aa6df7 | ||
|
|
c2dc038ac9 | ||
|
|
0c2b3d2d03 | ||
|
|
1d562452df | ||
|
|
4c90664aa2 | ||
|
|
90dbc36402 | ||
|
|
a60b09e491 | ||
|
|
deeda425a8 | ||
|
|
6fcbc9f0cd | ||
|
|
7518d8c6b1 | ||
|
|
eb25a9163f | ||
|
|
e2f6e07e42 | ||
|
|
17b9519fa9 | ||
|
|
adcef1d887 | ||
|
|
7398304d16 | ||
|
|
09ff7e82f1 | ||
|
|
47072763ee |
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -9,14 +9,14 @@ jobs:
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.7]
|
||||
python-version: [3.8]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up Python 3.7
|
||||
- name: Set up Python 3.8
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.7
|
||||
python-version: 3.8
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
2
.idea/recipes.iml
generated
2
.idea/recipes.iml
generated
@@ -16,7 +16,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (recipes)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.8 (recipes)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="jquery-3.4.1" level="application" />
|
||||
<orderEntry type="library" name="pretty-checkbox" level="application" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:18.04
|
||||
FROM python:3.8-slim-buster
|
||||
|
||||
RUN mkdir /Recipes
|
||||
WORKDIR /Recipes
|
||||
|
||||
@@ -2,10 +2,103 @@ from django.contrib import admin
|
||||
from .models import *
|
||||
|
||||
|
||||
admin.site.register(Recipe)
|
||||
class UserPreferenceAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'theme', 'nav_color')
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
return obj.user.get_user_name()
|
||||
|
||||
|
||||
admin.site.register(UserPreference, UserPreferenceAdmin)
|
||||
|
||||
|
||||
class StorageAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'method')
|
||||
|
||||
|
||||
admin.site.register(Storage, StorageAdmin)
|
||||
|
||||
|
||||
class SyncAdmin(admin.ModelAdmin):
|
||||
list_display = ('storage', 'path', 'active', 'last_checked')
|
||||
|
||||
|
||||
admin.site.register(Sync, SyncAdmin)
|
||||
|
||||
|
||||
class SyncLogAdmin(admin.ModelAdmin):
|
||||
list_display = ('sync', 'status', 'msg', 'created_at')
|
||||
|
||||
|
||||
admin.site.register(SyncLog, SyncLogAdmin)
|
||||
|
||||
admin.site.register(Keyword)
|
||||
|
||||
admin.site.register(Sync)
|
||||
admin.site.register(SyncLog)
|
||||
admin.site.register(RecipeImport)
|
||||
admin.site.register(Storage)
|
||||
|
||||
class RecipeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'internal', 'created_by', 'storage')
|
||||
|
||||
@staticmethod
|
||||
def created_by(obj):
|
||||
return obj.created_by.get_user_name()
|
||||
|
||||
|
||||
admin.site.register(Recipe, RecipeAdmin)
|
||||
|
||||
admin.site.register(Unit)
|
||||
admin.site.register(Ingredient)
|
||||
|
||||
|
||||
class RecipeIngredientAdmin(admin.ModelAdmin):
|
||||
list_display = ('recipe', 'ingredient', 'amount', 'unit')
|
||||
|
||||
|
||||
admin.site.register(RecipeIngredient, RecipeIngredientAdmin)
|
||||
|
||||
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
list_display = ('recipe', 'name', 'created_at')
|
||||
|
||||
@staticmethod
|
||||
def name(obj):
|
||||
return obj.created_by.get_user_name()
|
||||
|
||||
|
||||
admin.site.register(Comment, CommentAdmin)
|
||||
|
||||
|
||||
class RecipeImportAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'storage', 'file_path')
|
||||
|
||||
|
||||
admin.site.register(RecipeImport, RecipeImportAdmin)
|
||||
|
||||
|
||||
class RecipeBookAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'user_name')
|
||||
|
||||
@staticmethod
|
||||
def user_name(obj):
|
||||
return obj.user.get_user_name()
|
||||
|
||||
|
||||
admin.site.register(RecipeBook, RecipeBookAdmin)
|
||||
|
||||
|
||||
class RecipeBookEntryAdmin(admin.ModelAdmin):
|
||||
list_display = ('book', 'recipe')
|
||||
|
||||
|
||||
admin.site.register(RecipeBookEntry, RecipeBookEntryAdmin)
|
||||
|
||||
|
||||
class MealPlanAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'recipe', 'meal', 'date')
|
||||
|
||||
@staticmethod
|
||||
def user(obj):
|
||||
return obj.user.get_user_name()
|
||||
|
||||
|
||||
admin.site.register(MealPlan, MealPlanAdmin)
|
||||
|
||||
@@ -44,3 +44,11 @@ class RecipeFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = ['name', 'keywords', 'ingredients', 'internal']
|
||||
|
||||
|
||||
class IngredientFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr='icontains')
|
||||
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = ['name']
|
||||
|
||||
@@ -27,6 +27,8 @@ class DateWidget(forms.DateInput):
|
||||
|
||||
|
||||
class UserPreferenceForm(forms.ModelForm):
|
||||
prefix = 'preference'
|
||||
|
||||
class Meta:
|
||||
model = UserPreference
|
||||
fields = ('theme', 'nav_color')
|
||||
@@ -36,6 +38,18 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class UserNameForm(forms.ModelForm):
|
||||
prefix = 'name'
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('first_name', 'last_name')
|
||||
|
||||
help_texts = {
|
||||
'first_name': _('Both fields are optional. If none are given the username will be displayed instead')
|
||||
}
|
||||
|
||||
|
||||
class ExternalRecipeForm(forms.ModelForm):
|
||||
file_path = forms.CharField(disabled=True, required=False)
|
||||
storage = forms.ModelChoiceField(queryset=Storage.objects.all(), disabled=True, required=False)
|
||||
@@ -141,6 +155,13 @@ class KeywordForm(forms.ModelForm):
|
||||
widgets = {'icon': EmojiPickerTextInput}
|
||||
|
||||
|
||||
class IngredientForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = ('name', 'recipe')
|
||||
widgets = {'recipe': SelectWidget}
|
||||
|
||||
|
||||
class StorageForm(forms.ModelForm):
|
||||
username = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), required=False)
|
||||
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
|
||||
|
||||
Binary file not shown.
@@ -3,25 +3,25 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-02-18 23:20+0100\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"
|
||||
"Language: \n"
|
||||
"POT-Creation-Date: 2020-03-18 12:13+0100\n"
|
||||
"PO-Revision-Date: 2020-03-18 12:19+0100\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\filters.py:15 .\cookbook\templates\base.html:98
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:28
|
||||
#: .\cookbook\templates\forms\ingredients.html:33
|
||||
#: .\cookbook\templates\recipe_view.html:67
|
||||
#: .\cookbook\templates\forms\ingredients.html:34
|
||||
#: .\cookbook\templates\recipe_view.html:104 .\cookbook\views\lists.py:45
|
||||
msgid "Ingredients"
|
||||
msgstr "Zutaten"
|
||||
|
||||
@@ -30,13 +30,14 @@ msgid ""
|
||||
"Color of the top navigation bar. Not all colors work with all themes, just "
|
||||
"try them out!"
|
||||
msgstr ""
|
||||
"Farbe der oberen Navigationsleiste. Nicht alle Farben passen, daher einfach mal ausprobieren!"
|
||||
"Farbe der oberen Navigationsleiste. Nicht alle Farben passen, daher einfach "
|
||||
"mal ausprobieren!"
|
||||
|
||||
#: .\cookbook\forms.py:49 .\cookbook\forms.py:67 .\cookbook\forms.py:189
|
||||
#: .\cookbook\forms.py:49 .\cookbook\forms.py:67 .\cookbook\forms.py:196
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: .\cookbook\forms.py:50 .\cookbook\forms.py:68 .\cookbook\forms.py:190
|
||||
#: .\cookbook\forms.py:50 .\cookbook\forms.py:68 .\cookbook\forms.py:197
|
||||
#: .\cookbook\templates\stats.html:22
|
||||
msgid "Keywords"
|
||||
msgstr "Schlagwörter"
|
||||
@@ -49,7 +50,7 @@ msgstr "Zubereitungszeit in Minuten"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
msgstr "Wartezeit (kochen/backen) in Minuten"
|
||||
|
||||
#: .\cookbook\forms.py:53 .\cookbook\forms.py:191
|
||||
#: .\cookbook\forms.py:53 .\cookbook\forms.py:198
|
||||
msgid "Path"
|
||||
msgstr "Pfad"
|
||||
|
||||
@@ -66,8 +67,8 @@ msgid ""
|
||||
"Include <code>- [ ]</code> in list for easier usage in markdown based "
|
||||
"documents."
|
||||
msgstr ""
|
||||
"Füge <code>- [ ]</code> vor den Zutaten ein um sie besser in einem Markdown Dokument "
|
||||
"zu verwenden."
|
||||
"Füge <code>- [ ]</code> vor den Zutaten ein um sie besser in einem Markdown "
|
||||
"Dokument zu verwenden."
|
||||
|
||||
#: .\cookbook\forms.py:94
|
||||
msgid "New Unit"
|
||||
@@ -105,15 +106,15 @@ msgstr "Zutat die ersetzt werden soll."
|
||||
msgid "Add your comment: "
|
||||
msgstr "Schreibe einen Kommentar:"
|
||||
|
||||
#: .\cookbook\forms.py:148
|
||||
#: .\cookbook\forms.py:155
|
||||
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:151
|
||||
#: .\cookbook\forms.py:158
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr "Bei Nextcloud leer lassen, bei Dropbox API Token eingeben"
|
||||
msgstr "Bei Nextcloud leer lassen, bei Dropbox API Token eingeben."
|
||||
|
||||
#: .\cookbook\forms.py:159
|
||||
#: .\cookbook\forms.py:166
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -121,33 +122,33 @@ msgstr ""
|
||||
"Bei Dropbox leer lassen, bei Nextcloud Server URL angeben (<code>/remote.php/"
|
||||
"webdav/</code> wird automatisch hinzugefügt)"
|
||||
|
||||
#: .\cookbook\forms.py:178
|
||||
#: .\cookbook\forms.py:185
|
||||
msgid "Search String"
|
||||
msgstr "Such Wort"
|
||||
|
||||
#: .\cookbook\forms.py:192
|
||||
#: .\cookbook\forms.py:199
|
||||
msgid "File ID"
|
||||
msgstr "Datei ID"
|
||||
|
||||
#: .\cookbook\models.py:172
|
||||
#: .\cookbook\models.py:190
|
||||
msgid "Breakfast"
|
||||
msgstr "Frühstück"
|
||||
|
||||
#: .\cookbook\models.py:172
|
||||
#: .\cookbook\models.py:190
|
||||
msgid "Lunch"
|
||||
msgstr "Mittagessen"
|
||||
|
||||
#: .\cookbook\models.py:172
|
||||
#: .\cookbook\models.py:190
|
||||
msgid "Dinner"
|
||||
msgstr "Abendessen"
|
||||
|
||||
#: .\cookbook\models.py:172
|
||||
#: .\cookbook\models.py:190
|
||||
msgid "Other"
|
||||
msgstr "Andere"
|
||||
|
||||
#: .\cookbook\tables.py:75
|
||||
#: .\cookbook\tables.py:83
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:49
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:176
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:160
|
||||
#: .\cookbook\templates\generic\delete_template.html:5
|
||||
#: .\cookbook\templates\generic\delete_template.html:13
|
||||
#: .\cookbook\templates\generic\edit_template.html:25
|
||||
@@ -160,78 +161,80 @@ msgstr "Löschen"
|
||||
msgid "Cookbook"
|
||||
msgstr "Kochbuch"
|
||||
|
||||
#: .\cookbook\templates\base.html:82
|
||||
#: .\cookbook\templates\base.html:85
|
||||
msgid "Utensils"
|
||||
msgstr "Utensilien"
|
||||
|
||||
#: .\cookbook\templates\base.html:89
|
||||
msgid "Books"
|
||||
msgstr "Bücher"
|
||||
|
||||
#: .\cookbook\templates\base.html:86 .\cookbook\templates\meal_plan.html:4
|
||||
#: .\cookbook\templates\meal_plan.html:13 .\cookbook\views\edit.py:261
|
||||
#: .\cookbook\views\edit.py:462 .\cookbook\views\new.py:130
|
||||
#: .\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:90
|
||||
#, fuzzy
|
||||
#| msgid "Shopping List"
|
||||
#: .\cookbook\templates\base.html:95
|
||||
msgid "Shopping"
|
||||
msgstr "Einkaufsliste"
|
||||
|
||||
#: .\cookbook\templates\base.html:96
|
||||
#: .\cookbook\templates\base.html:105
|
||||
msgid "Tags"
|
||||
msgstr "Schlagwörter"
|
||||
|
||||
#: .\cookbook\templates\base.html:100 .\cookbook\views\edit.py:151
|
||||
#: .\cookbook\views\edit.py:407 .\cookbook\views\lists.py:17
|
||||
#: .\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
|
||||
msgid "Keyword"
|
||||
msgstr "Schlagwort"
|
||||
|
||||
#: .\cookbook\templates\base.html:102
|
||||
#: .\cookbook\templates\base.html:111
|
||||
msgid "Batch Edit"
|
||||
msgstr "Massenbearbeitung"
|
||||
|
||||
#: .\cookbook\templates\base.html:107
|
||||
#: .\cookbook\templates\base.html:116
|
||||
msgid "Storage Data"
|
||||
msgstr "Datenquellen"
|
||||
|
||||
#: .\cookbook\templates\base.html:111
|
||||
#: .\cookbook\templates\base.html:120
|
||||
msgid "Storage Backends"
|
||||
msgstr "Speicher Quellen"
|
||||
|
||||
#: .\cookbook\templates\base.html:113
|
||||
#: .\cookbook\templates\base.html:122
|
||||
msgid "Configure Sync"
|
||||
msgstr "Sync Einstellen"
|
||||
|
||||
#: .\cookbook\templates\base.html:115
|
||||
#: .\cookbook\templates\base.html:124
|
||||
msgid "Import Recipes"
|
||||
msgstr "Importierte Rezepte"
|
||||
|
||||
#: .\cookbook\templates\base.html:117 .\cookbook\views\lists.py:25
|
||||
#: .\cookbook\templates\base.html:126 .\cookbook\views\lists.py:26
|
||||
msgid "Import Log"
|
||||
msgstr "Import Log"
|
||||
|
||||
#: .\cookbook\templates\base.html:119 .\cookbook\templates\stats.html:10
|
||||
#: .\cookbook\templates\base.html:128 .\cookbook\templates\stats.html:10
|
||||
msgid "Statistics"
|
||||
msgstr "Statistiken"
|
||||
|
||||
#: .\cookbook\templates\base.html:121
|
||||
#: .\cookbook\templates\base.html:130
|
||||
msgid "Units & Ingredients"
|
||||
msgstr "Einheiten & Zutaten"
|
||||
|
||||
#: .\cookbook\templates\base.html:130 .\cookbook\templates\settings.html:6
|
||||
#: .\cookbook\templates\base.html:145 .\cookbook\templates\settings.html:6
|
||||
#: .\cookbook\templates\settings.html:11
|
||||
msgid "Settings"
|
||||
msgstr "Einstellungen"
|
||||
|
||||
#: .\cookbook\templates\base.html:135
|
||||
#: .\cookbook\templates\base.html:148
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: .\cookbook\templates\base.html:140
|
||||
#: .\cookbook\templates\base.html:152
|
||||
msgid "Logout"
|
||||
msgstr "Ausloggen"
|
||||
|
||||
#: .\cookbook\templates\base.html:143
|
||||
#: .\cookbook\templates\base.html:157
|
||||
#: .\cookbook\templates\registration\login.html:44
|
||||
msgid "Login"
|
||||
msgstr "Einloggen"
|
||||
@@ -250,7 +253,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:135
|
||||
#: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:143
|
||||
msgid "Sync"
|
||||
msgstr "Synchronisieren"
|
||||
|
||||
@@ -308,7 +311,7 @@ msgstr "Rezept Importieren"
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:47
|
||||
#: .\cookbook\templates\generic\edit_template.html:23
|
||||
#: .\cookbook\templates\generic\new_template.html:23
|
||||
#: .\cookbook\templates\recipe_view.html:214
|
||||
#: .\cookbook\templates\recipe_view.html:340
|
||||
#: .\cookbook\templates\settings.html:33 .\cookbook\templates\settings.html:47
|
||||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
@@ -323,6 +326,8 @@ 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>."
|
||||
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\generic\edit_template.html:27
|
||||
@@ -335,20 +340,25 @@ msgstr "Angucken"
|
||||
msgid "Delete original file"
|
||||
msgstr "Original löschen"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:159
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:208
|
||||
#: .\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
|
||||
msgid "Ingredient"
|
||||
msgstr "Zutat"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:164
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:147
|
||||
msgid "Amount"
|
||||
msgstr "Menge"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:166
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:149
|
||||
msgid "Unit"
|
||||
msgstr "Einheit"
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:179
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:154
|
||||
msgid "Note"
|
||||
msgstr "Notiz "
|
||||
|
||||
#: .\cookbook\templates\forms\edit_internal_recipe.html:163
|
||||
msgid "Are you sure that you want to delete this ingredient?"
|
||||
msgstr "Bist du sicher das du diese Zutat löschen willst?"
|
||||
|
||||
@@ -367,27 +377,27 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Dieses Formular kann genutzt werden wenn versehentlich zwei (oder mehr) Einheiten"
|
||||
"oder Zutaten erstellt wurden die eigentlich identisch\n"
|
||||
" Dieses Formular kann genutzt werden wenn versehentlich zwei (oder "
|
||||
"mehr) Einheitenoder Zutaten erstellt wurden die eigentlich identisch\n"
|
||||
" sein sollen.\n"
|
||||
" Es vereint zwei Zutaten oder Einheiten und aktualisiert alle entsprechenden "
|
||||
"Rezepte.\n"
|
||||
" Es vereint zwei Zutaten oder Einheiten und aktualisiert alle "
|
||||
"entsprechenden Rezepte.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\forms\ingredients.html:24
|
||||
msgid "Units"
|
||||
msgstr "Einheiten"
|
||||
|
||||
#: .\cookbook\templates\forms\ingredients.html:29
|
||||
#: .\cookbook\templates\forms\ingredients.html:26
|
||||
msgid "Are you sure that you want to merge these two units ?"
|
||||
msgstr "Bist du sicher diese beiden Einheiten zusammengeführt werden sollen ?"
|
||||
|
||||
#: .\cookbook\templates\forms\ingredients.html:30
|
||||
#: .\cookbook\templates\forms\ingredients.html:39
|
||||
#: .\cookbook\templates\forms\ingredients.html:31
|
||||
#: .\cookbook\templates\forms\ingredients.html:40
|
||||
msgid "Merge"
|
||||
msgstr "Zusammenführen"
|
||||
|
||||
#: .\cookbook\templates\forms\ingredients.html:38
|
||||
#: .\cookbook\templates\forms\ingredients.html:36
|
||||
msgid "Are you sure that you want to merge these two ingredients ?"
|
||||
msgstr "Bist du sicher diese beiden Zutaten zusammengeführt werden sollen ?"
|
||||
|
||||
@@ -410,7 +420,11 @@ msgstr "Bearbeiten"
|
||||
msgid "List"
|
||||
msgstr "Liste"
|
||||
|
||||
#: .\cookbook\templates\generic\list_template.html:19
|
||||
#: .\cookbook\templates\generic\list_template.html:25
|
||||
msgid "Filter"
|
||||
msgstr "Filter"
|
||||
|
||||
#: .\cookbook\templates\generic\list_template.html:30
|
||||
msgid "Import all"
|
||||
msgstr "Alle importieren"
|
||||
|
||||
@@ -428,8 +442,8 @@ msgid "next"
|
||||
msgstr "nächste"
|
||||
|
||||
#: .\cookbook\templates\include\recipe_open_modal.html:28
|
||||
#: .\cookbook\views\edit.py:295 .\cookbook\views\edit.py:354
|
||||
#: .\cookbook\views\edit.py:374 .\cookbook\views\new.py:34
|
||||
#: .\cookbook\views\delete.py:21 .\cookbook\views\edit.py:315
|
||||
#: .\cookbook\views\new.py:34
|
||||
msgid "Recipe"
|
||||
msgstr "Rezept"
|
||||
|
||||
@@ -437,7 +451,7 @@ msgstr "Rezept"
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: .\cookbook\templates\include\recipe_open_modal.html:56
|
||||
#: .\cookbook\templates\include\recipe_open_modal.html:53
|
||||
msgid "Open Recipe"
|
||||
msgstr "Rezept öffnen"
|
||||
|
||||
@@ -467,15 +481,23 @@ msgstr ""
|
||||
"oder Accounts mit limitiertem Zugriff verwendet werden.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\index.html:21
|
||||
#: .\cookbook\templates\index.html:27
|
||||
msgid "Search recipe ..."
|
||||
msgstr "Suche Rezept ..."
|
||||
|
||||
#: .\cookbook\templates\index.html:40
|
||||
#: .\cookbook\templates\index.html:41
|
||||
msgid "New Recipe"
|
||||
msgstr "Neues Rezept"
|
||||
|
||||
#: .\cookbook\templates\index.html:46
|
||||
msgid "Advanced Search"
|
||||
msgstr "Erweiterte Suche"
|
||||
|
||||
#: .\cookbook\templates\index.html:62
|
||||
#: .\cookbook\templates\index.html:50
|
||||
msgid "Reset Search"
|
||||
msgstr "Suche zurücksetzen"
|
||||
|
||||
#: .\cookbook\templates\index.html:78
|
||||
msgid "Log in to view Recipies"
|
||||
msgstr "Bitte einloggen um Rezepte zu sehen"
|
||||
|
||||
@@ -483,44 +505,54 @@ msgstr "Bitte einloggen um Rezepte zu sehen"
|
||||
msgid "Week"
|
||||
msgstr "Woche"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:31
|
||||
#: .\cookbook\templates\recipe_view.html:67
|
||||
msgid "in"
|
||||
msgstr "in"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:36
|
||||
#: .\cookbook\templates\recipe_view.html:181
|
||||
#: .\cookbook\templates\recipe_view.html:72
|
||||
#: .\cookbook\templates\recipe_view.html:293
|
||||
msgid "by"
|
||||
msgstr "von"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:47
|
||||
#: .\cookbook\templates\recipe_view.html:84
|
||||
msgid "Preparation time ca."
|
||||
msgstr "Zubereitungszeit ca."
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:52
|
||||
#: .\cookbook\templates\recipe_view.html:89
|
||||
msgid "Waiting time ca."
|
||||
msgstr "Zubereitungszeit ca."
|
||||
msgstr "Wartezeit ca."
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:114
|
||||
#: .\cookbook\templates\recipe_view.html:170
|
||||
msgid "Recipe Image"
|
||||
msgstr "Rezept Bild"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:133
|
||||
#: .\cookbook\templates\recipe_view.html:193
|
||||
#: .\cookbook\templates\recipe_view.html:227
|
||||
msgid "View external recipe"
|
||||
msgstr "Externes Rezept ansehen"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:144
|
||||
#: .\cookbook\templates\recipe_view.html:205
|
||||
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
|
||||
msgid "External recipe"
|
||||
msgstr "Externes Rezept"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:146
|
||||
#: .\cookbook\templates\recipe_view.html:214
|
||||
msgid ""
|
||||
"\n"
|
||||
" This is an external recipe, which means you can only "
|
||||
"view it by opening the link above.\n"
|
||||
" You can convert this recipe to a fancy recipe by "
|
||||
"pressing the convert button. The original file\n"
|
||||
" will still be accessible.\n"
|
||||
" "
|
||||
" This is an external recipe, which means "
|
||||
"you can only view it by opening the link\n"
|
||||
" above.\n"
|
||||
" You can convert this recipe to a fancy "
|
||||
"recipe by pressing the convert button. The\n"
|
||||
" original\n"
|
||||
" file\n"
|
||||
" will still be accessible.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Dies ist ein externes Rezept. Das bedeutet das es "
|
||||
@@ -530,16 +562,16 @@ msgstr ""
|
||||
"bleibt weiterhin verfügbar.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:154
|
||||
#: .\cookbook\templates\recipe_view.html:225
|
||||
msgid "Convert now!"
|
||||
msgstr "Jetzt umwandeln!"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:163
|
||||
#: .\cookbook\templates\recipe_view.html:289
|
||||
msgid "Comments"
|
||||
msgstr "Kommentare"
|
||||
|
||||
#: .\cookbook\templates\recipe_view.html:172 .\cookbook\views\edit.py:212
|
||||
#: .\cookbook\views\edit.py:429
|
||||
#: .\cookbook\templates\recipe_view.html:309 .\cookbook\views\delete.py:103
|
||||
#: .\cookbook\views\edit.py:234
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
@@ -612,65 +644,69 @@ msgstr[0] "Massenbearbeitung erfolgreich. %(count)d Rezept wurde aktualisiert."
|
||||
msgstr[1] ""
|
||||
"Massenbearbeitung erfolgreich. %(count)d Rezepte wurden aktualisiert."
|
||||
|
||||
#: .\cookbook\views\edit.py:109
|
||||
msgid "Recipe saved!"
|
||||
msgstr "Rezept gespeichert"
|
||||
|
||||
#: .\cookbook\views\edit.py:111
|
||||
msgid "There was an error saving this recipe!"
|
||||
msgstr "Es gab einen Fehler beim Speichern des Rezepts"
|
||||
|
||||
#: .\cookbook\views\edit.py:160 .\cookbook\views\edit.py:203
|
||||
msgid "You cannot edit this comment!"
|
||||
msgstr "Du kannst diesen Kommentar nicht bearbeiten!"
|
||||
|
||||
#: .\cookbook\views\edit.py:179
|
||||
msgid "Storage saved!"
|
||||
msgstr "Speicherquelle gespeichert"
|
||||
|
||||
#: .\cookbook\views\edit.py:182
|
||||
msgid "There was an error updating this storage backend.!"
|
||||
msgstr "Es gab einen Fehler beim aktualisierung dieser Speicher Quelle"
|
||||
|
||||
#: .\cookbook\views\edit.py:229 .\cookbook\views\edit.py:385
|
||||
#: .\cookbook\views\lists.py:34
|
||||
#: .\cookbook\views\delete.py:48 .\cookbook\views\edit.py:251
|
||||
#: .\cookbook\views\lists.py:35
|
||||
msgid "Import"
|
||||
msgstr "Rezept Importieren"
|
||||
|
||||
#: .\cookbook\views\edit.py:245 .\cookbook\views\edit.py:440
|
||||
#: .\cookbook\views\new.py:112
|
||||
msgid "Recipe Book"
|
||||
msgstr "Rezeptbuch"
|
||||
|
||||
#: .\cookbook\views\edit.py:283
|
||||
msgid "Changes saved!"
|
||||
msgstr "Änderungen gespeichert"
|
||||
|
||||
#: .\cookbook\views\edit.py:287
|
||||
msgid "Error saving changes!"
|
||||
msgstr "Fehler beim Speichern der Daten."
|
||||
|
||||
#: .\cookbook\views\edit.py:317
|
||||
msgid "Units merged!"
|
||||
msgstr "Einheiten zusammengeführt"
|
||||
|
||||
#: .\cookbook\views\edit.py:330
|
||||
msgid "Ingredients merged!"
|
||||
msgstr "Zutaten zusammengeführt"
|
||||
|
||||
#: .\cookbook\views\edit.py:396
|
||||
#: .\cookbook\views\delete.py:59
|
||||
msgid "Monitor"
|
||||
msgstr "Monitor"
|
||||
|
||||
#: .\cookbook\views\edit.py:418 .\cookbook\views\lists.py:42
|
||||
#: .\cookbook\views\delete.py:92 .\cookbook\views\lists.py:53
|
||||
#: .\cookbook\views\new.py:64
|
||||
msgid "Storage Backend"
|
||||
msgstr "Speicher Quelle"
|
||||
|
||||
#: .\cookbook\views\edit.py:451
|
||||
#: .\cookbook\views\delete.py:114 .\cookbook\views\edit.py:267
|
||||
#: .\cookbook\views\new.py:112
|
||||
msgid "Recipe Book"
|
||||
msgstr "Rezeptbuch"
|
||||
|
||||
#: .\cookbook\views\delete.py:125
|
||||
msgid "Bookmarks"
|
||||
msgstr "Lesezeichen"
|
||||
|
||||
#: .\cookbook\views\edit.py:117
|
||||
msgid "Recipe saved!"
|
||||
msgstr "Rezept gespeichert"
|
||||
|
||||
#: .\cookbook\views\edit.py:119
|
||||
msgid "There was an error saving this recipe!"
|
||||
msgstr "Es gab einen Fehler beim Speichern des Rezepts"
|
||||
|
||||
#: .\cookbook\views\edit.py:184
|
||||
msgid "You cannot edit this storage!"
|
||||
msgstr "Du kannst diese Speicherquelle nicht bearbeiten!"
|
||||
|
||||
#: .\cookbook\views\edit.py:203
|
||||
msgid "Storage saved!"
|
||||
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:225
|
||||
msgid "You cannot edit this comment!"
|
||||
msgstr "Du kannst diesen Kommentar nicht bearbeiten!"
|
||||
|
||||
#: .\cookbook\views\edit.py:303
|
||||
msgid "Changes saved!"
|
||||
msgstr "Änderungen gespeichert"
|
||||
|
||||
#: .\cookbook\views\edit.py:307
|
||||
msgid "Error saving changes!"
|
||||
msgstr "Fehler beim Speichern der Daten."
|
||||
|
||||
#: .\cookbook\views\edit.py:337
|
||||
msgid "Units merged!"
|
||||
msgstr "Einheiten zusammengeführt"
|
||||
|
||||
#: .\cookbook\views\edit.py:350
|
||||
msgid "Ingredients merged!"
|
||||
msgstr "Zutaten zusammengeführt"
|
||||
|
||||
#: .\cookbook\views\new.py:86
|
||||
msgid "Imported new recipe!"
|
||||
msgstr "Importier neue Rezepte"
|
||||
|
||||
19
cookbook/migrations/0027_ingredient_recipe.py
Normal file
19
cookbook/migrations/0027_ingredient_recipe.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.0.4 on 2020-03-17 17:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0026_auto_20200219_1605'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ingredient',
|
||||
name='recipe',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.Recipe'),
|
||||
),
|
||||
]
|
||||
19
cookbook/migrations/0028_auto_20200317_1901.py
Normal file
19
cookbook/migrations/0028_auto_20200317_1901.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.0.4 on 2020-03-17 18:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0027_ingredient_recipe'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recipeingredient',
|
||||
name='ingredient',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Ingredient'),
|
||||
),
|
||||
]
|
||||
19
cookbook/migrations/0029_auto_20200317_1901.py
Normal file
19
cookbook/migrations/0029_auto_20200317_1901.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.0.4 on 2020-03-17 18:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0028_auto_20200317_1901'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recipeingredient',
|
||||
name='unit',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Unit'),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0030_recipeingredient_note.py
Normal file
18
cookbook/migrations/0030_recipeingredient_note.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.4 on 2020-03-17 18:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0029_auto_20200317_1901'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='recipeingredient',
|
||||
name='note',
|
||||
field=models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,8 +1,25 @@
|
||||
import re
|
||||
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext as _
|
||||
from django.db import models
|
||||
|
||||
|
||||
def get_user_name(self):
|
||||
if not (name := f"{self.first_name} {self.last_name}") == " ":
|
||||
return name
|
||||
else:
|
||||
return self.username
|
||||
|
||||
|
||||
auth.models.User.add_to_class('get_user_name', get_user_name)
|
||||
|
||||
|
||||
def get_model_name(model):
|
||||
return ('_'.join(re.findall('[A-Z][^A-Z]*', model.__name__))).lower()
|
||||
|
||||
|
||||
class UserPreference(models.Model):
|
||||
# Themes
|
||||
BOOTSTRAP = 'BOOTSTRAP'
|
||||
@@ -28,6 +45,9 @@ class UserPreference(models.Model):
|
||||
theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY)
|
||||
nav_color = models.CharField(choices=COLORS, max_length=128, default=PRIMARY)
|
||||
|
||||
def __str__(self):
|
||||
return self.user
|
||||
|
||||
|
||||
class Storage(models.Model):
|
||||
DROPBOX = 'DB'
|
||||
@@ -64,6 +84,9 @@ class SyncLog(models.Model):
|
||||
msg = models.TextField(default="")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.created_at}:{self.sync} - {self.status}"
|
||||
|
||||
|
||||
class Keyword(models.Model):
|
||||
name = models.CharField(max_length=64, unique=True)
|
||||
@@ -115,16 +138,18 @@ class Unit(models.Model):
|
||||
|
||||
class Ingredient(models.Model):
|
||||
name = models.CharField(unique=True, max_length=128)
|
||||
recipe = models.ForeignKey(Recipe, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class RecipeIngredient(models.Model):
|
||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT, null=True)
|
||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT)
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True)
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.amount) + ' ' + str(self.unit) + ' ' + str(self.ingredient)
|
||||
|
||||
@@ -27,6 +27,15 @@ class KeywordTable(tables.Table):
|
||||
fields = ('id', 'icon', 'name')
|
||||
|
||||
|
||||
class IngredientTable(tables.Table):
|
||||
id = tables.LinkColumn('edit_ingredient', args=[A('id')])
|
||||
|
||||
class Meta:
|
||||
model = Keyword
|
||||
template_name = 'generic/table_template.html'
|
||||
fields = ('id', 'name')
|
||||
|
||||
|
||||
class StorageTable(tables.Table):
|
||||
id = tables.LinkColumn('edit_storage', args=[A('id')])
|
||||
|
||||
@@ -44,7 +53,7 @@ class ImportLogTable(tables.Table):
|
||||
if value == 'SUCCESS':
|
||||
return format_html('<span class="badge badge-success">%s</span>' % value)
|
||||
else:
|
||||
return format_html('<span class="badge badge-error">%s</span>' % value)
|
||||
return format_html('<span class="badge badge-danger">%s</span>' % value)
|
||||
|
||||
class Meta:
|
||||
model = SyncLog
|
||||
@@ -71,7 +80,7 @@ class SyncTable(tables.Table):
|
||||
|
||||
class RecipeImportTable(tables.Table):
|
||||
id = tables.LinkColumn('new_recipe_import', args=[A('id')])
|
||||
delete = tables.TemplateColumn("<a href='{% url 'delete_import' record.id %}' >" + _('Delete') + "</a>")
|
||||
delete = tables.TemplateColumn("<a href='{% url 'delete_recipe_import' record.id %}' >" + _('Delete') + "</a>")
|
||||
|
||||
class Meta:
|
||||
model = RecipeImport
|
||||
|
||||
@@ -74,77 +74,90 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {% if request.resolver_match.url_name == "index" %}active{% endif %}">
|
||||
<li class="nav-item {% if request.resolver_match.url_name in 'index,edit_recipe,edit_internal_recipe,edit_external_recipe,view_recipe' %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'index' %}"><i class="fas fa-book"></i> {% trans 'Cookbook' %}<span
|
||||
class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item {% if request.resolver_match.url_name == "view_books" %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_books' %}"><i class="fas fa-bookmark"></i> {% trans 'Books' %}
|
||||
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'view_books,view_plan,view_shopping,list_ingredient' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-mortar-pestle"></i> {% trans 'Utensils' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'view_books' %}"><i
|
||||
class="fas fa-bookmark fa-fw"></i> {% trans 'Books' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_plan' %}"><i
|
||||
class="fas fa-calendar fa-fw"></i> {% trans 'Meal-Plan' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_shopping' %}"><i
|
||||
class="fas fa-shopping-cart fa-fw"></i> {% trans 'Shopping' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'list_ingredient' %}"><i
|
||||
class="fas fa-leaf fa-fw"></i> {% trans 'Ingredients' %}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item {% if request.resolver_match.url_name == "view_plan" %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_plan' %}"><i class="fas fa-calendar"></i> {% trans 'Meal-Plan' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item {% if request.resolver_match.url_name == "view_shopping" %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_shopping' %}"><i class="fas fa-shopping-cart"></i> {% trans 'Shopping' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_keyword,data_batch_edit' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-tags"></i> {% trans 'Tags' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'list_keyword' %}"><i
|
||||
class="fas fa-tags"></i> {% trans 'Keyword' %}</a>
|
||||
class="fas fa-tags fa-fw"></i> {% trans 'Keyword' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_batch_edit' %}"><i
|
||||
class="fas fa-edit"></i> {% trans 'Batch Edit' %}</a>
|
||||
class="fas fa-edit fa-fw"></i> {% trans 'Batch Edit' %}</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_storage,data_sync,list_recipe_import,list_sync_log,data_stats,edit_ingredient' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fas fa-database"></i> {% trans 'Storage Data' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'list_storage' %}"><i
|
||||
class="fas fa-database"></i> {% trans 'Storage Backends' %}</a>
|
||||
class="fas fa-database fa-fw"></i> {% trans 'Storage Backends' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_sync' %}"><i
|
||||
class="fas fa-sync-alt"></i> {% trans 'Configure Sync' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_import' %}"><i
|
||||
class="far fa-file-alt"></i> {% trans 'Import Recipes' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'list_import_log' %}"><i
|
||||
class="fas fa-history"></i> {% trans 'Import Log' %}</a>
|
||||
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>
|
||||
<a class="dropdown-item" href="{% url 'list_sync_log' %}"><i
|
||||
class="fas fa-history fa-fw"></i> {% trans 'Import Log' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_stats' %}"><i
|
||||
class="fas fa-chart-line"></i> {% trans 'Statistics' %}</a>
|
||||
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"></i> {% trans 'Units & Ingredients' %}</a>
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units & Ingredients' %}</a>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item {% if request.resolver_match.url_name == "view_settings" %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_settings' %}"><i
|
||||
class="fas fa-user-cog"></i> {% trans 'Settings' %}</a>
|
||||
</li>
|
||||
{% if user.is_superuser %}
|
||||
{% 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 }}
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'view_settings' %}"><i
|
||||
class="fas fa-user-cog fa-fw"></i> {% trans 'Settings' %}</a>
|
||||
{% if user.is_superuser %}
|
||||
<a class="dropdown-item" href="{% url 'admin:index' %}"><i
|
||||
class="fas fa-user-shield fa-fw"></i> {% trans 'Admin' %}</a>
|
||||
{% endif %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}"><i
|
||||
class="fas fa-sign-out-alt fa-fw"></i> {% trans 'Logout' %}</a>
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:index' %}"><i
|
||||
class="fas fa-user-shield"></i> {% trans 'Admin' %}</a>
|
||||
<a class="nav-link" href="{% url 'login' %}">{% trans 'Login' %} <i class="fas fa-sign-in-alt"></i></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
{% if user.is_authenticated %}
|
||||
<a class="nav-link" href="{% url 'logout' %}">{% trans 'Logout' %} {{ user.get_username }} <i
|
||||
class="fas fa-sign-out-alt"></i></a>
|
||||
{% else %}
|
||||
<a class="nav-link" href="{% url 'login' %}">{% trans 'Login' %} <i class="fas fa-sign-in-alt"></i></a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
<br/>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<h2>{% trans 'Recipe Books' %}</h2>
|
||||
</div>
|
||||
<div class="col col-md-3" style="text-align: right">
|
||||
<a href="{% url 'new_book' %}" class="btn btn-success"><i
|
||||
<a href="{% url 'new_recipe_book' %}" class="btn btn-success"><i
|
||||
class="fas fa-plus-circle"></i> {% trans 'New Book' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
<hr>
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
<a href="{% url 'redirect_delete' form.instance|get_class|lower form.instance.pk %}"
|
||||
<a href="{% delete_url form.instance|get_class form.instance.pk %}"
|
||||
class="btn btn-danger"><i class="fas fa-trash-alt"></i> {% trans 'Delete' %}</a>
|
||||
{% if view_url %}
|
||||
<a href="{{ view_url }}" class="btn btn-info"><i class="far fa-eye"></i> {% trans 'View' %}</a>
|
||||
@@ -151,6 +151,7 @@
|
||||
validator: "required",
|
||||
editor: select2UnitEditor
|
||||
},
|
||||
{title: "{% trans 'Note' %}", field: "note", editor: "input"},
|
||||
{
|
||||
formatter: function (cell, formatterParams) {
|
||||
return "<span style='color:red'><i class=\"fas fa-trash-alt\"></i></span>"
|
||||
@@ -181,16 +182,20 @@
|
||||
});
|
||||
|
||||
// load initial value
|
||||
$('#id_ingredients').val(JSON.stringify(data))
|
||||
$('#id_ingredients').val(JSON.stringify(data));
|
||||
|
||||
function addIngredientRow() {
|
||||
data.push({
|
||||
ingredient__name: "{% trans 'Ingredient' %}",
|
||||
amount: "100",
|
||||
unit__name: "g",
|
||||
note: "",
|
||||
id: Math.floor(Math.random() * 10000000),
|
||||
delete: false,
|
||||
});
|
||||
input = table.rowManager.rows[((table.rowManager.rows).length) - 1].cells[1].getElement()
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
document.onkeyup = function (e) {
|
||||
|
||||
@@ -22,21 +22,22 @@
|
||||
<br/>
|
||||
|
||||
<h4>{% trans 'Units' %}</h4>
|
||||
<form action="{% url 'edit_ingredient' %}" method="post">
|
||||
<form action="{% url 'edit_ingredient' %}" method="post"
|
||||
onsubmit="return confirm('{% trans 'Are you sure that you want to merge these two units ?' %}')">
|
||||
{% csrf_token %}
|
||||
{{ units_form|crispy }}
|
||||
<button class="btn btn-danger" type="submit"
|
||||
onclick="confirm('{% trans 'Are you sure that you want to merge these two units ?' %}')"><i
|
||||
><i
|
||||
class="fas fa-sync-alt"></i> {% trans 'Merge' %}</button>
|
||||
</form>
|
||||
|
||||
<h4>{% trans 'Ingredients' %}</h4>
|
||||
<form action="{% url 'edit_ingredient' %}" method="post">
|
||||
<form action="{% url 'edit_ingredient' %}" method="post"
|
||||
onsubmit="return confirm('{% trans 'Are you sure that you want to merge these two ingredients ?' %}')">
|
||||
{% csrf_token %}
|
||||
{{ ingredients_form|crispy }}
|
||||
<button class="btn btn-danger" type="submit"
|
||||
onclick="confirm('{% trans 'Are you sure that you want to merge these two ingredients ?' %}')"><i
|
||||
class="fas fa-sync-alt"></i> {% trans 'Merge' %}</button>
|
||||
<button class="btn btn-danger" type="submit">
|
||||
<i class="fas fa-sync-alt"></i> {% trans 'Merge' %}</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<h3>{% trans 'Edit' %} {{ title }}</h3>
|
||||
|
||||
{% if form.Meta.model|get_class == 'Storage' %}
|
||||
{% if form.Meta.model|get_class_name == 'Storage' %}
|
||||
{% include 'include/storage_backend_warning.html' %}
|
||||
{% endif %}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
<a href="{% url 'redirect_delete' form.instance|get_class|lower form.instance.pk %}"
|
||||
<a href="{% delete_url form.instance|get_class form.instance.pk %}"
|
||||
class="btn btn-danger"><i class="fas fa-trash-alt"></i> {% trans 'Delete' %}</a>
|
||||
{% if view_url %}
|
||||
<a href="{{ view_url }}" class="btn btn-info"><i class="far fa-eye"></i> {% trans 'View' %}</a>
|
||||
|
||||
@@ -15,6 +15,17 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
</h3>
|
||||
|
||||
{% if filter %}
|
||||
<br/>
|
||||
<br/>
|
||||
<form action="." method="get">
|
||||
{% csrf_token %}
|
||||
{{ filter.form|crispy }}
|
||||
<button type="submit" class="btn btn-success">{% trans 'Filter' %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if import_btn %}
|
||||
<a href="{% url 'data_batch_import' %}" class="btn btn-warning">{% trans 'Import all' %}</a>
|
||||
<br/>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<h3>{% trans 'New' %} {{ title }} </h3>
|
||||
|
||||
{% if form.Meta.model|get_class == 'Storage' %}
|
||||
{% if form.Meta.model|get_class_name == 'Storage' %}
|
||||
{% include 'include/storage_backend_warning.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
{% block extra_head %}
|
||||
{{ filter.form.media }}
|
||||
|
||||
<style>
|
||||
.dropdown-toggle-no-arrow::after {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -21,29 +27,36 @@
|
||||
<input type="text" class="form-control" placeholder="{% trans 'Search recipe ...' %}"
|
||||
id="{{ filter.form.name.id_for_label }}" name="{{ filter.form.name.name }}"
|
||||
aria-describedby="button-addon4">
|
||||
<div class="input-group-append" id="button-addon4">
|
||||
<button class="btn btn-primary" type="submit"><i class="fas fa-search"></i></button>
|
||||
<button class="btn btn-warning" type="button" onclick="window.location = window.location.pathname;"><i class="fas fa-backspace"></i></button>
|
||||
<button class="btn btn-success" type="button" onclick="location.href='{% url 'new_recipe' %}'"><i class="fas fa-plus-circle"></i></button>
|
||||
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="submit"><i class="fas fa-search"></i></button>
|
||||
<button type="button" class="btn btn-light dropdown-toggle dropdown-toggle-split dropdown-toggle-no-arrow"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<button class="dropdown-item" type="button"
|
||||
onclick="location.href='{% url 'new_recipe' %}'"><i
|
||||
class="fas fa-plus-circle fa-fw"></i> {% trans 'New Recipe' %}</button>
|
||||
<button data-toggle="collapse" href="#collapse_adv_search"
|
||||
role="button" class="dropdown-item"
|
||||
aria-expanded="false" type="button"
|
||||
aria-controls="collapse_adv_search"><i
|
||||
class="fas fa-search-plus fa-fw"></i> {% trans 'Advanced Search' %}
|
||||
</button>
|
||||
<button class="dropdown-item" type="button"
|
||||
onclick="window.location = window.location.pathname;"><i
|
||||
class="fas fa-sync fa-fw"></i> {% trans 'Reset Search' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ">
|
||||
<div class="col-md-2 offset-md-10" style="text-align: right">
|
||||
<a class="" data-toggle="collapse" href="#collapse_adv_search" role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse_adv_search"><i
|
||||
class="fas fa-search"></i> {% trans 'Advanced Search' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="collapse col-md-12" id="collapse_adv_search">
|
||||
<div>
|
||||
<div style="margin-top: 1vh">
|
||||
{{ filter.form.keywords | as_crispy_field }}
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{% block content %}
|
||||
|
||||
<h3>
|
||||
{% trans 'Meal-Plan' %} <a href="{% url 'new_plan' %}"><i class="fas fa-plus-circle"></i></a>
|
||||
{% trans 'Meal-Plan' %} <a href="{% url 'new_meal_plan' %}"><i class="fas fa-plus-circle"></i></a>
|
||||
</h3>
|
||||
|
||||
<div class="row">
|
||||
@@ -55,7 +55,7 @@
|
||||
{% for day_key, days_value in plan_value.days.items %}
|
||||
<td>
|
||||
{% for mp in days_value %}
|
||||
<a href="{% url 'edit_plan' mp.pk %}"><i class="fas fa-edit"></i></a>
|
||||
<a href="{% url 'edit_meal_plan' mp.pk %}"><i class="fas fa-edit"></i></a>
|
||||
<a href="{% url 'view_recipe' mp.recipe.id %}">{{ mp.recipe.name }}</a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<a class="btn btn-warning" href="{% url 'view_shopping' %}?r={{ recipe.pk }}"><i
|
||||
class="fas fa-shopping-cart"></i></a>
|
||||
{% endif %}
|
||||
<a class="btn btn-info" href="{% url 'new_plan' %}?recipe={{ recipe.pk }}"><i
|
||||
<a class="btn btn-info" href="{% url 'new_meal_plan' %}?recipe={{ recipe.pk }}"><i
|
||||
class="fas fa-calendar"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.internal %}
|
||||
<small>{% trans 'by' %} {{ recipe.created_by.username }}<br/></small>
|
||||
<small>{% trans 'by' %} {{ recipe.created_by.get_user_name }}<br/></small>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.keywords %}
|
||||
@@ -81,12 +81,14 @@
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.working_time and recipe.working_time != 0 %}
|
||||
<span class="badge badge-secondary">{% trans 'Preparation time ca.' %} {{ recipe.working_time }} min </span>
|
||||
<span class="badge badge-secondary"><i
|
||||
class="fas fa-user-clock"></i> {% trans 'Preparation time ca.' %} {{ recipe.working_time }} min </span>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.waiting_time and recipe.waiting_time != 0 %}
|
||||
<span
|
||||
class="badge badge-secondary">{% trans 'Waiting time ca.' %} {{ recipe.waiting_time }} min </span>
|
||||
class="badge badge-secondary"><i
|
||||
class="far fa-clock"></i> {% trans 'Waiting time ca.' %} {{ recipe.waiting_time }} min </span>
|
||||
{% endif %}
|
||||
|
||||
{% if recipe.waiting_time and recipe.waiting_time != 0 or recipe.working_time and recipe.working_time != 0 %}
|
||||
@@ -96,7 +98,7 @@
|
||||
|
||||
<div class="row">
|
||||
{% if ingredients %}
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="col-lg-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@@ -115,10 +117,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<table class="">
|
||||
<table class="table table-sm">
|
||||
{% for i in ingredients %}
|
||||
<tr>
|
||||
<td style="font-size: large">
|
||||
<td style="vertical-align: middle!important;">
|
||||
<div class="pretty p-default p-curve">
|
||||
<input type="checkbox"/>
|
||||
<div class="state p-success">
|
||||
@@ -134,11 +136,36 @@
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td style="font-size: large">{{ i.ingredient.name }}</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
{% if i.ingredient.recipe %}
|
||||
<a href="{% url 'view_recipe' i.ingredient.recipe.pk %}" target="_blank">
|
||||
{% endif %}
|
||||
{{ i.ingredient.name }}
|
||||
{% if i.ingredient.recipe %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
{% if i.note %}
|
||||
<button class="btn btn-light btn-sm" type="button" data-container="body"
|
||||
data-toggle="popover"
|
||||
data-placement="right" data-html="true" data-trigger="focus"
|
||||
data-content="{{ i.note }}">
|
||||
<i class="fas fa-info"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<!-- Bottom border -->
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,7 +294,18 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h5>{% trans 'Comments' %}</h5>
|
||||
<h5><i class="far fa-comments"></i> {% trans 'Comments' %}</h5>
|
||||
{% for c in comments %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<small class="card-title">{{ c.updated_at }} {% trans 'by' %} {{ c.created_by.username }}</small> <a
|
||||
href="{% url 'edit_comment' c.pk %}" class="d-print-none"><i class="fas fa-pencil-alt"></i></a><br/>
|
||||
{{ c.text }}
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
|
||||
<div class="d-print-none">
|
||||
|
||||
<form method="POST" class="post-form">
|
||||
@@ -282,17 +320,6 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% for c in comments %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<small class="card-title">{{ c.updated_at }} {% trans 'by' %} {{ c.created_by.username }}</small> <a
|
||||
href="{% url 'edit_comment' c.pk %}" class="d-print-none"><i class="fas fa-pencil-alt"></i></a><br/>
|
||||
{{ c.text }}
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
{% endfor %}
|
||||
|
||||
{% if recipe.storage %}
|
||||
{% include 'include/recipe_open_modal.html' %}
|
||||
{% endif %}
|
||||
@@ -327,6 +354,14 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="popover"]').popover()
|
||||
});
|
||||
|
||||
$('.popover-dismiss').popover({
|
||||
trigger: 'focus'
|
||||
});
|
||||
|
||||
function reloadIngredients() {
|
||||
factor = Number($('#in_factor').val());
|
||||
ingredients = {
|
||||
|
||||
@@ -14,7 +14,24 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4><i class="fas fa-language"></i> {% trans 'Language' %}</h4>
|
||||
<h4><i class="fas fa-user-edit fa-fw"></i> {% trans 'Account' %}</h4>
|
||||
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ user_name_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="user_name_form"><i class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ password_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="password_form"><i class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4><i class="fas fa-language fa-fw"></i> {% trans 'Language' %}</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
|
||||
@@ -39,12 +56,12 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4><i class="fas fa-palette"></i>{% trans 'Style' %}</h4>
|
||||
<h4><i class="fas fa-palette fa-fw"></i> {% trans 'Style' %}</h4>
|
||||
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
{{ preference_form|crispy }}
|
||||
<button class="btn btn-success" type="submit" name="preference_form"><i class="fas fa-save"></i> {% trans 'Save' %}</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
@@ -2,17 +2,29 @@ from django import template
|
||||
import markdown as md
|
||||
import bleach
|
||||
from bleach_whitelist import markdown_tags, markdown_attrs, all_styles, print_attrs
|
||||
from django.urls import reverse
|
||||
|
||||
from cookbook.helper.mdx_attributes import MarkdownFormatExtension
|
||||
from cookbook.models import get_model_name
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name='get_class')
|
||||
def get_class(value):
|
||||
@register.filter()
|
||||
def get_class_name(value):
|
||||
return value.__class__.__name__
|
||||
|
||||
|
||||
@register.filter()
|
||||
def get_class(value):
|
||||
return value.__class__
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def delete_url(model, pk):
|
||||
return reverse(f'delete_{get_model_name(model)}', args=[pk])
|
||||
|
||||
|
||||
@register.filter()
|
||||
def markdown(value):
|
||||
tags = markdown_tags + ['pre', 'table', 'td', 'tr', 'th', 'tbody', 'style', 'thead']
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.contrib import auth
|
||||
from django.urls import reverse
|
||||
|
||||
from cookbook.models import Recipe, RecipeIngredient, Ingredient, Unit
|
||||
from cookbook.models import Recipe, RecipeIngredient, Ingredient, Unit, Storage
|
||||
from cookbook.tests.views.test_views import TestViews
|
||||
|
||||
|
||||
@@ -97,3 +97,32 @@ class TestEditsRecipe(TestViews):
|
||||
with open('cookbook/tests/resources/image.png', 'rb') as file:
|
||||
r = self.client.post(url, {'name': "Changed", 'working_time': 15, 'waiting_time': 15, 'image': file})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_external_recipe_update(self):
|
||||
storage = Storage.objects.create(
|
||||
name='TestStorage',
|
||||
method=Storage.DROPBOX,
|
||||
created_by=auth.get_user(self.client),
|
||||
token='test',
|
||||
username='test',
|
||||
password='test',
|
||||
)
|
||||
|
||||
recipe = Recipe.objects.create(
|
||||
name='Test',
|
||||
created_by=auth.get_user(self.client),
|
||||
storage=storage,
|
||||
)
|
||||
|
||||
url = reverse('edit_external_recipe', args=[recipe.pk])
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.anonymous_client.get(url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
r = self.client.post(url, {'name': 'Test', 'working_time': 15, 'waiting_time': 15, })
|
||||
recipe.refresh_from_db()
|
||||
self.assertEqual(recipe.working_time, 15)
|
||||
self.assertEqual(recipe.waiting_time, 15)
|
||||
|
||||
39
cookbook/tests/edits/test_edits_storage.py
Normal file
39
cookbook/tests/edits/test_edits_storage.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.contrib import auth
|
||||
from django.urls import reverse
|
||||
|
||||
from cookbook.models import Storage
|
||||
from cookbook.tests.views.test_views import TestViews
|
||||
|
||||
|
||||
class TestEditsRecipe(TestViews):
|
||||
|
||||
def test_edit_storage(self):
|
||||
storage = Storage.objects.create(
|
||||
name='TestStorage',
|
||||
method=Storage.DROPBOX,
|
||||
created_by=auth.get_user(self.client),
|
||||
token='test',
|
||||
username='test',
|
||||
password='test',
|
||||
)
|
||||
|
||||
url = reverse('edit_storage', args=[storage.pk])
|
||||
r = self.anonymous_client.get(url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
r = self.another_client.get(url)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.superuser_client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
r = self.client.post(url, {'name': 'NewStorage', 'password': '1234_pw', 'token': '1234_token', 'method': Storage.DROPBOX})
|
||||
storage.refresh_from_db()
|
||||
self.assertEqual(storage.password, '1234_pw')
|
||||
self.assertEqual(storage.token, '1234_token')
|
||||
|
||||
r = self.client.post(url, {'name': 'NewStorage', 'password': '1234_pw', 'token': '1234_token', 'method': 'not_a_valid_method'})
|
||||
self.assertFormError(r, 'form', 'method', ['Select a valid choice. not_a_valid_method is not one of the available choices.'])
|
||||
@@ -6,8 +6,19 @@ from django.test import TestCase, Client
|
||||
class TestBase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.anonymous_client = Client()
|
||||
self.client.force_login(User.objects.get_or_create(username='test')[0])
|
||||
|
||||
self.client = Client()
|
||||
self.client.force_login(User.objects.get_or_create(username='client')[0])
|
||||
user = auth.get_user(self.client)
|
||||
self.assertTrue(user.is_authenticated)
|
||||
|
||||
self.another_client = Client()
|
||||
self.another_client.force_login(User.objects.get_or_create(username='another_client')[0])
|
||||
user = auth.get_user(self.another_client)
|
||||
self.assertTrue(user.is_authenticated)
|
||||
|
||||
self.superuser_client = Client()
|
||||
self.superuser_client.force_login(User.objects.get_or_create(username='superuser_client', is_superuser=True)[0])
|
||||
user = auth.get_user(self.superuser_client)
|
||||
self.assertTrue(user.is_authenticated)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from pydoc import locate
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from .views import *
|
||||
@@ -13,46 +15,17 @@ urlpatterns = [
|
||||
|
||||
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
|
||||
|
||||
path('new/recipe/', new.RecipeCreate.as_view(), name='new_recipe'),
|
||||
path('new/recipe_import/<int:import_id>/', new.create_new_external_recipe, name='new_recipe_import'),
|
||||
path('new/keyword/', new.KeywordCreate.as_view(), name='new_keyword'),
|
||||
path('new/storage/', new.StorageCreate.as_view(), name='new_storage'),
|
||||
path('new/book/', new.RecipeBookCreate.as_view(), name='new_book'),
|
||||
path('new/plan/', new.MealPlanCreate.as_view(), name='new_plan'),
|
||||
|
||||
path('list/keyword', lists.keyword, name='list_keyword'),
|
||||
path('list/import_log', lists.sync_log, name='list_import_log'),
|
||||
path('list/import', lists.recipe_import, name='list_import'),
|
||||
path('list/storage', lists.storage, name='list_storage'),
|
||||
|
||||
path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'),
|
||||
path('edit/recipe/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'),
|
||||
# for internal use only
|
||||
path('edit/recipe/external/<int:pk>/', edit.RecipeUpdate.as_view(), name='edit_external_recipe'),
|
||||
# for internal use only
|
||||
path('edit/recipe/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'), # for internal use only
|
||||
path('edit/recipe/external/<int:pk>/', edit.ExternalRecipeUpdate.as_view(), name='edit_external_recipe'), # for internal use only
|
||||
path('edit/recipe/convert/<int:pk>/', edit.convert_recipe, name='edit_convert_recipe'), # for internal use only
|
||||
|
||||
path('edit/keyword/<int:pk>/', edit.KeywordUpdate.as_view(), name='edit_keyword'),
|
||||
path('edit/sync/<int:pk>/', edit.SyncUpdate.as_view(), name='edit_sync'),
|
||||
path('edit/import/<int:pk>/', edit.ImportUpdate.as_view(), name='edit_import'),
|
||||
path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'),
|
||||
path('edit/comment/<int:pk>/', edit.CommentUpdate.as_view(), name='edit_comment'),
|
||||
path('edit/recipe-book/<int:pk>/', edit.RecipeBookUpdate.as_view(), name='edit_recipe_book'),
|
||||
path('edit/plan/<int:pk>/', edit.MealPlanUpdate.as_view(), name='edit_plan'),
|
||||
path('edit/ingredient/', edit.edit_ingredients, name='edit_ingredient'),
|
||||
|
||||
path('redirect/delete/<slug:name>/<int:pk>/', delete.delete_redirect, name='redirect_delete'),
|
||||
|
||||
path('delete/recipe/<int:pk>/', delete.RecipeDelete.as_view(), name='delete_recipe'),
|
||||
path('delete/recipe-source/<int:pk>/', delete.RecipeSourceDelete.as_view(), name='delete_recipe_source'),
|
||||
path('delete/keyword/<int:pk>/', delete.KeywordDelete.as_view(), name='delete_keyword'),
|
||||
path('delete/sync/<int:pk>/', delete.MonitorDelete.as_view(), name='delete_sync'),
|
||||
path('delete/import/<int:pk>/', delete.ImportDelete.as_view(), name='delete_import'),
|
||||
path('delete/storage/<int:pk>/', delete.StorageDelete.as_view(), name='delete_storage'),
|
||||
path('delete/comment/<int:pk>/', delete.CommentDelete.as_view(), name='delete_comment'),
|
||||
path('delete/recipe-book/<int:pk>/', delete.RecipeBookDelete.as_view(), name='delete_recipe_book'),
|
||||
path('delete/recipe-book-entry/<int:pk>/', delete.RecipeBookEntryDelete.as_view(), name='delete_recipe_book_entry'),
|
||||
path('delete/plan/<int:pk>/', delete.MealPlanDelete.as_view(), name='delete_plan'),
|
||||
path('delete/recipe-source/<int:pk>/', delete.delete_recipe_source, name='delete_recipe_source'),
|
||||
|
||||
path('data/sync', data.sync, name='data_sync'), # TODO move to generic "new" view
|
||||
path('data/batch/edit', data.batch_edit, name='data_batch_edit'),
|
||||
@@ -69,3 +42,21 @@ urlpatterns = [
|
||||
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),
|
||||
path('dal/unit/', dal.UnitAutocomplete.as_view(), name='dal_unit'),
|
||||
]
|
||||
|
||||
generic_models = (Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync, Comment, RecipeBookEntry, Keyword, Ingredient)
|
||||
|
||||
for m in generic_models:
|
||||
py_name = get_model_name(m)
|
||||
url_name = py_name.replace('_', '-')
|
||||
|
||||
if c := locate(f'cookbook.views.new.{m.__name__}Create'):
|
||||
urlpatterns.append(path(f'new/{url_name}/', c.as_view(), name=f'new_{py_name}'))
|
||||
|
||||
if c := locate(f'cookbook.views.edit.{m.__name__}Update'):
|
||||
urlpatterns.append(path(f'edit/{url_name}/<int:pk>/', c.as_view(), name=f'edit_{py_name}'))
|
||||
|
||||
if c := getattr(lists, py_name, None):
|
||||
urlpatterns.append(path(f'list/{url_name}/', c, name=f'list_{py_name}'))
|
||||
|
||||
if c := locate(f'cookbook.views.delete.{m.__name__}Delete'):
|
||||
urlpatterns.append(path(f'delete/{url_name}/<int:pk>/', c.as_view(), name=f'delete_{py_name}'))
|
||||
|
||||
@@ -61,7 +61,7 @@ def sync_all(request):
|
||||
|
||||
if not error:
|
||||
messages.add_message(request, messages.SUCCESS, _('Sync successful!'))
|
||||
return redirect('list_import')
|
||||
return redirect('list_recipe_import')
|
||||
else:
|
||||
messages.add_message(request, messages.ERROR, _('Error synchronizing with Storage'))
|
||||
return redirect('list_import')
|
||||
return redirect('list_recipe_import')
|
||||
|
||||
@@ -44,7 +44,7 @@ def batch_import(request):
|
||||
recipe.save()
|
||||
new_recipe.delete()
|
||||
|
||||
return redirect('list_import')
|
||||
return redirect('list_recipe_import')
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import DeleteView
|
||||
|
||||
from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeBook, \
|
||||
RecipeBookEntry, MealPlan
|
||||
RecipeBookEntry, MealPlan, Ingredient
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
|
||||
|
||||
# Generic Delete views
|
||||
def delete_redirect(request, name, pk):
|
||||
return redirect(('delete_' + name), pk)
|
||||
|
||||
|
||||
class RecipeDelete(LoginRequiredMixin, DeleteView):
|
||||
template_name = "generic/delete_template.html"
|
||||
model = Recipe
|
||||
@@ -26,44 +22,40 @@ class RecipeDelete(LoginRequiredMixin, DeleteView):
|
||||
return context
|
||||
|
||||
|
||||
class RecipeSourceDelete(LoginRequiredMixin, DeleteView):
|
||||
template_name = "generic/delete_template.html"
|
||||
model = Recipe
|
||||
success_url = reverse_lazy('index')
|
||||
def delete_recipe_source(request, pk):
|
||||
recipe = get_object_or_404(Recipe, pk=pk)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if self.object.storage.method == Storage.DROPBOX:
|
||||
Dropbox.delete_file(self.object) # TODO central location to handle storage type switches
|
||||
if self.object.storage.method == Storage.NEXTCLOUD:
|
||||
Nextcloud.delete_file(self.object)
|
||||
if recipe.storage.method == Storage.DROPBOX:
|
||||
Dropbox.delete_file(recipe) # TODO central location to handle storage type switches
|
||||
if recipe.storage.method == Storage.NEXTCLOUD:
|
||||
Nextcloud.delete_file(recipe)
|
||||
|
||||
return super(RecipeSourceDelete, self).delete(request, *args, **kwargs)
|
||||
recipe.storage = None
|
||||
recipe.file_path = ''
|
||||
recipe.file_uid = ''
|
||||
recipe.save()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(RecipeSourceDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Recipe")
|
||||
return context
|
||||
return HttpResponseRedirect(reverse('edit_recipe', args=[recipe.pk]))
|
||||
|
||||
|
||||
class ImportDelete(LoginRequiredMixin, DeleteView):
|
||||
class RecipeImportDelete(LoginRequiredMixin, DeleteView):
|
||||
template_name = "generic/delete_template.html"
|
||||
model = RecipeImport
|
||||
success_url = reverse_lazy('list_import')
|
||||
success_url = reverse_lazy('list_recipe_import')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ImportDelete, self).get_context_data(**kwargs)
|
||||
context = super(RecipeImportDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Import")
|
||||
return context
|
||||
|
||||
|
||||
class MonitorDelete(LoginRequiredMixin, DeleteView):
|
||||
class SyncDelete(LoginRequiredMixin, DeleteView):
|
||||
template_name = "generic/delete_template.html"
|
||||
model = Sync
|
||||
success_url = reverse_lazy('data_sync')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(MonitorDelete, self).get_context_data(**kwargs)
|
||||
context = super(SyncDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Monitor")
|
||||
return context
|
||||
|
||||
@@ -79,6 +71,17 @@ class KeywordDelete(LoginRequiredMixin, DeleteView):
|
||||
return context
|
||||
|
||||
|
||||
class IngredientDelete(LoginRequiredMixin, DeleteView):
|
||||
template_name = "generic/delete_template.html"
|
||||
model = Ingredient
|
||||
success_url = reverse_lazy('list_ingredient')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IngredientDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Ingredient")
|
||||
return context
|
||||
|
||||
|
||||
class StorageDelete(LoginRequiredMixin, DeleteView):
|
||||
template_name = "generic/delete_template.html"
|
||||
model = Storage
|
||||
|
||||
@@ -15,7 +15,7 @@ from django.utils.translation import gettext as _
|
||||
from django.views.generic import UpdateView
|
||||
|
||||
from cookbook.forms import ExternalRecipeForm, KeywordForm, StorageForm, SyncForm, InternalRecipeForm, CommentForm, \
|
||||
MealPlanForm, UnitMergeForm, IngredientMergeForm
|
||||
MealPlanForm, UnitMergeForm, IngredientMergeForm, IngredientForm
|
||||
from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeIngredient, RecipeBook, \
|
||||
MealPlan, Unit, Ingredient
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
@@ -86,6 +86,9 @@ def internal_recipe_update(request, pk):
|
||||
recipe_ingredient = RecipeIngredient()
|
||||
recipe_ingredient.recipe = recipe_instance
|
||||
|
||||
if 'note' in i:
|
||||
recipe_ingredient.note = i['note']
|
||||
|
||||
if Ingredient.objects.filter(name=i['ingredient__name']).exists():
|
||||
recipe_ingredient.ingredient = Ingredient.objects.get(name=i['ingredient__name'])
|
||||
else:
|
||||
@@ -118,7 +121,7 @@ def internal_recipe_update(request, pk):
|
||||
else:
|
||||
form = InternalRecipeForm(instance=recipe_instance)
|
||||
|
||||
ingredients = RecipeIngredient.objects.select_related('unit__name', 'ingredient__name').filter(recipe=recipe_instance).values('ingredient__name', 'unit__name', 'amount')
|
||||
ingredients = RecipeIngredient.objects.select_related('unit__name', 'ingredient__name').filter(recipe=recipe_instance).values('ingredient__name', 'unit__name', 'amount', 'note')
|
||||
|
||||
return render(request, 'forms/edit_internal_recipe.html',
|
||||
{'form': form, 'ingredients': json.dumps(list(ingredients)),
|
||||
@@ -157,16 +160,32 @@ class KeywordUpdate(LoginRequiredMixin, UpdateView):
|
||||
return context
|
||||
|
||||
|
||||
class IngredientUpdate(LoginRequiredMixin, UpdateView):
|
||||
template_name = "generic/edit_template.html"
|
||||
model = Ingredient
|
||||
form_class = IngredientForm
|
||||
|
||||
# TODO add msg box
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('edit_ingredient', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IngredientUpdate, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Ingredient")
|
||||
return context
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_storage(request, pk):
|
||||
instance = get_object_or_404(Storage, pk=pk)
|
||||
|
||||
if not (instance.created_by == request.user or request.user.is_superuser):
|
||||
messages.add_message(request, messages.ERROR, _('You cannot edit this comment!'))
|
||||
messages.add_message(request, messages.ERROR, _('You cannot edit this storage!'))
|
||||
return HttpResponseRedirect(reverse('list_storage'))
|
||||
|
||||
if request.method == "POST":
|
||||
form = StorageForm(request.POST)
|
||||
form = StorageForm(request.POST, instance=instance)
|
||||
if form.is_valid():
|
||||
instance.name = form.cleaned_data['name']
|
||||
instance.method = form.cleaned_data['method']
|
||||
@@ -182,7 +201,6 @@ def edit_storage(request, pk):
|
||||
instance.save()
|
||||
|
||||
messages.add_message(request, messages.SUCCESS, _('Storage saved!'))
|
||||
return HttpResponseRedirect(reverse('edit_storage', args=[pk]))
|
||||
else:
|
||||
messages.add_message(request, messages.ERROR, _('There was an error updating this storage backend.!'))
|
||||
else:
|
||||
@@ -191,8 +209,7 @@ def edit_storage(request, pk):
|
||||
pseudo_instance.token = '__NO__CHANGE__'
|
||||
form = StorageForm(instance=pseudo_instance)
|
||||
|
||||
return render(request, 'generic/edit_template.html',
|
||||
{'form': form, 'view_url': reverse('view_recipe', args=[pk])})
|
||||
return render(request, 'generic/edit_template.html', {'form': form})
|
||||
|
||||
|
||||
class CommentUpdate(LoginRequiredMixin, UpdateView):
|
||||
@@ -267,7 +284,7 @@ class MealPlanUpdate(LoginRequiredMixin, UpdateView):
|
||||
return context
|
||||
|
||||
|
||||
class RecipeUpdate(LoginRequiredMixin, UpdateView):
|
||||
class ExternalRecipeUpdate(LoginRequiredMixin, UpdateView):
|
||||
model = Recipe
|
||||
form_class = ExternalRecipeForm
|
||||
template_name = "generic/edit_template.html"
|
||||
@@ -277,26 +294,24 @@ class RecipeUpdate(LoginRequiredMixin, UpdateView):
|
||||
old_recipe = Recipe.objects.get(pk=self.object.pk)
|
||||
if not old_recipe.name == self.object.name:
|
||||
if self.object.storage.method == Storage.DROPBOX:
|
||||
Dropbox.rename_file(old_recipe,
|
||||
self.object.name) # TODO central location to handle storage type switches
|
||||
Dropbox.rename_file(old_recipe, self.object.name) # TODO central location to handle storage type switches
|
||||
if self.object.storage.method == Storage.NEXTCLOUD:
|
||||
Nextcloud.rename_file(old_recipe, self.object.name)
|
||||
|
||||
self.object.file_path = os.path.dirname(self.object.file_path) + '/' + self.object.name + \
|
||||
os.path.splitext(self.object.file_path)[1]
|
||||
self.object.file_path = os.path.dirname(self.object.file_path) + '/' + self.object.name + os.path.splitext(self.object.file_path)[1]
|
||||
|
||||
messages.add_message(self.request, messages.SUCCESS, _('Changes saved!'))
|
||||
return super(RecipeUpdate, self).form_valid(form)
|
||||
return super(ExternalRecipeUpdate, self).form_valid(form)
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.add_message(self.request, messages.ERROR, _('Error saving changes!'))
|
||||
return super(RecipeUpdate, self).form_valid(form)
|
||||
return super(ExternalRecipeUpdate, self).form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('edit_recipe', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(RecipeUpdate, self).get_context_data(**kwargs)
|
||||
context = super(ExternalRecipeUpdate, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Recipe")
|
||||
context['view_url'] = reverse('view_recipe', args=[self.object.pk])
|
||||
if self.object.storage:
|
||||
@@ -342,4 +357,3 @@ def edit_ingredients(request):
|
||||
ingredients_form = IngredientMergeForm()
|
||||
|
||||
return render(request, 'forms/ingredients.html', {'units_form': units_form, 'ingredients_form': ingredients_form})
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ from django.urls import reverse_lazy
|
||||
from django_tables2 import RequestConfig
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from cookbook.models import Keyword, SyncLog, RecipeImport, Storage
|
||||
from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable
|
||||
from cookbook.filters import IngredientFilter
|
||||
from cookbook.models import Keyword, SyncLog, RecipeImport, Storage, Ingredient
|
||||
from cookbook.tables import KeywordTable, ImportLogTable, RecipeImportTable, StorageTable, IngredientTable
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -34,6 +35,16 @@ def recipe_import(request):
|
||||
return render(request, 'generic/list_template.html', {'title': _("Import"), 'table': table, 'import_btn': True})
|
||||
|
||||
|
||||
@login_required
|
||||
def ingredient(request):
|
||||
f = IngredientFilter(request.GET, queryset=Ingredient.objects.all().order_by('pk'))
|
||||
|
||||
table = IngredientTable(f.qs)
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
|
||||
return render(request, 'generic/list_template.html', {'title': _("Ingredients"), 'table': table, 'filter': f})
|
||||
|
||||
|
||||
@login_required
|
||||
def storage(request):
|
||||
table = StorageTable(Storage.objects.all())
|
||||
|
||||
@@ -84,7 +84,7 @@ def create_new_external_recipe(request, import_id):
|
||||
RecipeImport.objects.get(id=import_id).delete()
|
||||
|
||||
messages.add_message(request, messages.SUCCESS, _('Imported new recipe!'))
|
||||
return redirect('list_import')
|
||||
return redirect('list_recipe_import')
|
||||
else:
|
||||
messages.add_message(request, messages.ERROR, _('There was an error importing this recipe!'))
|
||||
else:
|
||||
|
||||
@@ -3,7 +3,9 @@ import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django_tables2 import RequestConfig
|
||||
from django.utils.translation import gettext as _
|
||||
@@ -161,18 +163,35 @@ def settings(request):
|
||||
except UserPreference.DoesNotExist:
|
||||
up = None
|
||||
|
||||
user_name_form = UserNameForm(instance=request.user)
|
||||
password_form = PasswordChangeForm(request.user)
|
||||
|
||||
if request.method == "POST":
|
||||
form = UserPreferenceForm(request.POST)
|
||||
if form.is_valid():
|
||||
if not up:
|
||||
up = UserPreference(user=request.user)
|
||||
up.theme = form.cleaned_data['theme']
|
||||
up.nav_color = form.cleaned_data['nav_color']
|
||||
up.save()
|
||||
if 'preference_form' in request.POST:
|
||||
form = UserPreferenceForm(request.POST, prefix='preference')
|
||||
if form.is_valid():
|
||||
if not up:
|
||||
up = UserPreference(user=request.user)
|
||||
up.theme = form.cleaned_data['theme']
|
||||
up.nav_color = form.cleaned_data['nav_color']
|
||||
up.save()
|
||||
|
||||
if 'user_name_form' in request.POST:
|
||||
user_name_form = UserNameForm(request.POST, prefix='name')
|
||||
if user_name_form.is_valid():
|
||||
request.user.first_name = user_name_form.cleaned_data['first_name']
|
||||
request.user.last_name = user_name_form.cleaned_data['last_name']
|
||||
request.user.save()
|
||||
|
||||
if 'password_form' in request.POST:
|
||||
password_form = PasswordChangeForm(request.user, request.POST)
|
||||
if password_form.is_valid():
|
||||
user = password_form.save()
|
||||
update_session_auth_hash(request, user)
|
||||
|
||||
if up:
|
||||
form = UserPreferenceForm(instance=up)
|
||||
preference_form = UserPreferenceForm(instance=up)
|
||||
else:
|
||||
form = UserPreferenceForm()
|
||||
preference_form = UserPreferenceForm()
|
||||
|
||||
return render(request, 'settings.html', {'form': form})
|
||||
return render(request, 'settings.html', {'preference_form': preference_form, 'user_name_form': user_name_form, 'password_form': password_form})
|
||||
|
||||
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-02-18 23:20+0100\n"
|
||||
"POT-Creation-Date: 2020-03-18 12:13+0100\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"
|
||||
|
||||
Reference in New Issue
Block a user