Compare commits

...

206 Commits

Author SHA1 Message Date
vabene1111
6c22fb0ef4 Merge branch 'develop' 2021-08-12 15:12:55 +02:00
vabene1111
f869fc85ae fixed broken share links 2021-08-12 15:12:08 +02:00
vabene1111
f435110810 updated and compiled messages 2021-08-12 15:09:34 +02:00
Afaren
36a0693b49 Translated using Weblate (French)
Currently translated at 2.5% (2 of 78 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/fr/
2021-08-11 16:51:30 +00:00
Afaren
32a565c1e0 Translated using Weblate (French)
Currently translated at 90.8% (428 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/fr/
2021-08-11 16:51:30 +00:00
Afaren
f3fd087b81 Added translation using Weblate (French) 2021-08-10 15:08:26 +00:00
Danny Tsui
8e8d25071c Translated using Weblate (Chinese (Simplified))
Currently translated at 73.0% (57 of 78 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/zh_Hans/
2021-08-10 08:51:29 +00:00
Danny Tsui
129cc76624 Translated using Weblate (Chinese (Simplified))
Currently translated at 15.9% (75 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/zh_Hans/
2021-08-10 08:51:28 +00:00
Danny Tsui
0e48ee60cb Translated using Weblate (Chinese (Simplified))
Currently translated at 57.6% (45 of 78 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/zh_Hans/
2021-08-04 21:51:26 +00:00
Danny Tsui
a784421b5c Translated using Weblate (Chinese (Simplified))
Currently translated at 9.3% (44 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/zh_Hans/
2021-08-04 21:51:26 +00:00
Danny Tsui
314d8d41d1 Added translation using Weblate (Chinese (Simplified)) 2021-08-03 16:27:47 +00:00
Danny Tsui
02fd73f43e Added translation using Weblate (Chinese (Traditional)) 2021-08-03 15:59:53 +00:00
Danny Tsui
f12c47603d Added translation using Weblate (Chinese (Traditional)) 2021-08-03 15:59:19 +00:00
vabene1111
e341b96249 synology 2021-07-29 17:55:21 +02:00
vabene1111
7ed5606e9d Merge pull request #782 from vabene1111/dependabot/pip/boto3-1.18.9
Bump boto3 from 1.18.4 to 1.18.9
2021-07-29 17:11:33 +02:00
vabene1111
f3fffc1a3b Merge pull request #774 from vabene1111/dependabot/pip/recipe-scrapers-13.3.5
Bump recipe-scrapers from 13.3.4 to 13.3.5
2021-07-29 17:11:21 +02:00
vabene1111
c5a3e22542 Merge pull request #772 from vabene1111/dependabot/pip/python-dotenv-0.19.0
Bump python-dotenv from 0.18.0 to 0.19.0
2021-07-29 17:11:16 +02:00
vabene1111
c6990ef2d8 fixed fraction issue 2021-07-29 17:11:01 +02:00
vabene1111
12a438752b fixed old search made new search default 2021-07-29 16:55:57 +02:00
vabene1111
8642298eda recipe book context menu 2021-07-29 16:32:15 +02:00
dependabot[bot]
6260aba668 Bump boto3 from 1.18.4 to 1.18.9
Bumps [boto3](https://github.com/boto/boto3) from 1.18.4 to 1.18.9.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.18.4...1.18.9)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-29 00:03:37 +00:00
dependabot[bot]
c56489cfa0 Bump recipe-scrapers from 13.3.4 to 13.3.5
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 13.3.4 to 13.3.5.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/13.3.4...13.3.5)

---
updated-dependencies:
- dependency-name: recipe-scrapers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-26 00:17:52 +00:00
dependabot[bot]
33b84c3d6a Bump python-dotenv from 0.18.0 to 0.19.0
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.18.0 to 0.19.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.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-26 00:17:30 +00:00
vabene1111
45454eb27b test create pinned recipe component 2021-07-22 16:53:20 +02:00
vabene1111
f628823ab2 Merge pull request #751 from tandy-1000/patch-1
Update manual setup
2021-07-22 16:42:16 +02:00
vabene1111
781cbc16f7 space create initial name and markup 2021-07-22 16:39:42 +02:00
vabene1111
28e6c9d922 increased default file upload limit 2021-07-22 16:07:32 +02:00
vabene1111
d96bac47c4 Merge pull request #760 from vabene1111/dependabot/pip/requests-2.26.0
Bump requests from 2.25.1 to 2.26.0
2021-07-22 16:04:30 +02:00
vabene1111
1dafeb4db9 Merge pull request #762 from vabene1111/dependabot/pip/bleach-3.3.1
Bump bleach from 3.3.0 to 3.3.1
2021-07-22 16:04:25 +02:00
vabene1111
c02cdf6e68 Merge pull request #763 from vabene1111/dependabot/pip/recipe-scrapers-13.3.4
Bump recipe-scrapers from 13.3.2 to 13.3.4
2021-07-22 16:04:20 +02:00
vabene1111
3628835fd4 Merge pull request #767 from vabene1111/dependabot/pip/whitenoise-5.3.0
Bump whitenoise from 5.2.0 to 5.3.0
2021-07-22 16:04:16 +02:00
vabene1111
747b5937eb Merge pull request #770 from vabene1111/dependabot/pip/boto3-1.18.4
Bump boto3 from 1.17.110 to 1.18.4
2021-07-22 16:04:10 +02:00
dependabot[bot]
2fde8f9b52 Bump boto3 from 1.17.110 to 1.18.4
Bumps [boto3](https://github.com/boto/boto3) from 1.17.110 to 1.18.4.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.110...1.18.4)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-22 00:02:59 +00:00
tandy1000
7c06da89f0 simplify, add some fixes re static / media files 2021-07-21 09:59:14 +01:00
Jesse
2b17b12252 Translated using Weblate (Dutch)
Currently translated at 100.0% (76 of 76 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/nl/
2021-07-19 19:53:09 +00:00
Jesse
07afa7957a Translated using Weblate (Dutch)
Currently translated at 100.0% (471 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/nl/
2021-07-19 16:40:51 +00:00
dependabot[bot]
f0488c93d2 Bump whitenoise from 5.2.0 to 5.3.0
Bumps [whitenoise](https://github.com/evansd/whitenoise) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/evansd/whitenoise/releases)
- [Changelog](https://github.com/evansd/whitenoise/blob/master/docs/changelog.rst)
- [Commits](https://github.com/evansd/whitenoise/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: whitenoise
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-19 00:40:33 +00:00
Jesse
86a3d87276 Translated using Weblate (Dutch)
Currently translated at 93.8% (442 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/nl/
2021-07-16 09:53:08 +00:00
dependabot[bot]
fd14d79c12 Bump recipe-scrapers from 13.3.2 to 13.3.4
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 13.3.2 to 13.3.4.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/13.3.2...13.3.4)

---
updated-dependencies:
- dependency-name: recipe-scrapers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-15 00:03:06 +00:00
dependabot[bot]
4e852374cc Bump bleach from 3.3.0 to 3.3.1
Bumps [bleach](https://github.com/mozilla/bleach) from 3.3.0 to 3.3.1.
- [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.3.0...v3.3.1)

---
updated-dependencies:
- dependency-name: bleach
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-15 00:03:01 +00:00
dependabot[bot]
898b1699b7 Bump requests from 2.25.1 to 2.26.0
Bumps [requests](https://github.com/psf/requests) from 2.25.1 to 2.26.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/master/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.26.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-14 00:03:01 +00:00
vabene1111
e92dbcd63e compiled javascript 2021-07-13 16:47:45 +02:00
vabene1111
0c28e7e1b4 recipes as part of recipes/steps 2021-07-13 16:47:39 +02:00
vabene1111
f8c1411e4d Merge pull request #755 from vabene1111/dependabot/pip/simplejson-3.17.3
Bump simplejson from 3.17.2 to 3.17.3
2021-07-13 10:27:29 +02:00
vabene1111
f1a194e166 Merge pull request #757 from vabene1111/dependabot/pip/recipe-scrapers-13.3.2
Bump recipe-scrapers from 13.3.1 to 13.3.2
2021-07-13 10:27:18 +02:00
dependabot[bot]
f374e0cb27 Bump recipe-scrapers from 13.3.1 to 13.3.2
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 13.3.1 to 13.3.2.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/13.3.1...13.3.2)

---
updated-dependencies:
- dependency-name: recipe-scrapers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-13 08:27:13 +00:00
vabene1111
6f7a41f3b8 Merge pull request #758 from vabene1111/dependabot/pip/boto3-1.17.110
Bump boto3 from 1.17.108 to 1.17.110
2021-07-13 10:27:10 +02:00
vabene1111
a562b571b5 Merge pull request #754 from vabene1111/dependabot/pip/django-allauth-0.45.0
Bump django-allauth from 0.44.0 to 0.45.0
2021-07-13 10:26:42 +02:00
dependabot[bot]
d71420484c Bump boto3 from 1.17.108 to 1.17.110
Bumps [boto3](https://github.com/boto/boto3) from 1.17.108 to 1.17.110.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.108...1.17.110)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-13 00:02:31 +00:00
dependabot[bot]
bc7758e233 Bump simplejson from 3.17.2 to 3.17.3
Bumps [simplejson](https://github.com/simplejson/simplejson) from 3.17.2 to 3.17.3.
- [Release notes](https://github.com/simplejson/simplejson/releases)
- [Changelog](https://github.com/simplejson/simplejson/blob/master/CHANGES.txt)
- [Commits](https://github.com/simplejson/simplejson/compare/v3.17.2...v3.17.3)

---
updated-dependencies:
- dependency-name: simplejson
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 00:13:21 +00:00
dependabot[bot]
0f5185c677 Bump django-allauth from 0.44.0 to 0.45.0
Bumps [django-allauth](https://github.com/pennersr/django-allauth) from 0.44.0 to 0.45.0.
- [Release notes](https://github.com/pennersr/django-allauth/releases)
- [Changelog](https://github.com/pennersr/django-allauth/blob/master/ChangeLog.rst)
- [Commits](https://github.com/pennersr/django-allauth/compare/0.44.0...0.45.0)

---
updated-dependencies:
- dependency-name: django-allauth
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 00:13:14 +00:00
vabene1111
1e6096d5c8 Merge pull request #753 from vabene1111/dependabot/pip/boto3-1.17.108
Bump boto3 from 1.17.106 to 1.17.108
2021-07-09 08:08:20 +02:00
dependabot[bot]
bc2a092e79 Bump boto3 from 1.17.106 to 1.17.108
Bumps [boto3](https://github.com/boto/boto3) from 1.17.106 to 1.17.108.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.106...1.17.108)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-09 00:03:25 +00:00
vabene1111
9809f58c28 supermarket view done 2021-07-08 19:00:23 +02:00
tandy1000
589302a87d Add guidance on what to edit in .env 2021-07-07 19:23:00 +01:00
tandy1000
173eaf44a0 Add fix for no image on recipe page
https://github.com/vabene1111/recipes/issues/470#issuecomment-787210414
2021-07-07 19:10:19 +01:00
tandy1000
576c62b8a1 Update manual setup
I just went through a manual install on DietPi / Debian Unstable, and found that the manual needed some improving, this method works, whereas following the original manual was not a success for me.

The only line I would say I am unsure about is this one:
"Give the user permissions: `chown -R recipes:www-data /var/www/recipes`"
As I am not sure whether these permissions are necessary for this to work.

Hope this is useful, let me know if I can make further improvements.
2021-07-07 16:49:04 +01:00
vabene1111
da57e656eb several search related improvements 2021-07-07 17:45:05 +02:00
vabene1111
b42e6ac0f6 fixed cook log creation in old search 2021-07-07 17:21:14 +02:00
vabene1111
cebcf266fc Merge pull request #750 from vabene1111/dependabot/pip/pillow-8.3.1
Bump pillow from 8.3.0 to 8.3.1
2021-07-07 16:31:35 +02:00
vabene1111
d5ebb0047d Merge pull request #749 from vabene1111/dependabot/pip/boto3-1.17.106
Bump boto3 from 1.17.104 to 1.17.106
2021-07-07 16:31:31 +02:00
vabene1111
6d553035db Merge pull request #748 from vabene1111/dependabot/pip/recipe-scrapers-13.3.1
Bump recipe-scrapers from 13.3.0 to 13.3.1
2021-07-07 16:31:26 +02:00
dependabot[bot]
0f57dc9c8a Bump pillow from 8.3.0 to 8.3.1
Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.3.0 to 8.3.1.
- [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.3.0...8.3.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-07 00:02:44 +00:00
dependabot[bot]
31a4bc7747 Bump boto3 from 1.17.104 to 1.17.106
Bumps [boto3](https://github.com/boto/boto3) from 1.17.104 to 1.17.106.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.104...1.17.106)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-07 00:02:37 +00:00
dependabot[bot]
0eed67b5aa Bump recipe-scrapers from 13.3.0 to 13.3.1
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 13.3.0 to 13.3.1.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/13.3.0...13.3.1)

---
updated-dependencies:
- dependency-name: recipe-scrapers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-05 00:18:45 +00:00
vabene1111
3dbf40ff44 Merge pull request #746 from vabene1111/dependabot/pip/pillow-8.3.0
Bump pillow from 8.2.0 to 8.3.0
2021-07-03 10:31:32 +02:00
vabene1111
16459c1ec1 Merge pull request #745 from vabene1111/dependabot/pip/django-3.2.5
Bump django from 3.2.4 to 3.2.5
2021-07-03 10:31:20 +02:00
vabene1111
ba575ff79b Merge pull request #744 from vabene1111/dependabot/pip/boto3-1.17.104
Bump boto3 from 1.17.102 to 1.17.104
2021-07-03 10:31:02 +02:00
dependabot[bot]
2f9e407f49 Bump pillow from 8.2.0 to 8.3.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 8.2.0 to 8.3.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.2.0...8.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-02 00:02:44 +00:00
dependabot[bot]
1779c1ac14 Bump django from 3.2.4 to 3.2.5
Bumps [django](https://github.com/django/django) from 3.2.4 to 3.2.5.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.4...3.2.5)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-02 00:02:32 +00:00
dependabot[bot]
ca1c353575 Bump boto3 from 1.17.102 to 1.17.104
Bumps [boto3](https://github.com/boto/boto3) from 1.17.102 to 1.17.104.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.102...1.17.104)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-02 00:02:24 +00:00
Kaibu
cac643266b Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-06-30 15:37:18 +02:00
Kaibu
5518199f64 search and car styling 2021-06-30 15:36:56 +02:00
vabene1111
a508fa81c0 Merge pull request #740 from vabene1111/dependabot/pip/boto3-1.17.102
Bump boto3 from 1.17.101 to 1.17.102
2021-06-30 14:48:16 +02:00
vabene1111
c5bec8b69e fixed rendering of recipe cards without descriptions 2021-06-30 14:42:48 +02:00
Kaibu
c4905d39c1 minor ui/ux improvements 2021-06-30 04:12:52 +02:00
dependabot[bot]
5275e5eba7 Bump boto3 from 1.17.101 to 1.17.102
Bumps [boto3](https://github.com/boto/boto3) from 1.17.101 to 1.17.102.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.101...1.17.102)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-29 00:02:39 +00:00
vabene1111
272341f1dc further improvements to search view 2021-06-28 18:35:20 +02:00
vabene1111
78885987f0 small recipe card tweaks 2021-06-28 16:41:45 +02:00
vabene1111
cdbcc971b1 fixed old search rating 2021-06-28 16:31:48 +02:00
vabene1111
5378b4c577 Merge pull request #720 from Forceu/develop
Fixed Install URL in readme and documentation, added link to Docker Hub
2021-06-28 16:24:09 +02:00
vabene1111
269593cd98 Merge branch 'develop' into develop 2021-06-28 16:24:04 +02:00
vabene1111
82c83f4b8d Merge pull request #727 from vabene1111/dependabot/pip/django-webpack-loader-1.1.0
Bump django-webpack-loader from 1.0.0 to 1.1.0
2021-06-28 16:18:59 +02:00
vabene1111
42885cefc9 Merge pull request #739 from vabene1111/dependabot/pip/boto3-1.17.101
Bump boto3 from 1.17.97 to 1.17.101
2021-06-28 16:18:53 +02:00
dependabot[bot]
54a5009f98 Bump boto3 from 1.17.97 to 1.17.101
Bumps [boto3](https://github.com/boto/boto3) from 1.17.97 to 1.17.101.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.97...1.17.101)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-28 00:20:10 +00:00
Hrachya Kocharyan
7c49164387 Added translation using Weblate (Armenian) 2021-06-26 08:52:51 +00:00
Maximilian J
1b8d4e4494 Translated using Weblate (German)
Currently translated at 98.6% (73 of 74 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/de/
2021-06-24 15:49:37 +00:00
Maximilian J
932211e7f6 Translated using Weblate (German)
Currently translated at 94.4% (445 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/de/
2021-06-24 15:49:37 +00:00
vabene1111
9658993163 admin filter options and timestamp fields 2021-06-22 18:21:14 +02:00
vabene1111
13e3c98fac actually implemented setting 2021-06-22 17:55:18 +02:00
Jesse
9a9644dc6c Translated using Weblate (Dutch)
Currently translated at 92.3% (435 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/nl/
2021-06-22 09:12:37 +00:00
Kaibu
cc43b2a9b0 minor ui fixes 2021-06-22 01:03:26 +02:00
Kaibu
91c0dbd8d2 demo url will login filled 2021-06-22 00:10:27 +02:00
vabene1111
3271ec6867 fixed cookie name env 2021-06-21 17:29:00 +02:00
dependabot[bot]
d39b779db9 Bump django-webpack-loader from 1.0.0 to 1.1.0
Bumps [django-webpack-loader](https://github.com/django-webpack/django-webpack-loader) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/django-webpack/django-webpack-loader/releases)
- [Changelog](https://github.com/django-webpack/django-webpack-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/django-webpack/django-webpack-loader/commits)

---
updated-dependencies:
- dependency-name: django-webpack-loader
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 13:31:03 +00:00
vabene1111
536b0bad20 Merge pull request #728 from vabene1111/dependabot/npm_and_yarn/vue/postcss-7.0.36
Bump postcss from 7.0.35 to 7.0.36 in /vue
2021-06-21 15:30:28 +02:00
vabene1111
5c2020b8dd Merge pull request #725 from vabene1111/dependabot/pip/python-dotenv-0.18.0
Bump python-dotenv from 0.17.1 to 0.18.0
2021-06-21 15:30:08 +02:00
vabene1111
5851d061a2 Merge pull request #726 from vabene1111/dependabot/pip/recipe-scrapers-13.3.0
Bump recipe-scrapers from 13.2.8 to 13.3.0
2021-06-21 15:30:01 +02:00
dependabot[bot]
a8bcc1457d Bump python-dotenv from 0.17.1 to 0.18.0
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.17.1 to 0.18.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.17.1...v0.18.0)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 13:29:47 +00:00
dependabot[bot]
6ba9cb8b55 Bump postcss from 7.0.35 to 7.0.36 in /vue
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.35 to 7.0.36.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.35...7.0.36)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 13:29:45 +00:00
vabene1111
8e53cce3b2 Merge pull request #712 from vabene1111/dependabot/pip/boto3-1.17.97
Bump boto3 from 1.17.93 to 1.17.97
2021-06-21 15:29:08 +02:00
vabene1111
7f3a4ada75 Merge pull request #713 from vabene1111/dependabot/pip/psycopg2-binary-2.9.1
Bump psycopg2-binary from 2.8.6 to 2.9.1
2021-06-21 15:29:03 +02:00
vabene1111
0163988593 fixed loading of complex social account settings 2021-06-21 15:28:22 +02:00
vabene1111
bcf78aed0a fixed unit test 2021-06-21 13:51:47 +02:00
vabene1111
a7c89cc32e build javascript dependencies 2021-06-21 13:32:26 +02:00
vabene1111
e66502ee8f updated settings env variables 2021-06-21 13:15:21 +02:00
vabene1111
40a12f35d7 allow modifying session cookie behavior 2021-06-21 13:08:04 +02:00
vabene1111
fd1a399d03 more improvements to the ingredient parser + tests 2021-06-21 13:00:03 +02:00
vabene1111
538e45d20c fixed enable signup parameter for social auth 2021-06-21 12:43:52 +02:00
dependabot[bot]
0304e2a1ed Bump recipe-scrapers from 13.2.8 to 13.3.0
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 13.2.8 to 13.3.0.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/13.2.8...13.3.0)

---
updated-dependencies:
- dependency-name: recipe-scrapers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 00:17:39 +00:00
Kaibu
65245234d8 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-06-20 14:59:03 +02:00
Kaibu
a88d7625dc social login buttons 2021-06-20 14:58:56 +02:00
Marc Ole Bulling
1af06b6480 Fixed Installation link in docs 2021-06-20 13:50:50 +02:00
vabene1111
48270197fa Merge pull request #719 from vabene1111/master
Master
2021-06-20 13:50:26 +02:00
Marc Ole Bulling
86cea901b4 Fixed Install link in Readme, added link to Docker badge 2021-06-20 13:49:30 +02:00
vabene1111
f5312496e3 fixed url image import 2021-06-20 13:35:05 +02:00
vabene1111
7f57e7ab56 Fixed recipe create space 2021-06-19 14:44:49 +02:00
Oliver Cervera
c8d8dd581e Translated using Weblate (Italian)
Currently translated at 76.8% (362 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/it/
2021-06-18 23:12:35 +00:00
vabene1111
256c1a7d41 improved ingredient parser 2021-06-18 17:36:31 +02:00
vabene1111
7aa71dc744 layering fix 2021-06-18 16:29:46 +02:00
vabene1111
07c34ea7b5 account and social logic/templates 2021-06-18 16:13:28 +02:00
dependabot[bot]
2d2582b449 Bump psycopg2-binary from 2.8.6 to 2.9.1
Bumps [psycopg2-binary](https://github.com/psycopg/psycopg2) from 2.8.6 to 2.9.1.
- [Release notes](https://github.com/psycopg/psycopg2/releases)
- [Changelog](https://github.com/psycopg/psycopg2/blob/master/NEWS)
- [Commits](https://github.com/psycopg/psycopg2/commits)

---
updated-dependencies:
- dependency-name: psycopg2-binary
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-18 00:02:50 +00:00
dependabot[bot]
4f81cb10de Bump boto3 from 1.17.93 to 1.17.97
Bumps [boto3](https://github.com/boto/boto3) from 1.17.93 to 1.17.97.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.93...1.17.97)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-18 00:02:43 +00:00
vabene1111
0256864904 social auth stuff 2021-06-17 22:40:33 +02:00
vabene1111
7725665aa4 django needs two files 2021-06-17 16:05:18 +02:00
vabene1111
74e3d09065 moved to new migration 2021-06-17 15:23:39 +02:00
vabene1111
0522fa0236 formatting 2021-06-17 15:15:42 +02:00
vabene1111
38aeb285c5 fixes space migration fo I/N/S 2021-06-17 15:02:06 +02:00
vabene1111
a9a0716c45 import response improvements 2021-06-17 14:08:03 +02:00
vabene1111
afc31b313f Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-06-17 13:38:59 +02:00
vabene1111
151f43b0d5 lots of importer adjustments 2021-06-17 13:38:57 +02:00
Kaibu
a695261b9c share intend 2021-06-17 03:01:22 +02:00
Sander
1229a37d74 Translated using Weblate (Dutch)
Currently translated at 80.8% (381 of 471 strings)

Translation: Tandoor/Recipes Backend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-backend/nl/
2021-06-16 20:12:34 +00:00
Kaibu
ef200a4283 ui fixes 2021-06-16 19:13:03 +02:00
vabene1111
018fcf27ea migrated space to ingredient, step and nutritional value 2021-06-15 22:56:25 +02:00
vabene1111
b3504699b1 allow disable sharing for spaces 2021-06-15 21:12:43 +02:00
vabene1111
cdf4476345 recipe share link in modal 2021-06-15 20:57:25 +02:00
vabene1111
0edc9f48c9 fixed invite link signup 2021-06-15 20:13:25 +02:00
vabene1111
41885f7d05 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-06-15 09:04:10 +02:00
vabene1111
2b7769e92f fixed report abuse button showing even if not shared 2021-06-15 09:04:05 +02:00
vabene1111
6a47d56da4 fixed latest shopping list view 2021-06-14 20:50:02 +02:00
vabene1111
d456d4ae64 disable email verification for social accounts 2021-06-14 19:06:01 +02:00
vabene1111
7b55bcb045 indicate adv. settings set by button color search view 2021-06-14 19:03:53 +02:00
vabene1111
bee1d717c5 added support for multiple image types 2021-06-14 18:53:51 +02:00
vabene1111
c91fc096b3 Merge pull request #673 from MaxJa4/develop
Create image_processing.py
2021-06-14 17:48:00 +02:00
vabene1111
2297210a3f Merge pull request #696 from vabene1111/dependabot/pip/django-crispy-forms-1.12.0
Bump django-crispy-forms from 1.11.2 to 1.12.0
2021-06-14 17:35:15 +02:00
vabene1111
d59f14001c Merge pull request #697 from vabene1111/dependabot/pip/recipe-scrapers-13.2.8
Bump recipe-scrapers from 13.2.7 to 13.2.8
2021-06-14 17:35:09 +02:00
vabene1111
5e5941397b Merge pull request #698 from vabene1111/dependabot/pip/boto3-1.17.93
Bump boto3 from 1.17.90 to 1.17.93
2021-06-14 17:35:04 +02:00
dependabot[bot]
3ca71c6847 Bump boto3 from 1.17.90 to 1.17.93
Bumps [boto3](https://github.com/boto/boto3) from 1.17.90 to 1.17.93.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.90...1.17.93)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 06:47:21 +00:00
dependabot[bot]
2c381eb870 Bump recipe-scrapers from 13.2.7 to 13.2.8
Bumps [recipe-scrapers](https://github.com/hhursev/recipe-scrapers) from 13.2.7 to 13.2.8.
- [Release notes](https://github.com/hhursev/recipe-scrapers/releases)
- [Commits](https://github.com/hhursev/recipe-scrapers/compare/13.2.7...13.2.8)

---
updated-dependencies:
- dependency-name: recipe-scrapers
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 06:47:10 +00:00
dependabot[bot]
230a368d38 Bump django-crispy-forms from 1.11.2 to 1.12.0
Bumps [django-crispy-forms](https://github.com/django-crispy-forms/django-crispy-forms) from 1.11.2 to 1.12.0.
- [Release notes](https://github.com/django-crispy-forms/django-crispy-forms/releases)
- [Changelog](https://github.com/django-crispy-forms/django-crispy-forms/blob/main/CHANGELOG.md)
- [Commits](https://github.com/django-crispy-forms/django-crispy-forms/compare/1.11.2...1.12.0)

---
updated-dependencies:
- dependency-name: django-crispy-forms
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 06:46:56 +00:00
vabene1111
5db7267a7d actual meal plan fixes 2021-06-13 18:11:31 +02:00
vabene1111
528da111f9 Revert "meal plan fixes"
This reverts commit 926097a699.
2021-06-13 18:11:22 +02:00
vabene1111
926097a699 meal plan fixes 2021-06-13 18:11:05 +02:00
vabene1111
0b3e86f6fd imprioved recipe print view 2021-06-13 17:04:52 +02:00
vabene1111
24e4ea354d catch all exception image import 2021-06-13 16:55:27 +02:00
vabene1111
08bc87960b added abuse prevention for share links 2021-06-12 21:15:23 +02:00
vabene1111
9751821f76 updated base language files 2021-06-12 20:30:41 +02:00
vabene1111
fa12d02a3d fixed tests 2021-06-12 20:22:30 +02:00
vabene1111
9856857c51 updated openeats importer 2021-06-11 15:28:11 +02:00
Kaibu
c52cd359a1 mockup updated 2021-06-09 19:04:14 +02:00
vabene1111
5e9ce955bc minor fixes 2021-06-09 18:53:20 +02:00
vabene1111
f7d85bb4b8 improvements to recipekeeper importer 2021-06-09 17:23:14 +02:00
vabene1111
46db6d4186 Merge pull request #591 from itsmegb/importer-reciepekeeper
New Importer - Reciepe Keeper
2021-06-09 16:42:49 +02:00
vabene1111
6724328b51 Merge branch 'develop' into importer-reciepekeeper 2021-06-09 16:42:28 +02:00
vabene1111
0da39f2e1f Merge pull request #674 from smilerz/fix_666
fix url import
2021-06-09 15:54:36 +02:00
vabene1111
99fbc5e97c added openeats importer 2021-06-09 15:52:36 +02:00
vabene1111
a0b6261275 importer openeats basics 2021-06-09 15:12:12 +02:00
vabene1111
061aefd233 new file system updates 2021-06-09 14:02:33 +02:00
vabene1111
d4e332456b Merge pull request #675 from vabene1111/dependabot/pip/boto3-1.17.90
Bump boto3 from 1.17.89 to 1.17.90
2021-06-09 12:39:23 +02:00
vabene1111
500bb3af72 Merge pull request #655 from vabene1111/dependabot/npm_and_yarn/vue/ws-6.2.2
Bump ws from 6.2.1 to 6.2.2 in /vue
2021-06-09 12:39:19 +02:00
dependabot[bot]
888106bb6f Bump boto3 from 1.17.89 to 1.17.90
Bumps [boto3](https://github.com/boto/boto3) from 1.17.89 to 1.17.90.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.17.89...1.17.90)

---
updated-dependencies:
- dependency-name: boto3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-09 05:26:57 +00:00
smilerz
8daa0ada9b removed if statement checking if ingredient name exists from ingredient_from_text parser 2021-06-08 15:48:02 -05:00
MaxJa4
98711619ff Create image_processing.py
Added central function to rescale and compress images for recipes (or in general).
Switched from previously used PNG format to 75% JPEG format for a 5-10x file size reduction with hardly any quality loss.
2021-06-08 22:39:45 +02:00
vabene1111
9eb17df575 added basic support for files 2021-06-08 20:17:48 +02:00
vabene1111
c71a7dad24 updated javascript dependencies 2021-06-08 17:26:39 +02:00
Kaibu
b92e51c0c7 login form style fixes 2021-06-08 14:55:18 +02:00
vabene1111
87e8268a43 build sources 2021-06-08 13:12:40 +02:00
vabene1111
5b79db0725 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop
# Conflicts:
#	cookbook/static/vue/js/recipe_search_view.js
#	cookbook/static/vue/js/recipe_view.js
#	vue/webpack-stats.json
2021-06-08 13:12:22 +02:00
vabene1111
3aade540c1 file view 2021-06-08 13:12:04 +02:00
Kaibu
74f155b6f5 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2021-06-08 12:22:16 +02:00
Kaibu
2539d19ff4 minor theme fixes 2021-06-08 12:22:08 +02:00
Jesse
166f4c5f6b Translated using Weblate (Dutch)
Currently translated at 100.0% (55 of 55 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/nl/
2021-06-08 10:19:35 +00:00
vabene1111
ed313cbf9a temporary fix to merge ingredients without recipes 2021-06-07 18:28:16 +02:00
vabene1111
b1db591e9f fixed importer description overflow 2021-06-07 17:17:34 +02:00
vabene1111
94c51f90cd fixed recipe book entry remove 2021-06-07 17:12:00 +02:00
vabene1111
3074d916dc many signup and space management fixes 2021-06-07 16:46:28 +02:00
vabene1111
bf467b1ec0 manage link for hosted version 2021-06-07 16:09:26 +02:00
vabene1111
348c1c78f1 split signup forms working again 2021-06-07 16:03:10 +02:00
vabene1111
913e896906 fixed invite link system 2021-06-05 19:01:05 +02:00
vabene1111
a0a673a0c9 uncoment debug code 2021-06-05 18:41:48 +02:00
Kaibu
3d60379ed0 style fixes 2021-06-05 18:10:10 +02:00
vabene1111
fd7e20a46b signup captcha support + privacy/terms support 2021-06-05 16:40:28 +02:00
vabene1111
a970f0c00e made tandoor theme default 2021-06-05 16:38:38 +02:00
vabene1111
297dd6244a model metrics 2021-06-05 15:06:54 +02:00
dependabot[bot]
c9fcbc9ff0 Bump ws from 6.2.1 to 6.2.2 in /vue
Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/commits)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-05 12:42:19 +00:00
vabene1111
c83eb1a42b prometheus basics and aws fix 2021-06-05 14:41:32 +02:00
vabene1111
8181a6d416 s3 signature version default 2021-06-05 14:14:53 +02:00
vabene1111
4a8b50aeba enable caching for signed s3 urls 2021-06-05 13:43:10 +02:00
vabene1111
388ef32475 actually apply limits everywhere 2021-06-04 17:05:03 +02:00
vabene1111
bfe72210df fixed recipe creation broke with new max_recipes setting 2021-06-04 17:01:58 +02:00
vabene1111
02c5aed0a3 moved demo to space setting 2021-06-04 16:56:18 +02:00
its_me_gb
0ec29636b3 Add original source url to the first recipe step 2021-04-30 09:14:31 +01:00
its_me_gb
d00fa10b9f Import the recipe image from the zip file. 2021-04-28 13:16:55 +01:00
its_me_gb
eba3bfa828 Basic import functionality working 2021-04-27 15:32:07 +01:00
its_me_gb
6e91f34779 add RecipeKeeper as an import/export method 2021-04-26 16:55:08 +01:00
203 changed files with 28556 additions and 6839 deletions

View File

@@ -57,7 +57,9 @@ GUNICORN_MEDIA=0
# S3_ACCESS_KEY=
# S3_SECRET_ACCESS_KEY=
# S3_BUCKET_NAME=
# S3_REGION_NAME= # default none, set your region might be required
# S3_QUERYSTRING_AUTH=1 # default true, set to 0 to serve media from a public bucket without signed urls
# S3_QUERYSTRING_EXPIRE=3600 # number of seconds querystring are valid for
# S3_ENDPOINT_URL= # when using a custom endpoint like minio
# Email Settings, see https://docs.djangoproject.com/en/3.2/ref/settings/#email-host
@@ -79,12 +81,27 @@ REVERSE_PROXY_AUTH=0
# Default settings for spaces, apply per space and can be changed in the admin view
# SPACE_DEFAULT_MAX_RECIPES=0 # 0=unlimited recipes
# SPACE_DEFAULT_MAX_USERS=0 # 0=unlimited users per space
# SPACE_DEFAULT_FILES=1 # 1=can upload files (images, etc.) NOT IMPLEMENTED YET
# SPACE_DEFAULT_MAX_FILES=0 # Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.
# SPACE_DEFAULT_ALLOW_SHARING=1 # Allow users to share recipes with public links
# allow people to create accounts on your application instance (without an invite link)
# when unset: 0 (false)
# ENABLE_SIGNUP=0
# If signup is enabled you might want to add a captcha to it to prevent spam
# HCAPTCHA_SITEKEY=
# HCAPTCHA_SECRET=
# if signup is enabled you might want to provide urls to data protection policies or terms and conditions
# TERMS_URL=
# PRIVACY_URL=
# IMPRINT_URL=
# enable serving of prometheus metrics under the /metrics path
# ATTENTION: view is not secured (as per the prometheus default way) so make sure to secure it
# trough your web server (or leave it open of you dont care if the stats are exposed)
# ENABLE_METRICS=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,
@@ -95,4 +112,9 @@ REVERSE_PROXY_AUTH=0
# SOCIAL_DEFAULT_ACCESS = 1
# if SOCIAL_DEFAULT_ACCESS is used, which group should be added
# SOCIAL_DEFAULT_GROUP=guest
# SOCIAL_DEFAULT_GROUP=guest
# Django session cookie settings. Can be changed to allow a single django application to authenticate several applications
# when running under the same database
# SESSION_COOKIE_DOMAIN=.example.com
# SESSION_COOKIE_NAME=sessionid # use this only to not interfere with non unified django applications under the same top level domain

View File

@@ -9,18 +9,17 @@
<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" >
<a href="https://github.com/vabene1111/recipes/actions" target="_blank" rel="noopener noreferrer"><img src="https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=master" ></a>
<a href="https://github.com/vabene1111/recipes/stargazers" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/github/stars/vabene1111/recipes" ></a>
<a href="https://github.com/vabene1111/recipes/network/members" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/github/forks/vabene1111/recipes" ></a>
<a href="https://hub.docker.com/r/vabene1111/recipes" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/docker/pulls/vabene1111/recipes" ></a>
<a href="https://github.com/vabene1111/recipes/releases/latest" rel="noopener noreferrer"><img src="https://img.shields.io/github/v/release/vabene1111/recipes" ></a>
</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/install/docker.html" 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>
<a href="https://app.tandoor.dev/accounts/login/?demo" target="_blank" rel="noopener noreferrer">Demo</a>
</p>
![Preview](docs/preview.png)

View File

@@ -8,7 +8,7 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
ShoppingList, ShoppingListEntry, ShoppingListRecipe,
Space, Step, Storage, Sync, SyncLog, Unit, UserPreference,
ViewLog, Supermarket, SupermarketCategory, SupermarketCategoryRelation,
ImportLog, TelegramBot, BookmarkletImport)
ImportLog, TelegramBot, BookmarkletImport, UserFile)
class CustomUserAdmin(UserAdmin):
@@ -23,14 +23,20 @@ admin.site.unregister(Group)
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'message')
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
search_fields = ('name', 'created_by__username')
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
date_hierarchy = 'created_at'
admin.site.register(Space, SpaceAdmin)
class UserPreferenceAdmin(admin.ModelAdmin):
list_display = ('name', 'space', 'theme', 'nav_color', 'default_page', 'search_style',)
list_display = ('name', 'space', 'theme', 'nav_color', 'default_page', 'search_style',) # TODO add new fields
search_fields = ('user__username', 'space__name')
list_filter = ('theme', 'nav_color', 'default_page', 'search_style')
date_hierarchy = 'created_at'
@staticmethod
def name(obj):
@@ -42,6 +48,7 @@ admin.site.register(UserPreference, UserPreferenceAdmin)
class StorageAdmin(admin.ModelAdmin):
list_display = ('name', 'method')
search_fields = ('name',)
admin.site.register(Storage, StorageAdmin)
@@ -49,6 +56,7 @@ admin.site.register(Storage, StorageAdmin)
class SyncAdmin(admin.ModelAdmin):
list_display = ('storage', 'path', 'active', 'last_checked')
search_fields = ('storage__name', 'path')
admin.site.register(Sync, SyncAdmin)
@@ -77,6 +85,7 @@ admin.site.register(Keyword)
class StepAdmin(admin.ModelAdmin):
list_display = ('name', 'type', 'order')
search_fields = ('name', 'type')
admin.site.register(Step, StepAdmin)
@@ -84,6 +93,9 @@ admin.site.register(Step, StepAdmin)
class RecipeAdmin(admin.ModelAdmin):
list_display = ('name', 'internal', 'created_by', 'storage')
search_fields = ('name', 'created_by__username')
list_filter = ('internal',)
date_hierarchy = 'created_at'
@staticmethod
def created_by(obj):
@@ -98,6 +110,7 @@ admin.site.register(Food)
class IngredientAdmin(admin.ModelAdmin):
list_display = ('food', 'amount', 'unit')
search_fields = ('food__name', 'unit__name')
admin.site.register(Ingredient, IngredientAdmin)
@@ -105,6 +118,8 @@ admin.site.register(Ingredient, IngredientAdmin)
class CommentAdmin(admin.ModelAdmin):
list_display = ('recipe', 'name', 'created_at')
search_fields = ('text', 'user__username')
date_hierarchy = 'created_at'
@staticmethod
def name(obj):
@@ -123,6 +138,7 @@ admin.site.register(RecipeImport, RecipeImportAdmin)
class RecipeBookAdmin(admin.ModelAdmin):
list_display = ('name', 'user_name')
search_fields = ('name', 'created_by__username')
@staticmethod
def user_name(obj):
@@ -152,6 +168,7 @@ admin.site.register(MealPlan, MealPlanAdmin)
class MealTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'order')
search_fields = ('name', 'created_by__username')
admin.site.register(MealType, MealTypeAdmin)
@@ -166,7 +183,7 @@ admin.site.register(ViewLog, ViewLogAdmin)
class InviteLinkAdmin(admin.ModelAdmin):
list_display = (
'username', 'group', 'valid_until',
'group', 'valid_until',
'created_by', 'created_at', 'used_by'
)
@@ -235,3 +252,10 @@ class BookmarkletImportAdmin(admin.ModelAdmin):
admin.site.register(BookmarkletImport, BookmarkletImportAdmin)
class UserFileAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'file_size_kb', 'created_at',)
admin.site.register(UserFile, UserFileAdmin)

View File

@@ -1,10 +1,12 @@
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.forms import widgets
from django.utils.translation import gettext_lazy as _
from django_scopes import scopes_disabled
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
from emoji_picker.widgets import EmojiPickerTextInput
from hcaptcha.fields import hCaptchaField
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, Storage, Sync, Unit, User,
@@ -119,17 +121,20 @@ class ImportExportBase(forms.Form):
SAFRON = 'SAFRON'
CHEFTAP = 'CHEFTAP'
PEPPERPLATE = 'PEPPERPLATE'
RECIPEKEEPER = 'RECIPEKEEPER'
RECETTETEK = 'RECETTETEK'
RECIPESAGE = 'RECIPESAGE'
DOMESTICA = 'DOMESTICA'
MEALMASTER = 'MEALMASTER'
REZKONV = 'REZKONV'
OPENEATS = 'OPENEATS'
type = forms.ChoiceField(choices=(
(DEFAULT, _('Default')), (PAPRIKA, 'Paprika'), (NEXTCLOUD, 'Nextcloud Cookbook'),
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
(PEPPERPLATE, 'Pepperplate'), (RECETTETEK, 'RecetteTek'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'),
(MEALMASTER, 'MealMaster'), (REZKONV, 'RezKonv'), (OPENEATS, 'Openeats'), (RECIPEKEEPER, 'Recipe Keeper'),
))
@@ -411,19 +416,11 @@ class InviteLinkForm(forms.ModelForm):
return email
def clean_username(self):
username = self.cleaned_data['username']
with scopes_disabled():
if username != '' and (User.objects.filter(username=username).exists() or InviteLink.objects.filter(username=username).exists()):
raise ValidationError(_('Username already taken!'))
return username
class Meta:
model = InviteLink
fields = ('username', 'email', 'group', 'valid_until', 'space')
fields = ('email', 'group', 'valid_until', 'space')
help_texts = {
'username': _('A username is not required, if left blank the new user can choose one.'),
'email': _('An email address is not required but if present the invite link will be send to the user.')
'email': _('An email address is not required but if present the invite link will be send to the user.'),
}
field_classes = {
'space': SafeModelChoiceField,
@@ -447,6 +444,21 @@ class SpaceJoinForm(forms.Form):
token = forms.CharField()
class AllAuthSignupForm(forms.Form):
captcha = hCaptchaField()
terms = forms.BooleanField(label=_('Accept Terms and Privacy'))
def __init__(self, **kwargs):
super(AllAuthSignupForm, self).__init__(**kwargs)
if settings.PRIVACY_URL == '' and settings.TERMS_URL == '':
self.fields.pop('terms')
if settings.HCAPTCHA_SECRET == '':
self.fields.pop('captcha')
def signup(self, request, user):
pass
class UserCreateForm(forms.Form):
name = forms.CharField(label='Username')
password = forms.CharField(

View File

@@ -7,6 +7,8 @@ from django.contrib import messages
from django.core.cache import caches
from gettext import gettext as _
from cookbook.models import InviteLink
class AllAuthCustomAdapter(DefaultAccountAdapter):
@@ -14,7 +16,11 @@ class AllAuthCustomAdapter(DefaultAccountAdapter):
"""
Whether to allow sign ups.
"""
if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP:
signup_token = False
if 'signup_token' in request.session and InviteLink.objects.filter(valid_until__gte=datetime.datetime.today(), used_by=None, uuid=request.session['signup_token']).exists():
signup_token = True
if (request.resolver_match.view_name == 'account_signup' or request.resolver_match.view_name == 'socialaccount_signup') and not settings.ENABLE_SIGNUP and not signup_token:
return False
else:
return super(AllAuthCustomAdapter, self).is_open_for_signup(request)

View File

@@ -0,0 +1,19 @@
import hashlib
from django.conf import settings
from django.core.cache import cache
from storages.backends.s3boto3 import S3Boto3Storage
class CachedS3Boto3Storage(S3Boto3Storage):
def url(self, name, **kwargs):
key = hashlib.md5(f'recipes_media_urls_{name}'.encode('utf-8')).hexdigest()
if result := cache.get(key):
return result
result = super(CachedS3Boto3Storage, self).url(name, **kwargs)
timeout = int(settings.AWS_QUERYSTRING_EXPIRE * .95)
cache.set(key, result, timeout)
return result

View File

@@ -0,0 +1,13 @@
from django.conf import settings
def context_settings(request):
return {
'EMAIL_ENABLED': settings.EMAIL_HOST != '',
'SIGNUP_ENABLED': settings.ENABLE_SIGNUP,
'CAPTCHA_ENABLED': settings.HCAPTCHA_SITEKEY != '',
'HOSTED': settings.HOSTED,
'TERMS_URL': settings.TERMS_URL,
'PRIVACY_URL': settings.PRIVACY_URL,
'IMPRINT_URL': settings.IMPRINT_URL,
}

View File

@@ -0,0 +1,45 @@
import os
import sys
from PIL import Image
from io import BytesIO
def rescale_image_jpeg(image_object, base_width=720):
img = Image.open(image_object)
icc_profile = img.info.get('icc_profile') # remember color profile to not mess up colors
width_percent = (base_width / float(img.size[0]))
height = int((float(img.size[1]) * float(width_percent)))
img = img.resize((base_width, height), Image.ANTIALIAS)
img_bytes = BytesIO()
img.save(img_bytes, 'JPEG', quality=75, optimize=True, icc_profile=icc_profile)
return img_bytes
def rescale_image_png(image_object, base_width=720):
basewidth = 720
wpercent = (basewidth / float(image_object.size[0]))
hsize = int((float(image_object.size[1]) * float(wpercent)))
img = image_object.resize((basewidth, hsize), Image.ANTIALIAS)
im_io = BytesIO()
img.save(im_io, 'PNG', quality=70)
return img
def get_filetype(name):
try:
return os.path.splitext(name)[1]
except:
return '.jpeg'
def handle_image(request, image_object, filetype='.jpeg'):
if sys.getsizeof(image_object) / 8 > 500:
if filetype == '.jpeg':
return rescale_image_jpeg(image_object), filetype
if filetype == '.png':
return rescale_image_png(image_object), filetype
return image_object, filetype

View File

@@ -1,3 +1,4 @@
import re
import string
import unicodedata
@@ -22,20 +23,16 @@ def parse_fraction(x):
def parse_amount(x):
amount = 0
unit = ''
note = ''
did_check_frac = False
end = 0
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
)
)
):
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:
if "/" in x[:end]:
@@ -55,7 +52,11 @@ def parse_amount(x):
unit = x[end + 1:]
except ValueError:
unit = x[end:]
return amount, unit
if unit.startswith('(') or unit.startswith('-'): # i dont know any unit that starts with ( or - so its likely an alternative like 1L (500ml) Water or 2-3
unit = ''
note = x
return amount, unit, note
def parse_ingredient_with_comma(tokens):
@@ -106,6 +107,13 @@ def parse(x):
unit = ''
ingredient = ''
note = ''
unit_note = ''
# if the string contains parenthesis early on remove it and place it at the end
# because its likely some kind of note
if re.match('(.){1,6}\s\((.[^\(\)])+\)\s', x):
match = re.search('\((.[^\(])+\)', x)
x = x[:match.start()] + x[match.end():] + ' ' + x[match.start():match.end()]
tokens = x.split()
if len(tokens) == 1:
@@ -114,17 +122,17 @@ def parse(x):
else:
try:
# try to parse first argument as amount
amount, unit = parse_amount(tokens[0])
amount, unit, unit_note = 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
if len(tokens) > 2:
try:
if not unit == '':
# a unit is already found, no need to try the second argument for a fraction # noqa: E501
# 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 # noqa: E501
raise ValueError
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½' # noqa: E501
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½'
amount += parse_fraction(tokens[1])
# assume that units can't end with a comma
if len(tokens) > 3 and not tokens[2].endswith(','):
@@ -142,7 +150,10 @@ def parse(x):
# 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]
if unit == '':
unit = tokens[1]
else:
note = tokens[1]
except ValueError:
ingredient, note = parse_ingredient(tokens[1:])
else:
@@ -158,11 +169,16 @@ def parse(x):
ingredient, note = parse_ingredient(tokens)
except ValueError:
ingredient = ' '.join(tokens[1:])
if unit_note not in note:
note += ' ' + unit_note
return amount, unit.strip(), ingredient.strip(), note.strip()
# small utility functions to prevent emtpy unit/food creation
def get_unit(unit, space):
if not unit:
return None
if len(unit) > 0:
u, created = Unit.objects.get_or_create(name=unit, space=space)
return u
@@ -170,6 +186,8 @@ def get_unit(unit, space):
def get_food(food, space):
if not food:
return None
if len(food) > 0:
f, created = Food.objects.get_or_create(name=food, space=space)
return f

View File

@@ -1,6 +1,8 @@
"""
Source: https://djangosnippets.org/snippets/1703/
"""
from django.conf import settings
from django.core.cache import caches
from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import ModelFormMixin
@@ -90,7 +92,18 @@ def share_link_valid(recipe, share):
: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
CACHE_KEY = f'recipe_share_{recipe.pk}_{share}'
if c := caches['default'].get(CACHE_KEY, False):
return c
if link := ShareLink.objects.filter(recipe=recipe, uuid=share, abuse_blocked=False).first():
if 0 < settings.SHARING_LIMIT < link.request_count:
return False
link.request_count += 1
link.save()
caches['default'].set(CACHE_KEY, True, timeout=3)
return True
return False
except ValidationError:
return False
@@ -121,15 +134,18 @@ class GroupRequiredMixin(object):
def dispatch(self, request, *args, **kwargs):
if not has_group_permission(request.user, self.groups_required):
if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
messages.add_message(request, messages.ERROR,
_('You are not logged in and therefore cannot view this page!'))
return HttpResponseRedirect(reverse_lazy('account_login') + '?next=' + request.path)
else:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
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!'))
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
@@ -141,17 +157,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!'))
messages.add_message(request, messages.ERROR,
_('You are not logged in and therefore cannot view this page!'))
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!'))
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!'))
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

View File

@@ -21,17 +21,21 @@ def search_recipes(request, queryset, params):
search_internal = params.get('internal', None)
search_random = params.get('random', False)
search_new = params.get('new', False)
search_last_viewed = int(params.get('last_viewed', 0))
if search_last_viewed > 0:
last_viewed_recipes = ViewLog.objects.filter(created_by=request.user, space=request.space,
created_at__gte=datetime.now() - timedelta(days=14)).order_by('pk').values_list('recipe__pk', flat=True).distinct()
return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes)-search_last_viewed:])
return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes) - min(len(last_viewed_recipes), search_last_viewed):])
queryset = queryset.annotate(
new_recipe=Case(When(created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
default=Value(0), )).order_by('-new_recipe', 'name')
if search_new == 'true':
queryset = queryset.annotate(
new_recipe=Case(When(created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
default=Value(0), )).order_by('-new_recipe', 'name')
else:
queryset = queryset.order_by('name')
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
'django.db.backends.postgresql']:

View File

@@ -101,22 +101,21 @@ def get_from_scraper(scrape, space):
for x in scrape.ingredients():
try:
amount, unit, ingredient, note = parse_single_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
}
)
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(
{
@@ -359,3 +358,11 @@ def normalize_string(string):
unescaped_string = re.sub(r'\n\s*\n', '\n\n', unescaped_string)
unescaped_string = unescaped_string.replace("\xa0", " ").replace("\t", " ").strip()
return unescaped_string
def iso_duration_to_minutes(string):
match = re.match(
r'P((?P<years>\d+)Y)?((?P<months>\d+)M)?((?P<weeks>\d+)W)?((?P<days>\d+)D)?T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+)S)?',
string
).groupdict()
return int(match['days'] or 0) * 24 * 60 + int(match['hours'] or 0) * 60 + int(match['minutes'] or 0)

View File

@@ -16,7 +16,10 @@ class ScopeMiddleware:
with scopes_disabled():
return self.get_response(request)
if request.path.startswith('/signup/'):
if request.path.startswith('/signup/') or request.path.startswith('/invite/'):
return self.get_response(request)
if request.path.startswith('/accounts/'):
return self.get_response(request)
with scopes_disabled():

View File

@@ -40,7 +40,7 @@ class Pepperplate(Integration):
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n'
instruction='\n'.join(directions) + '\n\n', space=self.request.space,
)
for ingredient in ingredients:
@@ -49,7 +49,7 @@ class Pepperplate(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)

View File

@@ -38,7 +38,7 @@ class ChefTap(Integration):
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, space=self.request.space, )
step = Step.objects.create(instruction='\n'.join(directions))
step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space,)
if source_url != '':
step.instruction += '\n' + source_url
@@ -50,7 +50,7 @@ class ChefTap(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)

View File

@@ -3,6 +3,7 @@ import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.image_processing import get_filetype
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
@@ -54,7 +55,7 @@ class Chowdown(Integration):
recipe.keywords.add(keyword)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions)
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions), space=self.request.space,
)
for ingredient in ingredients:
@@ -62,7 +63,7 @@ class Chowdown(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)
@@ -71,7 +72,7 @@ class Chowdown(Integration):
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)))
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)), filetype=get_filetype(z.filename))
return recipe

View File

@@ -1,9 +1,11 @@
import json
from io import BytesIO
from re import match
from zipfile import ZipFile
from rest_framework.renderers import JSONRenderer
from cookbook.helper.image_processing import get_filetype
from cookbook.integration.integration import Integration
from cookbook.serializer import RecipeExportSerializer
@@ -15,8 +17,9 @@ class Default(Integration):
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')))
images = list(filter(lambda v: match('image.*', v), recipe_zip.namelist()))
if images:
self.import_recipe_image(recipe, BytesIO(recipe_zip.read(images[0])), filetype=get_filetype(images[0]))
return recipe
def decode_recipe(self, string):

View File

@@ -28,7 +28,7 @@ class Domestica(Integration):
recipe.save()
step = Step.objects.create(
instruction=file['directions']
instruction=file['directions'], space=self.request.space,
)
if file['source'] != '':
@@ -40,12 +40,12 @@ class Domestica(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)
if file['image'] != '':
self.import_recipe_image(recipe, BytesIO(base64.b64decode(file['image'].replace('data:image/jpeg;base64,', ''))))
self.import_recipe_image(recipe, BytesIO(base64.b64decode(file['image'].replace('data:image/jpeg;base64,', ''))), filetype='.jpeg')
return recipe

View File

@@ -1,5 +1,7 @@
import datetime
import json
import os
import re
import uuid
from io import BytesIO, StringIO
from zipfile import ZipFile, BadZipFile
@@ -11,6 +13,7 @@ from django.utils.translation import gettext as _
from django_scopes import scope
from cookbook.forms import ImportExportBase
from cookbook.helper.image_processing import get_filetype
from cookbook.models import Keyword, Recipe
@@ -58,7 +61,7 @@ class Integration:
recipe_zip_obj.writestr(filename, recipe_stream.getvalue())
recipe_stream.close()
try:
recipe_zip_obj.writestr('image.png', r.image.file.read())
recipe_zip_obj.writestr(f'image{get_filetype(r.image.file.name)}', r.image.file.read())
except ValueError:
pass
@@ -104,26 +107,54 @@ class Integration:
try:
self.files = files
for f in files:
if '.zip' in f['name'] or '.paprikarecipes' in f['name']:
if 'RecipeKeeper' in f['name']:
import_zip = ZipFile(f['file'])
file_list = []
for z in import_zip.filelist:
if self.import_file_name_filter(z):
try:
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
except Exception as e:
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
file_list.append(z)
il.total_recipes += len(file_list)
for z in file_list:
data_list = self.split_recipe_file(import_zip.read(z.filename).decode('utf-8'))
for d in data_list:
recipe = self.get_recipe_from_file(d)
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
il.imported_recipes += 1
il.save()
import_zip.close()
elif '.zip' in f['name'] or '.paprikarecipes' in f['name']:
import_zip = ZipFile(f['file'])
file_list = []
for z in import_zip.filelist:
if self.import_file_name_filter(z):
file_list.append(z)
il.total_recipes += len(file_list)
for z in file_list:
try:
recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
il.imported_recipes += 1
il.save()
except Exception as e:
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
import_zip.close()
elif '.json' in f['name'] or '.txt' in f['name']:
data_list = self.split_recipe_file(f['file'])
il.total_recipes += len(data_list)
for d in data_list:
try:
recipe = self.get_recipe_from_file(d)
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
il.imported_recipes += 1
il.save()
except Exception as e:
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
elif '.rtk' in f['name']:
@@ -131,12 +162,16 @@ class Integration:
for z in import_zip.filelist:
if self.import_file_name_filter(z):
data_list = self.split_recipe_file(import_zip.read(z.filename).decode('utf-8'))
il.total_recipes += len(data_list)
for d in data_list:
try:
recipe = self.get_recipe_from_file(d)
recipe.keywords.add(self.keyword)
il.msg += f'{recipe.pk} - {recipe.name} \n'
self.handle_duplicates(recipe, import_duplicates)
il.imported_recipes += 1
il.save()
except Exception as e:
il.msg += f'-------------------- \n ERROR \n{e}\n--------------------\n'
import_zip.close()
@@ -148,6 +183,9 @@ class Integration:
except BadZipFile:
il.msg += 'ERROR ' + _(
'Importer expected a .zip file. Did you choose the correct importer type for your data ?') + '\n'
except:
il.msg += 'ERROR ' + _(
'An unexpected error occurred during the import. Please make sure you have uploaded a valid file.') + '\n'
if len(self.ignored_recipes) > 0:
il.msg += '\n' + _(
@@ -170,13 +208,14 @@ class Integration:
self.ignored_recipes.append(recipe.name)
@staticmethod
def import_recipe_image(recipe, image_file):
def import_recipe_image(recipe, image_file, filetype='.jpeg'):
"""
Adds an image to a recipe naming it correctly
:param recipe: Recipe object
:param image_file: ByteIO stream containing the image
:param filetype: type of file to write bytes to, default to .jpeg if unknown
"""
recipe.image = File(image_file, name=f'{uuid.uuid4()}_{recipe.pk}.png')
recipe.image = File(image_file, name=f'{uuid.uuid4()}_{recipe.pk}{filetype}')
recipe.save()
def get_recipe_from_file(self, file):

View File

@@ -3,6 +3,7 @@ import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.image_processing import get_filetype
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
@@ -11,40 +12,55 @@ from cookbook.models import Recipe, Step, Food, Unit, Ingredient
class Mealie(Integration):
def import_file_name_filter(self, zip_info_object):
return re.match(r'^recipes/([A-Za-z\d-])+.json$', zip_info_object.filename)
return re.match(r'^recipes/([A-Za-z\d-])+/([A-Za-z\d-])+.json$', zip_info_object.filename)
def get_recipe_from_file(self, file):
recipe_json = json.loads(file.getvalue().decode("utf-8"))
description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
recipe = Recipe.objects.create(
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
name=recipe_json['name'].strip(), description=description,
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']:
for s in recipe_json['recipe_instructions']:
step = Step.objects.create(
instruction=s['text']
instruction=s['text'], space=self.request.space,
)
if not ingredients_added:
ingredients_added = True
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
))
if len(recipe_json['description'].strip()) > 500:
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
for ingredient in recipe_json['recipe_ingredient']:
try:
if ingredient['food']:
f = get_food(ingredient['food'], self.request.space)
u = get_unit(ingredient['unit'], self.request.space)
amount = ingredient['quantity']
note = ingredient['note']
else:
amount, unit, ingredient, note = parse(ingredient['note'])
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
except:
pass
recipe.steps.add(step)
for f in self.files:
if '.zip' in f['name']:
import_zip = ZipFile(f['file'])
for z in import_zip.filelist:
if re.match(f'^images/{recipe_json["slug"]}.jpg$', z.filename):
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
try:
self.import_recipe_image(recipe, BytesIO(import_zip.read(f'recipes/{recipe_json["slug"]}/images/min-original.webp')), filetype=get_filetype(f'recipes/{recipe_json["slug"]}/images/original'))
except:
pass
return recipe

View File

@@ -44,7 +44,7 @@ class MealMaster(Integration):
recipe.keywords.add(keyword)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n'
instruction='\n'.join(directions) + '\n\n', space=self.request.space,
)
for ingredient in ingredients:
@@ -53,7 +53,7 @@ class MealMaster(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)

View File

@@ -3,6 +3,7 @@ import re
from io import BytesIO
from zipfile import ZipFile
from cookbook.helper.image_processing import get_filetype
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
@@ -16,8 +17,10 @@ class NextcloudCookbook(Integration):
def get_recipe_from_file(self, file):
recipe_json = json.loads(file.getvalue().decode("utf-8"))
description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
recipe = Recipe.objects.create(
name=recipe_json['name'].strip(), description=recipe_json['description'].strip(),
name=recipe_json['name'].strip(), description=description,
created_by=self.request.user, internal=True,
servings=recipe_json['recipeYield'], space=self.request.space)
@@ -27,9 +30,12 @@ class NextcloudCookbook(Integration):
ingredients_added = False
for s in recipe_json['recipeInstructions']:
step = Step.objects.create(
instruction=s
instruction=s, space=self.request.space,
)
if not ingredients_added:
if len(recipe_json['description'].strip()) > 500:
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
ingredients_added = True
for ingredient in recipe_json['recipeIngredient']:
@@ -37,7 +43,7 @@ class NextcloudCookbook(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)
@@ -46,7 +52,7 @@ class NextcloudCookbook(Integration):
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)))
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)), filetype=get_filetype(z.filename))
return recipe

View File

@@ -0,0 +1,71 @@
import json
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 OpenEats(Integration):
def get_recipe_from_file(self, file):
recipe = Recipe.objects.create(name=file['name'].strip(), created_by=self.request.user, internal=True,
servings=file['servings'], space=self.request.space, waiting_time=file['cook_time'], working_time=file['prep_time'])
instructions = ''
if file["info"] != '':
instructions += file["info"]
if file["directions"] != '':
instructions += file["directions"]
if file["source"] != '':
instructions += file["source"]
step = Step.objects.create(instruction=instructions, space=self.request.space,)
for ingredient in file['ingredients']:
f = get_food(ingredient['food'], self.request.space)
u = get_unit(ingredient['unit'], self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=ingredient['amount'], space=self.request.space,
))
recipe.steps.add(step)
return recipe
def split_recipe_file(self, file):
recipe_json = json.loads(file.read())
recipe_dict = {}
ingredient_group_dict = {}
for o in recipe_json:
if o['model'] == 'recipe.recipe':
recipe_dict[o['pk']] = {
'name': o['fields']['title'],
'info': o['fields']['info'],
'directions': o['fields']['directions'],
'source': o['fields']['source'],
'prep_time': o['fields']['prep_time'],
'cook_time': o['fields']['cook_time'],
'servings': o['fields']['servings'],
'ingredients': [],
}
if o['model'] == 'ingredient.ingredientgroup':
ingredient_group_dict[o['pk']] = o['fields']['recipe']
for o in recipe_json:
if o['model'] == 'ingredient.ingredient':
ingredient = {
'food': o['fields']['title'],
'unit': o['fields']['measurement'],
'amount': round(o['fields']['numerator'] / o['fields']['denominator'], 2),
}
recipe_dict[ingredient_group_dict[o['fields']['ingredient_group']]]['ingredients'].append(ingredient)
return list(recipe_dict.values())
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -23,10 +23,10 @@ class Paprika(Integration):
name=recipe_json['name'].strip(), created_by=self.request.user, internal=True, space=self.request.space)
if 'description' in recipe_json:
recipe.description = recipe_json['description'].strip()
recipe.description = '' if len(recipe_json['description'].strip()) > 500 else recipe_json['description'].strip()
try:
if re.match(r'([0-9])+\s(.)*', recipe_json['servings'] ):
if re.match(r'([0-9])+\s(.)*', recipe_json['servings']):
s = recipe_json['servings'].split(' ')
recipe.servings = s[0]
recipe.servings_text = s[1]
@@ -55,9 +55,12 @@ class Paprika(Integration):
pass
step = Step.objects.create(
instruction=instructions
instruction=instructions, space=self.request.space,
)
if len(recipe_json['description'].strip()) > 500:
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
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)
@@ -70,7 +73,7 @@ class Paprika(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
except AttributeError:
pass
@@ -78,6 +81,6 @@ class Paprika(Integration):
recipe.steps.add(step)
if recipe_json.get("photo_data", None):
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])))
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])), filetype='.jpeg')
return recipe

View File

@@ -7,6 +7,7 @@ from zipfile import ZipFile
import imghdr
from django.utils.translation import gettext as _
from cookbook.helper.image_processing import get_filetype
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
@@ -25,7 +26,7 @@ class RecetteTek(Integration):
recipe_list = [r for r in recipe_json]
return recipe_list
def get_recipe_from_file(self, file):
# Create initial recipe with just a title and a decription
@@ -44,7 +45,7 @@ class RecetteTek(Integration):
if not instructions:
instructions = ''
step = Step.objects.create(instruction=instructions)
step = Step.objects.create(instruction=instructions, space=self.request.space,)
# Append the original import url to the step (if it exists)
try:
@@ -53,7 +54,7 @@ class RecetteTek(Integration):
step.save()
except Exception as e:
print(recipe.name, ': failed to import source url ', str(e))
try:
# Process the ingredients. Assumes 1 ingredient per line.
for ingredient in file['ingredients'].split('\n'):
@@ -62,7 +63,7 @@ class RecetteTek(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
except Exception as e:
print(recipe.name, ': failed to parse recipe ingredients ', str(e))
@@ -96,7 +97,7 @@ class RecetteTek(Integration):
recipe.waiting_time = int(file['cookingTime'])
except Exception as e:
print(recipe.name, ': failed to parse cooking time ', str(e))
recipe.save()
# Import the recipe keywords
@@ -110,20 +111,20 @@ class RecetteTek(Integration):
pass
# TODO: Parse Nutritional Information
# Import the original image from the zip file, if we cannot do that, attempt to download it again.
try:
if file['pictures'][0] !='':
if file['pictures'][0] != '':
image_file_name = file['pictures'][0].split('/')[-1]
for f in self.files:
if '.rtk' in f['name']:
import_zip = ZipFile(f['file'])
self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name)))
self.import_recipe_image(recipe, BytesIO(import_zip.read(image_file_name)), filetype=get_filetype(image_file_name))
else:
if file['originalPicture'] != '':
response=requests.get(file['originalPicture'])
response = requests.get(file['originalPicture'])
if imghdr.what(BytesIO(response.content)) != None:
self.import_recipe_image(recipe, BytesIO(response.content))
self.import_recipe_image(recipe, BytesIO(response.content), filetype=get_filetype(file['originalPicture']))
else:
raise Exception("Original image failed to download.")
except Exception as e:

View File

@@ -0,0 +1,80 @@
import re
from bs4 import BeautifulSoup
from io import BytesIO
from zipfile import ZipFile
from django.utils.translation import gettext as _
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes
from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
class RecipeKeeper(Integration):
def import_file_name_filter(self, zip_info_object):
return re.match(r'^recipes.html$', zip_info_object.filename)
def split_recipe_file(self, file):
recipe_html = BeautifulSoup(file, 'html.parser')
return recipe_html.find_all('div', class_='recipe-details')
def get_recipe_from_file(self, file):
# 'file' comes is as a beautifulsoup object
recipe = Recipe.objects.create(name=file.find("h2", {"itemprop": "name"}).text.strip(), created_by=self.request.user, internal=True, space=self.request.space, )
# add 'Courses' and 'Categories' as keywords
for course in file.find_all("span", {"itemprop": "recipeCourse"}):
keyword, created = Keyword.objects.get_or_create(name=course.text, space=self.request.space)
recipe.keywords.add(keyword)
for category in file.find_all("meta", {"itemprop": "recipeCategory"}):
keyword, created = Keyword.objects.get_or_create(name=category.get("content"), space=self.request.space)
recipe.keywords.add(keyword)
try:
recipe.servings = parse_servings(file.find("span", {"itemprop": "recipeYield"}).text.strip())
recipe.working_time = iso_duration_to_minutes(file.find("span", {"meta": "prepTime"}).text.strip())
recipe.waiting_time = iso_duration_to_minutes(file.find("span", {"meta": "cookTime"}).text.strip())
recipe.save()
except AttributeError:
pass
step = Step.objects.create(instruction='', space=self.request.space,)
for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"):
if ingredient.text == "":
continue
amount, unit, ingredient, note = parse(ingredient.text.strip())
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
for s in file.find("div", {"itemprop": "recipeDirections"}).find_all("p"):
if s.text == "":
continue
step.instruction += s.text + ' \n'
if file.find("span", {"itemprop": "recipeSource"}).text != '':
step.instruction += "\n\nImported from: " + file.find("span", {"itemprop": "recipeSource"}).text
step.save()
source_url_added = True
recipe.steps.add(step)
# import the Primary recipe image that is stored in the Zip
try:
for f in self.files:
if '.zip' in f['name']:
import_zip = ZipFile(f['file'])
self.import_recipe_image(recipe, BytesIO(import_zip.read(file.find("img", class_="recipe-photo").get("src"))), filetype='.jpeg')
except Exception as e:
pass
return recipe
def get_file_from_recipe(self, recipe):
raise NotImplementedError('Method not implemented in storage integration')

View File

@@ -36,7 +36,7 @@ class RecipeSage(Integration):
ingredients_added = False
for s in file['recipeInstructions']:
step = Step.objects.create(
instruction=s['text']
instruction=s['text'], space=self.request.space,
)
if not ingredients_added:
ingredients_added = True
@@ -46,7 +46,7 @@ class RecipeSage(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)

View File

@@ -43,7 +43,7 @@ class RezKonv(Integration):
recipe.keywords.add(keyword)
step = Step.objects.create(
instruction='\n'.join(directions) + '\n\n'
instruction='\n'.join(directions) + '\n\n', space=self.request.space,
)
for ingredient in ingredients:
@@ -52,7 +52,7 @@ class RezKonv(Integration):
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)

View File

@@ -43,14 +43,14 @@ class Safron(Integration):
recipe = Recipe.objects.create(name=title, description=description, created_by=self.request.user, internal=True, space=self.request.space, )
step = Step.objects.create(instruction='\n'.join(directions))
step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space,)
for ingredient in ingredients:
amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space)
u = get_unit(unit, self.request.space)
step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note
food=f, unit=u, amount=amount, note=note, space=self.request.space,
))
recipe.steps.add(step)

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

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

Binary file not shown.

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

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.3 on 2021-06-04 14:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0124_alter_userpreference_theme'),
]
operations = [
migrations.AddField(
model_name='space',
name='demo',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.3 on 2021-06-05 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0125_space_demo'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero')], default='TANDOOR', max_length=128),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 3.2.3 on 2021-06-07 14:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0126_alter_userpreference_theme'),
]
operations = [
migrations.RemoveField(
model_name='invitelink',
name='username',
),
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 3.2.3 on 2021-06-08 10: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', '0127_remove_invitelink_username'),
]
operations = [
migrations.CreateModel(
name='UserFile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('file', models.FileField(upload_to='files/')),
('file_size_kb', models.IntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.2.3 on 2021-06-08 10:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0128_userfile'),
]
operations = [
migrations.RemoveField(
model_name='space',
name='allow_files',
),
migrations.AddField(
model_name='space',
name='max_file_storage_mb',
field=models.IntegerField(default=0, help_text='Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.3 on 2021-06-08 10:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0129_auto_20210608_1233'),
]
operations = [
migrations.AlterField(
model_name='userfile',
name='file_size_kb',
field=models.IntegerField(blank=True, default=0),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.2.4 on 2021-06-08 17:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0130_alter_userfile_file_size_kb'),
]
operations = [
migrations.AddField(
model_name='step',
name='file',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.userfile'),
),
migrations.AlterField(
model_name='step',
name='type',
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time'), ('FILE', 'File')], default='TEXT', max_length=16),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-06-12 18:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0131_auto_20210608_1929'),
]
operations = [
migrations.AddField(
model_name='sharelink',
name='request_count',
field=models.IntegerField(default=0),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-06-12 18:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0132_sharelink_request_count'),
]
operations = [
migrations.AddField(
model_name='sharelink',
name='abuse_blocked',
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2021-06-15 19:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0133_sharelink_abuse_blocked'),
]
operations = [
migrations.AddField(
model_name='space',
name='allow_sharing',
field=models.BooleanField(default=True),
),
]

View File

@@ -0,0 +1,29 @@
# Generated by Django 3.2.4 on 2021-06-15 20:10
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0134_space_allow_sharing'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='nutritioninformation',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='step',
name='space',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-06-17 11:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0135_auto_20210615_2210'),
]
operations = [
migrations.AddField(
model_name='importlog',
name='imported_recipes',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='importlog',
name='total_recipes',
field=models.IntegerField(default=0),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 3.2.4 on 2021-06-17 13:01
from django.db import migrations
from django.db.models import Subquery, OuterRef
from django_scopes import scopes_disabled
from django.db import migrations, models
import django.db.models.deletion
def migrate_spaces(apps, schema_editor):
with scopes_disabled():
Recipe = apps.get_model('cookbook', 'Recipe')
Step = apps.get_model('cookbook', 'Step')
Ingredient = apps.get_model('cookbook', 'Ingredient')
NutritionInformation = apps.get_model('cookbook', 'NutritionInformation')
Step.objects.filter(recipe__isnull=True).delete()
Ingredient.objects.filter(step__recipe__isnull=True).delete()
NutritionInformation.objects.filter(recipe__isnull=True).delete()
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
NutritionInformation.objects.update(space=Subquery(NutritionInformation.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0136_auto_20210617_1343'),
]
operations = [
migrations.RunPython(migrate_spaces),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 3.2.4 on 2021-06-17 14:02
from django.db import migrations
from django.db.models import Subquery, OuterRef
from django_scopes import scopes_disabled
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0137_auto_20210617_1501'),
]
operations = [
migrations.AlterField(
model_name='ingredient',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AlterField(
model_name='nutritioninformation',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AlterField(
model_name='step',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.2.4 on 2021-06-22 16:14
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0138_auto_20210617_1602'),
]
operations = [
migrations.AddField(
model_name='space',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.2.4 on 2021-06-22 16:19
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0139_space_created_at'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.2.5 on 2021-07-13 08:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0140_userpreference_created_at'),
]
operations = [
migrations.AddField(
model_name='step',
name='step_recipe',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.recipe'),
),
migrations.AlterField(
model_name='step',
name='type',
field=models.CharField(choices=[('TEXT', 'Text'), ('TIME', 'Time'), ('FILE', 'File'), ('RECIPE', 'Recipe')], default='TEXT', max_length=16),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.2.5 on 2021-07-29 14:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0141_auto_20210713_1042'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large'), ('NEW', 'New')], default='NEW', max_length=64),
),
]

View File

@@ -1,3 +1,5 @@
import operator
import pathlib
import re
import uuid
from datetime import date, timedelta
@@ -5,10 +7,12 @@ from datetime import date, timedelta
from annoying.fields import AutoOneToOneField
from django.contrib import auth
from django.contrib.auth.models import Group, User
from django.core.files.uploadedfile import UploadedFile, InMemoryUploadedFile
from django.core.validators import MinLengthValidator
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext as _
from django_prometheus.models import ExportModelOperationsMixin
from django_scopes import ScopedManager
from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT,
@@ -52,18 +56,23 @@ class PermissionModelMixin:
def get_space(self):
p = '.'.join(self.get_space_key())
if getattr(self, p, None):
return getattr(self, p)
raise NotImplementedError('get space for method not implemented and standard fields not available')
try:
if space := operator.attrgetter(p)(self):
return space
except AttributeError:
raise NotImplementedError('get space for method not implemented and standard fields not available')
class Space(models.Model):
class Space(ExportModelOperationsMixin('space'), models.Model):
name = models.CharField(max_length=128, default='Default')
created_by = models.ForeignKey(User, on_delete=models.PROTECT, null=True)
created_at = models.DateTimeField(auto_now_add=True)
message = models.CharField(max_length=512, default='', blank=True)
max_recipes = models.IntegerField(default=0)
allow_files = models.BooleanField(default=True)
max_file_storage_mb = models.IntegerField(default=0, help_text=_('Maximum file storage for space in MB. 0 for unlimited, -1 to disable file upload.'))
max_users = models.IntegerField(default=0)
allow_sharing = models.BooleanField(default=True)
demo = models.BooleanField(default=False)
def __str__(self):
return self.name
@@ -78,11 +87,11 @@ class UserPreference(models.Model, PermissionModelMixin):
TANDOOR = 'TANDOOR'
THEMES = (
(TANDOOR, 'Tandoor'),
(BOOTSTRAP, 'Bootstrap'),
(DARKLY, 'Darkly'),
(FLATLY, 'Flatly'),
(SUPERHERO, 'Superhero'),
(TANDOOR, 'Tandoor')
)
# Nav colors
@@ -124,7 +133,7 @@ class UserPreference(models.Model, PermissionModelMixin):
SEARCH_STYLE = ((SMALL, _('Small')), (LARGE, _('Large')), (NEW, _('New')))
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY)
theme = models.CharField(choices=THEMES, max_length=128, default=TANDOOR)
nav_color = models.CharField(
choices=COLORS, max_length=128, default=PRIMARY
)
@@ -134,7 +143,7 @@ class UserPreference(models.Model, PermissionModelMixin):
choices=PAGES, max_length=64, default=SEARCH
)
search_style = models.CharField(
choices=SEARCH_STYLE, max_length=64, default=LARGE
choices=SEARCH_STYLE, max_length=64, default=NEW
)
show_recent = models.BooleanField(default=True)
plan_share = models.ManyToManyField(
@@ -145,6 +154,7 @@ class UserPreference(models.Model, PermissionModelMixin):
shopping_auto_sync = models.IntegerField(default=5)
sticky_navbar = models.BooleanField(default=STICKY_NAV_PREF_DEFAULT)
created_at = models.DateTimeField(auto_now_add=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE, null=True)
objects = ScopedManager(space='space')
@@ -247,7 +257,7 @@ class SyncLog(models.Model, PermissionModelMixin):
return f"{self.created_at}:{self.sync} - {self.status}"
class Keyword(models.Model, PermissionModelMixin):
class Keyword(ExportModelOperationsMixin('keyword'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=64)
icon = models.CharField(max_length=16, blank=True, null=True)
description = models.TextField(default="", blank=True)
@@ -267,7 +277,7 @@ class Keyword(models.Model, PermissionModelMixin):
unique_together = (('space', 'name'),)
class Unit(models.Model, PermissionModelMixin):
class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True)
@@ -281,7 +291,7 @@ class Unit(models.Model, PermissionModelMixin):
unique_together = (('space', 'name'),)
class Food(models.Model, PermissionModelMixin):
class Food(ExportModelOperationsMixin('food'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL)
@@ -298,7 +308,7 @@ class Food(models.Model, PermissionModelMixin):
unique_together = (('space', 'name'),)
class Ingredient(models.Model, PermissionModelMixin):
class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, PermissionModelMixin):
food = models.ForeignKey(Food, on_delete=models.PROTECT, null=True, blank=True)
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True, blank=True)
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
@@ -307,14 +317,8 @@ class Ingredient(models.Model, PermissionModelMixin):
no_amount = models.BooleanField(default=False)
order = models.IntegerField(default=0)
objects = ScopedManager(space='step__recipe__space')
@staticmethod
def get_space_key():
return 'step', 'recipe', 'space'
def get_space(self):
return self.step_set.first().recipe_set.first().space
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return str(self.amount) + ' ' + str(self.unit) + ' ' + str(self.food)
@@ -323,13 +327,15 @@ class Ingredient(models.Model, PermissionModelMixin):
ordering = ['order', 'pk']
class Step(models.Model, PermissionModelMixin):
class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixin):
TEXT = 'TEXT'
TIME = 'TIME'
FILE = 'FILE'
RECIPE = 'RECIPE'
name = models.CharField(max_length=128, default='', blank=True)
type = models.CharField(
choices=((TEXT, _('Text')), (TIME, _('Time')),),
choices=((TEXT, _('Text')), (TIME, _('Time')), (FILE, _('File')), (RECIPE, _('Recipe')),),
default=TEXT,
max_length=16
)
@@ -337,16 +343,12 @@ class Step(models.Model, PermissionModelMixin):
ingredients = models.ManyToManyField(Ingredient, blank=True)
time = models.IntegerField(default=0, blank=True)
order = models.IntegerField(default=0)
file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True)
show_as_header = models.BooleanField(default=True)
step_recipe = models.ForeignKey('Recipe', default=None, blank=True, null=True, on_delete=models.PROTECT)
objects = ScopedManager(space='recipe__space')
@staticmethod
def get_space_key():
return 'recipe', 'space'
def get_space(self):
return self.recipe_set.first().space
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def get_instruction_render(self):
from cookbook.helper.template_helper import render_instructions
@@ -367,20 +369,14 @@ class NutritionInformation(models.Model, PermissionModelMixin):
max_length=512, default="", null=True, blank=True
)
objects = ScopedManager(space='recipe__space')
@staticmethod
def get_space_key():
return 'recipe', 'space'
def get_space(self):
return self.recipe_set.first().space
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return 'Nutrition'
return f'Nutrition {self.pk}'
class Recipe(models.Model, PermissionModelMixin):
class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
description = models.CharField(max_length=512, blank=True, null=True)
servings = models.IntegerField(default=1)
@@ -412,7 +408,7 @@ class Recipe(models.Model, PermissionModelMixin):
return self.name
class Comment(models.Model, PermissionModelMixin):
class Comment(ExportModelOperationsMixin('comment'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
text = models.TextField()
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
@@ -446,7 +442,7 @@ class RecipeImport(models.Model, PermissionModelMixin):
return self.name
class RecipeBook(models.Model, PermissionModelMixin):
class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
description = models.TextField(blank=True)
icon = models.CharField(max_length=16, blank=True, null=True)
@@ -460,7 +456,7 @@ class RecipeBook(models.Model, PermissionModelMixin):
return self.name
class RecipeBookEntry(models.Model, PermissionModelMixin):
class RecipeBookEntry(ExportModelOperationsMixin('book_entry'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
book = models.ForeignKey(RecipeBook, on_delete=models.CASCADE)
@@ -495,7 +491,7 @@ class MealType(models.Model, PermissionModelMixin):
return self.name
class MealPlan(models.Model, PermissionModelMixin):
class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
title = models.CharField(max_length=64, blank=True, default='')
@@ -520,7 +516,7 @@ class MealPlan(models.Model, PermissionModelMixin):
return f'{self.get_label()} - {self.date} - {self.meal_type.name}'
class ShoppingListRecipe(models.Model, PermissionModelMixin):
class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True)
servings = models.DecimalField(default=1, max_digits=8, decimal_places=4)
@@ -543,7 +539,7 @@ class ShoppingListRecipe(models.Model, PermissionModelMixin):
return None
class ShoppingListEntry(models.Model, PermissionModelMixin):
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True)
food = models.ForeignKey(Food, on_delete=models.CASCADE)
unit = models.ForeignKey(Unit, on_delete=models.CASCADE, null=True, blank=True)
@@ -573,7 +569,7 @@ class ShoppingListEntry(models.Model, PermissionModelMixin):
return None
class ShoppingList(models.Model, PermissionModelMixin):
class ShoppingList(ExportModelOperationsMixin('shopping_list'), models.Model, PermissionModelMixin):
uuid = models.UUIDField(default=uuid.uuid4)
note = models.TextField(blank=True, null=True)
recipes = models.ManyToManyField(ShoppingListRecipe, blank=True)
@@ -591,9 +587,11 @@ class ShoppingList(models.Model, PermissionModelMixin):
return f'Shopping list {self.id}'
class ShareLink(models.Model, PermissionModelMixin):
class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
uuid = models.UUIDField(default=uuid.uuid4)
request_count = models.IntegerField(default=0)
abuse_blocked = models.BooleanField(default=False)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
@@ -608,9 +606,8 @@ def default_valid_until():
return date.today() + timedelta(days=14)
class InviteLink(models.Model, PermissionModelMixin):
class InviteLink(ExportModelOperationsMixin('invite_link'), models.Model, PermissionModelMixin):
uuid = models.UUIDField(default=uuid.uuid4)
username = models.CharField(blank=True, max_length=64)
email = models.EmailField(blank=True)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
valid_until = models.DateField(default=default_valid_until)
@@ -641,7 +638,7 @@ class TelegramBot(models.Model, PermissionModelMixin):
return f"{self.name}"
class CookLog(models.Model, PermissionModelMixin):
class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
@@ -655,7 +652,7 @@ class CookLog(models.Model, PermissionModelMixin):
return self.recipe.name
class ViewLog(models.Model, PermissionModelMixin):
class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
@@ -672,6 +669,10 @@ class ImportLog(models.Model, PermissionModelMixin):
running = models.BooleanField(default=True)
msg = models.TextField(default="")
keyword = models.ForeignKey(Keyword, null=True, blank=True, on_delete=models.SET_NULL)
total_recipes = models.IntegerField(default=0)
imported_recipes = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
@@ -682,7 +683,7 @@ class ImportLog(models.Model, PermissionModelMixin):
return f"{self.created_at}:{self.type}"
class BookmarkletImport(models.Model, PermissionModelMixin):
class BookmarkletImport(ExportModelOperationsMixin('bookmarklet_import'), models.Model, PermissionModelMixin):
html = models.TextField()
url = models.CharField(max_length=256, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
@@ -690,3 +691,20 @@ class BookmarkletImport(models.Model, PermissionModelMixin):
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
class UserFile(ExportModelOperationsMixin('user_files'), models.Model, PermissionModelMixin):
name = models.CharField(max_length=128)
file = models.FileField(upload_to='files/')
file_size_kb = models.IntegerField(default=0, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
space = models.ForeignKey(Space, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, InMemoryUploadedFile):
self.file.name = f'{uuid.uuid4()}' + pathlib.Path(self.file.name).suffix
self.file_size_kb = round(self.file.size / 1000)
super(UserFile, self).save(*args, **kwargs)

View File

@@ -1,13 +1,12 @@
from decimal import Decimal
from gettext import gettext as _
from django.contrib.auth.models import User
from django.db.models import QuerySet
from django.db.models import QuerySet, Sum, Avg
from drf_writable_nested import (UniqueFieldsMixin,
WritableNestedModelSerializer)
from rest_framework import serializers
from rest_framework.exceptions import ValidationError, NotAuthenticated, NotFound, ParseError
from rest_framework.fields import ModelField
from rest_framework.serializers import BaseSerializer, Serializer
from rest_framework.exceptions import ValidationError, NotFound
from cookbook.models import (Comment, CookLog, Food, Ingredient, Keyword,
MealPlan, MealType, NutritionInformation, Recipe,
@@ -15,7 +14,7 @@ from cookbook.models import (Comment, CookLog, Food, Ingredient, Keyword,
ShareLink, ShoppingList, ShoppingListEntry,
ShoppingListRecipe, Step, Storage, Sync, SyncLog,
Unit, UserPreference, ViewLog, SupermarketCategory, Supermarket,
SupermarketCategoryRelation, ImportLog, BookmarkletImport)
SupermarketCategoryRelation, ImportLog, BookmarkletImport, UserFile)
from cookbook.templatetags.custom_tags import markdown
@@ -103,6 +102,51 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
)
class UserFileSerializer(serializers.ModelSerializer):
def check_file_limit(self, validated_data):
if self.context['request'].space.max_file_storage_mb == -1:
raise ValidationError(_('File uploads are not enabled for this Space.'))
try:
current_file_size_mb = UserFile.objects.filter(space=self.context['request'].space).aggregate(Sum('file_size_kb'))['file_size_kb__sum'] / 1000
except TypeError:
current_file_size_mb = 0
if (validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5) > self.context['request'].space.max_file_storage_mb != 0:
raise ValidationError(_('You have reached your file upload limit.'))
def create(self, validated_data):
self.check_file_limit(validated_data)
validated_data['created_by'] = self.context['request'].user
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def update(self, instance, validated_data):
self.check_file_limit(validated_data)
return super().update(instance, validated_data)
class Meta:
model = UserFile
fields = ('name', 'file', 'file_size_kb', 'id',)
read_only_fields = ('id', 'file_size_kb')
extra_kwargs = {"file": {"required": False, }}
class UserFileViewSerializer(serializers.ModelSerializer):
def create(self, validated_data):
raise ValidationError('Cannot create File over this view')
def update(self, instance, validated_data):
return instance
class Meta:
model = UserFile
fields = ('name', 'file', 'id',)
read_only_fields = ('id', 'file')
class StorageSerializer(SpacedModelSerializer):
def create(self, validated_data):
@@ -177,7 +221,7 @@ class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
def create(self, validated_data):
obj, created = Unit.objects.get_or_create(name=validated_data['name'].strip(), space=self.context['request'].space)
return obj
def update(self, instance, validated_data):
validated_data['name'] = validated_data['name'].strip()
return super(UnitSerializer, self).update(instance, validated_data)
@@ -202,7 +246,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
fields = ('id', 'name')
class SupermarketCategoryRelationSerializer(SpacedModelSerializer):
class SupermarketCategoryRelationSerializer(WritableNestedModelSerializer):
category = SupermarketCategorySerializer()
class Meta:
@@ -239,6 +283,10 @@ class IngredientSerializer(WritableNestedModelSerializer):
unit = UnitSerializer(allow_null=True)
amount = CustomDecimalField()
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Ingredient
fields = (
@@ -251,6 +299,12 @@ class StepSerializer(WritableNestedModelSerializer):
ingredients = IngredientSerializer(many=True)
ingredients_markdown = serializers.SerializerMethodField('get_ingredients_markdown')
ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue')
file = UserFileViewSerializer(allow_null=True, required=False)
step_recipe_data = serializers.SerializerMethodField('get_step_recipe_data')
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
def get_ingredients_vue(self, obj):
return obj.get_instruction_render()
@@ -258,22 +312,65 @@ class StepSerializer(WritableNestedModelSerializer):
def get_ingredients_markdown(self, obj):
return obj.get_instruction_render()
def get_step_recipe_data(self, obj):
# check if root type is recipe to prevent infinite recursion
# can be improved later to allow multi level embedding
if obj.step_recipe and type(self.parent.root) == RecipeSerializer:
return StepRecipeSerializer(obj.step_recipe).data
class Meta:
model = Step
fields = (
'id', 'name', 'type', 'instruction', 'ingredients', 'ingredients_markdown',
'ingredients_vue', 'time', 'order', 'show_as_header'
'ingredients_vue', 'time', 'order', 'show_as_header', 'file', 'step_recipe', 'step_recipe_data'
)
class StepRecipeSerializer(WritableNestedModelSerializer):
steps = StepSerializer(many=True)
class Meta:
model = Recipe
fields = (
'id', 'name', 'steps',
)
class NutritionInformationSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = NutritionInformation
fields = ('id', 'carbohydrates', 'fats', 'proteins', 'calories', 'source')
class RecipeOverviewSerializer(WritableNestedModelSerializer):
class RecipeBaseSerializer(WritableNestedModelSerializer):
def get_recipe_rating(self, obj):
try:
rating = obj.cooklog_set.filter(created_by=self.context['request'].user, rating__gt=0).aggregate(Avg('rating'))
if rating['rating__avg']:
return rating['rating__avg']
except TypeError:
pass
return 0
def get_recipe_last_cooked(self, obj):
try:
last = obj.cooklog_set.filter(created_by=self.context['request'].user).last()
if last:
return last.created_at
except TypeError:
pass
return None
class RecipeOverviewSerializer(RecipeBaseSerializer):
keywords = KeywordLabelSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
def create(self, validated_data):
pass
@@ -286,22 +383,24 @@ class RecipeOverviewSerializer(WritableNestedModelSerializer):
fields = (
'id', 'name', 'description', 'image', 'keywords', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'servings', 'file_path'
'internal', 'servings', 'servings_text', 'rating', 'last_cooked',
)
read_only_fields = ['image', 'created_by', 'created_at']
class RecipeSerializer(WritableNestedModelSerializer):
class RecipeSerializer(RecipeBaseSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
steps = StepSerializer(many=True)
keywords = KeywordSerializer(many=True)
rating = serializers.SerializerMethodField('get_recipe_rating')
last_cooked = serializers.SerializerMethodField('get_recipe_last_cooked')
class Meta:
model = Recipe
fields = (
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at',
'internal', 'nutrition', 'servings', 'file_path', 'servings_text',
'internal', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
)
read_only_fields = ['image', 'created_by', 'created_at']
@@ -342,6 +441,14 @@ class RecipeBookSerializer(SpacedModelSerializer):
class RecipeBookEntrySerializer(serializers.ModelSerializer):
book_content = serializers.SerializerMethodField(method_name='get_book_content', read_only=True)
recipe_content = serializers.SerializerMethodField(method_name='get_recipe_content', read_only=True)
def get_book_content(self, obj):
return RecipeBookSerializer(context={'request': self.context['request']}).to_representation(obj.book)
def get_recipe_content(self, obj):
return RecipeOverviewSerializer(context={'request': self.context['request']}).to_representation(obj.recipe)
def create(self, validated_data):
book = validated_data['book']
@@ -351,7 +458,7 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
class Meta:
model = RecipeBookEntry
fields = ('id', 'book', 'recipe',)
fields = ('id', 'book', 'book_content', 'recipe', 'recipe_content',)
class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
@@ -475,7 +582,7 @@ class ImportLogSerializer(serializers.ModelSerializer):
class Meta:
model = ImportLog
fields = ('id', 'type', 'msg', 'running', 'keyword', 'created_by', 'created_at')
fields = ('id', 'type', 'msg', 'running', 'keyword', 'total_recipes', 'imported_recipes', 'created_by', 'created_at')
read_only_fields = ('created_by',)
@@ -533,6 +640,10 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
unit = UnitExportSerializer(allow_null=True)
amount = CustomDecimalField()
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Ingredient
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount')
@@ -541,6 +652,10 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
class StepExportSerializer(WritableNestedModelSerializer):
ingredients = IngredientExportSerializer(many=True)
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Step
fields = ('name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header')

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2000px"
height="2000px"
viewBox="0 0 2000 2000"
version="1.1"
id="SVGRoot"
sodipodi:docname="spinner.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<defs
id="defs265" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.24748737"
inkscape:cx="507.59315"
inkscape:cy="671.7335"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata268">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path5640-9"
d="m 1363.6895,468.03149 c -6.2646,0 -11.3386,5.07398 -11.3386,11.33858 0,6.25981 5.074,11.33859 11.3386,11.33859 6.2645,0 11.3386,-5.07878 11.3386,-11.33859 0,-6.2646 -5.0741,-11.33858 -11.3386,-11.33858 z m 63.425,-26.44248 42.4018,-162.5339 h -219.2126 c -12.5054,0 -22.6772,10.17173 -22.6772,22.67732 v 75.5904 c 0,12.50559 10.1718,22.67716 22.6772,22.67716 h 48.3118 l 3.5576,40.89453 c -17.0411,6.52913 -29.1923,22.91807 -29.1923,42.25508 v 22.67717 c 0,8.34795 6.7703,15.11811 15.1181,15.11811 H 1439.28 c 8.3483,0 15.1181,-6.77016 15.1181,-15.11811 V 483.1496 c 0,-18.61414 -11.2391,-34.5733 -27.2836,-41.56059 z m -176.8108,-56.70713 c -4.1621,0 -7.5591,-3.3874 -7.5591,-7.55905 v -75.5904 c 0,-4.17165 3.397,-7.55905 7.5591,-7.55905 h 39.1086 l 7.8898,90.7085 z m 199.644,-90.7085 -7.8897,30.23606 h -74.589 c -2.0882,0 -3.7795,1.69138 -3.7795,3.77968 v 7.55891 c 0,2.0883 1.6913,3.77953 3.7795,3.77953 h 70.6488 l -7.8897,30.23622 h -62.7591 c -2.0882,0 -3.7795,1.69137 -3.7795,3.77952 v 7.55906 c 0,2.0883 1.6913,3.77952 3.7795,3.77952 h 58.8144 l -13.8048,52.91339 h -95.4001 L 1304.5871,294.17338 Z M 1439.28,505.82677 H 1288.0989 V 483.1496 c 0,-16.67244 13.564,-30.23622 30.2362,-30.23622 h 90.7087 c 16.6726,0 30.2362,13.56378 30.2362,30.23622 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5684-6-0"
d="m 1740.0225,645.7638 h -53.5609 l 50.4238,-50.42358 c 11.3904,-11.39059 8.2723,-30.59063 -6.1322,-37.79536 a 23.601733,23.601733 0 0 0 -10.5495,-2.48976 c -5.0177,0 -9.9972,1.59693 -14.1641,4.71969 l -114.652,85.98901 h -85.6959 c -2.0881,0 -3.7795,1.6913 -3.7795,3.77957 v 7.55905 c 0,2.08815 1.6914,3.77945 3.7795,3.77945 h 11.3386 V 676 c 0,38.4661 23.9811,71.2583 57.7746,84.4488 -6.0283,7.9276 -10.2519,17.3056 -11.7732,27.6095 -0.6848,4.6441 2.8252,8.8866 7.5213,8.8866 h 104.6078 c 4.6961,0 8.2065,-4.2378 7.5213,-8.8866 -1.5163,-10.3039 -5.74,-19.6866 -11.7728,-27.6095 33.7935,-13.1905 57.7746,-45.9827 57.7746,-84.4488 v -15.11813 h 11.3386 c 2.0881,0 3.7795,-1.6913 3.7795,-3.77945 v -7.55905 c 0,-2.08827 -1.6914,-3.77957 -3.7795,-3.77957 z m -24.9166,-73.89453 c 8.3055,-6.22673 18.5246,5.34331 11.088,12.77961 l -61.11,61.11492 h -48.5008 z M 1713.5658,676 c 0,31.3276 -18.9026,58.9464 -48.1512,70.3654 l -18.6664,7.285 12.1323,15.9495 c 2.8729,3.7797 5.1357,7.9135 6.7042,12.2269 h -85.4506 c 1.5685,-4.3134 3.8267,-8.4472 6.7041,-12.2269 l 12.1323,-15.9495 -18.6663,-7.285 C 1551.0506,734.9464 1532.1484,707.3276 1532.1484,676 v -15.11813 h 181.4174 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5695-2"
d="M 1075.5907,1636.198 H 924.40969 a 30.236221,30.236221 0 0 0 -30.2366,30.2361 v 196.5355 a 15.118111,15.118111 0 0 0 15.11811,15.1181 h 181.4177 a 15.118111,15.118111 0 0 0 15.118,-15.1181 v -196.5355 a 30.236221,30.236221 0 0 0 -30.2362,-30.2361 z m 15.1181,226.7716 H 909.2912 v -151.1811 h 181.4176 z m 0,-166.2992 H 909.2912 v -30.2363 a 15.118111,15.118111 0 0 1 15.11849,-15.118 h 151.18101 a 15.118111,15.118111 0 0 1 15.1181,15.118 z m -22.6775,-30.2363 a 7.5590556,7.5590556 0 1 0 7.5594,7.5591 7.5590556,7.5590556 0 0 0 -7.5594,-7.5591 z m -30.2359,0 a 7.5590556,7.5590556 0 1 0 7.5591,7.5591 7.5590556,7.5590556 0 0 0 -7.5591,-7.5591 z m -75.59041,0 a 7.5590556,7.5590556 0 1 0 7.5587,7.5591 7.5590556,7.5590556 0 0 0 -7.5587,-7.5591 z m -30.23625,0 a 7.5590556,7.5590556 0 1 0 7.55906,7.5591 7.5590556,7.5590556 0 0 0 -7.55906,-7.5591 z m 0,181.4175 h 136.06256 a 7.5590556,7.5590556 0 0 0 7.5594,-7.5591 v -105.8268 a 7.5590556,7.5590556 0 0 0 -7.5594,-7.559 H 931.96874 a 7.5590556,7.5590556 0 0 0 -7.55905,7.559 v 105.8268 a 7.5590556,7.5590556 0 0 0 7.55905,7.5591 z m 7.55906,-105.8268 h 120.9444 v 90.7086 H 939.5278 Z m 98.2676,15.118 h -75.59041 a 7.5591,7.5591 0 0 0 0,15.1182 h 75.59041 a 7.5591,7.5591 0 0 0 0,-15.1182 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5673-8-4-5"
d="m 1467.3153,1614.6952 c -6.378,-5.3858 -14.4567,-8.315 -22.7717,-8.315 -10.3464,0 -20.0787,4.4881 -26.8819,12.3307 l -9.6378,-42.0473 c -7.4173,-32.5984 -35.7165,-55.3228 -68.7874,-55.3228 -5.3386,0 -10.7244,0.6139 -15.9685,1.8425 -37.9842,8.8347 -61.748,47.2442 -53.0078,85.5592 l 21.0708,92.126 c 0.189,0.8974 0.5197,1.7479 0.7559,2.6456 -4.4409,1.4646 -8.5039,5.5748 -8.5039,10.5827 v 37.7952 c 0,6.2835 5.0551,11.3386 11.2441,11.3386 h 143.1968 c 6.189,0 11.9055,-5.0551 11.9055,-11.3386 v -37.7952 c 0,-5.3858 -4.4409,-9.6378 -9.4015,-10.8189 l 31.37,-37.9843 c 12.5671,-15.2126 10.4884,-37.8897 -4.5826,-50.5984 z m -32.5512,133.4173 h -136.063 v -30.2362 h 136.063 z m 25.6063,-92.5039 -38.9764,47.1497 h -113.7638 c -0.5669,-1.7482 -1.2756,-3.4962 -1.7008,-5.3388 l -21.0708,-92.1259 c -6.8976,-30.1889 11.811,-60.4253 41.7637,-67.4174 4.1575,-0.9453 8.3622,-1.4645 12.567,-1.4645 26.0787,0 48.3779,17.9055 54.2362,43.6064 l 12.9921,56.8818 3.4016,14.8347 9.685,-11.7166 9.1654,-11.1023 c 3.9213,-4.7718 9.685,-7.4646 15.8268,-7.4646 4.8189,0 9.4488,1.7007 13.1811,4.8189 8.7874,7.37 9.9685,20.504 2.6929,29.3386 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5706-9"
d="m 455.99659,983.47136 c -22.56427,-58.66282 -79.4948,-97.74958 -143.13917,-97.74958 -63.64457,0 -120.58155,39.08676 -143.12757,97.74958 -25.31405,0.97615 -28.28993,22.26634 -28.28993,27.32554 a 27.43272,27.43272 0 0 0 27.50999,27.2958 c 1.61917,0 2.83909,-0.3566 4.35685,-0.4524 l 19.32015,57.9249 a 27.379152,27.379152 0 0 0 25.96867,18.713 h 188.52348 a 27.379152,27.379152 0 0 0 25.96829,-18.719 l 19.30226,-57.9189 c 1.51209,0.1014 2.73806,0.4524 4.35693,0.4524 a 27.43272,27.43272 0 0 0 27.5278,-27.2958 c 0,-5.22 -3.06519,-26.272 -28.27775,-27.32554 z m -40.97343,106.05254 a 8.3327858,8.3327858 0 0 1 -7.9041,5.7079 H 218.5895 a 8.3327858,8.3327858 0 0 1 -7.90412,-5.702 l -18.60593,-55.8237 a 75.631939,75.631939 0 0 0 17.37958,-9.9338 c 5.41071,-4.0116 7.49951,-5.6128 15.12436,0.039 7.21381,5.3568 19.32588,14.2849 40.4377,14.2849 21.11197,0 33.23583,-8.9638 40.47356,-14.3325 5.39857,-3.9937 7.44592,-5.5948 15.00504,0.028 7.23173,5.3568 19.32022,14.2848 40.43164,14.2848 21.11189,0 33.21824,-8.9637 40.43777,-14.3205 6.47601,-4.8032 8.40441,-4.8807 14.95147,0 a 75.536702,75.536702 0 0 0 17.30819,9.9339 l -18.6056,55.8297 z m 41.73547,-70.4775 c -14.8447,0 -22.73066,-5.8568 -29.11133,-10.6005 -17.6834,-13.09438 -29.79589,-5.7675 -37.62855,0.039 -6.33277,4.708 -14.22515,10.5648 -29.08742,10.5648 -14.86181,0 -22.74814,-5.8568 -29.12872,-10.5768 -18.93914,-14.09428 -31.84332,-4.3152 -37.67614,0 -6.34458,4.72 -14.23157,10.5768 -29.11144,10.5768 -14.87973,0 -22.76637,-5.8568 -29.16451,-10.5768 -4.03546,-2.976 -17.68349,-14.87988 -37.77753,0 -6.35673,4.7021 -14.28477,10.5589 -29.16452,10.5589 a 8.2554502,8.2554502 0 1 1 0,-16.5109 c 3.20802,0 6.59458,0.2861 15.92147,-4.86268 31.74184,-91.61298 118.33737,-92.88672 128.02748,-92.88672 9.52324,0 96.25581,1.19038 127.96763,92.85092 9.37404,5.30318 12.24317,4.94608 15.93934,4.94608 a 8.25545,8.25545 0 1 1 0,16.5109 z m -196.779,-84.70277 a 9.5648466,9.5648466 0 0 0 -12.7788,4.26161 l -9.5233,19.04637 a 9.5231832,9.5231832 0 0 0 4.26186,12.77891 c 5.6782,2.7975 10.92787,-0.59555 12.77845,-4.26159 l 9.5233,-19.04632 a 9.5231832,9.5231832 0 0 0 -4.26151,-12.77898 z m 118.52804,4.25571 a 9.5252893,9.5252893 0 1 0 -17.04022,8.51727 l 9.52286,19.04637 c 1.84526,3.67236 7.10105,7.05909 12.77882,4.26159 a 9.5231832,9.5231832 0 0 0 4.2615,-12.77891 z m -65.65025,-5.26154 a 9.5231832,9.5231832 0 0 0 -9.52334,9.5232 v 19.04625 a 9.5233032,9.5233032 0 1 0 19.04658,0 V 942.861 a 9.5231832,9.5231832 0 0 0 -9.52324,-9.5232 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.595201" />
<path
id="path5662-5"
d="m 1075.5909,154.17314 a 44.962217,44.962217 0 0 0 -27.2742,9.3261 C 1037.2899,148.7401 1019.8425,139.0551 1000,139.0551 c -19.84243,0 -37.28983,9.685 -48.31663,24.44414 a 45.146469,45.146469 0 0 0 -72.62811,36.02827 c 0,19.71972 30.23621,105.82684 30.23621,105.82684 v 60.47252 a 15.118115,15.118115 0 0 0 15.1181,15.11803 h 151.18133 a 15.118115,15.118115 0 0 0 15.1181,-15.11803 v -60.47252 c 0,0 30.2362,-86.10712 30.2362,-105.82684 a 45.354343,45.354343 0 0 0 -45.3543,-45.35437 z M 931.96867,358.26781 v -37.79527 h 136.06323 v 37.79527 z M 1069.3264,297.7953 h -13.9609 l 5.1073,-63.66146 a 3.7795287,3.7795287 0 0 0 -3.4628,-4.0676 l -7.5591,-0.60506 h -0.3061 a 3.7795287,3.7795287 0 0 0 -3.7795,3.48185 l -5.1969,64.86621 h -32.92 V 233.5431 a 3.7795287,3.7795287 0 0 0 -3.7796,-3.77952 h -7.55897 a 3.7795287,3.7795287 0 0 0 -3.77986,3.77952 v 64.25193 h -32.324 l -5.1968,-64.86606 a 3.7795287,3.7795287 0 0 0 -3.7799,-3.48201 h -0.3062 l -7.5587,0.60507 a 3.7795287,3.7795287 0 0 0 -3.4631,4.06764 l 5.1352,63.67563 h -13.9604 c -13.8237,-39.3165 -28.72444,-88.18587 -28.94176,-98.26779 a 22.474022,22.474022 0 0 1 36.24566,-17.95272 l 18.1984,13.80476 13.6724,-18.2976 c 7.285,-9.75598 18.274,-15.34968 30.15113,-15.34968 11.8771,0 22.8661,5.5937 30.1511,15.34019 l 13.6729,18.29764 18.1984,-13.79531 a 22.474022,22.474022 0 0 1 36.2456,17.915 c -0.2192,10.11964 -15.1181,58.98901 -28.9417,98.30551 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5739"
d="m 689.18112,501.24409 6.7087,-77.85831 c -19.88984,-15.63783 -33.25984,-36.37803 -33.25984,-61.18099 0.0476,-44.55122 41.66925,-83.14968 71.85815,-83.14968 10.44099,0 18.89764,8.1733 18.89764,18.18894 v 205.51174 c 0,10.01578 -8.45665,18.18909 -18.89764,18.18909 h -26.45661 c -10.77162,0 -19.74803,-8.8348 -18.8504,-19.70079 z m -11.38586,-139.0393 c 0,24.09438 15.21263,40.39355 33.92126,54.09438 l -7.46453,86.26772 c -0.13644,1.74799 1.60619,3.25984 3.77953,3.25984 h 26.45661 c 2.03158,0 3.77953,-1.41778 3.77953,-3.07094 V 297.24405 c 0,-1.65347 -1.74795,-3.0708 -3.77953,-3.0708 -21.07082,0 -56.69287,31.08651 -56.69287,68.03154 z m -37.46457,-67.18118 c -1.55913,-9.21252 -10.01567,-15.9685 -21.77945,-15.9685 -5.62205,0 -11.38587,1.60637 -15.7324,5.29137 -3.96839,-3.35433 -9.44878,-5.29137 -15.73221,-5.29137 -6.28354,0 -11.76382,1.93704 -15.73236,5.29137 -4.34634,-3.685 -10.1102,-5.29137 -15.73221,-5.29137 -11.90555,0 -20.22047,6.85047 -21.7796,15.9685 -0.85096,4.44087 -7.22823,39.96854 -7.22823,54.85039 0,24.04714 12.61413,43.27568 33.54323,52.29918 l -5.38579,99.59059 c -0.56689,10.39359 7.70071,19.18111 18.1417,19.18111 h 28.34641 c 10.39386,0 18.70874,-8.74027 18.14181,-19.18111 l -5.3859,-99.59059 c 20.88197,-9.0235 33.54342,-28.25204 33.54342,-52.29918 0,-14.88185 -6.3781,-50.40952 -7.22842,-54.85039 z m -41.76382,97.41733 5.76386,110.12595 c 0.0915,1.74799 -1.27499,3.25984 -3.07095,3.25984 h -28.34641 c -1.748,0 -3.16532,-1.46491 -3.07083,-3.25984 l 5.76374,-110.12595 c -20.03146,-4.15748 -33.8739,-20.31504 -33.8739,-42.56694 0,-14.03145 6.99208,-52.25204 6.99208,-52.25204 0.75523,-4.67713 13.37001,-4.58264 13.93694,0.0911 V 355.587 c 0.42561,5.433 13.32294,5.52752 13.937,0.0911 l 3.49614,-58.06295 c 0.75523,-4.58264 13.18111,-4.58264 13.93694,0 l 3.49613,58.06295 c 0.61395,5.3859 13.51182,5.29138 13.93701,-0.0911 v -57.82681 c 0.56689,-4.67716 13.18107,-4.77154 13.93693,-0.0911 0,0 6.9921,38.22055 6.9921,52.25205 0.0476,22.11023 -13.70079,38.36217 -33.82678,42.51961 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5772-1"
d="m 1751.068,1034.0124 c -6.2598,0 -11.3384,5.0739 -11.3384,11.3384 0,6.2597 5.0786,11.3383 11.3384,11.3383 6.2597,0 11.3383,-5.0786 11.3383,-11.3383 0,-6.2645 -5.0786,-11.3384 -11.3383,-11.3384 z m -79.3686,-64.25069 c 0,-6.26453 -5.0786,-11.3384 -11.3384,-11.3384 -6.2597,0 -11.3383,5.07387 -11.3383,11.3384 0,6.25969 5.0786,11.33828 11.3383,11.33828 6.2598,0 11.3384,-5.07859 11.3384,-11.33828 z m 3.7795,64.25069 c -6.2598,0 -11.3383,5.0739 -11.3383,11.3384 0,6.2597 5.0785,11.3383 11.3383,11.3383 6.2596,0 11.3384,-5.0786 11.3384,-11.3383 0,-6.2645 -5.0788,-11.3384 -11.3384,-11.3384 z m 45.3534,-45.3535 c -6.2597,0 -11.3384,5.07386 -11.3384,11.33839 0,6.25971 5.0787,11.33841 11.3384,11.33841 6.2597,0 11.3384,-5.0787 11.3384,-11.33841 0,-6.26453 -5.0787,-11.33839 -11.3384,-11.33839 z m 105.1256,11.25331 c -33.0562,-0.40123 -59.7485,-27.25459 -59.7485,-60.40509 -33.1505,0 -59.9989,-26.68762 -60.4051,-59.73902 -3.1275,-0.47698 -6.2786,-0.71299 -9.4156,-0.71299 -9.7699,0 -19.4547,2.29133 -28.2467,6.76995 l -32.6592,16.6391 a 62.465402,62.465402 0 0 0 -27.3019,27.3112 l -16.5824,32.5411 a 62.665709,62.665709 0 0 0 -6.0564,38.24344 l 5.7069,36.0371 a 62.623193,62.623193 0 0 0 17.5556,34.4685 l 25.875,25.8704 a 62.378004,62.378004 0 0 0 34.3506,17.5084 l 36.2402,5.7354 c 3.2409,0.5155 6.5007,0.7652 9.751,0.7652 9.7888,0 19.4879,-2.3056 28.2939,-6.7937 l 32.6592,-16.639 a 62.465402,62.465402 0 0 0 27.3018,-27.3112 l 16.5825,-32.5412 c 5.9337,-11.636 8.036,-24.8357 6.0991,-37.74735 z m -19.5681,30.87819 -16.5825,32.5458 c -4.573,8.9762 -11.7304,16.1335 -20.6971,20.702 l -32.6593,16.6391 c -6.6046,3.3636 -14.0171,5.1446 -21.4295,5.1446 -2.4614,0 -4.9464,-0.1935 -7.3841,-0.5813 l -36.2356,-5.7353 c -9.921,-1.5731 -18.9208,-6.1605 -26.0262,-13.2658 l -25.8751,-25.8704 c -7.1337,-7.129 -11.7352,-16.1713 -13.3131,-26.1397 l -5.707,-36.03228 c -1.5826,-9.97784 0.015,-20.01219 4.5921,-29.01204 l 16.5823,-32.54109 c 4.5731,-8.97622 11.7305,-16.13351 20.6973,-20.70194 l 32.6544,-16.64383 c 5.3527,-2.72591 11.2439,-4.40784 17.2485,-4.94162 6.2125,29.83412 30.1601,53.36111 60.1169,58.98788 5.6266,29.96157 29.1538,53.90912 58.9926,60.11682 -0.5193,6.0141 -2.1921,11.8769 -4.9746,17.3288 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472433" />
<path
id="path5627-3"
d="m 1556.6704,1294.1237 c -8.3858,0 -15.1181,-6.7323 -15.1181,-15.118 0,-14.6458 15.1181,-10.8662 15.1181,-30.2363 5.6693,0 15.1181,13.9371 15.1181,26.4567 0,12.5197 -6.7323,18.8976 -15.1181,18.8976 z m 60.4724,0 c -8.3858,0 -15.118,-6.7323 -15.118,-15.118 0,-14.6458 15.118,-10.8662 15.118,-30.2363 5.6693,0 15.1182,13.9371 15.1182,26.4567 0,12.5197 -6.7323,18.8976 -15.1182,18.8976 z m 60.4725,0 c -8.3858,0 -15.1181,-6.7323 -15.1181,-15.118 0,-14.6458 15.1181,-10.8662 15.1181,-30.2363 5.6693,0 15.1181,13.9371 15.1181,26.4567 0,12.5197 -6.7323,18.8976 -15.1181,18.8976 z m 22.6771,75.5907 h -15.118 v -68.0316 h -15.1182 v 68.0316 h -45.3543 v -68.0316 h -15.1181 v 68.0316 h -45.3543 v -68.0316 h -15.1181 v 68.0316 h -15.1181 c -12.5197,0 -22.6772,10.1573 -22.6772,22.6771 v 98.2677 h 211.6535 v -98.2677 c 0,-12.5198 -10.1575,-22.6771 -22.6772,-22.6771 z m 7.5591,105.8267 h -181.4173 v -34.036 c 7.6639,-4.4783 11.3045,-11.3183 20.1968,-11.3183 13.2052,0 14.7652,15.118 35.315,15.118 20.2408,0 22.3072,-15.118 35.1968,-15.118 13.2983,0 14.7407,15.118 35.315,15.118 20.4837,0 22.0947,-15.118 35.315,-15.118 8.7345,0 12.3992,6.8386 20.0787,11.3173 z m 0,-53.0901 c -4.5476,-3.72 -10.0238,-7.3823 -20.0787,-7.3823 -20.5181,0 -22.1221,15.1181 -35.315,15.1181 -13.0842,0 -14.8602,-15.1181 -35.315,-15.1181 -20.2403,0 -22.3076,15.1181 -35.1968,15.1181 -13.2983,0 -14.7411,-15.1181 -35.315,-15.1181 -10.1399,0 -15.6382,3.6728 -20.1968,7.3974 v -30.0746 c 0,-4.1678 3.3912,-7.5591 7.5591,-7.5591 h 166.2991 c 4.168,0 7.5591,3.3913 7.5591,7.5591 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5794"
d="m 455.82468,561.09456 c -59.4562,0 -111.33852,39.33117 -140.22863,73.01275 l -57.67284,-51.27053 c -8.03297,-6.61302 -19.97164,0.40092 -17.95038,10.50915 L 255.94899,676 239.96708,758.66025 c -2.01551,10.10208 9.91131,17.11599 17.92597,10.52702 l 57.67356,-51.25873 c 28.91344,33.64577 80.82557,72.9769 140.25807,72.9769 90.81482,0 164.43402,-91.92438 164.43402,-114.90544 0,-22.98105 -73.6192,-114.90544 -164.43402,-114.90544 z m 0,210.66009 c -50.77599,0 -98.29963,-34.27421 -125.83995,-66.35192 l -12.09918,-14.09391 c -18.54503,15.24893 -7.27783,5.31435 -54.74779,48.8408 12.93728,-68.15931 11.15984,-58.87111 12.2299,-64.14962 -1.07006,-5.27254 0.71277,4.02766 -12.22415,-64.14959 47.56512,43.59825 36.26847,33.63976 54.74778,48.84688 l 12.09918,-14.09398 c 27.54104,-32.07776 75.03488,-66.35793 125.83421,-66.35793 76.21859,0 140.366,75.69999 145.28359,95.75462 -4.91759,20.05463 -69.065,95.75465 -145.28359,95.75465 z m -12.94948,-149.82603 -6.72448,-6.76864 a 4.7565564,4.7877296 0 0 0 -6.73058,0 l -50.34816,50.66611 a 4.7565564,4.7877296 0 0 0 0,6.76863 l 6.72484,6.76862 a 4.7565564,4.7877296 0 0 0 6.72448,0 l 50.3539,-50.64218 a 4.7565564,4.7877296 0 0 0 0,-6.79254 z m 66.59166,9.57547 -6.72448,-6.7686 a 4.7565564,4.7877296 0 0 0 -6.73023,0 l -88.40107,88.9679 a 4.7565564,4.7877296 0 0 0 0,6.76865 l 6.7252,6.76861 a 4.7565564,4.7877296 0 0 0 6.72449,0 l 88.40609,-88.94403 a 4.7565564,4.7877296 0 0 0 0,-6.79253 z m 21.81517,41.10869 a 4.7565564,4.7877296 0 0 0 -6.73096,0 l -50.34798,50.66612 a 4.7565564,4.7877296 0 0 0 0,6.76863 l 6.72413,6.76861 a 4.7565564,4.7877296 0 0 0 6.7252,0 l 50.35336,-50.64219 a 4.7565564,4.7877296 0 0 0 0,-6.77461 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.596513" />
<path
id="path5728"
d="m 385.71444,1248.7695 c -66.79359,0 -120.94515,54.1512 -120.94515,120.9449 0,66.7937 54.15156,120.9448 120.94515,120.9448 66.79355,0 120.94484,-54.1511 120.94484,-120.9448 0,-66.7937 -54.15129,-120.9449 -120.94484,-120.9449 z m 0,226.7716 c -58.35119,0 -105.82666,-47.4756 -105.82666,-105.8267 0,-58.3512 47.47547,-105.8268 105.82666,-105.8268 58.35107,0 105.82669,47.4756 105.82669,105.8268 0,58.3511 -47.47562,105.8267 -105.82669,105.8267 z m 74.82984,-105.8267 13.3606,-13.3607 c 1.47405,-1.474 1.47405,-3.8693 0,-5.3433 l -5.34324,-5.3433 c -1.47401,-1.474 -3.8694,-1.474 -5.34342,0 l -13.36059,13.3606 -21.3826,-21.3779 10.6913,-10.6913 8.01736,8.0173 c 1.47402,1.4739 3.86922,1.4739 5.34323,0 l 5.34342,-5.3433 c 1.47402,-1.4741 1.47402,-3.8693 0,-5.3433 l -8.01736,-8.0174 2.67394,-2.674 c 1.47402,-1.4739 1.47402,-3.8693 0,-5.3433 l -5.34323,-5.3434 c -1.47401,-1.4739 -3.86921,-1.4739 -5.34323,0 l -2.67413,2.6741 -8.01736,-8.0172 c -1.47402,-1.474 -3.86922,-1.474 -5.34323,0 l -5.34327,5.3431 c -1.47402,1.4741 -1.47402,3.8694 0,5.3434 l 8.01721,8.0173 -10.6913,10.6913 -21.3826,-21.3779 13.36524,-13.3606 c 1.47401,-1.474 1.47401,-3.8692 0,-5.3433 l -5.34788,-5.3622 c -1.47402,-1.474 -3.86941,-1.474 -5.34342,0 l -13.36528,13.3654 -13.36539,-13.3654 c -1.47402,-1.474 -3.86922,-1.474 -5.34324,0 l -5.34346,5.3433 c -1.47397,1.474 -1.47397,3.8692 0,5.3434 l 13.36544,13.3606 -21.37796,21.3779 -10.6913,-10.6914 8.01717,-8.0173 c 1.47402,-1.4739 1.47402,-3.8693 0,-5.3433 l -5.34323,-5.3433 c -1.47402,-1.474 -3.86922,-1.474 -5.34323,0 l -8.01736,8.0174 -2.67413,-2.6741 c -1.47402,-1.474 -3.86922,-1.474 -5.34323,0 l -5.34312,5.3433 c -1.47402,1.474 -1.47402,3.8693 0,5.3434 l 2.67364,2.6739 -8.01714,8.0173 c -1.47401,1.4741 -1.47401,3.8694 0,5.3433 l 5.3435,5.3434 c 1.47401,1.474 3.8691,1.474 5.34312,0 l 8.01736,-8.0174 10.6913,10.6914 -21.37814,21.378 -13.36517,-13.3607 c -1.47402,-1.4739 -3.86948,-1.4739 -5.3435,0 l -5.35748,5.3575 c -1.47401,1.4741 -1.47401,3.8693 0,5.3433 l 13.36555,13.3654 -13.36555,13.3653 c -1.47401,1.4741 -1.47401,3.8693 0,5.3433 l 5.3435,5.3434 c 1.47402,1.474 3.8691,1.474 5.34312,0 l 13.36554,-13.3606 21.37803,21.3778 -10.69149,10.6914 -8.01706,-8.0173 c -1.47401,-1.474 -3.86948,-1.474 -5.34349,0 l -5.3435,5.3432 c -1.47364,1.474 -1.47364,3.8694 0,5.3434 l 8.01751,8.0172 -2.67401,2.6741 c -1.47402,1.474 -1.47402,3.8694 0,5.3434 l 5.34349,5.3432 c 1.47402,1.4741 3.8691,1.4741 5.34312,0 l 2.67394,-2.6739 8.01736,8.0172 c 1.47402,1.474 3.86941,1.474 5.34342,0 l 5.34323,-5.3433 c 1.47402,-1.474 1.47402,-3.8692 0,-5.3432 l -8.01736,-8.0175 10.6913,-10.6912 21.3828,21.3827 -13.36544,13.3652 c -1.47401,1.474 -1.47401,3.8695 0,5.3433 l 5.34343,5.3433 c 1.47401,1.4741 3.86921,1.4741 5.34323,0 l 13.37488,-13.3606 13.36528,13.3653 c 1.47401,1.474 3.8694,1.474 5.34342,0 l 5.34323,-5.3433 c 1.47401,-1.4739 1.47401,-3.8692 0,-5.3432 l -13.36543,-13.3653 21.38279,-21.3828 10.6913,10.6913 -8.01736,8.0173 c -1.47402,1.474 -1.47402,3.8694 0,5.3434 l 5.34323,5.3433 c 1.47402,1.474 3.86941,1.474 5.34342,0 l 8.01736,-8.0173 2.67394,2.674 c 1.47402,1.474 3.86922,1.474 5.34323,0 l 5.34342,-5.3434 c 1.47406,-1.474 1.47406,-3.8692 0,-5.3432 l -2.67409,-2.6741 8.01736,-8.0172 c 1.47402,-1.4741 1.47402,-3.8694 0,-5.3434 l -5.34327,-5.3433 c -1.47401,-1.474 -3.8694,-1.474 -5.34342,0 l -8.01717,8.0174 -10.69149,-10.6914 21.37799,-21.378 13.3654,13.3606 c 1.47405,1.474 3.86925,1.474 5.34326,0 l 5.34343,-5.3432 c 1.47397,-1.474 1.47397,-3.8694 0,-5.3433 z m -74.82984,-53.452 21.3778,21.378 -21.3778,21.3827 -21.37795,-21.3781 z m -53.45201,53.452 21.37795,-21.3827 21.38261,21.3827 -21.38261,21.3827 z m 53.45201,53.4519 -21.38275,-21.3827 21.38275,-21.3826 21.38264,21.3826 z m 32.06929,-32.074 -21.37795,-21.3779 21.3826,-21.378 21.37795,21.378 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472441" />
<path
id="path5717-1"
d="m 692.85695,1593.1058 a 3.8361918,3.8361918 0 0 0 3.77953,3.685 h 7.55867 a 3.7511533,3.7511533 0 0 0 3.77991,-3.8739 c -0.99288,-19.8897 -9.11811,-34.6297 -19.65317,-45.1178 -8.69329,-8.8345 -9.96888,-18.0471 -10.34683,-23.1022 a 3.7180827,3.7180827 0 0 0 -3.77953,-3.3543 l -7.55905,0.045 a 3.7794994,3.7794994 0 0 0 -3.73191,4.1102 49.823248,49.823248 0 0 0 14.74016,32.9289 c 8.17285,8.1259 14.36183,19.2282 15.21222,34.6768 z m -52.67679,0 a 3.8361918,3.8361918 0 0 0 3.77953,3.685 h 7.55905 a 3.7511533,3.7511533 0 0 0 3.77953,-3.8739 c -0.99212,-19.8897 -9.11811,-34.6297 -19.65354,-45.1178 -8.64567,-8.8345 -9.92088,-18.0471 -10.29884,-23.1022 a 3.7322557,3.7322557 0 0 0 -3.82677,-3.3543 l -7.55905,0.045 a 3.7794994,3.7794994 0 0 0 -3.73229,4.1102 c 0.47244,7.0393 2.45669,20.5982 14.74016,32.9289 8.16831,8.1259 14.35729,19.2282 15.21222,34.6768 z m 128.50318,18.9448 H 557.03131 a 15.117997,15.117997 0 0 0 -15.11811,15.1179 c 0,44.7399 24.35868,83.6971 60.47206,104.6214 v 16.3227 a 15.117997,15.117997 0 0 0 15.11773,15.118 h 90.70791 a 15.117997,15.117997 0 0 0 15.11849,-15.118 v -16.3227 c 36.11263,-20.9243 60.47169,-59.8815 60.47169,-104.6214 a 15.117997,15.117997 0 0 0 -15.11774,-15.1179 z m -60.47244,111.0228 v 25.0392 h -90.70791 v -25.0392 c -62.52207,-36.2218 -60.47168,-87.7695 -60.47168,-95.9049 h 211.65203 c 0,8.2488 1.59194,59.9476 -60.47244,95.9049 z"
inkscape:connector-curvature="0"
style="fill:#303030;fill-opacity:1;stroke-width:0.472437" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

1129
cookbook/static/css/app.min.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,91 @@
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/static/webfonts/poppins_devanagari_400.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/static/webfonts/poppins_devanagari_400.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/static/webfonts/poppins_latin_ext_400.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/static/webfonts/poppins_latin_ext_400.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/static/webfonts/poppins_latin_400.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(/static/webfonts/poppins_latin_400.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/static/webfonts/poppins_devanagari_500.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/static/webfonts/poppins_devanagari_500.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/static/webfonts/poppins_latin_ext_500.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/static/webfonts/poppins_latin_ext_500.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/static/webfonts/poppins_latin_500.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(/static/webfonts/poppins_latin_500.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/static/webfonts/poppins_devanagari_700.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/static/webfonts/poppins_devanagari_700.woff2) format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/static/webfonts/poppins_latin_ext_700.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/static/webfonts/poppins_latin_ext_700.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/static/webfonts/poppins_latin_700.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
font-family: 'Poppins';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(/static/webfonts/poppins_latin_700.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -92,7 +100,7 @@
--indigo: #6610f2;
--purple: #6f42c1;
--pink: #e83e8c;
--#a7240e: #dc3545;
-- #a7240e: #dc3545;
--orange: #fd7e14;
--yellow: #ffc107;
--green: #28a745;
@@ -1812,7 +1820,9 @@ pre code {
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.form-control {
transition: none
}
@@ -2275,7 +2285,9 @@ select.form-control[multiple], select.form-control[size], textarea.form-control
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.btn {
transition: none
}
@@ -2807,7 +2819,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
transition: opacity .15s linear
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.fade {
transition: none
}
@@ -2828,7 +2842,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
transition: height .35s ease
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.collapsing {
transition: none
}
@@ -3439,7 +3455,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
transition: transform .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.custom-switch .custom-control-label:after {
transition: none
}
@@ -3621,7 +3639,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
appearance: none
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.custom-range::-webkit-slider-thumb {
transition: none
}
@@ -3652,7 +3672,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
appearance: none
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.custom-range::-moz-range-thumb {
transition: none
}
@@ -3685,7 +3707,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
appearance: none
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.custom-range::-ms-thumb {
transition: none
}
@@ -3738,7 +3762,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.custom-control-label:before, .custom-file-label, .custom-select {
transition: none
}
@@ -4164,12 +4190,12 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
}
.navbar-dark .navbar-toggler {
color: hsla(0, 0%, 18%, .5);
border-color: hsla(0, 0%, 18%, .1)
color: rgba(46, 46, 46, 0.5);
border-color: rgba(46, 46, 46, 0.5);
}
.navbar-dark .navbar-toggler-icon {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(46, 46, 46, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")
}
.navbar-dark .navbar-text {
@@ -4235,7 +4261,7 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
.card-header {
padding: .75rem 1.25rem;
margin-bottom: 0;
background-color: rgba(0, 0, 0, .03);
background-color: #ffffff;
border-bottom: 1px solid rgba(0, 0, 0, .125)
}
@@ -4249,7 +4275,7 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
.card-footer {
padding: .75rem 1.25rem;
background-color: rgba(0, 0, 0, .03);
background-color: #ffffff;
border-top: 1px solid rgba(0, 0, 0, .125)
}
@@ -4551,7 +4577,9 @@ input[type=button].btn-block, input[type=reset].btn-block, input[type=submit].bt
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.badge {
transition: none
}
@@ -4772,9 +4800,9 @@ a.badge-dark.focus, a.badge-dark:focus {
}
.alert-success {
color: #316f5d;
background-color: #dff7f0;
border-color: #d2f4ea
color: #2e2e2e;
background-color: #82aa8b;
border-color: #82aa8b
}
.alert-success hr {
@@ -4893,7 +4921,9 @@ a.badge-dark.focus, a.badge-dark:focus {
transition: width .6s ease
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.progress-bar {
transition: none
}
@@ -4909,7 +4939,9 @@ a.badge-dark.focus, a.badge-dark:focus {
animation: progress-bar-stripes 1s linear infinite
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.progress-bar-animated {
-webkit-animation: none;
animation: none
@@ -5358,7 +5390,9 @@ a.close.disabled {
transform: translateY(-50px)
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.modal.fade .modal-dialog {
transition: none
}
@@ -5838,7 +5872,9 @@ a.close.disabled {
transition: transform .6s ease-in-out
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.carousel-item {
transition: none
}
@@ -5873,7 +5909,9 @@ a.close.disabled {
transition: opacity 0s .6s
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.carousel-fade .active.carousel-item-left, .carousel-fade .active.carousel-item-right {
transition: none
}
@@ -5894,7 +5932,9 @@ a.close.disabled {
transition: opacity .15s ease
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.carousel-control-next, .carousel-control-prev {
transition: none
}
@@ -5961,7 +6001,9 @@ a.close.disabled {
transition: opacity .6s ease
}
@media (prefers-#a7240euced-motion: #a7240euce) {
@media (prefers-#a7240euced-motion: #a7240euce
) {
.carousel-indicators li {
transition: none
}
@@ -10117,9 +10159,9 @@ footer a:hover {
}
.btn-light:hover {
background: transparent;
background-color: hsla(0, 0%, 18%, .5);
color: #cfd5cd;
border: 1px solid #cfd5cd
border: 1px solid hsla(0, 0%, 18%, .5)
}
.btn-dark {
@@ -10360,16 +10402,6 @@ footer a:hover {
box-shadow: none
}
.modal-content {
width: 500px
}
@media (max-width: 575px) {
.modal-content {
width: 300px
}
}
.modal-content .modal-header {
justify-content: center;
border: none
@@ -10385,6 +10417,12 @@ footer a:hover {
padding: 5px 0 20px 39px
}
.modal-content .modal-footer {
border: none;
justify-content: flex-start;
padding: 5px 0 20px 39px
}
/*# sourceMappingURL=maps/style.min.css.map */
.bg-header {
@@ -10402,7 +10440,7 @@ footer a:hover {
background-color: transparent !important;
}
textarea, input:not([type="submit"]):not([class="multiselect__input"]), select {
textarea, input:not([type="submit"]):not([class="multiselect__input"]):not([class="select2-search__field"]), select {
background-color: white !important;
border-radius: .25rem !important;
border: 1px solid #ced4da !important;
@@ -10413,10 +10451,14 @@ textarea, input:not([type="submit"]):not([class="multiselect__input"]), select {
color: #212529 !important;
}
.multiselect__tag-icon:hover,.multiselect__tag-icon:focus {
.multiselect__tag-icon:hover, .multiselect__tag-icon:focus {
background-color: #a7240e !important;
}
.multiselect__tag-icon:after {
color: #212529 !important
}
.form-control-search {
font-size: 20px;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/user_file_view.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Recipes"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><div id="app"></div><script src="js/chunk-vendors.js"></script></body></html>

View File

@@ -141,17 +141,17 @@ class ShoppingListTable(tables.Table):
class InviteLinkTable(tables.Table):
link = tables.TemplateColumn(
"<a href='{% url 'view_signup' record.uuid %}' >" + _('Link') + "</a>"
"<input value='{{ request.scheme }}://{{ request.get_host }}{% url 'view_invite' record.uuid %}' class='form-control' />"
)
delete = tables.TemplateColumn(
"<a href='{% url 'delete_invite_link' record.id %}' >" + _('Delete') + "</a>" # noqa: E501
delete_link = tables.TemplateColumn(
"<a href='{% url 'delete_invite_link' record.pk %}' >" + _('Delete') + "</a>", verbose_name=_('Delete')
)
class Meta:
model = InviteLink
template_name = 'generic/table_template.html'
fields = (
'username', 'group', 'valid_until', 'created_by', 'created_at'
'username', 'group', 'valid_until',
)

View File

@@ -6,6 +6,14 @@
{% block title %}{% trans "E-mail Addresses" %}{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans 'Email' %}</li>
</ol>
</nav>
<h3>{% trans "E-mail Addresses" %}</h3>
{% if user.emailaddress_set.all %}
<p>{% trans 'The following e-mail addresses are associated with your account:' %}</p>

View File

@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %}
{% load static %}
{% load account socialaccount %}
@@ -8,6 +9,7 @@
{% block content %}
<div class="row">
<div class="col-12" style="text-align: center">
<h3>{% trans "Sign In" %}</h3>
@@ -16,7 +18,8 @@
<div class="row">
<div class="col-6 offset-3">
<div class="col-sm-12 col-lg-6 col-md-6 offset-lg-3 offset-md-3">
<hr>
<form class="login" method="POST" action="{% url 'account_login' %}">
{% csrf_token %}
{{ form | crispy }}
@@ -25,12 +28,13 @@
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
{% endif %}
<button class="btn btn-primary" type="submit">{% trans "Sign In" %}</button>
<a class="btn btn-success" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
<button class="btn btn-success" type="submit">{% trans "Sign In" %}</button>
<a class="btn btn-secondary" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
{% if settings.EMAIL_HOST != '' %}
<a class="btn btn-secondary"
href="{% url 'account_reset_password' %}">{% trans "Reset Password" %}</a>
{% if EMAIL_ENABLED %}
<a class="btn btn-warning float-right d-none d-xl-block d-lg-block"
href="{% url 'account_reset_password' %}">{% trans "Reset My Password" %}</a>
<p class="d-xl-none d-lg-none">{% trans 'Lost your password?' %} <a href="{% url 'account_reset_password' %}">{% trans "Reset My Password" %}</a></p>
{% endif %}
</form>
</div>
@@ -40,13 +44,13 @@
{% if socialaccount_providers %}
<div class="row" style="margin-top: 2vh">
<div class="col-6 offset-3">
<div class="col-sm-12 col-lg-6 col-md-6 offset-lg-3 offset-md-3">
<h5>{% trans "Social Login" %}</h5>
<span>{% trans 'You can use any of the following providers to sign in.' %}</span>
<br/>
<br/>
<ul class="socialaccount_providers">
<ul class="socialaccount_providers list-unstyled">
{% include "socialaccount/snippets/provider_list.html" with process="login" %}
</ul>

View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans 'Password' %}</li>
</ol>
</nav>
<h1>{% trans "Change Password" %}</h1>
<form method="POST" action="{% url 'account_change_password' %}" class="password_change">
{% csrf_token %}
{{ form | crispy }}
<button type="submit" name="action" class="btn btn-success">{% trans "Change Password" %}</button>
<a href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
</form>
{% endblock %}

View File

@@ -8,25 +8,31 @@
{% block content %}
<h3>{% trans "Password Reset" %}</h3>
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% endif %}
<div class="row">
<div class="col-12" style="text-align: center">
<h3>{% trans "Password Reset" %}</h3>
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% endif %}
</div>
</div>
{% if settings.EMAIL_HOST != '' %}
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
{% csrf_token %}
{{ form | crispy }}
<input type="submit" class="btn btn-success" value="{% trans 'Reset My Password' %}"/>
<a class="btn btn-primary" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
<a class="btn btn-info" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
</form>
{% else %}
<p>{% trans 'Password reset is disabled on this instance.' %}</p>
{% endif %}
<div class="row">
<div class="col-sm-12 col-lg-6 col-md-6 offset-lg-3 offset-md-3">
<hr>
{% if EMAIL_ENABLED %}
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}</p>
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
{% csrf_token %}
{{ form | crispy }}
<input type="submit" class="btn btn-warning float-right" value="{% trans 'Reset My Password' %}"/>
</form>
{% else %}
<p>{% trans 'Password reset is disabled on this instance.' %}</p>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %}
{% block head_title %}{% trans "Set Password" %}{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'view_settings' %}">{% trans 'Settings' %}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans 'Password' %}</li>
</ol>
</nav>
<h1>{% trans "Set Password" %}</h1>
<form method="POST" action="{% url 'account_set_password' %}" class="password_set">
{% csrf_token %}
{{ form | crispy }}
<input type="submit" class="btn btn-primary" name="action" value="{% trans 'Set Password' %}"/>
</form>
{% endblock %}

View File

@@ -1,19 +1,74 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load crispy_forms_filters %}
{% load i18n %}
{% block title %}{% trans 'Register' %}{% endblock %}
{% block content %}
<h3>{% trans 'Create your Account' %}</h3>
<div class="row">
<div class="col-12" style="text-align: center">
<h3>{% trans "Create an Account" %}</h3>
</div>
</div>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}</button>
</form>
<div class="row">
<div class="col-sm-12 col-lg-6 col-md-6 offset-lg-3 offset-md-3">
<hr>
<form method="post">
{% csrf_token %}
<p>{% trans 'Already have an account?' %} <a href="{% url 'account_login' %}">{% trans "Sign In" %}</a></p>
<div class="form-group">
{{ form.username |as_crispy_field }}
</div>
<div class="form-group">
{{ form.email |as_crispy_field }}
</div>
<div class="form-group">
{{ form.email2 |as_crispy_field }}
</div>
<div class="form-group">
{{ form.password1 |as_crispy_field }}
</div>
<div class="form-group">
{{ form.password2 |as_crispy_field }}
</div>
{% if TERMS_URL != '' or PRIVACY_URL != '' %}
<div class="form-group">
{{ form.terms |as_crispy_field }}
<small>
{% trans 'I accept the follwoing' %}
{% if TERMS_URL != '' %}
<a href="{{ TERMS_URL }}" target="_blank"
rel="noreferrer nofollow">{% trans 'Terms and Conditions' %}</a>
{% endif %}
{% if TERMS_URL != '' or PRIVACY_URL != '' %}
{% trans 'and' %}
{% endif %}
{% if PRIVACY_URL != '' %}
<a href="{{ PRIVACY_URL }}" target="_blank"
rel="noreferrer nofollow">{% trans 'Privacy Policy' %}</a>
{% endif %}
</small>
</div>
{% endif %}
{% if CAPTCHA_ENABLED %}
<div class="form-group">
{{ form.captcha.errors }}
{{ form.captcha }}
</div>
{% endif %}
<button class="btn btn-success" type="submit"><i class="fas fa-save"></i> {% trans 'Create User' %}
</button>
</form>
<p>{% trans 'Already have an account?' %} <a href="{% url 'account_login' %}">{% trans "Sign In" %}</a></p>
</div>
</div>
{% endblock %}

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