Compare commits

...

490 Commits

Author SHA1 Message Date
vabene1111
0862c0f0bc add migration to automatically assign group to existing superusers 2021-04-06 18:10:22 +02:00
vabene1111
a811d4a55c Merge branch 'beta' into develop 2021-04-05 18:21:55 +02:00
vabene1111
01a53ad8ec paprika improvements and importer fixes 2021-04-05 17:55:06 +02:00
vabene1111
c7e20716f5 more testing deployments 2021-04-05 17:16:26 +02:00
vabene1111
7adc4ad50a testing with newer base image 2021-04-05 16:01:49 +02:00
vabene1111
61ded5094f testing with manual wheel install 2021-04-05 15:45:21 +02:00
vabene1111
7aff5dc44a set cryptography manually 2021-04-05 15:24:00 +02:00
vabene1111
b5e5ff9bf8 testing docker build 2021-04-05 15:03:29 +02:00
vabene1111
a8434ce745 Merge pull request #520 from vabene1111/dependabot/pip/python-dotenv-0.17.0
Bump python-dotenv from 0.16.0 to 0.17.0
2021-04-05 12:16:30 +02:00
vabene1111
cf926295cf Merge pull request #521 from vabene1111/dependabot/pip/pytest-6.2.3
Bump pytest from 6.2.2 to 6.2.3
2021-04-05 12:16:23 +02:00
dependabot[bot]
dbd7ae4adc Bump pytest from 6.2.2 to 6.2.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.2 to 6.2.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.2...6.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 06:57:49 +00:00
dependabot[bot]
ba20fa1ff5 Bump python-dotenv from 0.16.0 to 0.17.0
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.16.0 to 0.17.0.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v0.16.0...v0.17.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 06:57:35 +00:00
vabene1111
a5b92f5672 fixed template render issues 2021-04-04 11:16:48 +02:00
vabene1111
ed412d11b7 Merge pull request #516 from vabene1111/dependabot/pip/pillow-8.2.0
Bump pillow from 8.1.2 to 8.2.0
2021-04-02 13:07:48 +02:00
dependabot[bot]
b36d0620e0 Bump pillow from 8.1.2 to 8.2.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.1.2 to 8.2.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/8.1.2...8.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-02 06:02:28 +00:00
vabene1111
d16d77f640 Merge pull request #511 from vabene1111/dependabot/pip/recipe-scrapers-12.2.2
Bump recipe-scrapers from 12.2.1 to 12.2.2
2021-03-29 20:49:55 +02:00
vabene1111
d72f90b90e Merge pull request #512 from vabene1111/dependabot/pip/djangorestframework-3.12.4
Bump djangorestframework from 3.12.3 to 3.12.4
2021-03-29 20:49:49 +02:00
vabene1111
9c78bcd662 Merge pull request #513 from vabene1111/dependabot/pip/python-dotenv-0.16.0
Bump python-dotenv from 0.15.0 to 0.16.0
2021-03-29 20:49:45 +02:00
vabene1111
b8c8fc3b58 Merge pull request #514 from vabene1111/dependabot/pip/gunicorn-20.1.0
Bump gunicorn from 20.0.4 to 20.1.0
2021-03-29 20:49:39 +02:00
dependabot[bot]
e1110000ab Bump gunicorn from 20.0.4 to 20.1.0
Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 20.0.4 to 20.1.0.
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](https://github.com/benoitc/gunicorn/compare/20.0.4...20.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-29 06:54:27 +00:00
dependabot[bot]
00194f68bf Bump python-dotenv from 0.15.0 to 0.16.0
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v0.15.0...v0.16.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-29 06:54:20 +00:00
dependabot[bot]
4f4c324c30 Bump djangorestframework from 3.12.3 to 3.12.4
Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.12.3 to 3.12.4.
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-29 06:54:13 +00:00
dependabot[bot]
106129e779 Bump recipe-scrapers from 12.2.1 to 12.2.2
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 12.2.1 to 12.2.2.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/12.2.1...12.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-29 06:54:06 +00:00
vabene1111
c5d509bf9e added default space/group for o auth signup 2021-03-28 22:16:12 +02:00
vabene1111
8b0f9bc2e7 fixed broken asset link in base html 2021-03-28 22:00:51 +02:00
vabene1111
4db17874c4 added meal master import 2021-03-28 21:50:52 +02:00
vabene1111
f6c491a8e6 added rezkonv importer 2021-03-28 21:13:26 +02:00
vabene1111
e4a9f56352 update integration file split 2021-03-28 19:49:03 +02:00
vabene1111
c1287407a3 added export support for recipe sage 2021-03-28 19:29:09 +02:00
vabene1111
ba1e18410a Merge pull request #510 from ModdedGamers/develop
Cheftap import tweak
2021-03-28 19:08:47 +02:00
vabene1111
7f8e29f1bc added recipe sage and domestica imports 2021-03-28 18:58:37 +02:00
Modded Gamers
6334bee608 Update import_export.md 2021-03-28 12:38:56 -04:00
Modded Gamers
681c57201a Cheftap import tweak
When I submitted my testing .zip, I didn't submit my normal export, because that has some personal data in the recipes. Instead, I made one by had, which was a mistake. The folder contained inside is also called cheftap_export.
2021-03-28 12:37:59 -04:00
vabene1111
738f0781b2 added import for pepperplate 2021-03-28 18:22:30 +02:00
vabene1111
6143e31e1a tweaked chef tap import 2021-03-28 18:04:12 +02:00
vabene1111
d6e6ab24c2 Cheftap import 2021-03-28 17:56:53 +02:00
vabene1111
0492182803 Merge pull request #503 from vabene1111/dependabot/pip/lxml-4.6.3
Bump lxml from 4.6.2 to 4.6.3
2021-03-28 17:10:36 +02:00
vabene1111
22713d884e Merge pull request #504 from vabene1111/dependabot/pip/django-crispy-forms-1.11.2
Bump django-crispy-forms from 1.11.1 to 1.11.2
2021-03-28 17:10:30 +02:00
vabene1111
bd14b77f62 Merge pull request #507 from vabene1111/dependabot/pip/djangorestframework-3.12.3
Bump djangorestframework from 3.12.2 to 3.12.3
2021-03-28 17:10:27 +02:00
dependabot[bot]
6a89ed1bb6 Bump djangorestframework from 3.12.2 to 3.12.3
Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.12.2 to 3.12.3.
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/compare/3.12.2...3.12.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-26 05:58:54 +00:00
vabene1111
25717f6a79 space max 2021-03-24 00:06:34 +01:00
dependabot[bot]
d3ecd52fd2 Bump django-crispy-forms from 1.11.1 to 1.11.2
Bumps [django-crispy-forms](https://github.com/django-crispy-forms/django-crispy-forms) from 1.11.1 to 1.11.2.
- [Release notes](https://github.com/django-crispy-forms/django-crispy-forms/releases)
- [Changelog](https://github.com/django-crispy-forms/django-crispy-forms/blob/master/CHANGELOG.md)
- [Commits](https://github.com/django-crispy-forms/django-crispy-forms/compare/1.11.1...1.11.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 07:16:11 +00:00
dependabot[bot]
1830eb4edc Bump lxml from 4.6.2 to 4.6.3
Bumps [lxml](https://github.com/lxml/lxml) from 4.6.2 to 4.6.3.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.6.2...lxml-4.6.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 07:16:06 +00:00
vabene1111
81a8734fac Merge pull request #499 from sebimarkgraf/fix/432-ios-webclip-support
Add iOS webclip icon support
2021-03-20 22:41:13 +01:00
vabene1111
abcef54e72 Merge pull request #498 from vabene1111/dependabot/pip/recipe-scrapers-12.2.1
Bump recipe-scrapers from 12.2.0 to 12.2.1
2021-03-20 22:30:43 +01:00
vabene1111
e15c92cda5 Merge pull request #501 from smilerz/main_fork
fix json direct import when wrapped in @graph
2021-03-20 22:26:06 +01:00
smilerz
45dba6fad2 fix json direct import when wrapped in @graph 2021-03-19 13:23:55 -05:00
Sebastian Markgraf
f67bb3cb98 Add generated icons from icongenerator. 2021-03-19 15:49:12 +01:00
Sebastian Markgraf
53b584da56 Fix errors in favicon SVG. 2021-03-19 12:48:58 +01:00
vabene1111
58fc26904b fixed url importer stuff 2021-03-19 10:05:17 +01:00
dependabot[bot]
7527646319 Bump recipe-scrapers from 12.2.0 to 12.2.1
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 12.2.0 to 12.2.1.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/12.2.0...12.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-19 05:51:22 +00:00
vabene1111
d3b1139a22 fixed migration 2021-03-19 00:33:06 +01:00
vabene1111
f04a51c1ad fixed empty units/foods (again) 2021-03-19 00:19:29 +01:00
vabene1111
7fbff9f3b5 sligthly improved login redirects 2021-03-18 23:42:24 +01:00
vabene1111
bb0f3e1778 build recipe view 2021-03-18 23:25:25 +01:00
vabene1111
6ab8d6bd0d Merge pull request #497 from smilerz/recipe_description
Recipe description
2021-03-18 23:23:35 +01:00
vabene1111
69a51e0640 fixed broken messages queing up 2021-03-18 23:20:31 +01:00
vabene1111
1425e795ff telegram create list if not exists and remove debug code 2021-03-18 22:51:06 +01:00
vabene1111
afadc61d5d telegram bot 2021-03-18 22:34:53 +01:00
smilerz
ce8524b247 added data validation on name and description length 2021-03-18 16:03:13 -05:00
smilerz
fd09ae1510 add description to recipe on submit 2021-03-18 15:55:11 -05:00
smilerz
9137fbfb97 Merge remote-tracking branch 'upstream/develop' into recipe_description 2021-03-18 15:33:50 -05:00
vabene1111
f0ac55c20e Merge pull request #388 from cesarblancg/develop
Optimized dockerfile
2021-03-18 20:53:14 +01:00
vabene1111
249663bd91 Merge branch 'develop' into develop 2021-03-18 20:53:08 +01:00
vabene1111
55920501b8 small tweaks 2021-03-18 20:48:28 +01:00
vabene1111
cc3e00e75f Merge branch 'develop' into url_import_recipes
# Conflicts:
#	cookbook/helper/recipe_url_import.py
#	cookbook/tests/api/test_api_keyword.py
#	cookbook/tests/other/test_edits_recipe.py
#	cookbook/views/api.py
#	requirements.txt
2021-03-18 20:38:51 +01:00
vabene1111
661f7ae789 fixed slice fix again 2021-03-18 20:34:37 +01:00
vabene1111
8bfbd96398 fixed slicing issue 2021-03-18 20:24:42 +01:00
vabene1111
8a051b531d import response view 2021-03-18 20:00:13 +01:00
vabene1111
de9456e3d7 import log api 2021-03-18 18:48:29 +01:00
vabene1111
950315936e import log working 2021-03-18 18:30:12 +01:00
vabene1111
af1bc19fd8 fixed importer threading stuff 2021-03-18 18:08:22 +01:00
vabene1111
906da25301 prevent duplicate imports 2021-03-18 17:02:02 +01:00
vabene1111
fe1ddf1237 improved paprika importer 2021-03-18 14:43:19 +01:00
vabene1111
48c90c483a merged PR 474 adding manual json import 2021-03-18 12:36:00 +01:00
vabene1111
3c1b6a5f3a updated unraid docs image 2021-03-18 12:08:12 +01:00
vabene1111
780c929162 fixed url import space 2021-03-18 12:07:02 +01:00
vabene1111
e00b6b9293 fixed meal plan creation from recipe context menu 2021-03-18 11:56:47 +01:00
vabene1111
8c9ee37c46 updated external image viewer 2021-03-18 11:53:32 +01:00
vabene1111
45a0bda758 updated transaltion 2021-03-18 11:53:14 +01:00
vabene1111
e71417e77f Merge pull request #480 from auanasgheps/patch-1
Update Italian Translation
2021-03-18 11:19:58 +01:00
vabene1111
29e6eda9cd Merge pull request #463 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_nl
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'nl'
2021-03-18 11:19:18 +01:00
vabene1111
32c984c7f0 Merge pull request #482 from vabene1111/dependabot/pip/pillow-8.1.2
Bump pillow from 8.1.0 to 8.1.2
2021-03-18 11:19:02 +01:00
vabene1111
b68d2ba384 Merge pull request #485 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_es
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'es'
2021-03-18 11:18:28 +01:00
vabene1111
daf0be37fd upgraded javascritp dependencies 2021-03-18 11:18:09 +01:00
vabene1111
2e625715cc updated help request template 2021-03-18 11:14:34 +01:00
vabene1111
6a25428b3c fixed shopping list share permission 2021-03-18 11:04:55 +01:00
vabene1111
cb78f75f19 fixed shopping list auto sync flickering 2021-03-18 10:56:12 +01:00
vabene1111
77cfcb4602 and pytest django 2021-03-17 23:52:41 +01:00
vabene1111
d2a4a9d953 addded pytest as dependency 2021-03-17 23:50:37 +01:00
vabene1111
9280540927 updated ci command 2021-03-17 23:48:49 +01:00
vabene1111
2f77532111 Merge branch 'feature/spaces' into develop 2021-03-17 23:47:35 +01:00
vabene1111
2149f4034b several shopping list improvements 2021-03-17 23:28:28 +01:00
vabene1111
76eeed1a77 all tests migrated and improved 2021-03-17 22:10:58 +01:00
vabene1111
ad0d802e41 more tests 2021-03-17 21:18:54 +01:00
vabene1111
e41464cb31 recipe api test 2021-03-17 20:53:12 +01:00
vabene1111
ab3f7bf671 step api test 2021-03-17 20:46:49 +01:00
vabene1111
e968a57c06 shopping list api tests and refactors 2021-03-17 20:42:17 +01:00
vabene1111
641feede74 space nested serializers 2021-03-17 19:55:34 +01:00
vabene1111
b48708652f delete model check fixes + sync log 2021-03-17 00:27:53 +01:00
vabene1111
c2addc1121 storage api test 2021-03-17 00:14:54 +01:00
vabene1111
a25109e16c added sync api test 2021-03-17 00:03:36 +01:00
vabene1111
ae81b10dbd added supermarket api test 2021-03-16 23:50:51 +01:00
vabene1111
4d6d84bf5b fixed some tests + added user pref tests 2021-03-16 23:46:54 +01:00
vabene1111
a8a132e2a1 fixed recipe book entry api and added test 2021-03-12 12:33:20 +01:00
vabene1111
3b0413c30e add test for recipe book 2021-03-12 10:50:08 +01:00
vabene1111
d767743b64 user name api test 2021-03-12 10:16:51 +01:00
vabene1111
e8f7caebd1 test unit api 2021-03-12 09:52:46 +01:00
vabene1111
a7b7272bec view log api test 2021-03-12 09:50:20 +01:00
vabene1111
d7402f60c5 meal plan api tests 2021-03-12 09:44:16 +01:00
vabene1111
d049cf6d3d ingredient test finished 2021-03-11 21:50:54 +01:00
vabene1111
fdcdf6a026 added food test 2021-03-11 18:23:00 +01:00
vabene1111
7e38e946a5 fixed various space related bugs 2021-03-11 16:41:05 +01:00
vabene1111
b552badff7 fixed limit random recipe 2021-03-11 15:37:37 +01:00
transifex-integration[bot]
d10c84b66e Apply translations in es
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'es' language.
2021-03-10 23:51:17 +00:00
vabene1111
e7f8d58a7d added test for cook log 2021-03-10 21:25:18 +01:00
vabene1111
528f329ebb new keyword api test working 2021-03-10 20:31:00 +01:00
vabene1111
b9b7a125f0 more tests working but scopes broken 2021-03-10 19:46:08 +01:00
vabene1111
4742056223 tests basically working 2021-03-10 18:18:19 +01:00
vabene1111
ba4c3b95e5 removed deprecated functions/packages 2021-03-10 18:17:31 +01:00
dependabot[bot]
4c03520371 Bump pillow from 8.1.0 to 8.1.2
Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.1.0 to 8.1.2.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/8.1.0...8.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-08 06:58:23 +00:00
smilerz
1f5ddd9af7 limit description in search to 2 lines 2021-03-06 17:36:51 -06:00
smilerz
d3e6b34a63 added description to search and recipe view 2021-03-06 16:32:59 -06:00
Oliver Cervera
323f424630 Update Italian Translation
Italian translation updates.
Manual PR since Transifex doesn't do it.
2021-03-06 13:03:03 +01:00
smilerz
eed6e9d3a5 catch index error 2021-03-04 15:30:20 -06:00
smilerz
f4af7ffb0b except do it right this time 2021-03-04 15:03:33 -06:00
smilerz
3b072d5dd9 check for scrape.schema before using it 2021-03-04 14:59:56 -06:00
smilerz
11240dcf48 Merge branch 'recipe_description' of github.com:smilerz/recipes into recipe_description 2021-03-04 09:30:27 -06:00
smilerz
0b5fc1a9f4 added description to url_import 2021-03-04 09:20:26 -06:00
smilerz
049449bda3 removed recipe description from url_import template 2021-03-04 09:19:25 -06:00
smilerz
e5a19302f0 added description to url_import 2021-03-04 08:38:01 -06:00
smilerz
e1fa939757 pulled description field from url_import template 2021-03-04 08:33:02 -06:00
smilerz
3da33e364e fixed test 2021-03-03 21:15:12 -06:00
smilerz
bfaed434cc refactored url_import to use recipe-scraper 2021-03-03 21:08:34 -06:00
vabene1111
b6acc17e5a Create url_import.md 2021-02-26 10:02:14 +01:00
vabene1111
5fd03e7cdc basics of pytest 2021-02-25 22:36:20 +01:00
vabene1111
54e71f2910 last commit 2021-02-25 22:20:06 +01:00
vabene1111
c99c944130 fixed normal django test running with scopes 2021-02-25 22:19:51 +01:00
transifex-integration[bot]
94c9185bcf Apply translations in nl
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'nl' language.
2021-02-25 18:59:22 +00:00
vabene1111
74e731e334 inject space context into serializer writes 2021-02-25 16:45:47 +01:00
vabene1111
d01f7409bf removed django-random-queryset
the looping strategy is not efficient when using spacing as ids for a single user might be spaced far apart. A single user also only has a few hundred recipes so using the order by ? method should be more efficient and also automatically be compatible with spaces
2021-02-25 16:44:42 +01:00
vabene1111
29ab6cfb2d Merge branch 'develop' into feature/spaces
# Conflicts:
#	cookbook/views/data.py
#	cookbook/views/views.py
2021-02-25 15:59:32 +01:00
vabene1111
59b2da933d fixed tests 2021-02-25 08:27:22 +01:00
vabene1111
99b5f9a3ec Merge pull request #457 from ProfessorSalty/height-bugfix
Fix height issues in Safari by removing height rules
2021-02-25 08:18:37 +01:00
vabene1111
9057eac42c Merge pull request #460 from vabene1111/dependabot/pip/markdown-3.3.4
Bump markdown from 3.3.3 to 3.3.4
2021-02-25 08:18:27 +01:00
vabene1111
24b5cdff85 Merge pull request #459 from smilerz/fix-456
Fix 456
2021-02-25 08:14:55 +01:00
dependabot[bot]
f2630c3ba0 Bump markdown from 3.3.3 to 3.3.4
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.3.3 to 3.3.4.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.3.3...3.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-25 06:02:05 +00:00
smilerz
21740522bc fixed URL import when ingredient is a string 2021-02-24 20:13:06 -06:00
smilerz
47090ce863 added tests for add keyword and add duplicate keyword 2021-02-24 18:52:43 -06:00
smilerz
3ac22c08ff fixed duplicate keyword handling 2021-02-24 18:52:04 -06:00
smilerz
cc62b088fd fix URL import when recipeYield is a list 2021-02-24 17:52:40 -06:00
Greg Smith
2c34425135 Remove fixed height rule 2021-02-24 17:36:06 -05:00
vabene1111
205f76d128 fix that does not really fix anything
see #453/454
2021-02-23 21:43:14 +01:00
vabene1111
4147bc61c7 Merge pull request #390 from smilerz/develop
Add keywords during edit & url import
2021-02-23 21:12:45 +01:00
vabene1111
dfae453925 Merge pull request #450 from albcp/develop
Add environment variable to include Sub-Path in resolve.js.
2021-02-23 21:09:56 +01:00
vabene1111
7507cae44c added some info to .env for DB_OPTIONS 2021-02-23 20:56:53 +01:00
vabene1111
28312774bd Merge pull request #434 from angelnu/develop
Add DB_OPTIONS to enable DBs with SSL
2021-02-23 20:55:23 +01:00
vabene1111
058723d583 compiled translations and added hy 2021-02-23 20:54:42 +01:00
vabene1111
db4abdd31d Merge pull request #426 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_it
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'it'
2021-02-23 20:50:35 +01:00
vabene1111
727b0e9e61 Merge pull request #403 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_hy
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'hy'
2021-02-23 20:49:40 +01:00
vabene1111
aa41146735 Merge pull request #448 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_de
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'de'
2021-02-23 20:48:51 +01:00
vabene1111
670fc9bf35 Merge pull request #451 from vabene1111/dependabot/pip/django-autocomplete-light-3.8.2
Bump django-autocomplete-light from 3.8.1 to 3.8.2
2021-02-23 20:48:27 +01:00
vabene1111
d9a5649adc Merge pull request #446 from vabene1111/dependabot/pip/django-3.1.7
Bump django from 3.1.6 to 3.1.7
2021-02-23 20:48:15 +01:00
vabene1111
5ed300a3ea Merge pull request #445 from vabene1111/dependabot/pip/django-crispy-forms-1.11.1
Bump django-crispy-forms from 1.11.0 to 1.11.1
2021-02-23 20:48:04 +01:00
vabene1111
59cc22a877 wip serializer permission 2021-02-23 20:44:13 +01:00
Alberto
8dffc07072 get JS_REVERSE_SCRIPT_PREFIX from SCRIPT_NAME 2021-02-23 18:57:59 +01:00
dependabot[bot]
76c7ad1ff5 Bump django-autocomplete-light from 3.8.1 to 3.8.2
Bumps [django-autocomplete-light](https://github.com/yourlabs/django-autocomplete-light) from 3.8.1 to 3.8.2.
- [Release notes](https://github.com/yourlabs/django-autocomplete-light/releases)
- [Changelog](https://github.com/yourlabs/django-autocomplete-light/blob/master/CHANGELOG)
- [Commits](https://github.com/yourlabs/django-autocomplete-light/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-23 06:02:25 +00:00
vabene1111
7f391c25a4 fixed test 2021-02-22 19:59:43 +01:00
Alberto
bccc41d177 Add environment variable to include Sub-Path in resolve.js. 2021-02-22 19:56:33 +01:00
transifex-integration[bot]
cc882082d2 Apply translations in de
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'de' language.
2021-02-22 15:41:54 +00:00
dependabot[bot]
689918c1ac Bump django from 3.1.6 to 3.1.7
Bumps [django](https://github.com/django/django) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.6...3.1.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 07:00:34 +00:00
dependabot[bot]
1c43be3899 Bump django-crispy-forms from 1.11.0 to 1.11.1
Bumps [django-crispy-forms](https://github.com/django-crispy-forms/django-crispy-forms) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/django-crispy-forms/django-crispy-forms/releases)
- [Changelog](https://github.com/django-crispy-forms/django-crispy-forms/blob/master/CHANGELOG.md)
- [Commits](https://github.com/django-crispy-forms/django-crispy-forms/compare/1.11.0...1.11.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 07:00:29 +00:00
vabene1111
40387428e7 recipe filter 2021-02-21 16:49:34 +01:00
vabene1111
46fb02376e recipe share basics 2021-02-21 16:37:04 +01:00
vabene1111
24e43e3e2e updated migrations + setup process update 2021-02-21 15:34:35 +01:00
vabene1111
846c660811 usr signup and more 2021-02-21 15:08:43 +01:00
vabene1111
beb4aa634f forms 2021-02-20 23:42:44 +01:00
vabene1111
58c6077925 basic test stuff 2021-02-20 22:56:29 +01:00
vabene1111
d7675d4b80 ingredients and helpers 2021-02-20 21:26:16 +01:00
vabene1111
e2b1115b3b fixed servings blank 2021-02-20 18:50:28 +01:00
vabene1111
96c963795e more spaces work 2021-02-20 18:47:14 +01:00
Vegetto
804adde964 Parse DB_OPTIONS dict 2021-02-20 16:54:42 +01:00
Vegetto
5aa918f478 Document DB_OPTIONS env 2021-02-20 16:53:02 +01:00
Vegetto
a44f72a030 Add DB_OPTIONS to enable DBs with SSL 2021-02-20 16:42:48 +01:00
vabene1111
ad163509b4 updated edit and added space find methods 2021-02-19 18:09:42 +01:00
vabene1111
fb58d35029 space stuff partially working 2021-02-19 15:50:55 +01:00
transifex-integration[bot]
79f823cd62 Apply translations in it
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'it' language.
2021-02-19 13:42:50 +00:00
vabene1111
c42266b82c basic space stuff 2021-02-19 11:53:30 +01:00
smilerz
c60c3f1876 Revert "minor edit"
This reverts commit 35f3ecc7eb.
2021-02-18 07:28:26 -06:00
smilerz
fc5455a0f2 Revert "added new keyword management page"
This reverts commit 6962b0e218.
2021-02-18 07:27:43 -06:00
smilerz
28d8f62af7 Revert "add filter to new keywords page"
This reverts commit c6dd55df4e.
2021-02-18 07:25:31 -06:00
vabene1111
b6b505c361 fixed nutrition scaling fractiosn 2021-02-18 11:11:07 +01:00
vabene1111
97cef449c9 fixed recipe template rendering scaling issue 2021-02-18 09:37:17 +01:00
vabene1111
fef6f695ce chowdown import folder syntax support 2021-02-18 09:36:13 +01:00
vabene1111
73a24a8ef0 chowdown doc warning 2021-02-18 09:29:11 +01:00
vabene1111
e727cae020 fixed typo that broke fractions 2021-02-18 09:17:42 +01:00
smilerz
c6dd55df4e add filter to new keywords page 2021-02-17 10:45:31 -06:00
smilerz
6962b0e218 added new keyword management page 2021-02-17 07:09:19 -06:00
vabene1111
894d2d2e6b gracful recipes api error on wrong format of update_at 2021-02-16 22:14:40 +01:00
transifex-integration[bot]
8bf4a32dfd Apply translations in hy
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'hy' language.
2021-02-16 19:31:09 +00:00
smilerz
505650518e Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-02-16 07:45:22 -06:00
smilerz
35f3ecc7eb minor edit 2021-02-16 07:44:41 -06:00
vabene1111
543e52d596 updated preview image 2021-02-15 22:05:04 +01:00
vabene1111
f39433142d typo 2021-02-15 21:48:21 +01:00
vabene1111
f2765c75c6 favicon path in markdown docs 2021-02-15 21:16:31 +01:00
vabene1111
47049808b7 Merge branch 'beta' into develop 2021-02-15 21:02:12 +01:00
vabene1111
150d4c7309 refactor assset and paths 2021-02-15 21:01:54 +01:00
vabene1111
d116d08adf Merge pull request #401 from tdvantine/patch-3
Update .env.template
2021-02-15 20:51:42 +01:00
vabene1111
82d2e479b2 Merge pull request #400 from tdvantine/patch-2
Update Recipes.conf
2021-02-15 20:48:35 +01:00
tdvantine
df81aec02e Update .env.template
Matched the db user to that of the install instructions, fixed some grammar/spelling error and simplified wording...hopefully.
2021-02-15 11:25:58 -07:00
tdvantine
74779fc488 Update Recipes.conf 2021-02-15 10:25:10 -07:00
vabene1111
ac9922ff61 manifest fixes 2021-02-15 16:08:18 +01:00
vabene1111
ff0cd6fa93 fixed import log 2021-02-15 15:30:19 +01:00
vabene1111
777f4518be servings default to 1 on import 2021-02-15 15:16:15 +01:00
vabene1111
84591fd17a fixed issue with shopping list saving 2021-02-15 15:13:59 +01:00
vabene1111
7536425e39 added czech locale 2021-02-15 15:13:40 +01:00
vabene1111
9d28ce48fe Merge pull request #397 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_cs
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'cs'
2021-02-15 15:11:50 +01:00
vabene1111
d6b438b5f4 Merge pull request #398 from l0c4lh057/master
Fix ingredient parsing for fractions using '/'
2021-02-14 18:51:18 +01:00
Aaron
87d6ca0200 Fix ingredient parsing for fractions using '/'
Even though ingredients like '1 1/2 something' already worked fine and got converted to 1.5 something
I just came across a recipe using '1/2' as the whole amount without any whole number before that.
Apparently I overlooked that case before so I now also fixed that.
2021-02-14 18:43:38 +01:00
transifex-integration[bot]
bcda57a4fa Apply translations in cs
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'cs' language.
2021-02-14 13:12:22 +00:00
vabene1111
3e55207a8d fixed path 2021-02-13 18:19:38 +01:00
vabene1111
80eee945a0 updated logo path 2021-02-13 18:08:05 +01:00
vabene1111
3436ef4877 added cname file 2021-02-13 18:06:40 +01:00
vabene1111
20e9d4a990 new style and icon 2021-02-13 18:04:33 +01:00
vabene1111
3a1c9aa462 Update README.md 2021-02-13 15:54:08 +01:00
vabene1111
de9f0ad8f8 Update README.md 2021-02-13 15:51:21 +01:00
vabene1111
61f43f78ec Update README.md 2021-02-13 15:42:20 +01:00
vabene1111
a3f2c1bed2 Testing new logo 2021-02-13 15:28:10 +01:00
smilerz
e0a0eeeecc add keyword during url import 2021-02-09 12:55:40 -06:00
smilerz
4a4dafd69c add keyword during recipe edit 2021-02-09 11:17:31 -06:00
vabene1111
6781128c1b added help to import/export 2021-02-09 18:02:45 +01:00
vabene1111
73b7f60222 translations 2021-02-09 18:01:32 +01:00
vabene1111
46a9d19374 safron import 2021-02-09 17:34:38 +01:00
vabene1111
6ba1ff4505 added chowdown importer 2021-02-09 17:15:47 +01:00
vabene1111
58c5b2c301 add paprika import image support 2021-02-09 16:10:28 +01:00
vabene1111
5d1d6d4248 added mealie importer 2021-02-08 21:19:46 +01:00
vabene1111
0f251bee9b nextcloud import instructions 2021-02-08 19:27:18 +01:00
vabene1111
149c5b5f5e nextcloud import working with images 2021-02-08 19:19:56 +01:00
vabene1111
7d051336d3 basic nextcloud import 2021-02-08 19:09:45 +01:00
vabene1111
79da8db889 added paprika import 2021-02-08 13:47:06 +01:00
vabene1111
ec842aa657 testing docker with new dependecy 2021-02-08 11:55:40 +01:00
vabene1111
61c2d5eb61 import export docs and docker tests 2021-02-08 11:52:22 +01:00
vabene1111
41e3ec41e9 update pip during build 2021-02-08 11:41:28 +01:00
vabene1111
086570ce90 using new import/export module 2021-02-08 11:38:38 +01:00
vabene1111
d2783429a1 added documentation to importer 2021-02-08 11:26:41 +01:00
vabene1111
de19a10cba more abstract import/export interface 2021-02-08 11:18:13 +01:00
vabene1111
f312631676 import tag recipes and show results after import 2021-02-08 10:54:04 +01:00
vabene1111
6c52b7bbd9 remove pre commit from build dependencies 2021-02-08 10:02:30 +01:00
vabene1111
900f1a6f7a Merge pull request #389 from vabene1111/dependabot/pip/pre-commit-2.10.1
Bump pre-commit from 2.10.0 to 2.10.1
2021-02-08 08:46:06 +01:00
dependabot[bot]
ff0a7c5262 Bump pre-commit from 2.10.0 to 2.10.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.10.0 to 2.10.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v2.10.0...v2.10.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 06:47:48 +00:00
vabene1111
e0acd1de83 importing with image files working 2021-02-07 14:50:43 +01:00
vabene1111
585c31490a export recipe without wrting files 2021-02-07 14:01:37 +01:00
vabene1111
3e7f96c0b8 basic export to zip working 2021-02-07 13:43:24 +01:00
vabene1111
d45adc1688 wip on new import/export system 2021-02-07 13:27:15 +01:00
cesarblancg
c857d092b1 Optimized dockerfile 2021-02-04 10:07:43 +01:00
vabene1111
b0fe98c091 fixed shopping url 2021-02-03 23:50:33 +01:00
vabene1111
103878e107 shopping list shortcut link 2021-02-03 23:05:42 +01:00
vabene1111
175fca2b39 Merge pull request #386 from cesarblancg/develop
Solving Apple web app issue
2021-02-03 20:35:32 +01:00
vabene1111
4600aab13a fixed unit merge bug 2021-02-03 20:28:36 +01:00
vabene1111
966a107414 fixed merge of uncreated food entries 2021-02-03 20:15:42 +01:00
vabene1111
69674e2648 allow entry mode toggle 2021-02-03 19:51:43 +01:00
vabene1111
bcd2e44493 added simple entry mode to shopping 2021-02-03 19:38:09 +01:00
cesarblancg
e745e4be0c Solving ipad pro issue 2021-02-03 18:49:48 +01:00
vabene1111
3afd18ccdc Merge pull request #378 from vabene1111/dependabot/pip/django-3.1.6
Bump django from 3.1.5 to 3.1.6
2021-02-02 08:47:50 +01:00
dependabot[bot]
24f1fb228e Bump django from 3.1.5 to 3.1.6
Bumps [django](https://github.com/django/django) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.5...3.1.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-02 07:47:36 +00:00
vabene1111
431e213514 Merge pull request #379 from vabene1111/dependabot/pip/bleach-3.3.0
Bump bleach from 3.2.3 to 3.3.0
2021-02-02 08:46:57 +01:00
dependabot[bot]
1e00fa16db Bump bleach from 3.2.3 to 3.3.0
Bumps [bleach](https://github.com/mozilla/bleach) from 3.2.3 to 3.3.0.
- [Release notes](https://github.com/mozilla/bleach/releases)
- [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v3.2.3...v3.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-02 06:06:55 +00:00
vabene1111
ac1c283efb Merge pull request #373 from vabene1111/dependabot/pip/jinja2-2.11.3
Bump jinja2 from 2.11.2 to 2.11.3
2021-02-01 09:42:10 +01:00
vabene1111
0a6a8b760f Merge pull request #374 from vabene1111/dependabot/pip/django-crispy-forms-1.11.0
Bump django-crispy-forms from 1.10.0 to 1.11.0
2021-02-01 09:42:01 +01:00
dependabot[bot]
4cdd784259 Bump django-crispy-forms from 1.10.0 to 1.11.0
Bumps [django-crispy-forms](https://github.com/django-crispy-forms/django-crispy-forms) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/django-crispy-forms/django-crispy-forms/releases)
- [Changelog](https://github.com/django-crispy-forms/django-crispy-forms/blob/master/CHANGELOG.md)
- [Commits](https://github.com/django-crispy-forms/django-crispy-forms/compare/1.10.0...1.11.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 07:31:37 +00:00
dependabot[bot]
461cb20a4f Bump jinja2 from 2.11.2 to 2.11.3
Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.2 to 2.11.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/master/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/2.11.2...2.11.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 07:31:33 +00:00
vabene1111
7ebf4d5e2a Merge pull request #366 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_nl
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'nl'
2021-01-31 13:01:21 +01:00
transifex-integration[bot]
e50d3233fd Apply translations in nl
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'nl' language.
2021-01-29 18:41:14 +00:00
vabene1111
cc980b2e8a fixed broken test for good and improved file api 2021-01-28 16:21:19 +01:00
vabene1111
93e965697a updated test to reflect new permission 2021-01-28 16:17:29 +01:00
vabene1111
8d65d20d1f account linking and docs update 2021-01-28 16:10:26 +01:00
vabene1111
a112824578 maybe fixed container build 2021-01-28 15:19:12 +01:00
vabene1111
6192277778 working on authentication 2021-01-28 15:11:38 +01:00
vabene1111
148324b37f fixed nutrition value database duplication 2021-01-28 14:44:23 +01:00
vabene1111
c30ce471c2 api permissions + shopping list on mobile 2021-01-28 14:41:00 +01:00
vabene1111
63cfa14a21 allauth working and integrated 2021-01-28 13:40:07 +01:00
vabene1111
53c715b6f6 Merge branch 'develop' into feature/allauth
# Conflicts:
#	requirements.txt
2021-01-28 12:07:30 +01:00
vabene1111
96146a388a added recipes to shopping list 2021-01-28 11:20:51 +01:00
vabene1111
d2a0bb1ec1 Merge pull request #362 from vabene1111/dependabot/pip/pre-commit-2.10.0
Bump pre-commit from 2.9.3 to 2.10.0
2021-01-28 10:48:54 +01:00
dependabot[bot]
d826b9f38a Bump pre-commit from 2.9.3 to 2.10.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.9.3 to 2.10.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v2.9.3...v2.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-28 06:15:44 +00:00
vabene1111
e8d9cc6ad9 shopping list null entry 2021-01-27 17:48:08 +01:00
vabene1111
e9689d347c Merge pull request #356 from vabene1111/dependabot/pip/bleach-3.2.3
Bump bleach from 3.2.2 to 3.2.3
2021-01-27 08:44:11 +01:00
dependabot[bot]
b275fdcf62 Bump bleach from 3.2.2 to 3.2.3
Bumps [bleach](https://github.com/mozilla/bleach) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/mozilla/bleach/releases)
- [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v3.2.2...v3.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-27 06:17:50 +00:00
vabene1111
a0ebc47ade api permission fixes + shopping list default view mode 2021-01-26 22:15:09 +01:00
vabene1111
b698fad83a fixed no_amount ingredients showing units in ingredient list 2021-01-26 22:00:46 +01:00
vabene1111
5e53c66eaa added ingredient headers again 2021-01-26 21:55:36 +01:00
vabene1111
37008ef290 added unaccent filter for recipe search 2021-01-26 21:14:33 +01:00
vabene1111
35ee5847ca shopping list and docs 2021-01-26 20:58:19 +01:00
vabene1111
935dee853e added ordering of supermarket categories 2021-01-26 19:19:42 +01:00
vabene1111
7b75e279b0 shopping list ui somewhat bearable 2021-01-26 18:21:45 +01:00
vabene1111
15c758b24a update food after changing category 2021-01-26 18:08:41 +01:00
vabene1111
26ec1724a5 updating food working 2021-01-26 17:56:43 +01:00
vabene1111
96c4823664 supermarket shopping list relation 2021-01-26 16:53:05 +01:00
vabene1111
5ab19b7958 supermarket category api working 2021-01-26 16:16:45 +01:00
vabene1111
09716f2b00 wip supermarket categories 2021-01-25 21:58:24 +01:00
vabene1111
138a29770a added supermarket categories to shopping 2021-01-25 21:20:40 +01:00
vabene1111
36584346cb fixed merging and added shopping ignored ingredients 2021-01-25 20:46:19 +01:00
vabene1111
c7dd5dd8bb wip shopping list stuff 2021-01-25 18:21:55 +01:00
vabene1111
a16ad2c887 added ingredient merging to shopping list 2021-01-25 17:24:03 +01:00
vabene1111
ca728b45ca made it possible to disable signup 2021-01-24 16:41:25 +01:00
vabene1111
9fd87dbf23 fixed misaligned check buttons 2021-01-24 16:14:56 +01:00
vabene1111
384a49b1c6 updated docs 2021-01-22 22:48:14 +01:00
vabene1111
477236009c disable settings in demo 2021-01-22 22:38:16 +01:00
vabene1111
93cff8873e docs type 2021-01-22 22:12:17 +01:00
vabene1111
d9feb61e85 updated documentation 2021-01-22 21:58:39 +01:00
vabene1111
00875c0d8e added help page linking 2021-01-22 21:32:29 +01:00
vabene1111
f1b7ed7d7a added option to choose webdav path 2021-01-22 20:29:29 +01:00
vabene1111
fce293e722 fixed tests 2021-01-21 20:58:20 +01:00
vabene1111
09062cb12c fixed recipe book api error 2021-01-21 20:57:26 +01:00
vabene1111
098f88e0b8 super basic working example 2021-01-21 20:54:55 +01:00
vabene1111
6992bf83aa added customizable Servings text 2021-01-21 20:05:46 +01:00
vabene1111
32044907bf template hint 2021-01-21 19:58:18 +01:00
vabene1111
3fcd613ca8 fixed side buttons in recipe edit moving under sticky nav 2021-01-21 19:45:12 +01:00
vabene1111
5b8a22762b fixed print for new recipe view 2021-01-21 19:40:47 +01:00
vabene1111
c41c319d25 added servings to url import 2021-01-21 19:39:06 +01:00
vabene1111
6690c3b206 fixed service worker caching problem 2021-01-21 19:08:36 +01:00
vabene1111
56bcd4f887 very basic offline page working 2021-01-21 18:39:24 +01:00
vabene1111
47c690526e service worker stuff work in progress 2021-01-21 16:16:49 +01:00
vabene1111
ec14338159 fixed share link with pre defined username 2021-01-21 14:42:49 +01:00
vabene1111
bf3fe1c716 Merge pull request #351 from vabene1111/dependabot/pip/pyyaml-5.4.1
Bump pyyaml from 5.4 to 5.4.1
2021-01-21 09:39:47 +01:00
vabene1111
fede79fc04 Merge pull request #352 from vabene1111/dependabot/pip/bleach-3.2.2
Bump bleach from 3.2.1 to 3.2.2
2021-01-21 09:39:34 +01:00
dependabot[bot]
9251613cd6 Bump bleach from 3.2.1 to 3.2.2
Bumps [bleach](https://github.com/mozilla/bleach) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/mozilla/bleach/releases)
- [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/bleach/compare/v3.2.1...v3.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-21 05:50:44 +00:00
dependabot[bot]
0bd6df9d57 Bump pyyaml from 5.4 to 5.4.1
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4 to 5.4.1.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.4...5.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-21 05:50:43 +00:00
vabene1111
24e660156c Merge pull request #348 from vabene1111/dependabot/pip/pyyaml-5.4
Bump pyyaml from 5.3.1 to 5.4
2021-01-20 10:33:51 +01:00
dependabot[bot]
345ffe4d6d Bump pyyaml from 5.3.1 to 5.4
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.3.1 to 5.4.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.3.1...5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-20 05:56:58 +00:00
vabene1111
e5b7cf5f30 further improvised import/export feature 2021-01-19 21:41:52 +01:00
vabene1111
b563447674 ingredient linking fixed + tests fixed 2021-01-19 21:26:20 +01:00
vabene1111
523a2b41d1 fixed keyboard import edit 2021-01-19 21:11:02 +01:00
vabene1111
a0741f6ad3 added warning message to meal plan if no type is given 2021-01-19 20:55:51 +01:00
vabene1111
b52c3d6bd4 testing with the offline page 2021-01-17 21:26:56 +01:00
vabene1111
803369a7a6 basic offline page working 2021-01-17 21:11:03 +01:00
vabene1111
2e3e629406 actually decent service worker wip 2021-01-17 18:33:12 +01:00
vabene1111
d7894e07e9 service worker stuff 2021-01-17 16:58:50 +01:00
vabene1111
63dbdfa4a6 removed service worker stuff 2021-01-17 14:47:13 +01:00
vabene1111
961b3f07b5 added demo setting 2021-01-17 14:37:34 +01:00
vabene1111
7a1ee9a9b2 fixed documentation mistake (again) 2021-01-17 14:06:56 +01:00
vabene1111
f18980a9e2 workbox expirments 2021-01-14 13:31:56 +01:00
vabene1111
08733751aa add to book reimplemented 2021-01-14 01:21:15 +01:00
vabene1111
b271f81af2 updated hook + compiled again 2021-01-14 00:05:42 +01:00
vabene1111
0900e4c57d cleanup 2021-01-14 00:02:11 +01:00
vabene1111
d99e523608 added pre commit hooks and some other workflow stuff 2021-01-14 00:00:26 +01:00
vabene1111
50829dce47 fixed access for shared recipes 2021-01-13 22:59:19 +01:00
vabene1111
910dc29f06 updated javascript files 2021-01-13 22:48:50 +01:00
vabene1111
486c871cb5 also revert boot script for now 2021-01-13 22:35:27 +01:00
vabene1111
2b94500ffe restored old dockerfile order until time for optimization 2021-01-13 22:32:54 +01:00
vabene1111
d25ea34512 moved back together 2021-01-13 22:30:47 +01:00
vabene1111
fbc3dcdfef test 2021-01-13 22:23:00 +01:00
vabene1111
642015b368 testing docker stuff 2021-01-13 22:17:40 +01:00
vabene1111
99f06955dc corrected servings factor 2021-01-13 21:35:39 +01:00
vabene1111
9e5a7b2cc0 added loading spinner and made new view the main recipe view 2021-01-13 20:59:31 +01:00
vabene1111
948eb9be3e keywords 2021-01-13 20:49:04 +01:00
vabene1111
ec778edb93 alignment 2021-01-13 20:41:42 +01:00
vabene1111
a431031c04 ingredient checking 2021-01-13 20:12:20 +01:00
vabene1111
082a656210 Revert "refactored ingredient components"
This reverts commit 9f51b9fd16.
2021-01-13 19:40:39 +01:00
vabene1111
9f51b9fd16 refactored ingredient components 2021-01-13 18:42:59 +01:00
vabene1111
98aadf2869 cooklog improvements 2021-01-13 18:11:42 +01:00
vabene1111
54107000af fixed some browser warnings 2021-01-13 18:05:02 +01:00
vabene1111
999fe2bc61 timers added in recipe view 2021-01-13 17:58:51 +01:00
vabene1111
23bd0a7d90 fixed recipe context menu 2021-01-13 15:51:13 +01:00
vabene1111
f9059f636c update docs + removed old import 2021-01-13 15:25:34 +01:00
vabene1111
95aff5c998 cook log 2021-01-13 15:21:51 +01:00
vabene1111
bf9b8a0230 updated ci + build static files 2021-01-13 13:28:31 +01:00
vabene1111
c79432567c valid_until migration fix 2021-01-13 13:26:57 +01:00
vabene1111
ddc484562b recipe description 2021-01-13 13:26:48 +01:00
vabene1111
97b5f64718 pretty decent looking view 2021-01-13 13:12:22 +01:00
vabene1111
b042ab72cd wip step design 2021-01-13 12:29:23 +01:00
vabene1111
a59ac44f3b recipe page styling 2021-01-13 10:57:38 +01:00
vabene1111
acafcbc077 WIP but slowly seeing progress 2021-01-13 03:28:10 +01:00
vabene1111
6ff0e3b7b3 slowly looking somewhat decently 2021-01-13 03:13:07 +01:00
vabene1111
bb43ed203a display of external recipes 2021-01-13 02:16:16 +01:00
vabene1111
1bb412e007 sanitize inputs of jinja so that output does not need to be 2021-01-13 01:34:21 +01:00
vabene1111
e69d1c3408 save 2021-01-13 01:23:20 +01:00
vabene1111
cd51d12618 still working 2021-01-13 01:16:52 +01:00
vabene1111
ee130f9077 its working 2021-01-13 01:14:03 +01:00
vabene1111
0eebd438ca commit 2021-01-13 01:08:51 +01:00
vabene1111
983d40f2c1 save WIP 2021-01-12 23:28:13 +01:00
vabene1111
bbd01fdb04 Merge branch 'develop' into feature/vue 2021-01-12 21:40:32 +01:00
vabene1111
ea2f493e01 fixed request context for recipe serializer on import 2021-01-12 21:40:14 +01:00
vabene1111
9b364d57c7 partially working recipe view 2021-01-12 21:32:24 +01:00
vabene1111
068a09e28e reverted config change 2021-01-12 21:13:06 +01:00
vabene1111
83c7e318ea secure proxy headers 2021-01-12 21:05:29 +01:00
vabene1111
24ed6a1cd2 recipe stuff 2021-01-12 20:51:28 +01:00
vabene1111
816ced83b5 currently everything working 2021-01-12 19:50:21 +01:00
vabene1111
b93fb99e1b Merge pull request #333 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_ca
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'ca'
2021-01-12 08:08:48 +01:00
transifex-integration[bot]
9203b8e985 Apply translations in ca
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'ca' language.
2021-01-11 22:37:24 +00:00
vabene1111
7b936ec4fd removed some debug code 2021-01-11 22:00:40 +01:00
vabene1111
99f0ab830b localization in vue apps working 2021-01-11 22:00:23 +01:00
vabene1111
34028587fc working on a recipe view 2021-01-11 18:31:06 +01:00
vabene1111
df0cfc3677 Merge branch 'develop' into feature/vue 2021-01-11 16:20:49 +01:00
vabene1111
16e2af8c5d remove debug code from service worker 2021-01-11 16:20:32 +01:00
vabene1111
02aec7d6d6 very WIP 2021-01-11 16:19:37 +01:00
vabene1111
cb913f6cea some test code and playing around 2021-01-11 15:04:35 +01:00
vabene1111
5bb20bd479 Merge branch 'develop' into feature/vue 2021-01-11 10:04:55 +01:00
vabene1111
c561ddc08c fixed share link 2021-01-11 10:04:45 +01:00
vabene1111
fd3743377b Merge pull request #328 from bloomcake/small-cleanup
Small cleanup
2021-01-11 09:55:14 +01:00
vabene1111
381c3bf3fa Merge pull request #329 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_it
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'it'
2021-01-11 09:43:43 +01:00
vabene1111
5d37a1dc0b Merge pull request #330 from vabene1111/dependabot/pip/django-tables2-2.3.4
Bump django-tables2 from 2.3.3 to 2.3.4
2021-01-11 09:43:34 +01:00
dependabot[bot]
cf07040ece Bump django-tables2 from 2.3.3 to 2.3.4
Bumps [django-tables2](https://github.com/jieter/django-tables2) from 2.3.3 to 2.3.4.
- [Release notes](https://github.com/jieter/django-tables2/releases)
- [Changelog](https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jieter/django-tables2/compare/v2.3.3...v2.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-11 07:06:07 +00:00
transifex-integration[bot]
f2028ee928 Apply translations in it
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'it' language.
2021-01-10 23:01:06 +00:00
enrico-kaack
0c39ddcf66 Fixed: inconsistent volume naming as mentioned in #317 2021-01-10 22:56:18 +01:00
Tobias Lindenberg
8ddbc34017 resolve conflicts
Signed-off-by: Tobias Lindenberg <tobias@lindenberg.pm>
2021-01-10 14:44:20 +01:00
Tobias Lindenberg
6ef06b2650 recipes 2021-01-10 14:36:10 +01:00
Tobias Lindenberg
67581c7fa4 tests/api 2021-01-10 14:32:41 +01:00
Tobias Lindenberg
bc1f28eda6 tests/edits 2021-01-10 14:23:52 +01:00
Tobias Lindenberg
61daf9d5c9 tests/other 2021-01-10 14:16:28 +01:00
Tobias Lindenberg
a37c77bb84 tests/views 2021-01-10 14:14:27 +01:00
Tobias Lindenberg
6d2c48a1c8 tests/test_setup 2021-01-10 14:11:29 +01:00
Tobias Lindenberg
bed5b72864 templatetags/theming_tags 2021-01-10 14:10:21 +01:00
Tobias Lindenberg
325d6e4326 templatetags/custom_tags 2021-01-10 14:08:23 +01:00
Tobias Lindenberg
f7ff700c7a provider/nextcloud 2021-01-10 14:06:58 +01:00
Tobias Lindenberg
41c8e53569 provider/dropbox 2021-01-10 14:03:57 +01:00
Tobias Lindenberg
ff8e431630 helper/init 2021-01-10 13:59:27 +01:00
Tobias Lindenberg
eb9b2ac6fe helper/template_helper 2021-01-10 13:57:51 +01:00
Tobias Lindenberg
1ad468e652 helper/recipe_url_import 2021-01-10 13:57:06 +01:00
Tobias Lindenberg
986bda0c81 helper/permission_helper 2021-01-10 13:51:55 +01:00
Tobias Lindenberg
bb361001b9 helper/permissions_config 2021-01-10 13:47:23 +01:00
Tobias Lindenberg
26aa0207aa helper/mdx_urlize 2021-01-10 13:46:53 +01:00
Tobias Lindenberg
8b1bd3c555 helper/ingredient_parser 2021-01-10 13:45:33 +01:00
Tobias Lindenberg
b59c7288b1 helper/ingredient_parser 2021-01-10 13:44:55 +01:00
Tobias Lindenberg
f5b456018d helper/dal 2021-01-10 13:41:09 +01:00
Tobias Lindenberg
4dad26102a fix recipeimport 2021-01-10 13:28:57 +01:00
Tobias Lindenberg
afc7718c95 urls 2021-01-10 13:28:18 +01:00
Tobias Lindenberg
e5ef19ffe4 tables 2021-01-10 13:15:01 +01:00
Tobias Lindenberg
ecf065db2b serializer 2021-01-10 13:12:06 +01:00
Tobias Lindenberg
4c03d1eb87 models 2021-01-10 13:09:23 +01:00
Tobias Lindenberg
b71e9fe57d forms 2021-01-10 13:05:04 +01:00
Tobias Lindenberg
7ab6276397 filters 2021-01-10 12:46:02 +01:00
Tobias Lindenberg
c7da37e7e7 admin 2021-01-10 12:44:42 +01:00
Tobias Lindenberg
00f9bc087c views/view 2021-01-10 12:42:24 +01:00
Tobias Lindenberg
f2c658cb2d views/new 2021-01-10 12:25:28 +01:00
Tobias Lindenberg
c35f71370e views/lists 2021-01-10 12:22:22 +01:00
Tobias Lindenberg
edb9c883f7 views/import_export 2021-01-10 12:19:39 +01:00
Tobias Lindenberg
0405c123f4 views/edit 2021-01-10 12:16:10 +01:00
Tobias Lindenberg
b84a330883 views/delete 2021-01-10 12:12:16 +01:00
Tobias Lindenberg
2d8c6ef44a views/data 2021-01-10 12:10:54 +01:00
Tobias Lindenberg
6af5f59c80 views/api 2021-01-10 12:06:49 +01:00
Tobias Lindenberg
3c4384e2f6 views/__init__ 2021-01-10 12:06:40 +01:00
vabene1111
e50239f067 added basic service worker 2021-01-10 12:00:16 +01:00
Tobias Lindenberg
4fa6919ca0 sort views imports 2021-01-10 11:55:21 +01:00
vabene1111
db6fe4256f added shortcuts to manifest 2021-01-10 10:27:50 +01:00
vabene1111
8a73f018f0 Merge pull request #326 from bloomcake/boomark-unique
Unique constraint on RecipeBookEntry
2021-01-09 23:27:40 +01:00
vabene1111
b93b16d6eb save WIP
not really getting the feel for this, will continue later, need to really learn how this works
2021-01-09 23:27:25 +01:00
vabene1111
6acf4bb831 Revert "temp save vue transition stuff"
This reverts commit 976cedd536.
2021-01-09 23:11:37 +01:00
Tobias Lindenberg
86134eecb4 added unique constraint on RecipeBookEntry table
Signed-off-by: Tobias Lindenberg <tobias@lindenberg.pm>
2021-01-09 22:17:56 +01:00
vabene1111
3716e2bb0f Merge pull request #324 from Hanser/manifest
added webmanifest to improve mobile (shortcut) experience
2021-01-09 09:16:34 +01:00
Johannes Stefan
b5e08a4828 added webmanifest to static files to improve mobile (shortcut) experience 2021-01-09 00:19:09 +01:00
vabene1111
976cedd536 temp save vue transition stuff 2021-01-08 22:31:48 +01:00
vabene1111
4af5a4e96e testing vue stuff 2021-01-08 00:14:31 +01:00
vabene1111
9044f9e1ff testing with vue mixings and components 2021-01-07 23:58:04 +01:00
vabene1111
4b719af4e1 fixed broken microdata blowing up ingredient lists 2021-01-07 23:09:37 +01:00
vabene1111
78fa5338d3 fixed ingredient dropping on parser fail 2021-01-07 22:56:05 +01:00
vabene1111
e9f2b875b9 formatting 2021-01-07 22:47:53 +01:00
vabene1111
fe3f817bc5 Merge pull request #322 from jakobwenzel/fixIngredientParser
Fix ingredient parser to allow Plural-suffixes
2021-01-07 22:35:48 +01:00
vabene1111
32f5cf64e5 stick navbar .env 2021-01-07 22:33:31 +01:00
vabene1111
5aadb66013 Merge pull request #321 from neferin12/develop
Sticky Navbar
2021-01-07 22:29:54 +01:00
Julian Pollinger
6225648e3a Minor formatting 2021-01-07 21:50:26 +01:00
Jakob Wenzel
29903af085 catch error when trying to parse into ingredient/note 2021-01-07 19:49:02 +01:00
Jakob Wenzel
8ed2562454 allow plural-suffixes in ingredient parser 2021-01-07 19:30:07 +01:00
Jakob Wenzel
fd255fd6ad added failing testcase for ingredient parser 2021-01-07 19:20:20 +01:00
Jakob Wenzel
8931fa8557 fix ingredient parser testcase
* call correct function
* flip assertion
2021-01-07 19:06:08 +01:00
Julian Pollinger
251bd88f70 Make the sticky navbar an option 2021-01-07 18:49:11 +01:00
vabene1111
2ac076afa5 readme and documentation 2021-01-07 16:45:01 +01:00
vabene1111
2d82fc1ddd fixed meal plan settings cookie expiring 2021-01-07 15:42:34 +01:00
vabene1111
ba9d85dfc9 updated docker configs 2021-01-07 15:24:56 +01:00
vabene1111
c752b2e81b Merge pull request #316 from KyleWJohnston/develop
Improve documentation formatting
2021-01-07 09:25:23 +01:00
Julian Pollinger
19df1cf65d Sticky Navbar 2021-01-06 19:09:41 +01:00
Julian Pollinger
ebdd8fc053 Update base.html 2021-01-06 18:57:25 +01:00
Kyle Johnston
924576c8ba Correct mistake on ports, clarify documentation
My understanding is if you want to access the app on port 3000, you
would use `docker run -d -p 3000:8080 ...` for a Docker command. For
Docker Compose, it's:
```
nginx_recipes:
  ports:
  - 3000:80
```
2021-01-06 07:30:10 -05:00
vabene1111
f4fa28bfbc Merge pull request #318 from vabene1111/dependabot/pip/jinja2-2.11.2
Bump jinja2 from 2.11.0 to 2.11.2
2021-01-06 07:54:35 +01:00
dependabot[bot]
0c2cb599ba Bump jinja2 from 2.11.0 to 2.11.2
Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.0 to 2.11.2.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/master/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/2.11.0...2.11.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 06:03:30 +00:00
Kyle Johnston
54f85196e7 Fix Docker install instructions
Not having the backslashes for the `docker run` command resulted in an
error.

Using `80:80` is ignored. While it will work on port 80, it fails when
using any other port such as `3000:80`. Using `3000:8080` works.

Volumes should have a colon on the end, otherwise you get a `ERROR:
yaml.scanner.ScannerError: mapping values are not allowed here`

That said, the new Docker install isn't working for me yet, but I love
the idea of bundling the nginx files with the recipes container.
2021-01-05 21:50:56 -05:00
Kyle Johnston
a1093ed918 Fix documentation formatting
Mostly just typos and some minor changes to help new users less familiar
with Docker tools. I preserved the original style as much as possible.
2021-01-05 21:19:16 -05:00
vabene1111
caa33810c4 added more documentation 2021-01-06 00:17:53 +01:00
vabene1111
fd8229684c improved docs and added unraid 2021-01-05 23:41:15 +01:00
vabene1111
320d94a223 fixed incorrect redirects for unauthenticated users 2021-01-05 23:24:58 +01:00
vabene1111
43ccc351c7 added changes typo fix 2021-01-05 22:54:57 +01:00
vabene1111
d36e4c6e0a fixed another typoe 2021-01-05 22:54:46 +01:00
vabene1111
fdeede5717 updated vue and vue bootrap + template improvements 2021-01-05 22:53:08 +01:00
vabene1111
738b601462 templating working 2021-01-05 22:07:46 +01:00
vabene1111
2c93a2f177 Merge branch 'develop' into feature/templating 2021-01-05 21:48:50 +01:00
vabene1111
6b2d164585 fixed broken import chain 2021-01-05 21:48:44 +01:00
vabene1111
ee707eba5c super basic templating working 2021-01-05 21:46:24 +01:00
vabene1111
f26b09cc0a fixed broken translations in help_texts 2021-01-05 21:26:42 +01:00
vabene1111
4e88f846af Merge pull request #312 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_it
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'it'
2021-01-05 13:11:08 +01:00
transifex-integration[bot]
6a1d892e8b Apply translations in it
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'it' language.
2021-01-05 11:44:52 +00:00
302 changed files with 35471 additions and 13789 deletions

View File

@@ -5,26 +5,27 @@ DEBUG=0
# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,...
ALLOWED_HOSTS=*
# random secret key, use for example base64 /dev/urandom | head -c50 to generate one
# random secret key, use for example `base64 /dev/urandom | head -c50` to generate one
SECRET_KEY=
# your default timezone
# your default timezone See https://timezonedb.com/time-zones for a list of timezones
TIMEZONE=Europe/Berlin
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
DB_ENGINE=django.db.backends.postgresql
# DB_OPTIONS= {} # e.g. {"sslmode":"require"} to enable ssl
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=djangodb
POSTGRES_USER=djangouser
POSTGRES_PASSWORD=
POSTGRES_DB=djangodb
# the default value for the user preference 'fractions' (enable/disable fraction support)
# when unset: 0 (disabled)
# default: disabled=0
FRACTION_PREF_DEFAULT=0
# the default value for the user preference 'comments' (enable/disable commenting system)
# when unset: 1 (true)
# default comments enabled=1
COMMENT_PREF_DEFAULT=1
# Users can set a amount of time after which the shopping list is refreshed when they are in viewing mode
@@ -32,6 +33,9 @@ COMMENT_PREF_DEFAULT=1
# might cause high load on the server. (Technically they can obviously refresh as often as they want with their own scripts)
SHOPPING_MIN_AUTOSYNC_INTERVAL=5
# Default for user setting sticky navbar
#STICKY_NAV_PREF_DEFAULT=1
# If staticfiles are stored at a different location uncomment and change accordingly
# STATIC_URL=/static/
@@ -44,9 +48,20 @@ SHOPPING_MIN_AUTOSYNC_INTERVAL=5
# when unset: 1 (true) - this is temporary until an appropriate amount of time has passed for everyone to migrate
GUNICORN_MEDIA=0
# allow authentication via reverse proxy (e.g. authelia), leave of if you dont know what you are doing
# docs: https://github.com/vabene1111/recipes/tree/develop/docs/docker/nginx-proxy%20with%20proxy%20authentication
# allow authentication via reverse proxy (e.g. authelia), leave off if you dont know what you are doing
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
# when unset: 0 (false)
REVERSE_PROXY_AUTH=0
# allows you to setup OAuth providers
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
# SOCIAL_PROVIDERS = allauth.socialaccount.providers.github, allauth.socialaccount.providers.nextcloud,
# Should a newly created user from a social provider get assigned to the default space and given permission by default ?
# ATTENTION: This feature might be deprecated in favor of a space join and public viewing system in the future
# default 0 (false), when 1 (true) users will be assigned space and group
# SOCIAL_DEFAULT_ACCESS = 1
# if SOCIAL_DEFAULT_ACCESS is used, which group should be added
# SOCIAL_DEFAULT_GROUP=guest

View File

@@ -6,14 +6,16 @@ labels: setup issue
assignees: ''
---
### Version
Please provide your current version (can be found on the system page since v0.8.4)
Version:
### Issue
## Issue
Please describe your problem here
## Setup Info
Version: (can be found on the system page since v0.8.4)
OS: e.g. Ubuntu 20.02
Other relevant information regarding your problem (proxies, firewalls, etc.)
### `.env`
Please include your `.env` config file (**make sure to remove/replace all secrets**)
```
@@ -25,3 +27,7 @@ When running with docker compose please provide your `docker-compose.yml`
```
docker-compose.yml content
```
### Logs
If you feel like there is anything interesting please post the output of `docker-compose logs` at
container startup and when the issue happens.

22
.github/ISSUE_TEMPLATE/url_import.md vendored Normal file
View File

@@ -0,0 +1,22 @@
---
name: Website Import
about: Anything related to website imports
title: ''
labels: enhancement, url_import
assignees: ''
---
### Version
Please provide your current version (can be found on the system page since v0.8.4)
Version:
### Information
Exact URL you are trying to import from:
When did the issue happen: When pressing the search button with the url / when importing after the page has loaded
Response/Message shown
```
Message
```

View File

@@ -22,6 +22,7 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt
python3 manage.py collectstatic --noinput
python3 manage.py collectstatic_js_reverse
- name: Django Testing project
run: |
python3 manage.py test
pytest

View File

@@ -0,0 +1,26 @@
name: publish beta image docker
on:
push:
branches:
- 'beta'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = 'beta'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
- name: Build and publish image
uses: ilteoood/docker_buildx@master
with:
publish: true
imageName: vabene1111/recipes
tag: beta
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}

2
.gitignore vendored
View File

@@ -77,3 +77,5 @@ postgresql/
/docker-compose.override.yml
vue/node_modules
.vscode/

View File

@@ -2,9 +2,13 @@
<dictionary name="vabene1111-PC">
<words>
<w>autosync</w>
<w>chowdown</w>
<w>csrftoken</w>
<w>gunicorn</w>
<w>ical</w>
<w>mealie</w>
<w>pepperplate</w>
<w>safron</w>
<w>traefik</w>
</words>
</dictionary>

7
.idea/recipes.iml generated
View File

@@ -20,10 +20,6 @@
</content>
<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" />
<orderEntry type="library" name="pdf" level="application" />
<orderEntry type="library" name="pdf_viewer" level="application" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
@@ -33,4 +29,7 @@
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="pytest" />
</component>
</module>

31
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,31 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: local
hooks:
- id: pre-commit-yarn-build
name: Build javascript files
entry: yarn --cwd ./vue build
always_run: true
language: system
types: [ python ]
pass_filenames: false
#- id: pre-commit-django-migrations
# name: Check django migrations
# entry: bash -c './venv/bin/activate && ./manage.py makemigrations --check'
# language: system
# types: [ python ]
# pass_filenames: false
# - id: pre-commit-django-make-messages
# name: Make messages if necessary
# entry: ./manage.py makemessages -i venv -a
# language: system
# types: [ python ]
# pass_filenames: false
# - id: pre-commit-django-compile-messages
# name: Compile messages if necessary
# entry: ./manage.py compilemessages -i venv
# language: system
# types: [ python ]
# pass_filenames: false

View File

@@ -1,18 +1,28 @@
FROM python:3.8-alpine
FROM python:3.9-alpine3.12
RUN apk add --no-cache postgresql-libs gettext zlib libjpeg libxml2-dev libxslt-dev
#Install all dependencies.
RUN apk add --no-cache postgresql-libs gettext zlib libjpeg libxml2-dev libxslt-dev py-cryptography
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
#This port will be used by gunicorn.
EXPOSE 8080
#Create app dir and install requirements.
RUN mkdir /opt/recipes
WORKDIR /opt/recipes
COPY . ./
RUN chmod +x boot.sh setup.sh
RUN ln -s /opt/recipes/setup.sh /usr/local/bin/createsuperuser
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev && \
COPY requirements.txt ./
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libressl-dev libffi-dev cargo && \
python -m venv venv && \
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \
venv/bin/pip install wheel==0.36.2 && \
venv/bin/pip install -r requirements.txt --no-cache-dir &&\
apk --purge del .build-deps
#Copy project and execute it.
COPY . ./
RUN chmod +x boot.sh
ENTRYPOINT ["/opt/recipes/boot.sh"]

102
README.md
View File

@@ -1,77 +1,65 @@
# Recipes
![CI](https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop)
![Stars](https://img.shields.io/github/stars/vabene1111/recipes)
![Forks](https://img.shields.io/github/forks/vabene1111/recipes)
![Docker Pulls](https://img.shields.io/docker/pulls/vabene1111/recipes)
<h1 align="center">
<br>
<a href="https://app.tandoor.dev"><img src="https://github.com/vabene1111/recipes/raw/develop/docs/logo_color.svg" height="256px" width="256px"></a>
<br>
Tandoor Recipes
<br>
</h1>
Recipes is a Django application to manage, tag and search recipes using either built in models or
external storage providers hosting PDF's, Images or other files.
<h4 align="center">The recipe manager that allows you to manage your ever growing collection of digital recipes.</h4>
<p align="center">
<img src="https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop" >
<img src="https://img.shields.io/github/stars/vabene1111/recipes" >
<img src="https://img.shields.io/github/forks/vabene1111/recipes" >
<img src="https://img.shields.io/docker/pulls/vabene1111/recipes" >
</p>
<p align="center">
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Documentation</a> •
<a href="https://app.tandoor.dev/" target="_blank" rel="noopener noreferrer">Demo</a>
</p>
![Preview](docs/preview.png)
[More Screenshots](https://imgur.com/a/V01151p)
## Features
- :package: **Sync** files with Dropbox and Nextcloud (more can easily be added)
- :mag: Powerful **search** with Djangos [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
- :label: Create and search for **tags**, assign them in batch to all files matching certain filters
- :page_facing_up: **Create recipes** locally within a nice, standardized web interface
- :arrow_down: **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
- :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
- :family: **Share** recipes with friends and comment on them to suggest or remember changes you made
- :heavy_division_sign: automatically convert decimal units to **fractions** for those who like this
- :whale: Easy setup with **Docker**
- :art: Customize your interface with **themes**
- :envelope: Export and import recipes from other users
- :earth_africa: localized in many languages thanks to the awesome community
- :heavy_plus_sign: Many more like recipe scaling, image compression, cookbooks, printing views, ...
- 📦 **Sync** files with Dropbox and Nextcloud (more can easily be added)
- 🔍 Powerful **search** with Djangos [TrigramSimilarity](https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/search/#trigram-similarity)
- 🏷️ Create and search for **tags**, assign them in batch to all files matching certain filters
- 📄 **Create recipes** locally within a nice, standardized web interface
- ⬇️ **Import recipes** from thousands of websites supporting [ld+json or microdata](https://schema.org/Recipe)
- 📱 Optimized for use on **mobile** devices like phones and tablets
- 🛒 Generate **shopping** lists from recipes
- 📆 Create a **Plan** on what to eat when
- 👪 **Share** recipes with friends and comment on them to suggest or remember changes you made
- automatically convert decimal units to **fractions** for those who like this
- 🐳 Easy setup with **Docker** and included examples for Kubernetes, Unraid and Synology
- 🎨 Customize your interface with **themes**
- ✉️ Export and import recipes from other users
- 🌍 localized in many languages thanks to the awesome community
- 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.
Some Documentation can be found [here](https://github.com/vabene1111/recipes/wiki)
Documentation can be found [here](https://docs.tandoor.dev/).
While this application has been around for a while and is actively used by many (including myself) it is still considered
While this application has been around for a while and is actively used by many (including myself), it is still considered
**beta** software that has a lot of rough edges and unpolished parts.
## Installation
Please refer to the Installation section of the [Documentation](https://vabene1111.github.io/recipes/).
## Contributing
Pull Requests and ideas are welcome, feel free to contribute in any way.
**If you want feel free to open an issue or pull request to add yourself to the list of awesome contributors.**
### Getting Started
This application is developed using the django framework for Python. They have excellent
[documentation](https://www.djangoproject.com/start/) on how to get started, so I will only give you the basics here
1. Clone this repository wherever you like and install the Python language for your OS (at least version 3.8)
2. Open it in your favorite editor/IDE (e.g. PyCharm)
1. if you want, create a virutal environment for all your packages.
3. Install all required packages by running `pip install -r requirements.txt`
4. Run the migrations `python manage.py migrate`
5. Start the development server `python manage.py runserver`
There is **no** need to set any environment variables. By default, a simple sqlite database is used and all settings are
populated from default values.
### Translating
There is a [transifex project](https://www.transifex.com/django-recipes/django-cookbook/) project to enable community driven translations. If you want to contribute a new language or help maintain an already existing one feel free to create a transifex account (using the link above) and request to join the project.
It is also possible to provide the translations directly by creating a new language using `manage.py makemessages -l <language_code> -i venv`. Once finished simply open a PR with the changed files.
## License
Beginning with version 0.10.0 the code in this repository is licensed under the [GNU AGPL v3](https://www.gnu.org/licenses/agpl-3.0.de.html) license with an
[common clause](https://commonsclause.com/) selling exception. See [LICENSE.md](https://github.com/vabene1111/recipes/blob/develop/LICENSE.md) for details.
**Reasoning**
> NOTE: There appears to be a whole range of legal issues with licensing anything else then the standard completely open licenses.
> I am in the process of getting some professional legal advice to sort out these issues.
> Please also see [Issue 238](https://github.com/vabene1111/recipes/issues/238) for some discussion and **reasoning** regarding the topic.
**Reasoning**
**This software and *all* its features are and will always be free for everyone to use and enjoy.**
The reason for the selling exception is that a significant amount of time was spend over multiple years to develop this software.

View File

@@ -3,6 +3,7 @@ source venv/bin/activate
echo "Updating database"
python manage.py migrate
python manage.py collectstatic_js_reverse
python manage.py collectstatic --noinput
echo "Done"

View File

@@ -1,16 +1,35 @@
from django.contrib import admin
from .models import *
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User, Group
from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
MealPlan, MealType, NutritionInformation, Recipe,
RecipeBook, RecipeBookEntry, RecipeImport, ShareLink,
ShoppingList, ShoppingListEntry, ShoppingListRecipe,
Space, Step, Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation, ImportLog, TelegramBot)
class CustomUserAdmin(UserAdmin):
def has_add_permission(self, request, obj=None):
return False
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
admin.site.unregister(Group)
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'message')
list_display = ('name', 'created_by', 'message')
admin.site.register(Space, SpaceAdmin)
class UserPreferenceAdmin(admin.ModelAdmin):
list_display = ('name', 'theme', 'nav_color', 'default_page', 'search_style', 'comments')
list_display = ('name', 'space', 'theme', 'nav_color', 'default_page', 'search_style',)
@staticmethod
def name(obj):
@@ -34,6 +53,18 @@ class SyncAdmin(admin.ModelAdmin):
admin.site.register(Sync, SyncAdmin)
class SupermarketCategoryInline(admin.TabularInline):
model = SupermarketCategoryRelation
class SupermarketAdmin(admin.ModelAdmin):
inlines = (SupermarketCategoryInline,)
admin.site.register(Supermarket, SupermarketAdmin)
admin.site.register(SupermarketCategory)
class SyncLogAdmin(admin.ModelAdmin):
list_display = ('sync', 'status', 'msg', 'created_at')
@@ -133,7 +164,10 @@ admin.site.register(ViewLog, ViewLogAdmin)
class InviteLinkAdmin(admin.ModelAdmin):
list_display = ('username', 'group', 'valid_until', 'created_by', 'created_at', 'used_by')
list_display = (
'username', 'group', 'valid_until',
'created_by', 'created_at', 'used_by'
)
admin.site.register(InviteLink, InviteLinkAdmin)
@@ -179,3 +213,17 @@ class NutritionInformationAdmin(admin.ModelAdmin):
admin.site.register(NutritionInformation, NutritionInformationAdmin)
class ImportLogAdmin(admin.ModelAdmin):
list_display = ('id', 'type', 'running', 'created_by', 'created_at',)
admin.site.register(ImportLog, ImportLogAdmin)
class TelegramBotAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'created_by',)
admin.site.register(TelegramBot, TelegramBotAdmin)

View File

@@ -1,67 +1,81 @@
import django_filters
from django.conf import settings
from django.contrib.postgres.search import TrigramSimilarity
from django.db.models import Q
from cookbook.forms import MultiSelectWidget
from cookbook.models import Recipe, Keyword, Food, ShoppingList
from django.conf import settings
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
from cookbook.forms import MultiSelectWidget
from cookbook.models import Food, Keyword, Recipe, ShoppingList
class RecipeFilter(django_filters.FilterSet):
name = django_filters.CharFilter(method='filter_name')
keywords = django_filters.ModelMultipleChoiceFilter(queryset=Keyword.objects.all(), widget=MultiSelectWidget,
method='filter_keywords')
foods = django_filters.ModelMultipleChoiceFilter(queryset=Food.objects.all(), widget=MultiSelectWidget,
method='filter_foods', label=_('Ingredients'))
with scopes_disabled():
class RecipeFilter(django_filters.FilterSet):
name = django_filters.CharFilter(method='filter_name')
keywords = django_filters.ModelMultipleChoiceFilter(
queryset=Keyword.objects.none(),
widget=MultiSelectWidget,
method='filter_keywords'
)
foods = django_filters.ModelMultipleChoiceFilter(
queryset=Food.objects.none(),
widget=MultiSelectWidget,
method='filter_foods',
label=_('Ingredients')
)
@staticmethod
def filter_keywords(queryset, name, value):
if not name == 'keywords':
def __init__(self, data=None, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(data, *args, **kwargs)
self.filters['foods'].queryset = Food.objects.filter(space=space).all()
self.filters['keywords'].queryset = Keyword.objects.filter(space=space).all()
@staticmethod
def filter_keywords(queryset, name, value):
if not name == 'keywords':
return queryset
for x in value:
queryset = queryset.filter(keywords=x)
return queryset
for x in value:
queryset = queryset.filter(keywords=x)
return queryset
@staticmethod
def filter_foods(queryset, name, value):
if not name == 'foods':
@staticmethod
def filter_foods(queryset, name, value):
if not name == 'foods':
return queryset
for x in value:
queryset = queryset.filter(steps__ingredients__food__name=x).distinct()
return queryset
for x in value:
queryset = queryset.filter(steps__ingredients__food__name=x).distinct()
return queryset
@staticmethod
def filter_name(queryset, name, value):
if not name == 'name':
@staticmethod
def filter_name(queryset, name, value):
if not name == 'name':
return queryset
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql_psycopg2':
queryset = queryset.annotate(similarity=TrigramSimilarity('name', value), ).filter(Q(similarity__gt=0.1) | Q(name__unaccent__icontains=value)).order_by('-similarity')
else:
queryset = queryset.filter(name__icontains=value)
return queryset
if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql_psycopg2':
queryset = queryset.annotate(similarity=TrigramSimilarity('name', value), ).filter(
Q(similarity__gt=0.1) | Q(name__icontains=value)).order_by('-similarity')
else:
queryset = queryset.filter(name__icontains=value)
return queryset
class Meta:
model = Recipe
fields = ['name', 'keywords', 'foods', 'internal']
class Meta:
model = Recipe
fields = ['name', 'keywords', 'foods', 'internal']
class IngredientFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains')
class FoodFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = Food
fields = ['name']
class Meta:
model = Food
fields = ['name']
class ShoppingListFilter(django_filters.FilterSet):
class ShoppingListFilter(django_filters.FilterSet):
def __init__(self, data=None, *args, **kwargs):
if data is not None:
data = data.copy()
data.setdefault("finished", False)
super(ShoppingListFilter, self).__init__(data, *args, **kwargs)
def __init__(self, data=None, *args, **kwargs):
if data is not None:
data = data.copy()
data.setdefault("finished", False)
super().__init__(data, *args, **kwargs)
class Meta:
model = ShoppingList
fields = ['finished']
class Meta:
model = ShoppingList
fields = ['finished']

View File

@@ -1,10 +1,12 @@
from django import forms
from django.forms import widgets
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
from emoji_picker.widgets import EmojiPickerTextInput
from .models import *
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, Storage, Sync, Unit, User,
UserPreference, SupermarketCategory, MealType, Space)
class SelectWidget(widgets.Select):
@@ -17,7 +19,8 @@ class MultiSelectWidget(widgets.SelectMultiple):
js = ('custom/js/form_multiselect.js',)
# yes there are some stupid browsers that still dont support this but i dont support people using these browsers
# Yes there are some stupid browsers that still dont support this but
# I dont support people using these browsers.
class DateWidget(forms.DateInput):
input_type = 'date'
@@ -31,19 +34,26 @@ class UserPreferenceForm(forms.ModelForm):
class Meta:
model = UserPreference
fields = ('default_unit', 'use_fractions', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share', 'ingredient_decimals', 'shopping_auto_sync', 'comments')
fields = (
'default_unit', 'use_fractions', 'theme', 'nav_color',
'sticky_navbar', 'default_page', 'show_recent', 'search_style',
'plan_share', 'ingredient_decimals', 'shopping_auto_sync',
'comments'
)
help_texts = {
'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.'),
'use_fractions': _('Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'),
'plan_share': _('Users with whom newly created meal plan/shopping list entries should be shared by default.'),
'show_recent': _('Show recently viewed recipes on search page.'),
'ingredient_decimals': _('Number of decimals to round ingredients.'),
'comments': _('If you want to be able to create and see comments underneath recipes.'),
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'), # noqa: E501
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.'), # noqa: E501
'use_fractions': _('Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'), # noqa: E501
'plan_share': _('Users with whom newly created meal plan/shopping list entries should be shared by default.'), # noqa: E501
'show_recent': _('Show recently viewed recipes on search page.'), # noqa: E501
'ingredient_decimals': _('Number of decimals to round ingredients.'), # noqa: E501
'comments': _('If you want to be able to create and see comments underneath recipes.'), # noqa: E501
'shopping_auto_sync': _(
'Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but might use a little bit '
'of mobile data. If lower than instance limit it is reset when saving.')
'Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but might use a little bit ' # noqa: E501
'of mobile data. If lower than instance limit it is reset when saving.' # noqa: E501
),
'sticky_navbar': _('Makes the navbar stick to the top of the page.') # noqa: E501
}
widgets = {
@@ -59,18 +69,25 @@ class UserNameForm(forms.ModelForm):
fields = ('first_name', 'last_name')
help_texts = {
'first_name': _('Both fields are optional. If none are given the username will be displayed instead')
'first_name': _('Both fields are optional. If none are given the username will be displayed instead') # noqa: E501
}
class ExternalRecipeForm(forms.ModelForm):
file_path = forms.CharField(disabled=True, required=False)
storage = forms.ModelChoiceField(queryset=Storage.objects.all(), disabled=True, required=False)
file_uid = forms.CharField(disabled=True, required=False)
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['keywords'].queryset = Keyword.objects.filter(space=space).all()
class Meta:
model = Recipe
fields = ('name', 'keywords', 'working_time', 'waiting_time', 'file_path', 'storage', 'file_uid')
fields = (
'name', 'description', 'servings', 'working_time', 'waiting_time',
'file_path', 'file_uid', 'keywords'
)
labels = {
'name': _('Name'),
@@ -81,89 +98,93 @@ class ExternalRecipeForm(forms.ModelForm):
'file_uid': _('Storage UID'),
}
widgets = {'keywords': MultiSelectWidget}
class InternalRecipeForm(forms.ModelForm):
ingredients = forms.CharField(widget=forms.HiddenInput(), required=False)
class Meta:
model = Recipe
fields = ('name', 'image', 'working_time', 'waiting_time', 'servings', 'keywords')
labels = {
'name': _('Name'),
'keywords': _('Keywords'),
'working_time': _('Preparation time in minutes'),
'waiting_time': _('Waiting time (cooking/baking) in minutes'),
'servings': _('Number of servings'),
field_classes = {
'keywords': SafeModelMultipleChoiceField,
}
widgets = {'keywords': MultiSelectWidget}
class ShoppingForm(forms.Form):
recipe = forms.ModelMultipleChoiceField(
queryset=Recipe.objects.filter(internal=True).all(),
widget=MultiSelectWidget
)
markdown_format = forms.BooleanField(
help_text=_('Include <code>- [ ]</code> in list for easier usage in markdown based documents.'),
required=False,
initial=False
)
class ImportExportBase(forms.Form):
DEFAULT = 'DEFAULT'
PAPRIKA = 'PAPRIKA'
NEXTCLOUD = 'NEXTCLOUD'
MEALIE = 'MEALIE'
CHOWDOWN = 'CHOWDOWN'
SAFRON = 'SAFRON'
CHEFTAP = 'CHEFTAP'
PEPPERPLATE = 'PEPPERPLATE'
RECIPESAGE = 'RECIPESAGE'
DOMESTICA = 'DOMESTICA'
MEALMASTER = 'MEALMASTER'
REZKONV = 'REZKONV'
type = forms.ChoiceField(choices=(
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
(PEPPERPLATE, 'Pepperplate'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'),
))
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(ImportExportBase):
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), 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 ExportForm(ImportExportBase):
recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none())
all = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['recipes'].queryset = Recipe.objects.filter(space=space).all()
class UnitMergeForm(forms.Form):
prefix = 'unit'
new_unit = forms.ModelChoiceField(
queryset=Unit.objects.all(),
new_unit = SafeModelChoiceField(
queryset=Unit.objects.none(),
widget=SelectWidget,
label=_('New Unit'),
help_text=_('New unit that other gets replaced by.'),
)
old_unit = forms.ModelChoiceField(
queryset=Unit.objects.all(),
old_unit = SafeModelChoiceField(
queryset=Unit.objects.none(),
widget=SelectWidget,
label=_('Old Unit'),
help_text=_('Unit that should be replaced.'),
)
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['new_unit'].queryset = Unit.objects.filter(space=space).all()
self.fields['old_unit'].queryset = Unit.objects.filter(space=space).all()
class FoodMergeForm(forms.Form):
prefix = 'food'
new_food = forms.ModelChoiceField(
queryset=Food.objects.all(),
new_food = SafeModelChoiceField(
queryset=Food.objects.none(),
widget=SelectWidget,
label=_('New Food'),
help_text=_('New food that other gets replaced by.'),
)
old_food = forms.ModelChoiceField(
queryset=Food.objects.all(),
old_food = SafeModelChoiceField(
queryset=Food.objects.none(),
widget=SelectWidget,
label=_('Old Food'),
help_text=_('Food that should be replaced.'),
)
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['new_food'].queryset = Food.objects.filter(space=space).all()
self.fields['old_food'].queryset = Food.objects.filter(space=space).all()
class CommentForm(forms.ModelForm):
prefix = 'comment'
@@ -188,52 +209,104 @@ class KeywordForm(forms.ModelForm):
class FoodForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['recipe'].queryset = Recipe.objects.filter(space=space).all()
self.fields['supermarket_category'].queryset = SupermarketCategory.objects.filter(space=space).all()
class Meta:
model = Food
fields = ('name', 'recipe')
fields = ('name', 'description', 'ignore_shopping', 'recipe', 'supermarket_category')
widgets = {'recipe': SelectWidget}
field_classes = {
'recipe': SafeModelChoiceField,
'supermarket_category': SafeModelChoiceField,
}
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'}),
required=False,
help_text=_('Leave empty for dropbox and enter app password for nextcloud.'))
token = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
required=False,
help_text=_('Leave empty for nextcloud and enter api token for dropbox.'))
username = forms.CharField(
widget=forms.TextInput(attrs={'autocomplete': 'new-password'}),
required=False
)
password = forms.CharField(
widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
required=False,
help_text=_('Leave empty for dropbox and enter app password for nextcloud.')
)
token = forms.CharField(
widget=forms.TextInput(
attrs={'autocomplete': 'new-password', 'type': 'password'}
),
required=False,
help_text=_('Leave empty for nextcloud and enter api token for dropbox.')
)
class Meta:
model = Storage
fields = ('name', 'method', 'username', 'password', 'token', 'url')
fields = ('name', 'method', 'username', 'password', 'token', 'url', 'path')
help_texts = {
'url': _(
'Leave empty for dropbox and enter only base url for nextcloud (<code>/remote.php/webdav/</code> is added automatically)'),
'url': _('Leave empty for dropbox and enter only base url for nextcloud (<code>/remote.php/webdav/</code> is added automatically)'),
}
class RecipeBookEntryForm(forms.ModelForm):
prefix = 'bookmark'
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['book'].queryset = RecipeBook.objects.filter(space=space).all()
class Meta:
model = RecipeBookEntry
fields = ('book',)
field_classes = {
'book': SafeModelChoiceField,
}
class SyncForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['storage'].queryset = Storage.objects.filter(space=space).all()
class Meta:
model = Sync
fields = ('storage', 'path', 'active')
field_classes = {
'storage': SafeModelChoiceField,
}
class BatchEditForm(forms.Form):
search = forms.CharField(label=_('Search String'))
keywords = forms.ModelMultipleChoiceField(queryset=Keyword.objects.all().order_by('id'), required=False,
widget=MultiSelectWidget)
keywords = forms.ModelMultipleChoiceField(
queryset=Keyword.objects.none(),
required=False,
widget=MultiSelectWidget
)
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['keywords'].queryset = Keyword.objects.filter(space=space).all().order_by('id')
class ImportRecipeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['keywords'].queryset = Keyword.objects.filter(space=space).all()
class Meta:
model = Recipe
fields = ('name', 'keywords', 'file_path', 'file_uid')
@@ -245,47 +318,94 @@ class ImportRecipeForm(forms.ModelForm):
'file_uid': _('File ID'),
}
widgets = {'keywords': MultiSelectWidget}
field_classes = {
'keywords': SafeModelChoiceField,
}
class RecipeBookForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['shared'].queryset = User.objects.filter(userpreference__space=space).all()
class Meta:
model = RecipeBook
fields = ('name', 'icon', 'description', 'shared')
widgets = {'icon': EmojiPickerTextInput, 'shared': MultiSelectWidget}
field_classes = {
'shared': SafeModelMultipleChoiceField,
}
class MealPlanForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
space = kwargs.pop('space')
super().__init__(*args, **kwargs)
self.fields['recipe'].queryset = Recipe.objects.filter(space=space).all()
self.fields['meal_type'].queryset = MealType.objects.filter(space=space).all()
self.fields['shared'].queryset = User.objects.filter(userpreference__space=space).all()
def clean(self):
cleaned_data = super(MealPlanForm, self).clean()
if cleaned_data['title'] == '' and cleaned_data['recipe'] is None:
raise forms.ValidationError(_('You must provide at least a recipe or a title.'))
raise forms.ValidationError(
_('You must provide at least a recipe or a title.')
)
return cleaned_data
class Meta:
model = MealPlan
fields = ('recipe', 'title', 'meal_type', 'note', 'servings', 'date', 'shared')
fields = (
'recipe', 'title', 'meal_type', 'note',
'servings', 'date', 'shared'
)
help_texts = {
'shared': _('You can list default users to share recipes with in the settings.'),
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
'shared': _('You can list default users to share recipes with in the settings.'), # noqa: E501
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>') # noqa: E501
}
widgets = {'recipe': SelectWidget, 'date': DateWidget, 'shared': MultiSelectWidget}
widgets = {
'recipe': SelectWidget,
'date': DateWidget,
'shared': MultiSelectWidget
}
field_classes = {
'recipe': SafeModelChoiceField,
'meal_type': SafeModelChoiceField,
'shared': SafeModelMultipleChoiceField,
}
class InviteLinkForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['space'].queryset = Space.objects.filter(created_by=user).all()
class Meta:
model = InviteLink
fields = ('username', 'group', 'valid_until')
fields = ('username', 'group', 'valid_until', 'space')
help_texts = {
'username': _('A username is not required, if left blank the new user can choose one.')
'username': _('A username is not required, if left blank the new user can choose one.') # noqa: E501
}
field_classes = {
'space': SafeModelChoiceField,
}
class UserCreateForm(forms.Form):
name = forms.CharField(label='Username')
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
password_confirm = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
password = forms.CharField(
widget=forms.TextInput(
attrs={'autocomplete': 'new-password', 'type': 'password'}
)
)
password_confirm = forms.CharField(
widget=forms.TextInput(
attrs={'autocomplete': 'new-password', 'type': 'password'}
)
)

View File

@@ -0,0 +1,19 @@
from django.conf import settings
from allauth.account.adapter import DefaultAccountAdapter
class AllAuthCustomAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
"""
Whether to allow sign ups.
"""
if request.resolver_match.view_name == 'account_signup':
return False
else:
return super(AllAuthCustomAdapter, self).is_open_for_signup(request)
# disable password reset for now
def send_mail(self, template_prefix, email, context):
pass

View File

@@ -0,0 +1,8 @@
from django.test.runner import DiscoverRunner
from django_scopes import scopes_disabled
class CustomTestRunner(DiscoverRunner):
def run_tests(self, *args, **kwargs):
with scopes_disabled():
return super().run_tests(*args, **kwargs)

View File

@@ -1 +1,6 @@
from cookbook.helper.dal import *
import cookbook.helper.dal
from cookbook.helper.AllAuthCustomAdapter import AllAuthCustomAdapter
__all__ = [
'dal',
]

View File

@@ -1,27 +1,16 @@
from cookbook.models import Food, Keyword, Recipe, Unit
from dal import autocomplete
from cookbook.models import Keyword, Recipe, Unit, Food
class BaseAutocomplete(autocomplete.Select2QuerySetView):
model = None
class KeywordAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Keyword.objects.none()
return self.model.objects.none()
qs = Keyword.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
class IngredientsAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Food.objects.none()
qs = Food.objects.all()
qs = self.model.objects.filter(space=self.request.space).all()
if self.q:
qs = qs.filter(name__icontains=self.q)
@@ -29,27 +18,17 @@ class IngredientsAutocomplete(autocomplete.Select2QuerySetView):
return qs
class RecipeAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Recipe.objects.none()
qs = Recipe.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs
class KeywordAutocomplete(BaseAutocomplete):
model = Keyword
class UnitAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Unit.objects.none()
class IngredientsAutocomplete(BaseAutocomplete):
model = Food
qs = Unit.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
class RecipeAutocomplete(BaseAutocomplete):
model = Recipe
return qs
class UnitAutocomplete(BaseAutocomplete):
model = Unit

View File

@@ -1,10 +1,14 @@
import unicodedata
import string
import unicodedata
from cookbook.models import Unit, Food
def parse_fraction(x):
if len(x) == 1 and 'fraction' in unicodedata.decomposition(x):
frac_split = unicodedata.decomposition(x[-1:]).split()
return float((frac_split[1]).replace('003', '')) / float((frac_split[3]).replace('003', ''))
return (float((frac_split[1]).replace('003', ''))
/ float((frac_split[3]).replace('003', '')))
else:
frac_split = x.split('/')
if not len(frac_split) == 2:
@@ -14,16 +18,30 @@ def parse_fraction(x):
except ZeroDivisionError:
raise ValueError
def parse_amount(x):
amount = 0
unit = ''
did_check_frac = False
end = 0
while end < len(x) and (x[end] in string.digits or ((x[end] == '.' or x[end] == ',') and end + 1 < len(x) and x[end+1] in string.digits)):
while (
end < len(x)
and (
x[end] in string.digits
or (
(x[end] == '.' or x[end] == ',' or x[end] == '/')
and end + 1 < len(x)
and x[end + 1] in string.digits
)
)
):
end += 1
if end > 0:
amount = float(x[:end].replace(',', '.'))
if "/" in x[:end]:
amount = parse_fraction(x[:end])
else:
amount = float(x[:end].replace(',', '.'))
else:
amount = parse_fraction(x[0])
end += 1
@@ -34,55 +52,61 @@ def parse_amount(x):
else:
try:
amount += parse_fraction(x[end])
unit = x[end+1:]
unit = x[end + 1:]
except ValueError:
unit = x[end:]
return amount, unit
def parse_ingredient_with_comma(tokens):
ingredient = ''
note = ''
start = 0
# search for first occurence of an argument ending in a comma
# search for first occurrence of an argument ending in a comma
while start < len(tokens) and not tokens[start].endswith(','):
start += 1
if start == len(tokens):
# no token ending in a comma found -> use everything as ingredient
ingredient = ' '.join(tokens)
else:
ingredient = ' '.join(tokens[:start+1])[:-1]
note = ' '.join(tokens[start+1:])
ingredient = ' '.join(tokens[:start + 1])[:-1]
note = ' '.join(tokens[start + 1:])
return ingredient, note
def parse_ingredient(tokens):
ingredient = ''
note = ''
if tokens[-1].endswith(')'):
# Check if the matching opening bracket is in the same token
if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]):
return parse_ingredient_with_comma(tokens)
# last argument ends with closing bracket -> look for opening bracket
start = len(tokens) - 1
while not tokens[start].startswith('(') and not start == 0:
start -= 1
if start == 0:
# the whole list is wrapped in brackets -> assume it is an error (e.g. assumed first argument was the unit)
# the whole list is wrapped in brackets -> assume it is an error (e.g. assumed first argument was the unit) # noqa: E501
raise ValueError
elif start < 0:
# no opening bracket anywhere -> just ignore the last bracket
ingredient, note = parse_ingredient_with_comma(tokens)
else:
# opening bracket found -> split in ingredient and note, remove brackets from note
# opening bracket found -> split in ingredient and note, remove brackets from note # noqa: E501
note = ' '.join(tokens[start:])[1:-1]
ingredient = ' '.join(tokens[:start])
else:
ingredient, note = parse_ingredient_with_comma(tokens)
return ingredient, note
def parse(x):
# initialize default values
amount = 0
unit = ''
ingredient = ''
note = ''
tokens = x.split()
if len(tokens) == 1:
# there only is one argument, that must be the ingredient
@@ -91,19 +115,20 @@ def parse(x):
try:
# try to parse first argument as amount
amount, unit = parse_amount(tokens[0])
# only try to parse second argument as amount if there are at least three arguments
# if it already has a unit there can't be a fraction for the amount
# only try to parse second argument as amount if there are at least
# three arguments if it already has a unit there can't be
# a fraction for the amount
if len(tokens) > 2:
try:
if not unit == '':
# a unit is already found, no need to try the second argument for a fraction
# probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except
# a unit is already found, no need to try the second argument for a fraction # noqa: E501
# probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except # noqa: E501
raise ValueError
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½'
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½' # noqa: E501
amount += parse_fraction(tokens[1])
# assume that units can't end with a comma
if len(tokens) > 3 and not tokens[2].endswith(','):
# try to use third argument as unit and everything else as ingredient, use everything as ingredient if it fails
# try to use third argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501
try:
ingredient, note = parse_ingredient(tokens[3:])
unit = tokens[2]
@@ -114,7 +139,7 @@ def parse(x):
except ValueError:
# assume that units can't end with a comma
if not tokens[1].endswith(','):
# try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails
# try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501
try:
ingredient, note = parse_ingredient(tokens[2:])
unit = tokens[1]
@@ -123,9 +148,29 @@ def parse(x):
else:
ingredient, note = parse_ingredient(tokens[1:])
else:
# only two arguments, first one is the amount which means this is the ingredient
# only two arguments, first one is the amount
# which means this is the ingredient
ingredient = tokens[1]
except ValueError:
# can't parse first argument as amount -> no unit -> parse everything as ingredient
ingredient, note = parse_ingredient(tokens)
try:
# can't parse first argument as amount
# -> no unit -> parse everything as ingredient
ingredient, note = parse_ingredient(tokens)
except ValueError:
ingredient = ' '.join(tokens[1:])
return amount, unit.strip(), ingredient.strip(), note.strip()
# small utility functions to prevent emtpy unit/food creation
def get_unit(unit, space):
if len(unit) > 0:
u, created = Unit.objects.get_or_create(name=unit, space=space)
return u
return None
def get_food(food, space):
if len(food) > 0:
f, created = Food.objects.get_or_create(name=food, space=space)
return f
return None

View File

@@ -1,5 +1,4 @@
import markdown
from markdown.treeprocessors import Treeprocessor
@@ -21,4 +20,8 @@ class StyleTreeprocessor(Treeprocessor):
class MarkdownFormatExtension(markdown.Extension):
def extendMarkdown(self, md, md_globals):
md.treeprocessors.register(StyleTreeprocessor(), 'StyleTreeprocessor', 10)
md.treeprocessors.register(
StyleTreeprocessor(),
'StyleTreeprocessor',
10
)

View File

@@ -1,4 +1,5 @@
"""A more liberal autolinker
"""
A more liberal autolinker
Inspired by Django's urlize function.
@@ -45,27 +46,30 @@ URLIZE_RE = '(%s)' % '|'.join([
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:
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. """
@@ -73,9 +77,12 @@ class UrlizeExtension(markdown.Extension):
""" 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()

View File

@@ -1,5 +1,4 @@
# Permission Config
from cookbook.helper.permission_helper import CustomIsUser, CustomIsOwner, CustomIsAdmin, CustomIsGuest
from cookbook.helper.permission_helper import CustomIsUser
class PermissionConfig:

View File

@@ -1,20 +1,19 @@
"""
Source: https://djangosnippets.org/snippets/1703/
"""
from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import ModelFormMixin
from cookbook.models import ShareLink
from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy, reverse
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from rest_framework import permissions
from rest_framework.permissions import SAFE_METHODS
from cookbook.models import ShareLink
# Helper Functions
def get_allowed_groups(groups_required):
"""
@@ -34,8 +33,8 @@ def get_allowed_groups(groups_required):
def has_group_permission(user, groups):
"""
Tests if a given user is member of a certain group (or any higher group)
Superusers always bypass permission checks. Unauthenticated users cant be member of any
group thus always return false.
Superusers always bypass permission checks.
Unauthenticated users cant be member of any group thus always return false.
:param user: django auth user object
:param groups: list or tuple of groups the user should be checked for
:return: True if user is in allowed groups, false otherwise
@@ -44,7 +43,7 @@ def has_group_permission(user, groups):
return False
groups_allowed = get_allowed_groups(groups)
if user.is_authenticated:
if user.is_superuser | bool(user.groups.filter(name__in=groups_allowed)):
if bool(user.groups.filter(name__in=groups_allowed)):
return True
return False
@@ -52,24 +51,19 @@ def has_group_permission(user, groups):
def is_object_owner(user, obj):
"""
Tests if a given user is the owner of a given object
test performed by checking user against the objects user and create_by field (if exists)
test performed by checking user against the objects user
and create_by field (if exists)
superusers bypass all checks, unauthenticated users cannot own anything
:param user django auth user object
:param obj any object that should be tested
:return: true if user is owner of object, false otherwise
"""
# TODO this could be improved/cleaned up by adding get_owner methods to all models that allow owner checks
if not user.is_authenticated:
return False
if user.is_superuser:
return True
if owner := getattr(obj, 'created_by', None):
return owner == user
if owner := getattr(obj, 'user', None):
return owner == user
if getattr(obj, 'get_owner', None):
try:
return obj.get_owner() == user
return False
except:
return False
def is_object_shared(user, obj):
@@ -81,12 +75,11 @@ def is_object_shared(user, obj):
:param obj any object that should be tested
:return: true if user is shared for object, false otherwise
"""
# TODO this could be improved/cleaned up by adding share checks for relevant objects
# TODO this could be improved/cleaned up by adding
# share checks for relevant objects
if not user.is_authenticated:
return False
if user.is_superuser:
return True
return user in obj.shared.all()
return user in obj.get_shared()
def share_link_valid(recipe, share):
@@ -94,7 +87,7 @@ def share_link_valid(recipe, share):
Verifies the validity of a share uuid
:param recipe: recipe object
:param share: share uuid
:return: true if a share link with the given recipe and uuid exists, false otherwise
:return: true if a share link with the given recipe and uuid exists
"""
try:
return True if ShareLink.objects.filter(recipe=recipe, uuid=share).exists() else False
@@ -106,8 +99,8 @@ def share_link_valid(recipe, share):
def group_required(*groups_required):
"""
Decorator that tests the requesting user to be member of at least one of the provided groups
or higher level groups
Decorator that tests the requesting user to be member
of at least one of the provided groups or higher level groups
:param groups_required: list of required groups
:return: true if member of group, false otherwise
"""
@@ -115,7 +108,7 @@ def group_required(*groups_required):
def in_groups(u):
return has_group_permission(u, groups_required)
return user_passes_test(in_groups, login_url='index')
return user_passes_test(in_groups, login_url='view_no_perm')
class GroupRequiredMixin(object):
@@ -130,6 +123,14 @@ class GroupRequiredMixin(object):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
try:
obj = self.get_object()
if obj.get_space() != request.space:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
except AttributeError:
pass
return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs)
@@ -138,12 +139,20 @@ 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'))
return HttpResponseRedirect(reverse_lazy('account_login') + '?next=' + request.path)
else:
if not is_object_owner(request.user, self.get_object()):
messages.add_message(request, messages.ERROR, _('You cannot interact with this object as it is not owned by you!'))
return HttpResponseRedirect(reverse('index'))
try:
obj = self.get_object()
if obj.get_space() != request.space:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
except AttributeError:
pass
return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs)
@@ -155,7 +164,7 @@ class CustomIsOwner(permissions.BasePermission):
verifies user has ownership over object
(either user or created_by or user is request user)
"""
message = _('You cannot interact with this object as it is not owned by you!')
message = _('You cannot interact with this object as it is not owned by you!') # noqa: E501
def has_permission(self, request, view):
return request.user.is_authenticated
@@ -164,12 +173,13 @@ class CustomIsOwner(permissions.BasePermission):
return is_object_owner(request.user, obj)
class CustomIsShared(permissions.BasePermission): # TODO function duplicate/too similar name
# TODO function duplicate/too similar name
class CustomIsShared(permissions.BasePermission):
"""
Custom permission class for django rest framework views
verifies user is shared for the object he is trying to access
"""
message = _('You cannot interact with this object as it is not owned by you!')
message = _('You cannot interact with this object as it is not owned by you!') # noqa: E501
def has_permission(self, request, view):
return request.user.is_authenticated

View File

@@ -1,20 +1,19 @@
import json
import random
import re
import unicodedata
from json import JSONDecodeError
import microdata
from bs4 import BeautifulSoup
from cookbook.helper.ingredient_parser import parse as parse_ingredient
from cookbook.models import Keyword
from django.http import JsonResponse
from django.utils.dateparse import parse_duration
from django.utils.translation import gettext as _
from cookbook.models import Keyword
from cookbook.helper.ingredient_parser import parse as parse_ingredient
from recipe_scrapers import _utils
def get_from_html(html_text, url):
def get_from_html(html_text, url, space):
soup = BeautifulSoup(html_text, "html.parser")
# first try finding ld+json as its most common
@@ -31,26 +30,37 @@ def get_from_html(html_text, url):
if '@type' in x and x['@type'] == 'Recipe':
ld_json_item = x
if '@type' in ld_json_item and ld_json_item['@type'] == 'Recipe':
return find_recipe_json(ld_json_item, url)
except JSONDecodeError as e:
return JsonResponse({'error': True, 'msg': _('The requested site provided malformed data and cannot be read.')}, status=400)
if ('@type' in ld_json_item
and ld_json_item['@type'] == 'Recipe'):
return JsonResponse(find_recipe_json(ld_json_item, url, space))
except JSONDecodeError:
return JsonResponse(
{
'error': True,
'msg': _('The requested site provided malformed data and cannot be read.') # noqa: E501
},
status=400)
# now try to find microdata
items = microdata.get_items(html_text)
for i in items:
md_json = json.loads(i.json())
if 'schema.org/Recipe' in str(md_json['type']):
return find_recipe_json(md_json['properties'], url)
return JsonResponse(find_recipe_json(md_json['properties'], url, space))
return JsonResponse({'error': True, 'msg': _('The requested site does not provide any recognized data format to import the recipe from.')}, status=400)
return JsonResponse(
{
'error': True,
'msg': _('The requested site does not provide any recognized data format to import the recipe from.') # noqa: E501
},
status=400)
def find_recipe_json(ld_json, url):
def find_recipe_json(ld_json, url, space):
if type(ld_json['name']) == list:
try:
ld_json['name'] = ld_json['name'][0]
except:
except Exception:
ld_json['name'] = 'ERROR'
# some sites use ingredients instead of recipeIngredients
@@ -59,8 +69,11 @@ def find_recipe_json(ld_json, url):
if 'recipeIngredient' in ld_json:
# some pages have comma separated ingredients in a single array entry
if len(ld_json['recipeIngredient']) == 1 and len(ld_json['recipeIngredient'][0]) > 30:
ld_json['recipeIngredient'] = ld_json['recipeIngredient'][0].split(',')
if (len(ld_json['recipeIngredient']) == 1
and type(ld_json['recipeIngredient']) == list):
ld_json['recipeIngredient'] = ld_json['recipeIngredient'][0].split(',') # noqa: E501
elif type(ld_json['recipeIngredient']) == str:
ld_json['recipeIngredient'] = ld_json['recipeIngredient'].split(',')
for x in ld_json['recipeIngredient']:
if '\n' in x:
@@ -71,38 +84,49 @@ def find_recipe_json(ld_json, url):
ingredients = []
for x in ld_json['recipeIngredient']:
try:
amount, unit, ingredient, note = parse_ingredient(x)
if ingredient:
ingredients.append({'amount': amount, 'unit': {'text': unit, 'id': random.randrange(10000, 99999)}, 'ingredient': {'text': ingredient, 'id': random.randrange(10000, 99999)}, "note": note, 'original': x})
except:
pass
if x.replace(' ', '') != '':
x = x.replace('&frac12;', "0.5").replace('&frac14;', "0.25").replace('&frac34;', "0.75")
try:
amount, unit, ingredient, note = parse_ingredient(x)
if ingredient:
ingredients.append(
{
'amount': amount,
'unit': {
'text': unit,
'id': random.randrange(10000, 99999)
},
'ingredient': {
'text': ingredient,
'id': random.randrange(10000, 99999)
},
'note': note,
'original': x
}
)
except Exception:
ingredients.append(
{
'amount': 0,
'unit': {
'text': '',
'id': random.randrange(10000, 99999)
},
'ingredient': {
'text': x,
'id': random.randrange(10000, 99999)
},
'note': '',
'original': x
}
)
ld_json['recipeIngredient'] = ingredients
else:
ld_json['recipeIngredient'] = []
if 'keywords' in ld_json:
keywords = []
# keywords as string
if type(ld_json['keywords']) == str:
ld_json['keywords'] = ld_json['keywords'].split(',')
# keywords as string in list
if type(ld_json['keywords']) == list and len(ld_json['keywords']) == 1 and ',' in ld_json['keywords'][0]:
ld_json['keywords'] = ld_json['keywords'][0].split(',')
# keywords as list
for kw in ld_json['keywords']:
if k := Keyword.objects.filter(name=kw).first():
keywords.append({'id': str(k.id), 'text': str(k).strip()})
else:
keywords.append({'id': "null", 'text': kw.strip()})
ld_json['keywords'] = keywords
else:
ld_json['keywords'] = []
ld_json['keywords'] = parse_keywords(listify_keywords(ld_json['keywords']), space)
if 'recipeInstructions' in ld_json:
instructions = ''
@@ -125,14 +149,15 @@ def find_recipe_json(ld_json, url):
instructions += str(i)
ld_json['recipeInstructions'] = instructions
ld_json['recipeInstructions'] = re.sub(r'\n\s*\n', '\n\n', ld_json['recipeInstructions'])
ld_json['recipeInstructions'] = re.sub(' +', ' ', ld_json['recipeInstructions'])
ld_json['recipeInstructions'] = ld_json['recipeInstructions'].replace('<p>', '')
ld_json['recipeInstructions'] = ld_json['recipeInstructions'].replace('</p>', '')
ld_json['recipeInstructions'] = re.sub(r'\n\s*\n', '\n\n', ld_json['recipeInstructions']) # noqa: E501
ld_json['recipeInstructions'] = re.sub(' +', ' ', ld_json['recipeInstructions']) # noqa: E501
ld_json['recipeInstructions'] = ld_json['recipeInstructions'].replace('<p>', '') # noqa: E501
ld_json['recipeInstructions'] = ld_json['recipeInstructions'].replace('</p>', '') # noqa: E501
else:
ld_json['recipeInstructions'] = ''
ld_json['recipeInstructions'] += '\n\n' + _('Imported from') + ' ' + url
if url != '':
ld_json['recipeInstructions'] += '\n\n' + _('Imported from') + ' ' + url
if 'image' in ld_json:
# check if list of images is returned, take first if so
@@ -148,9 +173,14 @@ def find_recipe_json(ld_json, url):
if 'cookTime' in ld_json:
try:
if type(ld_json['cookTime']) == list and len(ld_json['cookTime']) > 0:
if (type(ld_json['cookTime']) == list
and len(ld_json['cookTime']) > 0):
ld_json['cookTime'] = ld_json['cookTime'][0]
ld_json['cookTime'] = round(parse_duration(ld_json['cookTime']).seconds / 60)
ld_json['cookTime'] = round(
parse_duration(
ld_json['cookTime']
).seconds / 60
)
except TypeError:
ld_json['cookTime'] = 0
else:
@@ -158,16 +188,156 @@ def find_recipe_json(ld_json, url):
if 'prepTime' in ld_json:
try:
if type(ld_json['prepTime']) == list and len(ld_json['prepTime']) > 0:
if (type(ld_json['prepTime']) == list
and len(ld_json['prepTime']) > 0):
ld_json['prepTime'] = ld_json['prepTime'][0]
ld_json['prepTime'] = round(parse_duration(ld_json['prepTime']).seconds / 60)
ld_json['prepTime'] = round(
parse_duration(
ld_json['prepTime']
).seconds / 60
)
except TypeError:
ld_json['prepTime'] = 0
else:
ld_json['prepTime'] = 0
ld_json['servings'] = 1
try:
if 'recipeYield' in ld_json:
if type(ld_json['recipeYield']) == str:
ld_json['servings'] = int(re.findall(r'\b\d+\b', ld_json['recipeYield'])[0])
elif type(ld_json['recipeYield']) == list:
ld_json['servings'] = int(re.findall(r'\b\d+\b', ld_json['recipeYield'][0])[0])
except Exception as e:
print(e)
for key in list(ld_json):
if key not in ['prepTime', 'cookTime', 'image', 'recipeInstructions', 'keywords', 'name', 'recipeIngredient']:
if key not in [
'prepTime', 'cookTime', 'image', 'recipeInstructions',
'keywords', 'name', 'recipeIngredient', 'servings', 'description'
]:
ld_json.pop(key, None)
return JsonResponse(ld_json)
return ld_json
def get_from_scraper(scrape, space):
# converting the scrape_me object to the existing json format based on ld+json
recipe_json = {}
recipe_json['name'] = scrape.title()
try:
description = scrape.schema.data.get("description") or ''
recipe_json['prepTime'] = _utils.get_minutes(scrape.schema.data.get("prepTime")) or 0
recipe_json['cookTime'] = _utils.get_minutes(scrape.schema.data.get("cookTime")) or 0
except AttributeError:
description = ''
recipe_json['prepTime'] = 0
recipe_json['cookTime'] = 0
recipe_json['description'] = description
try:
servings = scrape.yields()
servings = int(re.findall(r'\b\d+\b', servings)[0])
except (AttributeError, ValueError, IndexError):
servings = 1
recipe_json['servings'] = servings
if recipe_json['cookTime'] + recipe_json['prepTime'] == 0:
try:
recipe_json['prepTime'] = scrape.total_time()
except AttributeError:
pass
try:
recipe_json['image'] = scrape.image()
except AttributeError:
pass
keywords = []
try:
if scrape.schema.data.get("keywords"):
keywords += listify_keywords(scrape.schema.data.get("keywords"))
if scrape.schema.data.get('recipeCategory'):
keywords += listify_keywords(scrape.schema.data.get("recipeCategory"))
if scrape.schema.data.get('recipeCuisine'):
keywords += listify_keywords(scrape.schema.data.get("recipeCuisine"))
recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), space)
except AttributeError:
recipe_json['keywords'] = keywords
try:
ingredients = []
for x in scrape.ingredients():
try:
amount, unit, ingredient, note = parse_ingredient(x)
if ingredient:
ingredients.append(
{
'amount': amount,
'unit': {
'text': unit,
'id': random.randrange(10000, 99999)
},
'ingredient': {
'text': ingredient,
'id': random.randrange(10000, 99999)
},
'note': note,
'original': x
}
)
except Exception:
ingredients.append(
{
'amount': 0,
'unit': {
'text': '',
'id': random.randrange(10000, 99999)
},
'ingredient': {
'text': x,
'id': random.randrange(10000, 99999)
},
'note': '',
'original': x
}
)
recipe_json['recipeIngredient'] = ingredients
except AttributeError:
recipe_json['recipeIngredient'] = ingredients
try:
recipe_json['recipeInstructions'] = scrape.instructions()
except AttributeError:
recipe_json['recipeInstructions'] = ""
recipe_json['recipeInstructions'] += "\n\nImported from " + scrape.url
return recipe_json
def parse_keywords(keyword_json, space):
keywords = []
# keywords as list
for kw in keyword_json:
if k := Keyword.objects.filter(name=kw, space=space).first():
keywords.append({'id': str(k.id), 'text': str(k)})
else:
keywords.append({'id': random.randrange(1111111, 9999999, 1), 'text': kw})
return keywords
def listify_keywords(keyword_list):
# keywords as string
if type(keyword_list) == str:
keyword_list = keyword_list.split(',')
# keywords as string in list
if (type(keyword_list) == list
and len(keyword_list) == 1
and ',' in keyword_list[0]):
keyword_list = keyword_list[0].split(',')
return [x.strip() for x in keyword_list]

View File

@@ -0,0 +1,33 @@
from django.shortcuts import redirect
from django.urls import reverse
from django_scopes import scope, scopes_disabled
from cookbook.views import views
class ScopeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
if request.path.startswith('/admin/'):
with scopes_disabled():
return self.get_response(request)
with scopes_disabled():
if request.user.userpreference.space is None and not reverse('account_logout') in request.path:
return views.no_space(request)
if request.user.groups.count() == 0 and not reverse('account_logout') in request.path:
return views.no_groups(request)
request.space = request.user.userpreference.space
# with scopes_disabled():
with scope(space=request.space):
return self.get_response(request)
else:
with scopes_disabled():
request.space = None
return self.get_response(request)

View File

@@ -0,0 +1,64 @@
import bleach
import markdown as md
from bleach_allowlist import markdown_attrs, markdown_tags
from cookbook.helper.mdx_attributes import MarkdownFormatExtension
from cookbook.helper.mdx_urlize import UrlizeExtension
from jinja2 import Template, TemplateSyntaxError, UndefinedError
from gettext import gettext as _
class IngredientObject(object):
amount = ""
unit = ""
food = ""
note = ""
def __init__(self, ingredient):
if ingredient.no_amount:
self.amount = ""
else:
self.amount = f"<scalable-number v-bind:number='{bleach.clean(str(ingredient.amount))}' v-bind:factor='ingredient_factor'></scalable-number>"
if ingredient.unit:
self.unit = bleach.clean(str(ingredient.unit))
else:
self.unit = ""
self.food = bleach.clean(str(ingredient.food))
self.note = bleach.clean(str(ingredient.note))
def __str__(self):
ingredient = self.amount
if self.unit != "":
ingredient += f' {self.unit}'
return f'{ingredient} {self.food}'
def render_instructions(step): # TODO deduplicate markdown cleanup code
instructions = step.instruction
tags = markdown_tags + [
'pre', 'table', 'td', 'tr', 'th', 'tbody', 'style', 'thead'
]
parsed_md = md.markdown(
instructions,
extensions=[
'markdown.extensions.fenced_code', 'tables',
UrlizeExtension(), MarkdownFormatExtension()
]
)
markdown_attrs['*'] = markdown_attrs['*'] + ['class']
instructions = bleach.clean(parsed_md, tags, markdown_attrs)
ingredients = []
for i in step.ingredients.all():
ingredients.append(IngredientObject(i))
try:
template = Template(instructions)
instructions = template.render(ingredients=ingredients)
except TemplateSyntaxError:
return _('Could not parse template code.') + ' Error: Template Syntax broken'
except UndefinedError:
return _('Could not parse template code.') + ' Error: Undefined Error'
return instructions

View File

@@ -0,0 +1,59 @@
import json
import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
class Pepperplate(Integration):
def get_recipe_from_file(self, file):
ingredient_mode = False
direction_mode = False
ingredients = []
directions = []
for fl in file.readlines():
line = fl.decode("utf-8")
if 'Title:' in line:
title = line.replace('Title:', '').replace('"', '').strip()
if 'Description:' in line:
description = line.replace('Description:', '').strip()
if 'Original URL:' in line or 'Source:' in line or 'Yield:' in line or 'Total:' in line:
if len(line.strip().split(':')[1]) > 0:
directions.append(line.strip() + '\n')
if ingredient_mode:
if len(line) > 2 and 'Instructions:' not in line:
ingredients.append(line.strip())
if direction_mode:
if len(line) > 2:
directions.append(line.strip() + '\n')
if 'Ingredients:' in line:
ingredient_mode = True
if 'Instructions:' in line:
ingredient_mode = False
direction_mode = True
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n'
)
for ingredient in ingredients:
if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -0,0 +1,60 @@
import re
from django.utils.translation import gettext as _
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
class ChefTap(Integration):
def import_file_name_filter(self, zip_info_object):
print("testing", zip_info_object.filename)
return re.match(r'^cheftap_export/([A-Za-z\d\w\s-])+.txt$', zip_info_object.filename)
def get_recipe_from_file(self, file):
source_url = ''
ingredient_mode = 0
ingredients = []
directions = []
for i, fl in enumerate(file.readlines(), start=0):
line = fl.decode("utf-8")
if i == 0:
title = line.strip()
else:
if line.startswith('https:') or line.startswith('http:'):
source_url = line.strip()
else:
if ingredient_mode == 1 and len(line.strip()) == 0:
ingredient_mode = 2
if re.match(r'^([0-9])[^.](.)*$', line) and ingredient_mode < 2:
ingredient_mode = 1
ingredients.append(line.strip())
else:
directions.append(line.strip())
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, space=self.request.space, )
step = Step.objects.create(instruction='\n'.join(directions))
if source_url != '':
step.instruction += '\n' + source_url
step.save()
for ingredient in ingredients:
if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -0,0 +1,79 @@
import json
import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
class Chowdown(Integration):
def import_file_name_filter(self, zip_info_object):
print("testing", zip_info_object.filename)
return re.match(r'^(_)*recipes/([A-Za-z\d\s-])+.md$', zip_info_object.filename)
def get_recipe_from_file(self, file):
ingredient_mode = False
direction_mode = False
description_mode = False
ingredients = []
directions = []
descriptions = []
for fl in file.readlines():
line = fl.decode("utf-8")
if 'title:' in line:
title = line.replace('title:', '').replace('"', '').strip()
if 'image:' in line:
image = line.replace('image:', '').strip()
if 'tags:' in line:
tags = line.replace('tags:', '').strip()
if ingredient_mode:
if len(line) > 2 and 'directions:' not in line:
ingredients.append(line[2:])
if '---' in line and direction_mode:
direction_mode = False
description_mode = True
if direction_mode:
if len(line) > 2:
directions.append(line[2:])
if 'ingredients:' in line:
ingredient_mode = True
if 'directions:' in line:
ingredient_mode = False
direction_mode = True
if description_mode and len(line) > 3 and '---' not in line:
descriptions.append(line)
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, space=self.request.space)
for k in tags.split(','):
keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space)
recipe.keywords.add(keyword)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions)
)
for ingredient in ingredients:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
for f in self.files:
if '.zip' in f['name']:
import_zip = ZipFile(f['file'])
for z in import_zip.filelist:
if re.match(f'^images/{image}$', z.filename):
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -0,0 +1,34 @@
import json
from io import BytesIO
from zipfile import ZipFile
from rest_framework.renderers import JSONRenderer
from cookbook.integration.integration import Integration
from cookbook.serializer import RecipeExportSerializer
class Default(Integration):
def get_recipe_from_file(self, file):
recipe_zip = ZipFile(file)
recipe_string = recipe_zip.read('recipe.json').decode("utf-8")
recipe = self.decode_recipe(recipe_string)
if 'image.png' in recipe_zip.namelist():
self.import_recipe_image(recipe, BytesIO(recipe_zip.read('image.png')))
return recipe
def decode_recipe(self, string):
data = json.loads(string)
serialized_recipe = RecipeExportSerializer(data=data, context={'request': self.request})
if serialized_recipe.is_valid():
recipe = serialized_recipe.save()
return recipe
return None
def get_file_from_recipe(self, recipe):
export = RecipeExportSerializer(recipe).data
return 'recipe.json', JSONRenderer().render(export).decode("utf-8")

View File

@@ -0,0 +1,56 @@
import base64
import json
from io import BytesIO
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient
class Domestica(Integration):
def get_recipe_from_file(self, file):
recipe = Recipe.objects.create(
name=file['name'].strip(),
created_by=self.request.user, internal=True,
space=self.request.space)
if file['servings'] != '':
recipe.servings = file['servings']
if file['timeCook'] != '':
recipe.waiting_time = file['timeCook']
if file['timePrep'] != '':
recipe.working_time = file['timePrep']
recipe.save()
step = Step.objects.create(
instruction=file['directions']
)
if file['source'] != '':
step.instruction += '\n' + file['source']
for ingredient in file['ingredients'].split('\n'):
if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
if file['image'] != '':
self.import_recipe_image(recipe, BytesIO(base64.b64decode(file['image'].replace('data:image/jpeg;base64,', ''))))
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')
def split_recipe_file(self, file):
return json.loads(file.read().decode("utf-8"))

View File

@@ -0,0 +1,186 @@
import datetime
import json
import uuid
from io import BytesIO, StringIO
from zipfile import ZipFile, BadZipFile
from django.core.files import File
from django.http import HttpResponse
from django.utils.formats import date_format
from django.utils.translation import gettext as _
from django_scopes import scope
from cookbook.forms import ImportExportBase
from cookbook.models import Keyword, Recipe
class Integration:
request = None
keyword = None
files = None
export_type = None
ignored_recipes = []
def __init__(self, request, export_type):
"""
Integration for importing and exporting recipes
:param request: request context of import session (used to link user to created objects)
"""
self.request = request
self.export_type = export_type
self.keyword = Keyword.objects.create(
name=f'Import {export_type} {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}.{datetime.datetime.now().strftime("%S")}',
description=f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}. Type: {export_type}',
icon='📥',
space=request.space
)
def do_export(self, recipes):
"""
Perform the export based on a list of recipes
:param recipes: list of recipe objects
:return: HttpResponse with a ZIP file that is directly downloaded
"""
# TODO this is temporary, find a better solution for different export formats when doing other exporters
if self.export_type != ImportExportBase.RECIPESAGE:
export_zip_stream = BytesIO()
export_zip_obj = ZipFile(export_zip_stream, 'w')
for r in recipes:
if r.internal and r.space == self.request.space:
recipe_zip_stream = BytesIO()
recipe_zip_obj = ZipFile(recipe_zip_stream, 'w')
recipe_stream = StringIO()
filename, data = self.get_file_from_recipe(r)
recipe_stream.write(data)
recipe_zip_obj.writestr(filename, recipe_stream.getvalue())
recipe_stream.close()
try:
recipe_zip_obj.write(r.image.path, 'image.png')
except ValueError:
pass
recipe_zip_obj.close()
export_zip_obj.writestr(str(r.pk) + '.zip', recipe_zip_stream.getvalue())
export_zip_obj.close()
response = HttpResponse(export_zip_stream.getvalue(), content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="export.zip"'
return response
else:
json_list = []
for r in recipes:
json_list.append(self.get_file_from_recipe(r))
response = HttpResponse(json.dumps(json_list), content_type='application/force-download')
response['Content-Disposition'] = 'attachment; filename="recipes.json"'
return response
def import_file_name_filter(self, zip_info_object):
"""
Since zipfile.namelist() returns all files in all subdirectories this function allows filtering of files
If false is returned the file will be ignored
By default all files are included
:param zip_info_object: ZipInfo object
:return: Boolean if object should be included
"""
return True
def do_import(self, files, il, import_duplicates):
"""
Imports given files
:param import_duplicates: if true duplicates are imported as well
:param files: List of in memory files
:param il: Import Log object to refresh while running
:return: HttpResponseRedirect to the recipe search showing all imported recipes
"""
with scope(space=self.request.space):
self.keyword.name = _('Import') + ' ' + str(il.pk)
self.keyword.save()
try:
self.files = files
for f in files:
if '.zip' in f['name'] or '.paprikarecipes' in f['name']:
import_zip = ZipFile(f['file'])
for z in import_zip.filelist:
if self.import_file_name_filter(z):
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
import_zip.close()
elif '.json' in f['name'] or '.txt' in f['name']:
data_list = self.split_recipe_file(f['file'])
for d in data_list:
recipe = self.get_recipe_from_file(d)
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
else:
recipe = self.get_recipe_from_file(f['file'])
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
except BadZipFile:
il.msg += 'ERROR ' + _('Importer expected a .zip file. Did you choose the correct importer type for your data ?') + '\n'
if len(self.ignored_recipes) > 0:
il.msg += '\n' + _('The following recipes were ignored because they already existed:') + ' ' + ', '.join(self.ignored_recipes) + '\n\n'
il.keyword = self.keyword
il.msg += (_('Imported %s recipes.') % Recipe.objects.filter(keywords=self.keyword).count()) + '\n'
il.running = False
il.save()
def handle_duplicates(self, recipe, import_duplicates):
"""
Checks if a recipe is already present, if so deletes it
:param recipe: Recipe object
:param import_duplicates: if duplicates should be imported
"""
if Recipe.objects.filter(space=self.request.space, name=recipe.name).count() > 1 and not import_duplicates:
recipe.delete()
self.ignored_recipes.append(recipe.name)
@staticmethod
def import_recipe_image(recipe, image_file):
"""
Adds an image to a recipe naming it correctly
:param recipe: Recipe object
:param image_file: ByteIO stream containing the image
"""
recipe.image = File(image_file, name=f'{uuid.uuid4()}_{recipe.pk}.png')
recipe.save()
def get_recipe_from_file(self, file):
"""
Takes any file like object and converts it into a recipe
:param file: ByteIO or any file like object, depends on provider
:return: Recipe object
"""
raise NotImplementedError('Method not implemented in integration')
def split_recipe_file(self, file):
"""
Takes a file that contains multiple recipes and splits it into a list of strings of various formats (e.g. json, text, ..)
:param file: ByteIO or any file like object, depends on provider
:return: list of strings
"""
raise NotImplementedError('Method not implemented in integration')
def get_file_from_recipe(self, recipe):
"""
Takes a recipe object and converts it to a string (depending on the format)
returns both the filename of the exported file and the file contents
:param recipe: Recipe object that should be converted
:returns:
- name - file name in export
- data - string content for file to get created in export zip
"""
raise NotImplementedError('Method not implemented in integration')

View File

@@ -0,0 +1,52 @@
import json
import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
class Mealie(Integration):
def import_file_name_filter(self, zip_info_object):
return re.match(r'^recipes/([A-Za-z\d-])+.json$', zip_info_object.filename)
def get_recipe_from_file(self, file):
recipe_json = json.loads(file.getvalue().decode("utf-8"))
recipe = Recipe.objects.create(
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
created_by=self.request.user, internal=True, space=self.request.space)
# TODO parse times (given in PT2H3M )
ingredients_added = False
for s in recipe_json['recipeInstructions']:
step = Step.objects.create(
instruction=s['text']
)
if not ingredients_added:
ingredients_added = True
for ingredient in recipe_json['recipeIngredient']:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
for f in self.files:
if '.zip' in f['name']:
import_zip = ZipFile(f['file'])
for z in import_zip.filelist:
if re.match(f'^images/{recipe_json["slug"]}.jpg$', z.filename):
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -0,0 +1,83 @@
import json
import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
class MealMaster(Integration):
def get_recipe_from_file(self, file):
print('------------ getting recipe')
servings = 1
ingredients = []
directions = []
for line in file.replace('\r', '').split('\n'):
print('testing line')
if not line.startswith('MMMMM') and line.strip != '':
if 'Title:' in line:
title = line.replace('Title:', '').strip()
else:
if 'Categories:' in line:
tags = line.replace('Categories:', '').strip()
else:
if 'Yield:' in line:
servings_text = line.replace('Yield:', '').strip()
else:
if re.match('\s{2,}([0-9])+', line):
ingredients.append(line.strip())
else:
directions.append(line.strip())
try:
servings = re.findall('([0-9])+', servings_text)[0]
except Exception as e:
print('failed parsing servings ', e)
recipe = Recipe.objects.create(name=title, servings=servings, created_by=self.request.user, internal=True, space=self.request.space)
for k in tags.split(','):
keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space)
recipe.keywords.add(keyword)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n'
)
for ingredient in ingredients:
if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')
def split_recipe_file(self, file):
recipe_list = []
current_recipe = ''
for fl in file.readlines():
line = fl.decode("ANSI")
if (line.startswith('MMMMM') or line.startswith('-----')) and 'meal-master' in line.lower():
if current_recipe != '':
recipe_list.append(current_recipe)
current_recipe = ''
else:
current_recipe = ''
else:
current_recipe += line + '\n'
if current_recipe != '':
recipe_list.append(current_recipe)
return recipe_list

View File

@@ -0,0 +1,54 @@
import json
import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
class NextcloudCookbook(Integration):
def import_file_name_filter(self, zip_info_object):
return re.match(r'^Recipes/([A-Za-z\d\s])+/recipe.json$', zip_info_object.filename)
def get_recipe_from_file(self, file):
recipe_json = json.loads(file.getvalue().decode("utf-8"))
recipe = Recipe.objects.create(
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
created_by=self.request.user, internal=True,
servings=recipe_json['recipeYield'], space=self.request.space)
# TODO parse times (given in PT2H3M )
# TODO parse keywords
ingredients_added = False
for s in recipe_json['recipeInstructions']:
step = Step.objects.create(
instruction=s
)
if not ingredients_added:
ingredients_added = True
for ingredient in recipe_json['recipeIngredient']:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
for f in self.files:
if '.zip' in f['name']:
import_zip = ZipFile(f['file'])
for z in import_zip.filelist:
if re.match(f'^Recipes/{recipe.name}/full.jpg$', z.filename):
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -0,0 +1,73 @@
import base64
import gzip
import json
import re
from io import BytesIO
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword
from gettext import gettext as _
class Paprika(Integration):
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')
def get_recipe_from_file(self, file):
with gzip.open(file, 'r') as recipe_zip:
recipe_json = json.loads(recipe_zip.read().decode("utf-8"))
recipe = Recipe.objects.create(
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
created_by=self.request.user, internal=True, space=self.request.space)
try:
if re.match(r'([0-9])+\s(.)*', recipe_json['servings'] ):
s = recipe_json['servings'].split(' ')
recipe.servings = s[0]
recipe.servings_text = s[1]
if len(recipe_json['cook_time'].strip()) > 0:
recipe.waiting_time = re.findall(r'\d+', recipe_json['cook_time'])[0]
if len(recipe_json['prep_time'].strip()) > 0:
recipe.working_time = re.findall(r'\d+', recipe_json['prep_time'])[0]
except Exception:
pass
recipe.save()
instructions = recipe_json['directions']
if len(recipe_json['notes'].strip()) > 0:
instructions += '\n\n### ' + _('Notes') + ' \n' + recipe_json['notes']
if len(recipe_json['nutritional_info'].strip()) > 0:
instructions += '\n\n### ' + _('Nutritional Information') + ' \n' + recipe_json['nutritional_info']
if len(recipe_json['source'].strip()) > 0 or len(recipe_json['source_url'].strip()) > 0:
instructions += '\n\n### ' + _('Source') + ' \n' + recipe_json['source'].strip() + ' \n' + recipe_json['source_url'].strip()
step = Step.objects.create(
instruction=instructions
)
if 'categories' in recipe_json:
for c in recipe_json['categories']:
keyword, created = Keyword.objects.get_or_create(name=c.strip(), space=self.request.space)
recipe.keywords.add(keyword)
for ingredient in recipe_json['ingredients'].split('\n'):
if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])))
return recipe

View File

@@ -0,0 +1,93 @@
import base64
import json
from io import BytesIO
import requests
from rest_framework.renderers import JSONRenderer
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient
class RecipeSage(Integration):
def get_recipe_from_file(self, file):
recipe = Recipe.objects.create(
name=file['name'].strip(),
created_by=self.request.user, internal=True,
space=self.request.space)
try:
if file['recipeYield'] != '':
recipe.servings = int(file['recipeYield'])
if file['totalTime'] != '':
recipe.waiting_time = int(file['totalTime']) - int(file['timePrep'])
if file['prepTime'] != '':
recipe.working_time = int(file['timePrep'])
recipe.save()
except Exception as e:
print('failed to parse yield or time ', str(e))
ingredients_added = False
for s in file['recipeInstructions']:
step = Step.objects.create(
instruction=s['text']
)
if not ingredients_added:
ingredients_added = True
for ingredient in file['recipeIngredient']:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
if len(file['image']) > 0:
try:
response = requests.get(file['image'][0])
self.import_recipe_image(recipe, BytesIO(response.content))
except Exception as e:
print('failed to import image ', str(e))
return recipe
def get_file_from_recipe(self, recipe):
data = {
'@context': 'http://schema.org',
'@type': 'Recipe',
'creditText': '',
'isBasedOn': '',
'name': recipe.name,
'description': recipe.description,
'prepTime': str(recipe.working_time),
'totalTime': str(recipe.waiting_time + recipe.working_time),
'recipeYield': str(recipe.servings),
'image': [],
'recipeCategory': [],
'comment': [],
'recipeIngredient': [],
'recipeInstructions': [],
}
for s in recipe.steps.all():
if s.type != Step.TIME:
data['recipeInstructions'].append({
'@type': 'HowToStep',
'text': s.instruction
})
for i in s.ingredients.all():
data['recipeIngredient'].append(f'{float(i.amount)} {i.unit} {i.food}')
return data
def split_recipe_file(self, file):
return json.loads(file.read().decode("utf-8"))

View File

@@ -0,0 +1,82 @@
import json
import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
class RezKonv(Integration):
def get_recipe_from_file(self, file):
ingredient_mode = False
direction_mode = False
ingredients = []
directions = []
for line in file.replace('\r', '').split('\n'):
if 'Titel:' in line:
title = line.replace('Titel:', '').strip()
if 'Kategorien:' in line:
tags = line.replace('Kategorien:', '').strip()
if ingredient_mode and ('quelle' in line.lower() or 'source' in line.lower()):
ingredient_mode = False
if ingredient_mode:
if line != '' and '===' not in line and 'Zubereitung' not in line:
ingredients.append(line.strip())
if direction_mode:
if line.strip() != '' and line.strip() != '=====':
directions.append(line.strip())
if 'Zutaten:' in line:
ingredient_mode = True
if 'Zubereitung:' in line:
ingredient_mode = False
direction_mode = True
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, space=self.request.space)
for k in tags.split(','):
keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space)
recipe.keywords.add(keyword)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n'
)
for ingredient in ingredients:
if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')
def split_recipe_file(self, file):
recipe_list = []
current_recipe = ''
for fl in file.readlines():
line = fl.decode("ANSI")
if line.startswith('=====') and 'rezkonv' in line.lower():
if current_recipe != '':
recipe_list.append(current_recipe)
current_recipe = ''
else:
current_recipe = ''
else:
current_recipe += line + '\n'
if current_recipe != '':
recipe_list.append(current_recipe)
return recipe_list

View File

@@ -0,0 +1,60 @@
from django.utils.translation import gettext as _
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient
class Safron(Integration):
def get_recipe_from_file(self, file):
ingredient_mode = False
direction_mode = False
ingredients = []
directions = []
for fl in file.readlines():
line = fl.decode("utf-8")
if 'Title:' in line:
title = line.replace('Title:', '').strip()
if 'Description:' in line:
description = line.replace('Description:', '').strip()
if 'Yield:' in line:
directions.append(_('Servings') + ' ' + line.replace('Yield:', '').strip() + '\n')
if 'Cook:' in line:
directions.append(_('Waiting time') + ' ' + line.replace('Cook:', '').strip() + '\n')
if 'Prep:' in line:
directions.append(_('Preparation Time') + ' ' + line.replace('Prep:', '').strip() + '\n')
if 'Cookbook:' in line:
directions.append(_('Cookbook') + ' ' + line.replace('Cookbook:', '').strip() + '\n')
if 'Section:' in line:
directions.append(_('Section') + ' ' + line.replace('Section:', '').strip() + '\n')
if ingredient_mode:
if len(line) > 2 and 'Instructions:' not in line:
ingredients.append(line.strip())
if direction_mode:
if len(line) > 2:
directions.append(line.strip())
if 'Ingredients:' in line:
ingredient_mode = True
if 'Instructions:' in line:
ingredient_mode = False
direction_mode = True
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space, )
step = Step.objects.create(instruction='\n'.join(directions))
for ingredient in ingredients:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
))
recipe.steps.add(step)
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,22 @@
# Generated by Django 3.0.2 on 2020-01-30 09:59
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_ingredient_units(apps, schema_editor):
Unit = apps.get_model('cookbook', 'Unit')
RecipeIngredients = apps.get_model('cookbook', 'RecipeIngredients')
with scopes_disabled():
Unit = apps.get_model('cookbook', 'Unit')
RecipeIngredients = apps.get_model('cookbook', 'RecipeIngredients')
for u in RecipeIngredients.objects.values('unit').distinct():
unit = Unit()
unit.name = u['unit']
unit.save()
for u in RecipeIngredients.objects.values('unit').distinct():
unit = Unit()
unit.name = u['unit']
unit.save()
for i in RecipeIngredients.objects.all():
i.unit_key = Unit.objects.get(name=i.unit)
i.save()
for i in RecipeIngredients.objects.all():
i.unit_key = Unit.objects.get(name=i.unit)
i.save()
class Migration(migrations.Migration):

View File

@@ -1,19 +1,21 @@
# Generated by Django 3.0.2 on 2020-02-16 22:09
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_ingredients(apps, schema_editor):
Ingredient = apps.get_model('cookbook', 'Ingredient')
RecipeIngredient = apps.get_model('cookbook', 'RecipeIngredient')
with scopes_disabled():
Ingredient = apps.get_model('cookbook', 'Ingredient')
RecipeIngredient = apps.get_model('cookbook', 'RecipeIngredient')
for u in RecipeIngredient.objects.values('name').distinct():
ingredient = Ingredient()
ingredient.name = u['name']
ingredient.save()
for u in RecipeIngredient.objects.values('name').distinct():
ingredient = Ingredient()
ingredient.name = u['name']
ingredient.save()
for i in RecipeIngredient.objects.all():
i.ingredient = Ingredient.objects.get(name=i.name)
i.save()
for i in RecipeIngredient.objects.all():
i.ingredient = Ingredient.objects.get(name=i.name)
i.save()
class Migration(migrations.Migration):

View File

@@ -1,15 +1,17 @@
# Generated by Django 3.0.5 on 2020-04-26 14:14
from django.db import migrations
from django_scopes import scopes_disabled
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'),
])
with scopes_disabled():
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):

View File

@@ -1,15 +1,17 @@
# Generated by Django 3.0.5 on 2020-04-27 16:00
from django.db import migrations
from django_scopes import scopes_disabled
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()
with scopes_disabled():
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):

View File

@@ -2,43 +2,45 @@
from django.db import migrations
from django.utils.translation import gettext as _
from django_scopes import scopes_disabled
def migrate_meal_types(apps, schema_editor):
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealType = apps.get_model('cookbook', 'MealType')
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealType = apps.get_model('cookbook', 'MealType')
breakfast = MealType.objects.create(
name=_('Breakfast'),
order=0,
)
breakfast = MealType.objects.create(
name=_('Breakfast'),
order=0,
)
lunch = MealType.objects.create(
name=_('Lunch'),
order=0,
)
lunch = MealType.objects.create(
name=_('Lunch'),
order=0,
)
dinner = MealType.objects.create(
name=_('Dinner'),
order=0,
)
dinner = MealType.objects.create(
name=_('Dinner'),
order=0,
)
other = MealType.objects.create(
name=_('Other'),
order=0,
)
other = MealType.objects.create(
name=_('Other'),
order=0,
)
for m in MealPlan.objects.all():
if m.meal == 'BREAKFAST':
m.meal_type = breakfast
if m.meal == 'LUNCH':
m.meal_type = lunch
if m.meal == 'DINNER':
m.meal_type = dinner
if m.meal == 'OTHER':
m.meal_type = other
for m in MealPlan.objects.all():
if m.meal == 'BREAKFAST':
m.meal_type = breakfast
if m.meal == 'LUNCH':
m.meal_type = lunch
if m.meal == 'DINNER':
m.meal_type = dinner
if m.meal == 'OTHER':
m.meal_type = other
m.save()
m.save()
class Migration(migrations.Migration):

View File

@@ -2,22 +2,24 @@
from django.db import migrations
from django.db.models import Q
from django_scopes import scopes_disabled
def migrate_meal_types(apps, schema_editor):
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealType = apps.get_model('cookbook', 'MealType')
User = apps.get_model('auth', 'User')
with scopes_disabled():
MealPlan = apps.get_model('cookbook', 'MealPlan')
MealType = apps.get_model('cookbook', 'MealType')
User = apps.get_model('auth', 'User')
for u in User.objects.all():
for t in MealType.objects.filter(created_by=None).all():
user_type = MealType.objects.create(
name=t.name,
created_by=u,
)
MealPlan.objects.filter(Q(created_by=u) and Q(meal_type=t)).update(meal_type=user_type)
for u in User.objects.all():
for t in MealType.objects.filter(created_by=None).all():
user_type = MealType.objects.create(
name=t.name,
created_by=u,
)
MealPlan.objects.filter(Q(created_by=u) and Q(meal_type=t)).update(meal_type=user_type)
MealType.objects.filter(created_by=None).delete()
MealType.objects.filter(created_by=None).delete()
class Migration(migrations.Migration):

View File

@@ -3,11 +3,14 @@
from django.db import migrations, models
import uuid
from django_scopes import scopes_disabled
def invalidate_shares(apps, schema_editor):
ShareLink = apps.get_model('cookbook', 'ShareLink')
with scopes_disabled():
ShareLink = apps.get_model('cookbook', 'ShareLink')
ShareLink.objects.all().delete()
ShareLink.objects.all().delete()
class Migration(migrations.Migration):

View File

@@ -1,16 +1,18 @@
# Generated by Django 3.0.7 on 2020-06-25 19:37
from django.db import migrations
from django_scopes import scopes_disabled
def migrate_ingredients(apps, schema_editor):
Recipe = apps.get_model('cookbook', 'Recipe')
Ingredient = apps.get_model('cookbook', 'Ingredient')
with scopes_disabled():
Recipe = apps.get_model('cookbook', 'Recipe')
Ingredient = apps.get_model('cookbook', 'Ingredient')
for r in Recipe.objects.all():
for i in Ingredient.objects.filter(recipe=r).all():
r.ingredients.add(i)
r.save()
for r in Recipe.objects.all():
for i in Ingredient.objects.filter(recipe=r).all():
r.ingredients.add(i)
r.save()
class Migration(migrations.Migration):

View File

@@ -1,21 +1,23 @@
# Generated by Django 3.0.7 on 2020-06-25 20:19
from django.db import migrations, models
from django_scopes import scopes_disabled
def create_default_step(apps, schema_editor):
Recipe = apps.get_model('cookbook', 'Recipe')
Step = apps.get_model('cookbook', 'Step')
with scopes_disabled():
Recipe = apps.get_model('cookbook', 'Recipe')
Step = apps.get_model('cookbook', 'Step')
for r in Recipe.objects.filter(internal=True).all():
s = Step.objects.create(
instruction=r.instructions
)
for i in r.ingredients.all():
s.ingredients.add(i)
s.save()
r.steps.add(s)
r.save()
for r in Recipe.objects.filter(internal=True).all():
s = Step.objects.create(
instruction=r.instructions
)
for i in r.ingredients.all():
s.ingredients.add(i)
s.save()
r.steps.add(s)
r.save()
class Migration(migrations.Migration):

View File

@@ -2,27 +2,29 @@
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
def convert_old_specials(apps, schema_editor):
Ingredient = apps.get_model('cookbook', 'Ingredient')
Food = apps.get_model('cookbook', 'Food')
Unit = apps.get_model('cookbook', 'Unit')
with scopes_disabled():
Ingredient = apps.get_model('cookbook', 'Ingredient')
Food = apps.get_model('cookbook', 'Food')
Unit = apps.get_model('cookbook', 'Unit')
for i in Ingredient.objects.all():
if i.amount == 0:
i.no_amount = True
if i.unit.name == 'Special:Header':
i.header = True
i.unit = None
i.food = None
i.save()
for i in Ingredient.objects.all():
if i.amount == 0:
i.no_amount = True
if i.unit.name == 'Special:Header':
i.header = True
i.unit = None
i.food = None
i.save()
try:
Unit.objects.filter(name='Special:Header').delete()
Food.objects.filter(name='Header').delete()
except Exception:
pass
try:
Unit.objects.filter(name='Special:Header').delete()
Food.objects.filter(name='Header').delete()
except Exception:
pass
class Migration(migrations.Migration):

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.1.5 on 2021-01-07 17:04
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0094_auto_20201231_1238'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='sticky_navbar',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2021, 1, 21)),
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 3.1.5 on 2021-01-09 19:44
from django.db import migrations
def delete_duplicate_bookmarks(apps, schema_editor):
"""
In this migration, a unique constraint is set on the fields `recipe` and `book`.
If there are already duplicate entries, the migration will fail.
Therefore all duplicate entries are deleted beforehand.
"""
RecipeBookEntry = apps.get_model('cookbook', 'RecipeBookEntry')
for row in RecipeBookEntry.objects.all():
if RecipeBookEntry.objects.filter(recipe=row.recipe, book=row.book).count() > 1:
row.delete()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0095_auto_20210107_1804'),
]
operations = [
# run function to delete duplicated bookmarks
migrations.RunPython(delete_duplicate_bookmarks),
migrations.AlterUniqueTogether(
name='recipebookentry',
unique_together={('recipe', 'book')},
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 3.1.5 on 2021-01-13 12:15
import datetime
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0096_auto_20210109_2044'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='description',
field=models.CharField(blank=True, max_length=512, null=True),
),
migrations.AlterField(
model_name='food',
name='name',
field=models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)]),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.5 on 2021-01-13 12:20
import cookbook.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0097_auto_20210113_1315'),
]
operations = [
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=cookbook.models.default_valid_until),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.5 on 2021-01-13 14:18
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0098_auto_20210113_1320'),
]
operations = [
migrations.AlterField(
model_name='cooklog',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.5 on 2021-01-21 19:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0099_auto_20210113_1518'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='servings_text',
field=models.CharField(blank=True, default='', max_length=32),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.5 on 2021-01-22 18:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0100_recipe_servings_text'),
]
operations = [
migrations.AddField(
model_name='storage',
name='path',
field=models.CharField(blank=True, default='', max_length=256),
),
]

View File

@@ -0,0 +1,46 @@
# Generated by Django 3.1.5 on 2021-01-25 10:47
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0101_storage_path'),
]
operations = [
migrations.CreateModel(
name='Supermarket',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)])),
('description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='SupermarketCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, validators=[django.core.validators.MinLengthValidator(1)])),
('description', models.TextField(blank=True, null=True)),
],
),
migrations.AddField(
model_name='food',
name='description',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='storage',
name='method',
field=models.CharField(choices=[('DB', 'Dropbox'), ('NEXTCLOUD', 'Nextcloud'), ('LOCAL', 'Local')], default='DB', max_length=128),
),
migrations.AddField(
model_name='food',
name='supermarket_category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.supermarketcategory'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.5 on 2021-01-25 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0102_auto_20210125_1147'),
]
operations = [
migrations.AddField(
model_name='food',
name='ignore_shopping',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 3.1.5 on 2021-01-25 20:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0103_food_ignore_shopping'),
]
operations = [
migrations.CreateModel(
name='SupermarketCategoryRelation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(default=0)),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.supermarketcategory')),
('supermarket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.supermarket')),
],
),
migrations.AddField(
model_name='supermarket',
name='categories',
field=models.ManyToManyField(through='cookbook.SupermarketCategoryRelation', to='cookbook.SupermarketCategory'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.1.5 on 2021-01-26 15:04
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0104_auto_20210125_2133'),
]
operations = [
migrations.AlterField(
model_name='supermarketcategoryrelation',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_to_supermarket', to='cookbook.supermarketcategory'),
),
migrations.AlterField(
model_name='supermarketcategoryrelation',
name='supermarket',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_to_supermarket', to='cookbook.supermarket'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.5 on 2021-01-26 15:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0105_auto_20210126_1604'),
]
operations = [
migrations.AddField(
model_name='shoppinglist',
name='supermarket',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.supermarket'),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 3.1.5 on 2021-01-28 14:35
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0106_shoppinglist_supermarket'),
]
operations = [
migrations.AlterModelOptions(
name='supermarketcategoryrelation',
options={'ordering': ('order',)},
),
]

View File

@@ -0,0 +1,146 @@
# Generated by Django 3.1.6 on 2021-02-19 13:10
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0107_auto_20210128_1535'),
]
operations = [
migrations.AddField(
model_name='cooklog',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='food',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='invitelink',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='keyword',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='mealplan',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='mealtype',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipe',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipebook',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipebookentry',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='recipeimport',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='sharelink',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglist',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglistentry',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='shoppinglistrecipe',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='storage',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='supermarket',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='supermarketcategory',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='sync',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='synclog',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='unit',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='userpreference',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
migrations.AddField(
model_name='viewlog',
name='space',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,63 @@
# Generated by Django 3.1.6 on 2021-02-21 11:04
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0108_auto_20210219_1410'),
]
operations = [
migrations.RemoveField(
model_name='recipebookentry',
name='space',
),
migrations.AlterField(
model_name='food',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='keyword',
name='name',
field=models.CharField(max_length=64),
),
migrations.AlterField(
model_name='supermarket',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='supermarketcategory',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterField(
model_name='unit',
name='name',
field=models.CharField(max_length=128, validators=[django.core.validators.MinLengthValidator(1)]),
),
migrations.AlterUniqueTogether(
name='food',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='keyword',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='supermarket',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='supermarketcategory',
unique_together={('space', 'name')},
),
migrations.AlterUniqueTogether(
name='unit',
unique_together={('space', 'name')},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-02-21 13:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0109_auto_20210221_1204'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]

View File

@@ -0,0 +1,32 @@
# Generated by Django 3.1.6 on 2021-02-21 13:19
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django_scopes import scopes_disabled
def set_default_owner(apps, schema_editor):
Space = apps.get_model('cookbook', 'Space')
User = apps.get_model('auth', 'user')
with scopes_disabled():
for x in Space.objects.all():
x.created_by = User.objects.filter(is_superuser=True).first()
x.save()
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0110_auto_20210221_1406'),
]
operations = [
migrations.AddField(
model_name='space',
name='created_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.RunPython(set_default_owner),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 3.1.7 on 2021-03-16 23:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0111_space_created_by'),
]
operations = [
migrations.RemoveField(
model_name='synclog',
name='space',
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 3.1.7 on 2021-03-17 19:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0112_remove_synclog_space'),
]
operations = [
migrations.RemoveField(
model_name='shoppinglistentry',
name='space',
),
migrations.RemoveField(
model_name='shoppinglistrecipe',
name='space',
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 3.1.7 on 2021-03-18 17:23
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0113_auto_20210317_2017'),
]
operations = [
migrations.CreateModel(
name='ImportLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('type', models.CharField(max_length=32)),
('running', models.BooleanField(default=True)),
('msg', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('keyword', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.keyword')),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]

Some files were not shown because too many files have changed in this diff Show More