diff --git a/.github/workflows/main.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows/main.yml rename to .github/workflows/ci.yml diff --git a/.github/workflows/docker-publish-dev.yml b/.github/workflows/docker-publish-dev.yml new file mode 100644 index 000000000..b777f1da7 --- /dev/null +++ b/.github/workflows/docker-publish-dev.yml @@ -0,0 +1,18 @@ +name: publish dev image docker +on: + push: + branches: + - '*' + - '*/*' + - '!master' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@2.13 + with: + name: vabene1111/recipes + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/docker-publish-latest.yml b/.github/workflows/docker-publish-latest.yml new file mode 100644 index 000000000..859027e27 --- /dev/null +++ b/.github/workflows/docker-publish-latest.yml @@ -0,0 +1,19 @@ +name: publish latest image docker +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Build and publish image + uses: ilteoood/docker_buildx@master + with: + publish: true + imageName: vabene1111/recipes + tag: latest + dockerHubUser: ${{ secrets.DOCKER_USERNAME }} + dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/docker-release-publish.yml b/.github/workflows/docker-publish-release.yml similarity index 56% rename from .github/workflows/docker-release-publish.yml rename to .github/workflows/docker-publish-release.yml index 951d2e8b5..eee5088bc 100644 --- a/.github/workflows/docker-release-publish.yml +++ b/.github/workflows/docker-publish-release.yml @@ -1,4 +1,4 @@ -name: Deploy Docker Image +name: publish tagged release docker on: push: @@ -11,12 +11,15 @@ jobs: name: Build image job steps: - name: Checkout master - uses: actions/checkout@master + uses: actions/checkout@master# + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} - name: Build and publish image uses: ilteoood/docker_buildx@master with: publish: true - imageName: recipes - tag: test + imageName: vabene1111/recipes + tag: ${{ steps.get_version.outputs.VERSION }} dockerHubUser: ${{ secrets.DOCKER_USERNAME }} dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index 519869f6a..000000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Publish Docker -on: [push] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Publish to Registry - uses: elgohr/Publish-Docker-Github-Action@2.13 - with: - name: vabene1111/recipes - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/README.md b/README.md index 402de933e..2ea1a8995 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Recipes is a Django application to manage, tag and search recipes using either b ![Preview](docs/preview.png) +[More Screenshots](https://imgur.com/a/V01151p) + ### Features - :package: **Sync** files with Dropbox and Nextcloud (more can easily be added) @@ -12,20 +14,22 @@ 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. ### Docker-Compose -2. Choose one of the included configurations [here](https://github.com/vabene1111/recipes/tree/develop/docs/docker). +2. Choose one of the included configurations [here](docs/docker). 2. Download the environment (config) file template and fill it out `wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env ` 3. Start the container `docker-compose up -d` 4. Create a default user by running `docker-compose exec web_recipes createsuperuser`. @@ -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!** @@ -46,30 +49,9 @@ While intermediate updates can be skipped when updating please make sure to **re 2. Pull the latest image using `docker-compose pull` 3. Start the container again using `docker-compose up -d` -# Documentation +## Kubernetes -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. +You can find a basic kubernetes setup [here](docs/k8s/). Please see the README in the folder for more detail. ## Contributing Pull Requests and ideas are welcome, feel free to contribute in any way. diff --git a/cookbook/admin.py b/cookbook/admin.py index 8127f0ad9..ed8cad2a1 100644 --- a/cookbook/admin.py +++ b/cookbook/admin.py @@ -3,7 +3,7 @@ from .models import * class UserPreferenceAdmin(admin.ModelAdmin): - list_display = ('name', 'theme', 'nav_color') + list_display = ('name', 'theme', 'nav_color', 'default_page', 'search_style') @staticmethod def name(obj): @@ -80,7 +80,7 @@ class RecipeBookAdmin(admin.ModelAdmin): @staticmethod def user_name(obj): - return obj.user.get_user_name() + return obj.created_by.get_user_name() admin.site.register(RecipeBook, RecipeBookAdmin) @@ -98,7 +98,7 @@ class MealPlanAdmin(admin.ModelAdmin): @staticmethod def user(obj): - return obj.user.get_user_name() + return obj.created_by.get_user_name() admin.site.register(MealPlan, MealPlanAdmin) diff --git a/cookbook/forms.py b/cookbook/forms.py index 1355bd9c0..52eb993ed 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -1,4 +1,3 @@ -from dal import autocomplete from django import forms from django.forms import widgets from django.utils.translation import gettext as _ @@ -31,10 +30,11 @@ class UserPreferenceForm(forms.ModelForm): class Meta: model = UserPreference - fields = ('theme', 'nav_color') + fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'search_style') help_texts = { - 'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!') + 'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'), + 'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.') } @@ -99,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' @@ -218,7 +237,8 @@ class ImportRecipeForm(forms.ModelForm): class RecipeBookForm(forms.ModelForm): class Meta: model = RecipeBook - fields = ('name',) + fields = ('name', 'icon', 'description', 'shared') + widgets = {'icon': EmojiPickerTextInput, 'shared': MultiSelectWidget} class MealPlanForm(forms.ModelForm): diff --git a/cookbook/helper/mdx_urlize.py b/cookbook/helper/mdx_urlize.py new file mode 100644 index 000000000..7df06430b --- /dev/null +++ b/cookbook/helper/mdx_urlize.py @@ -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'

http://example.com/

' + +>>> md.convert('go to http://example.com') +u'

go to http://example.com

' + +>>> md.convert('example.com') +u'

example.com

' + +>>> md.convert('example.net') +u'

example.net

' + +>>> md.convert('www.example.us') +u'

www.example.us

' + +>>> md.convert('(www.example.us/path/?name=val)') +u'

(www.example.us/path/?name=val)

' + +>>> md.convert('go to now!') +u'

go to http://example.com now!

' + +Negative examples: + +>>> md.convert('del.icio.us') +u'

del.icio.us

' + +""" + +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() diff --git a/cookbook/helper/permission_helper.py b/cookbook/helper/permission_helper.py new file mode 100644 index 000000000..10a310cc9 --- /dev/null +++ b/cookbook/helper/permission_helper.py @@ -0,0 +1,68 @@ +""" +Source: https://djangosnippets.org/snippets/1703/ +""" +from django.contrib import messages +from django.contrib.auth.decorators import user_passes_test +from django.utils.translation import gettext as _ +from django.http import HttpResponseRedirect +from django.urls import reverse_lazy, reverse + + +def get_allowed_groups(groups_required): + groups_allowed = tuple(groups_required) + if 'guest' in groups_required: + groups_allowed = groups_allowed + ('user', 'admin') + if 'user' in groups_required: + groups_allowed = groups_allowed + ('admin',) + return groups_allowed + + +def group_required(*groups_required): + """Requires user membership in at least one of the groups passed in.""" + + def in_groups(u): + groups_allowed = get_allowed_groups(groups_required) + if u.is_authenticated: + if u.is_superuser | bool(u.groups.filter(name__in=groups_allowed)): + return True + return False + + return user_passes_test(in_groups, login_url='index') + + +class GroupRequiredMixin(object): + """ + groups_required - list of strings, required param + """ + + groups_required = None + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!')) + return HttpResponseRedirect(reverse_lazy('login')) + else: + if not request.user.is_superuser: + group_allowed = get_allowed_groups(self.groups_required) + user_groups = [] + for group in request.user.groups.values_list('name', flat=True): + user_groups.append(group) + if len(set(user_groups).intersection(group_allowed)) <= 0: + messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!')) + return HttpResponseRedirect(reverse_lazy('index')) + return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs) + + +class OwnerRequiredMixin(object): + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!')) + return HttpResponseRedirect(reverse_lazy('login')) + else: + obj = self.get_object() + if not (obj.created_by == request.user or request.user.is_superuser): + messages.add_message(request, messages.ERROR, _('You cannot interact with this object as its not owned by you!')) + return HttpResponseRedirect(reverse('index')) + + return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs) diff --git a/cookbook/locale/de/LC_MESSAGES/django.mo b/cookbook/locale/de/LC_MESSAGES/django.mo index 07f72e42a..0552499b3 100644 Binary files a/cookbook/locale/de/LC_MESSAGES/django.mo and b/cookbook/locale/de/LC_MESSAGES/django.mo differ diff --git a/cookbook/locale/de/LC_MESSAGES/django.po b/cookbook/locale/de/LC_MESSAGES/django.po index 55d3f6024..7cc958be0 100644 --- a/cookbook/locale/de/LC_MESSAGES/django.po +++ b/cookbook/locale/de/LC_MESSAGES/django.po @@ -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 - [ ] in list for easier usage in markdown based " "documents." @@ -70,51 +82,63 @@ msgstr "" "Füge - [ ] 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 (/remote." "php/webdav/ is added automatically)" @@ -122,119 +146,128 @@ msgstr "" "Bei Dropbox leer lassen, bei Nextcloud Server URL angeben (/remote.php/" "webdav/ 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 Ctrl+Space to insert new Ingredient!
You can also save " "the recipe using Ctrl+Shift+S." @@ -329,36 +390,35 @@ msgstr "" "Benutze Strg+Leertaste um eine neue Zutat einzufügen!
Rezepte können mitStrg+Shift+S 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: %(object)s " -msgstr "Bist du sicher das %(title)s: %(object)s gelöscht werden soll" +msgstr "Bist du sicher das %(title)s: %(object)s 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!" diff --git a/cookbook/migrations/0032_userpreference_default_unit.py b/cookbook/migrations/0032_userpreference_default_unit.py new file mode 100644 index 000000000..974be08c7 --- /dev/null +++ b/cookbook/migrations/0032_userpreference_default_unit.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-04-13 20:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0031_auto_20200407_1841'), + ] + + operations = [ + migrations.AddField( + model_name='userpreference', + name='default_unit', + field=models.CharField(default='g', max_length=32), + ), + ] diff --git a/cookbook/migrations/0033_userpreference_default_page.py b/cookbook/migrations/0033_userpreference_default_page.py new file mode 100644 index 000000000..11fca7df1 --- /dev/null +++ b/cookbook/migrations/0033_userpreference_default_page.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-04-13 20:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0032_userpreference_default_unit'), + ] + + operations = [ + migrations.AddField( + model_name='userpreference', + name='default_page', + field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan')], default='SEARCH', max_length=64), + ), + ] diff --git a/cookbook/migrations/0034_auto_20200426_1614.py b/cookbook/migrations/0034_auto_20200426_1614.py new file mode 100644 index 000000000..ee3797aab --- /dev/null +++ b/cookbook/migrations/0034_auto_20200426_1614.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.5 on 2020-04-26 14:14 + +from django.db import migrations + + +def apply_migration(apps, schema_editor): + Group = apps.get_model('auth', 'Group') + Group.objects.bulk_create([ + Group(name=u'guest'), + Group(name=u'user'), + Group(name=u'admin'), + ]) + + +class Migration(migrations.Migration): + dependencies = [ + ('cookbook', '0033_userpreference_default_page'), + ] + + operations = [ + migrations.RunPython(apply_migration) + ] diff --git a/cookbook/migrations/0035_auto_20200427_1637.py b/cookbook/migrations/0035_auto_20200427_1637.py new file mode 100644 index 000000000..c388c7d6e --- /dev/null +++ b/cookbook/migrations/0035_auto_20200427_1637.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.5 on 2020-04-27 14:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0034_auto_20200426_1614'), + ] + + operations = [ + migrations.RenameField( + model_name='mealplan', + old_name='user', + new_name='created_by', + ), + migrations.RenameField( + model_name='recipebook', + old_name='user', + new_name='created_by', + ), + migrations.AlterField( + model_name='userpreference', + name='default_page', + field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan'), ('BOOKS', 'Books')], default='SEARCH', max_length=64), + ), + ] diff --git a/cookbook/migrations/0036_auto_20200427_1800.py b/cookbook/migrations/0036_auto_20200427_1800.py new file mode 100644 index 000000000..25ee2f9df --- /dev/null +++ b/cookbook/migrations/0036_auto_20200427_1800.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.5 on 2020-04-27 16:00 + +from django.db import migrations + + +def apply_migration(apps, schema_editor): + Group = apps.get_model('auth', 'Group') + User = apps.get_model('auth', 'User') + for u in User.objects.all(): + if u.groups.count() < 1: + u.groups.add(Group.objects.get(name='admin')) + u.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('cookbook', '0035_auto_20200427_1637'), + ] + + operations = [ + migrations.RunPython(apply_migration) + ] diff --git a/cookbook/migrations/0037_userpreference_search_style.py b/cookbook/migrations/0037_userpreference_search_style.py new file mode 100644 index 000000000..c90e3fedb --- /dev/null +++ b/cookbook/migrations/0037_userpreference_search_style.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.5 on 2020-05-02 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0036_auto_20200427_1800'), + ] + + operations = [ + migrations.AddField( + model_name='userpreference', + name='search_style', + field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large')], default='LARGE', max_length=64), + ), + ] diff --git a/cookbook/migrations/0038_auto_20200502_1259.py b/cookbook/migrations/0038_auto_20200502_1259.py new file mode 100644 index 000000000..0c0cf98d2 --- /dev/null +++ b/cookbook/migrations/0038_auto_20200502_1259.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.5 on 2020-05-02 10:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0037_userpreference_search_style'), + ] + + operations = [ + migrations.AddField( + model_name='recipebook', + name='description', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='recipebook', + name='icon', + field=models.CharField(blank=True, max_length=16, null=True), + ), + ] diff --git a/cookbook/migrations/0039_recipebook_shared.py b/cookbook/migrations/0039_recipebook_shared.py new file mode 100644 index 000000000..ea3144942 --- /dev/null +++ b/cookbook/migrations/0039_recipebook_shared.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-05-02 12:04 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('cookbook', '0038_auto_20200502_1259'), + ] + + operations = [ + migrations.AddField( + model_name='recipebook', + name='shared', + field=models.ManyToManyField(blank=True, related_name='shared_with', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/cookbook/migrations/0040_auto_20200502_1433.py b/cookbook/migrations/0040_auto_20200502_1433.py new file mode 100644 index 000000000..38c74689c --- /dev/null +++ b/cookbook/migrations/0040_auto_20200502_1433.py @@ -0,0 +1,22 @@ +# Generated by Django 3.0.5 on 2020-05-02 12:33 + +import annoying.fields +from django.conf import settings +from django.db import migrations +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('cookbook', '0039_recipebook_shared'), + ] + + operations = [ + migrations.AlterField( + model_name='userpreference', + name='user', + field=annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index ab3d14287..0f1763654 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -1,5 +1,6 @@ import re +from annoying.fields import AutoOneToOneField from django.contrib import auth from django.contrib.auth.models import User from django.utils.translation import gettext as _ @@ -41,12 +42,28 @@ class UserPreference(models.Model): COLORS = ((PRIMARY, 'Primary'), (SECONDARY, 'Secondary'), (SUCCESS, 'Success'), (INFO, 'Info'), (WARNING, 'Warning'), (DANGER, 'Danger'), (LIGHT, 'Light'), (DARK, 'Dark')) - user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) + # Default Page + SEARCH = 'SEARCH' + PLAN = 'PLAN' + BOOKS = 'BOOKS' + + PAGES = ((SEARCH, _('Search')), (PLAN, _('Meal-Plan')), (BOOKS, _('Books')), ) + + # Search Style + SMALL = 'SMALL' + LARGE = 'LARGE' + + SEARCH_STYLE = ((SMALL, _('Small')), (LARGE, _('Large')),) + + user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True) theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY) nav_color = models.CharField(choices=COLORS, max_length=128, default=PRIMARY) + default_unit = models.CharField(max_length=32, default='g') + default_page = models.CharField(choices=PAGES, max_length=64, default=SEARCH) + search_style = models.CharField(choices=SEARCH_STYLE, max_length=64, default=LARGE) def __str__(self): - return self.user + return str(self.user) class Storage(models.Model): @@ -145,8 +162,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) @@ -179,7 +196,10 @@ class RecipeImport(models.Model): class RecipeBook(models.Model): name = models.CharField(max_length=128) - user = models.ForeignKey(User, on_delete=models.CASCADE) + description = models.TextField(blank=True) + icon = models.CharField(max_length=16, blank=True, null=True) + shared = models.ManyToManyField(User, blank=True, related_name='shared_with') + created_by = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return self.name @@ -200,8 +220,8 @@ class MealPlan(models.Model): OTHER = 'OTHER' MEAL_TYPES = ((BREAKFAST, _('Breakfast')), (LUNCH, _('Lunch')), (DINNER, _('Dinner')), (OTHER, _('Other')),) - user = models.ForeignKey(User, on_delete=models.CASCADE) recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True) + created_by = models.ForeignKey(User, on_delete=models.CASCADE) meal = models.CharField(choices=MEAL_TYPES, max_length=128, default=BREAKFAST) note = models.TextField(blank=True) date = models.DateField() diff --git a/cookbook/static/recipe_no_image.svg b/cookbook/static/recipe_no_image.svg new file mode 100644 index 000000000..ba2976f63 --- /dev/null +++ b/cookbook/static/recipe_no_image.svg @@ -0,0 +1,66 @@ + + diff --git a/cookbook/tables.py b/cookbook/tables.py index ff7f0ca38..a0466c9a8 100644 --- a/cookbook/tables.py +++ b/cookbook/tables.py @@ -6,7 +6,14 @@ from django_tables2.utils import A # alias for Accessor from .models import * -class RecipeTable(tables.Table): +class ImageUrlColumn(tables.Column): + def render(self, value): + if value.url: + return value.url + return None + + +class RecipeTableSmall(tables.Table): id = tables.LinkColumn('edit_recipe', args=[A('id')]) name = tables.LinkColumn('view_recipe', args=[A('id')]) all_tags = tables.Column( @@ -18,6 +25,19 @@ class RecipeTable(tables.Table): fields = ('id', 'name', 'all_tags') +class RecipeTable(tables.Table): + edit = tables.TemplateColumn("" + _('Edit') + "") + name = tables.LinkColumn('view_recipe', args=[A('id')]) + all_tags = tables.Column( + attrs={'td': {'class': 'd-none d-lg-table-cell'}, 'th': {'class': 'd-none d-lg-table-cell'}}) + image = ImageUrlColumn() + + class Meta: + model = Recipe + template_name = 'recipes_table.html' + fields = ('id', 'name', 'all_tags', 'image', 'instructions', 'working_time', 'waiting_time', 'internal') + + class KeywordTable(tables.Table): id = tables.LinkColumn('edit_keyword', args=[A('id')]) diff --git a/cookbook/templates/base.html b/cookbook/templates/base.html index 0ba41c4df..dbd849281 100644 --- a/cookbook/templates/base.html +++ b/cookbook/templates/base.html @@ -54,28 +54,19 @@ {% block extra_head %} {% endblock %} - +