Compare commits

...

237 Commits
0.3.1 ... 0.8.4

Author SHA1 Message Date
vabene1111
723416575f added alt text 2020-06-10 15:52:18 +02:00
vabene1111
e34889953a cleanup and allow loading of images 2020-06-10 15:51:47 +02:00
vabene1111
ec8a879efa improved pdf viewer 2020-06-10 15:40:24 +02:00
vabene1111
f44ebe0d05 Update issue templates 2020-06-10 12:09:37 +02:00
vabene1111
13c82cdbf9 made remote auth a config setting 2020-06-10 12:02:27 +02:00
vabene1111
e9a60ece81 Merge pull request #88 from cazier/develop
Adding Reverse Proxy Authentication
2020-06-10 11:56:16 +02:00
vabene1111
9575a86480 fixed ingredient deletion error 2020-06-10 11:52:24 +02:00
vabene1111
12036b9972 fixes protect error on storage monitor delete #102 2020-06-10 11:45:52 +02:00
vabene1111
bffb260dfa fixed delete source permission 2020-06-10 11:42:44 +02:00
vabene1111
9d460a3623 Update docker-publish-release.yml 2020-06-10 11:38:45 +02:00
vabene1111
56c5f28348 Update docker-publish-latest.yml 2020-06-10 11:37:49 +02:00
vabene1111
29d4dcb73d fixed yaml formatting 2020-06-10 11:36:45 +02:00
vabene1111
e60441ec99 add version script to tagged releases 2020-06-10 11:34:05 +02:00
vabene1111
608e024caa formatting + further testing 2020-06-10 11:19:28 +02:00
vabene1111
f596b12f12 testing build ref version writing 2020-06-10 11:08:59 +02:00
vabene1111
bdd41a5ba2 removed redundant quotes 2020-06-10 10:47:47 +02:00
vabene1111
80e566917e version writer script fixes 2020-06-10 10:33:19 +02:00
vabene1111
4294c132c6 version script path fix 2020-06-10 10:24:54 +02:00
vabene1111
da12daaf03 add version to action 2020-06-10 10:15:28 +02:00
vabene1111
150cf5ebac testing auto version 2020-06-10 10:13:11 +02:00
vabene1111
01b9022451 version script test 2020-06-10 10:07:33 +02:00
vabene1111
1d6375bf84 fixed pdf js files missing 2020-06-09 12:28:48 +02:00
vabene1111
cfab867e0d removed no longer valid warning 2020-06-09 00:18:28 +02:00
vabene1111
29dd7c9ee7 add port mapping to plain example 2020-06-09 00:17:17 +02:00
vabene1111
72cb046e37 updated plain example 2020-06-09 00:16:04 +02:00
vabene1111
a555906a32 fixed user setup staff permission 2020-06-06 11:31:19 +02:00
Brendan Cazier
cdf4c0d1bb Re-adding import statement 2020-06-04 07:51:43 -05:00
vabene1111
dc7c688ed5 show english first in language chooser 2020-06-04 09:58:18 +02:00
vabene1111
8aa8f15ad7 fixed path + added language option dutch (nl) 2020-06-04 09:57:28 +02:00
vabene1111
6b8a231eee Merge pull request #95 from D0T1X/develop
added base translation file for Dutch
2020-06-04 09:33:17 +02:00
D0T1X
b0e338f08a added base translation files 2020-06-04 07:25:31 +02:00
D0T1X
1a6e0c8706 Uploaded Django.po 2020-06-04 07:24:45 +02:00
D0T1X
c7ceae4350 Create temp 2020-06-04 07:24:24 +02:00
D0T1X
caefa6099b Delete temp 2020-06-04 07:21:33 +02:00
D0T1X
2e4645bb0c Creatend nl/MC_MESSAGES folde 2020-06-04 07:20:23 +02:00
vabene1111
6c966f8ef6 added versions to requirements.txt 2020-06-03 23:43:25 +02:00
vabene1111
7140cb0f93 basic system information page 2020-06-03 23:07:19 +02:00
vabene1111
7f08815482 added .map files 2020-06-03 22:40:48 +02:00
vabene1111
6c0a81a4c5 fixed bnroken tooltips and buttons 2020-06-03 22:36:17 +02:00
vabene1111
9179bde8f9 name images with uuid instead of recipe ids
in order to stil be able to manually map images to recipes the name consists of uuid_recipeId.png
2020-06-03 22:09:07 +02:00
vabene1111
0684c47e2a addding missing cli flag to chmod 2020-06-03 21:52:42 +02:00
vabene1111
be52238413 boot script fix 2020-06-03 21:38:59 +02:00
vabene1111
a0332c432d moved permission fix to boot.sh 2020-06-03 21:31:56 +02:00
vabene1111
35b038a33c media folder permission fix 2020-06-03 21:28:42 +02:00
vabene1111
9ac3d79b95 updated examples 2020-06-03 20:54:38 +02:00
vabene1111
6f24b7d34e fixed media file serving 2020-06-03 20:38:48 +02:00
vabene1111
e84d15a7ed debugging media serving problems 2020-06-03 20:18:42 +02:00
vabene1111
8f268b3b75 corrected .env template 2020-06-03 20:00:44 +02:00
vabene1111
f9cb44c66b gunicorn media file serving 2020-06-03 19:58:01 +02:00
Brendan Cazier
81cd551975 Added some basic documentation 2020-06-03 10:40:12 -05:00
Brendan Cazier
55777fd948 Added REMOTE_USER auth 2020-06-03 10:16:17 -05:00
vabene1111
d09c6dbfed added automatic redirect for setup 2020-06-03 10:51:39 +02:00
vabene1111
67d7cd1d23 Update README.md 2020-06-02 22:30:33 +02:00
vabene1111
bf3337b5e1 Update README.md 2020-06-02 22:25:28 +02:00
vabene1111
0b0d214085 update translations 2020-06-02 22:10:50 +02:00
transifex-integration[bot]
60dc008b05 Apply translations in de
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'de' language.
2020-06-02 20:09:55 +00:00
transifex-integration[bot]
475b6e3728 Apply translations in de
at least 1% translated for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'de' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-06-02 19:51:54 +00:00
vabene1111
0e70cd83e2 added base translation files 2020-06-02 21:21:03 +02:00
vabene1111
27297d170a removed obsolte all_tags function 2020-06-02 11:07:11 +02:00
vabene1111
bc8f8b8138 improved keyword rendering in search + plan 2020-06-02 11:06:16 +02:00
vabene1111
87ba53fde9 remove spaces and change line break on shopping 2020-06-02 11:03:31 +02:00
vabene1111
098dda28a4 updated readme 2020-06-02 10:58:05 +02:00
vabene1111
8ffe6abb5c markdown docs lin 2020-06-02 10:56:18 +02:00
vabene1111
5706776002 added setting to disable recent viewed recipes 2020-06-02 10:53:32 +02:00
vabene1111
fbe528e935 added github link 2020-06-02 10:49:54 +02:00
vabene1111
0df86b940f setup page 2020-06-02 10:40:21 +02:00
vabene1111
81c3707090 some cleanup + env comments 2020-06-02 10:11:18 +02:00
vabene1111
9fd691b9f0 fixed ci/static path 2020-06-02 09:01:09 +02:00
vabene1111
821136787d fixed shopping list headers #78 2020-06-01 23:23:46 +02:00
vabene1111
6aedba09f3 removed some decentral cdn deps 2020-06-01 23:17:16 +02:00
vabene1111
7b17a1acfa removed all cdn dependencies 2020-06-01 23:13:38 +02:00
vabene1111
9dd538519f fixed recipe table image 2020-06-01 22:51:33 +02:00
vabene1111
b8821f1f72 Merge pull request #80 from cazier/develop
Made recipe images hyperlinked
2020-06-01 22:43:21 +02:00
Brendan Cazier
3420dcd07d Made recipe images hyperlinked 2020-06-01 12:36:47 -05:00
vabene1111
445c01bddc added basic setup template 2020-06-01 13:53:17 +02:00
vabene1111
dd5996084d make jquery local 2020-06-01 13:51:55 +02:00
vabene1111
dfb1d80ca0 fixed duplicates in recent view 2020-05-27 09:38:57 +02:00
vabene1111
744fbc7a46 revert psql distinct change 2020-05-15 13:12:52 +02:00
vabene1111
cd11cc58cf possible duplicate fix 2020-05-15 12:49:31 +02:00
vabene1111
569e385915 Revert "Create FUNDING.yml"
This reverts commit abf552cd18.
2020-05-13 13:27:46 +02:00
vabene1111
abf552cd18 Create FUNDING.yml 2020-05-13 13:22:26 +02:00
vabene1111
c6959488dc fixed typo on search page 2020-05-11 13:08:49 +02:00
vabene1111
85e3155b50 added group required filter to history view 2020-05-11 13:08:00 +02:00
vabene1111
f6aa50bbfc added history page 2020-05-11 12:59:54 +02:00
vabene1111
5ad27c015e markdown info central blockquote css 2020-05-11 12:44:31 +02:00
vabene1111
4a68a99907 show last viewd recipes on search page 2020-05-11 12:42:55 +02:00
vabene1111
123dc1a74d meal plan entry view 2020-05-08 00:10:23 +02:00
vabene1111
2e23fcfd5d added sharing to meal plan + fixed meal plan visibility 2020-05-07 23:16:24 +02:00
vabene1111
edbc21df19 Update README.md 2020-05-06 08:21:53 +02:00
vabene1111
f0e1c901c6 fixed print button tooltip messing up print 2020-05-04 20:48:17 +02:00
vabene1111
22e403e0ff added basic markdown doc 2020-05-03 00:43:13 +02:00
vabene1111
6a7b02b700 add special type of ingredients to allow headers 2020-05-02 23:46:57 +02:00
vabene1111
4aa2983681 order recipe ingredients 2020-05-02 23:05:36 +02:00
vabene1111
18888bc3ae search image heigth fixes 2020-05-02 22:10:02 +02:00
vabene1111
07a0a3f598 recipe book improvements 2020-05-02 21:59:32 +02:00
vabene1111
76e1274ba5 rating/last cooked display 2020-05-02 21:54:38 +02:00
vabene1111
598387efc8 fixed duplicate recipe books when sharing 2020-05-02 21:46:03 +02:00
vabene1111
f00ee7d9fa display log info 2020-05-02 21:44:12 +02:00
vabene1111
6abe6f2ee4 re added mistakingly deleted file 2020-05-02 21:43:53 +02:00
vabene1111
bd69f2d103 log button in view 2020-05-02 17:55:14 +02:00
vabene1111
6a963c26b2 recipe rating 2020-05-02 17:31:35 +02:00
vabene1111
4c08ade3ee fixed markdown bleach renderer again 2020-05-02 15:10:15 +02:00
vabene1111
37f7326f4c minor mealplan cleanups 2020-05-02 14:58:23 +02:00
vabene1111
c398fda15c Merge branch 'feature/plan-title' into develop 2020-05-02 14:53:26 +02:00
vabene1111
e9da17151a added title field and custom validation 2020-05-02 14:53:09 +02:00
vabene1111
fd4354f16d Merge pull request #57 from tourn/mealplan-recipes-optional
Allow mealplan items to have no recipes
2020-05-02 14:44:46 +02:00
vabene1111
0d0c6c9066 Merge branch 'feature/plan-title' into mealplan-recipes-optional 2020-05-02 14:44:15 +02:00
vabene1111
4620c78f5a user preference fixes and improvements 2020-05-02 14:41:54 +02:00
vabene1111
349b9629f8 added sharing to recipe books 2020-05-02 14:15:56 +02:00
vabene1111
64ee18c4d8 improved recipe book design 2020-05-02 13:58:42 +02:00
vabene1111
3a9e5a80ba final style touches + settings 2020-05-02 12:48:22 +02:00
vabene1111
de85a6b334 further search style improvements 2020-05-02 12:07:03 +02:00
vabene1111
25318b691d new search design improvements 2020-05-02 01:04:45 +02:00
vabene1111
77e778caac new search design basics + Boostrap fixes 2020-05-02 00:49:29 +02:00
vabene1111
b53f83a76c improved stats page 2020-04-29 17:18:12 +02:00
vabene1111
2304c43a60 fixed ingredient calculator rounding error 2020-04-29 16:21:45 +02:00
vabene1111
16963c17dc add default roles to existing users 2020-04-27 18:22:29 +02:00
vabene1111
1d9dc0f952 api permission tests 2020-04-27 17:57:43 +02:00
vabene1111
a9fe821067 added test for comments 2020-04-27 17:48:11 +02:00
vabene1111
c7b1b08516 updated tests 2020-04-27 17:13:43 +02:00
vabene1111
1617fa7a3f fixed permissions comments, books 2020-04-27 16:50:05 +02:00
vabene1111
ad467fae28 added basic group permission system 2020-04-26 17:21:44 +02:00
vabene1111
c7046bc705 fixed markdown urlize 2020-04-26 15:52:07 +02:00
vabene1111
52946a8e4c fixed broken emoji 2020-04-26 00:29:56 +02:00
vabene1111
dd6b77e029 added screenshots + refactor preview + moved docu 2020-04-26 00:28:14 +02:00
vabene1111
396c1f3d5f added tooltips to recipe view 2020-04-25 23:35:01 +02:00
vabene1111
379d5a5177 import export cleanup + features 2020-04-25 23:32:15 +02:00
vabene1111
85a4d5d432 basic import export working 2020-04-25 22:26:59 +02:00
vabene1111
43eb10e488 added basic exporting capability 2020-04-25 22:05:55 +02:00
vabene1111
d702c08a12 fixed urlize breaking markdown links 2020-04-25 10:46:27 +02:00
vabene1111
e78323d214 Update docker-publish-latest.yml 2020-04-15 15:05:36 +02:00
vabene1111
d2e866dd74 cleanup/refactor workflows 2020-04-15 14:48:18 +02:00
vabene1111
76687ad5df testing multi platform deployment 2020-04-15 12:34:58 +02:00
vabene1111
dab77e8e4f imrpoved index redirect + fixed tests 2020-04-13 23:11:33 +02:00
vabene1111
0b250c71aa actually fixed action yml 2020-04-13 22:55:50 +02:00
vabene1111
571f670db0 fixed action intendations 2020-04-13 22:54:17 +02:00
vabene1111
4e9e628162 added ability to change default page 2020-04-13 22:52:02 +02:00
vabene1111
4f49b06704 user setting default ingredient unit 2020-04-13 22:37:50 +02:00
vabene1111
8eb0c36665 fixed action invalid yaml 2020-04-13 21:53:36 +02:00
vabene1111
6f69c09aca Merge pull request #55 from hakoerber/kubernetes-manifests
Kubernetes manifests
2020-04-13 21:51:58 +02:00
vabene1111
8e6f153882 Merge pull request #56 from tourn/clickable-links
Make links in recipe clickable
2020-04-13 21:50:25 +02:00
vabene1111
07183fd40f actions testing 2020-04-13 21:39:20 +02:00
tourn
08cccfa133 Allow mealplan items to have no recipes
And display first line of notes in plan
2020-04-13 21:29:22 +02:00
tourn
04b7f0a398 Make links in recipe clickable 2020-04-13 21:27:22 +02:00
Hannes Körber
1735fda48f Add basic kubernetes manifest
Closes #50
2020-04-13 19:39:41 +02:00
Hannes Körber
1c9ea0eda7 Use relative links in README
See https://github.blog/2013-01-31-relative-links-in-markup-files/ for
more details.
2020-04-13 19:39:39 +02:00
vabene1111
83b5b6695c Update docker-release-publish.yml 2020-04-13 18:25:49 +02:00
vabene1111
342fb3c96d Update docker-release-publish.yml 2020-04-13 18:23:19 +02:00
vabene1111
b7a18466b5 testing multi plattform builds 2020-04-13 18:17:53 +02:00
vabene1111
0cdc4d51df Merge branch 'feature/webdav-root-option' into develop 2020-04-13 17:41:02 +02:00
vabene1111
e177669514 Merge pull request #51 from pataya23/develop
added 'webdav_root' option
2020-04-13 17:38:19 +02:00
pataya23
fd294dfcdd added 'webdav_root' option
otherwise I get an error (webdav3.exceptions.RemoteResourceNotFound: Remote resource: </path> not found)
2020-04-13 13:19:03 +02:00
vabene1111
bdd092e5d3 fixed ingredient amount numeric 2020-04-07 20:13:20 +02:00
vabene1111
f1c5a0ef5f character limit increased 2020-04-07 18:46:20 +02:00
vabene1111
1e8ff763d5 increased character limit keyword icon
thanks cazier for pointing out this issue and providing a PR https://github.com/vabene1111/recipes/pull/46
2020-04-07 18:46:09 +02:00
vabene1111
4cf6a3b219 Merge pull request #45 from tourn/improved-meal-plan
Add buttons to add a meal plan to a specific point in time
2020-04-05 14:47:55 +02:00
tourn
de145b6b18 Add some styling 2020-04-05 11:10:39 +02:00
vabene1111
84a8308bf3 updated tabulator js to 4.6 2020-03-31 01:20:49 +02:00
vabene1111
8d191fa1a1 set recipe name as page title 2020-03-31 01:11:14 +02:00
vabene1111
b47a0197e2 imroved recipe printing and view 2020-03-31 01:10:30 +02:00
tourn
4e7c5f9495 Add buttons to add a meal plan to a specific point in time 2020-03-30 22:06:17 +02:00
vabene1111
d704ddacdd fixed shopping list format 2020-03-27 21:47:00 +01:00
vabene1111
2c3140248c fixed broken links 2020-03-27 21:41:55 +01:00
vabene1111
6d5ea31f8e re added mistakingly removed command 2020-03-26 18:55:35 +01:00
vabene1111
b538761746 run container as root for now
since i want to realease this we will for now continue to run this as root inside the containerr. this can be fixed later, PR's welcome
2020-03-26 18:20:44 +01:00
vabene1111
913d858473 updated boot script to fix permission 2020-03-25 22:30:44 +01:00
vabene1111
574d088cdd Create docker-publish.yml 2020-03-24 18:04:52 +01:00
vabene1111
ed360ca1c7 updated readme 2020-03-24 17:37:16 +01:00
vabene1111
08848da4a3 Merge branch 'feature/docker-rewrite' into develop 2020-03-24 17:23:04 +01:00
vabene1111
3f1f63d7e0 remove image tag as it is default 2020-03-24 17:22:51 +01:00
vabene1111
3bd6557e59 use stable image 2020-03-24 17:18:27 +01:00
vabene1111
23cb98f631 Create docker-release-publish.yml 2020-03-24 17:16:24 +01:00
vabene1111
6e91c30245 fixed tests 2020-03-24 17:03:52 +01:00
vabene1111
d7e0fa821b updated examples 2020-03-24 16:44:19 +01:00
vabene1111
c67342df26 wip changes 2020-03-24 12:57:45 +01:00
vabene1111
e3b71d47f4 Merge pull request #39 from h4llow3En/develop
Run as alpine docker image and server static files with gunicorn
2020-03-23 20:03:09 +01:00
h4llow3En
1e3e03e4af Simplify first user creation 2020-03-20 10:50:34 +01:00
h4llow3En
391ab5ddac Don't use "latest" images 2020-03-20 10:02:59 +01:00
h4llow3En
e0c560c2d7 Remove debug output from Dockerfile 2020-03-20 09:28:33 +01:00
h4llow3En
be942bcb79 Fix "Update" description in readme 2020-03-19 18:02:28 +01:00
h4llow3En
6b27f0c8ab Cleanup and simplify deployment 2020-03-19 15:08:53 +01:00
h4llow3En
cc931189e8 Run as alpine docker image and server static files with gunicorn 2020-03-19 10:13:49 +01:00
vabene1111
1b45121385 case insenstitive import 2020-03-18 16:58:27 +01:00
vabene1111
97e2593f72 fixed single import 2020-03-18 16:50:28 +01:00
vabene1111
00539b9d1b fixed theme switching 2020-03-18 13:08:31 +01:00
vabene1111
1cadb1e85e added password change form 2020-03-18 13:06:39 +01:00
vabene1111
9e524a8f22 added user name change 2020-03-18 12:51:13 +01:00
vabene1111
a8a7d4e0f4 complete bottom border "hack" 2020-03-18 12:30:35 +01:00
vabene1111
d0cf396f68 ingredient mobile friendly 2020-03-18 12:29:40 +01:00
vabene1111
e45f3f3343 updated translations 2020-03-18 12:20:45 +01:00
vabene1111
13ea2ecd7d display ingredient note 2020-03-18 12:12:03 +01:00
vabene1111
25ba62e87c improved ingredient editing 2020-03-18 12:11:15 +01:00
vabene1111
48107b918d added ingredient list page 2020-03-18 11:36:39 +01:00
vabene1111
0b56e22af9 nav improvements 2020-03-18 11:36:24 +01:00
vabene1111
47128fbb79 properly align nav icon vertically 2020-03-18 11:19:17 +01:00
vabene1111
12f6aa6df7 changed docker base image for python 3.8 2020-03-17 23:55:16 +01:00
vabene1111
c2dc038ac9 note optional 2020-03-17 22:49:53 +01:00
vabene1111
0c2b3d2d03 added ingredient notes + removed null constraints 2020-03-17 22:47:17 +01:00
vabene1111
1d562452df changed behavior of delete original 2020-03-17 18:54:44 +01:00
vabene1111
4c90664aa2 fixed translation mistaek 2020-03-17 18:47:24 +01:00
vabene1111
90dbc36402 added ability to link recipes to ingredients 2020-03-17 18:44:11 +01:00
vabene1111
a60b09e491 fixed confirm message on unit/ingredeitn merge 2020-03-17 18:28:53 +01:00
vabene1111
deeda425a8 added tests for storage edit 2020-03-17 18:22:13 +01:00
vabene1111
6fcbc9f0cd some basic testing for external recipe edits 2020-03-17 17:48:23 +01:00
vabene1111
7518d8c6b1 fixed several rewrite issues 2020-03-17 17:36:05 +01:00
vabene1111
eb25a9163f fixed import log badge 2020-03-17 17:31:26 +01:00
vabene1111
e2f6e07e42 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2020-03-17 17:23:45 +01:00
vabene1111
17b9519fa9 refactor generic url creation 2020-03-17 17:23:39 +01:00
vabene1111
adcef1d887 Update main.yml 2020-03-17 16:41:21 +01:00
vabene1111
7398304d16 Update main.yml 2020-03-17 16:40:59 +01:00
vabene1111
09ff7e82f1 django admin cleanup 2020-03-17 16:16:04 +01:00
vabene1111
47072763ee cleand up search ui 2020-03-17 15:34:17 +01:00
vabene1111
86f2c9d89c cleand up and fixed ingredient edit table 2020-03-16 23:08:52 +01:00
vabene1111
c8eaa2a349 removed js test code 2020-03-16 22:50:52 +01:00
vabene1111
9853cecabb fixed keywords without icons 2020-03-16 14:48:04 +01:00
vabene1111
7b65252d47 documentation 2020-03-12 21:48:09 +01:00
vabene1111
f62ec51c91 ignore compose 2020-03-12 21:03:34 +01:00
vabene1111
9f8b93732f removed root docker compose 2020-03-12 20:57:27 +01:00
vabene1111
bf07fc7437 restructure 2020-03-12 20:48:52 +01:00
vabene1111
08837032ce ignore docker compose 2020-03-12 20:38:48 +01:00
vabene1111
abd655ce62 moved delete to seperate file 2020-03-01 12:34:59 +01:00
vabene1111
e755068a31 added test for png's 2020-03-01 12:13:36 +01:00
vabene1111
b5e35115fa fixed png's not working in recipes 2020-03-01 12:13:06 +01:00
vabene1111
c8cc140a78 improved recipe edit tests and fixed bugs 2020-02-29 00:00:13 +01:00
vabene1111
8c7a171d56 added more tests and test structure 2020-02-28 23:17:04 +01:00
vabene1111
df62717806 removed non internal recipes from shopping 2020-02-28 22:21:03 +01:00
vabene1111
a1e6bd5441 fixed text outside of colum 2020-02-28 22:12:18 +01:00
vabene1111
034e2c612b added internal recipe filter 2020-02-28 22:12:02 +01:00
vabene1111
45c85b9de8 fixed markdown blockquotes not really rendering 2020-02-28 22:09:16 +01:00
vabene1111
a9952b8f57 improved markdown rendering of tables and images 2020-02-28 21:53:27 +01:00
vabene1111
b8f16b50a7 simplified viewer + fixed characters escaping 2020-02-19 20:19:08 +01:00
vabene1111
752df5a1d2 cleaned up viewer 2020-02-19 19:39:32 +01:00
vabene1111
fe6e351349 testing pdf viewerr 2020-02-19 19:12:11 +01:00
vabene1111
8cc9273268 basic viewer working 2020-02-19 19:02:47 +01:00
vabene1111
0c1763b347 pdf display working 2020-02-19 18:13:11 +01:00
vabene1111
88dc713683 WIP pdf embedding 2020-02-19 16:55:13 +01:00
vabene1111
fc1cc70870 basic pdf embedding 2020-02-19 00:08:32 +01:00
346 changed files with 107884 additions and 1218 deletions

View File

@@ -7,4 +7,12 @@ docker-compose*
.gitignore
README.md
LICENSE
.vscode
.vscode
.env
.env.template
.github
.idea
LICENSE.md
docs
nginx
update.sh

View File

@@ -1,14 +1,26 @@
VIRTUAL_HOST=
LETSENCRYPT_HOST=
LETSENCRYPT_EMAIL=
# only set this to true when testing/debugging
DEBUG=0
DEBUG=1
# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,...
ALLOWED_HOSTS=*
# random secret key, use for example base64 /dev/urandom | head -c50 to generate one
SECRET_KEY=
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
DB_ENGINE=django.db.backends.postgresql_psycopg2
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=djangodb
POSTGRES_PASSWORD=
POSTGRES_DB=djangodb
POSTGRES_DB=djangodb
# Serve mediafiles directly using gunicorn. Basically everyone recommends not doing this. Please use any of the examples
# provided that include an additional nxginx container to handle media file serving.
# If you know what you are doing turn this back on (1) to serve media files using djangos serve() method.
GUNICORN_MEDIA=0
# allow authentication via reverse proxy (e.g. authelia), leave of if you dont know what you are doing
# docs: https://github.com/vabene1111/recipes/tree/develop/docs/docker/nginx-proxy%20with%20proxy%20authentication
REVERSE_PROXY_AUTH=0

15
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,15 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
### Version
Please provide your current version (can be found on the system page since v0.8.4)
Version:
### Bug description
A clear and concise description of what the bug is.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

27
.github/ISSUE_TEMPLATE/help-request.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
name: Help request
about: If there is anything wrong with your setup
title: ''
labels: setup issue
assignees: ''
---
### Version
Please provide your current version (can be found on the system page since v0.8.4)
Version:
### Issue
Please describe your problem here
### `.env`
Please include your `.env` config file (**make sure to remove/replace all secrets**)
```
env content
```
### `docker-compose.yml`
When running with docker compose please provide your `docker-compose.yml`
```
docker-compose.yml content
```

View File

@@ -9,18 +9,19 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [3.7]
python-version: [3.8]
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.7
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.7
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
python3 manage.py collectstatic --noinput
- name: Django Testing project
run: |
python3 manage.py test

View File

@@ -0,0 +1,26 @@
name: publish dev image docker
on:
push:
branches:
- '*'
- '*/*'
- '!master'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = 'develop'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@2.13
with:
name: vabene1111/recipes
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -0,0 +1,30 @@
name: publish latest image docker
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Get version number
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
- name: Build and publish image
uses: ilteoood/docker_buildx@master
with:
publish: true
imageName: vabene1111/recipes
tag: latest
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -0,0 +1,33 @@
name: publish tagged release docker
on:
push:
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
name: Build image job
steps:
- name: Checkout master
uses: actions/checkout@master#
- name: Get version number
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Update version file
uses: DamianReeves/write-file-action@v1.0
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
- name: Build and publish image
uses: ilteoood/docker_buildx@master
with:
publish: true
imageName: vabene1111/recipes
tag: ${{ steps.get_version.outputs.VERSION }}
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}

22
.gitignore vendored
View File

@@ -69,29 +69,11 @@ mediafiles/
\.idea/misc\.xml
\.idea/recipes\.iml
# Deployment
\.env
staticfiles/
postgresql/
postgresql/
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
/docker-compose.override.yml

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

8
.idea/dictionaries/vabene1111_PC.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="vabene1111-PC">
<words>
<w>gunicorn</w>
<w>traefik</w>
</words>
</dictionary>
</component>

View File

@@ -1,14 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="psycopg2" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -1,6 +1,5 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Default" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{jquery-3.4.1}" />
</component>
</project>

35
.idea/recipes.iml generated Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="recipes/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.8 (recipes)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jquery-3.4.1" level="application" />
<orderEntry type="library" name="pretty-checkbox" level="application" />
<orderEntry type="library" name="pdf" level="application" />
<orderEntry type="library" name="pdf_viewer" level="application" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/templates" />
<option value="$MODULE_DIR$/cookbook/templates" />
</list>
</option>
</component>
</module>

View File

@@ -1,24 +1,18 @@
FROM ubuntu:18.04
RUN mkdir /Recipes
WORKDIR /Recipes
ADD . /Recipes/
RUN apt-get update
RUN apt-get -y upgrade
RUN apt-get install -y \
python3 \
python3-pip \
postgresql-client \
gettext
RUN pip3 install --upgrade pip
RUN pip3 install -r requirements.txt
RUN apt-get autoremove -y
FROM python:3.8-alpine
RUN apk add --no-cache postgresql-libs gettext zlib libjpeg libxml2-dev libxslt-dev
ENV PYTHONUNBUFFERED 1
EXPOSE 8080
EXPOSE 8080
RUN mkdir /opt/recipes
WORKDIR /opt/recipes
COPY . ./
RUN chmod +x boot.sh setup.sh
RUN ln -s /opt/recipes/setup.sh /usr/local/bin/createsuperuser
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev && \
python -m venv venv && \
venv/bin/pip install -r requirements.txt --no-cache-dir &&\
apk --purge del .build-deps
ENTRYPOINT ["/opt/recipes/boot.sh"]

View File

@@ -1,7 +1,9 @@
# Recipes ![Continous Integration](https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop)
# Recipes ![CI](https://github.com/vabene1111/recipes/workflows/Continous%20Integration/badge.svg?branch=develop)
Recipes is a Django application to manage, tag and search recipes using either built in models or external storage providers hosting PDF's, Images or other files.
![Preview](preview.png)
![Preview](docs/preview.png)
[More Screenshots](https://imgur.com/a/V01151p)
### Features
@@ -12,63 +14,55 @@ Recipes is a Django application to manage, tag and search recipes using either b
- :iphone: Optimized for use on **mobile** devices like phones and tablets
- :shopping_cart: Generate **shopping** lists from recipes
- :calendar: Create a **Plan** on what to eat when
- :person_with_blond_hair: **Share** recipes with friends and comment on them to suggest or remember changes you made
- :family: **Share** recipes with friends and comment on them to suggest or remember changes you made
- :whale: Easy setup with **Docker**
- :art: Customize your interface with **themes**
- :envelope: Export and import recipes from other users
- :heavy_plus_sign: Many more like recipe scaling, image compression, cookbooks, printing views, ...
This application is meant for people with a collection of recipes they want to share with family and friends or simply store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as a public page.
This application is meant for people with a collection of recipes they want to share with family and friends or simply
store them in a nicely organized way. A basic permission system exists but this application is not meant to be run as a public page.
Some Documentation can be found [here](https://github.com/vabene1111/recipes/wiki)
# Installation
# Documentation
Most things should be straight forward but there are some more complicated things.
##### Storage Backends
A `Storage Backend` is a remote storage location where files are stored. To add a new backend click on `Storage Data` and then on `Storage Backends`. There click the plus button.
Enter a name (just a display name for you to identify it) and an API access Token for the account you want to use.
Dropboxes API tokens can be found on the [Dropboxes API explorer](https://dropbox.github.io/dropbox-api-v2-explorer/#auth_token/from_oauth1)
with the button on the top right. For Nextcloud you can use a App apssword created in the settings.
##### Adding Synced Paths
To add a new path from your Storage backend to the sync list, go to `Storage Data >> Configure Sync` and select the storage backend you want to use.
Then enter the path you want to monitor starting at the storage root (e.g. `/Folder/RecipesFolder`) and save it.
##### Syncing Data
To sync the recipes app with the storage backends press `Sync now` under `Storage Data >> Configure Sync`.
##### Import Recipes
All files found by the sync can be found under `Manage Data >> Import recipes`. There you can either import all at once without modifying them or import one by one, adding tags while importing.
##### Batch Edit
If you have many untagged recipes, you may want to edit them all at once. To do so, go to
`Storage Data >> Batch Edit`. Enter a word which should be contained in the recipe name and select the tags you want to apply.
When clicking submit, every recipe containing the word will be updated (tags are added).
> Currently the only option is word contains, maybe some more SQL like operators will be added later.
## Installation
The docker image (`vabene1111/recipes`) simply exposes the application on port `8080`. You may choose any preferred installation method, the following are just examples to make it easier.
### Docker-Compose
When cloning this repository, a simple docker-compose file is included. It is made for setups already running an nginx-reverse proxy network with lets encrypt companion but can be changed easily. Copy `.env.template` to `.env` and fill in the missing values accordingly.
Now simply start the containers and run the `update.sh` script that will apply all migrations and collect static files.
Create a default user by executing into the container with `docker-compose exec web_recipes sh` and run `python3 manage.py createsuperuser`.
2. Choose one of the included configurations [here](docs/docker).
2. Download the environment (config) file template and fill it out `wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env `
3. Start the container `docker-compose up -d`
4. Open the page to create the first user. Alternatively use `docker-compose exec web_recipes createsuperuser`
### Manual
**Python >= 3.8** is required to run this!
Copy `.env.template` to `.env` and fill in the missing values accordingly.
You can leave out the docker specific variables (VIRTUAL_HOST, LETSENCRYPT_HOST, LETSENCRYPT_EMAIL).
Make sure all variables are available to whatever serves your application.
Otherwise simply follow the instructions for any django based deployment
(for example this one http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html).
(for example [this one](http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)).
To start developing:
1. Clone the repository using your preferred method
2. Install requirements from `requirements.txt` either globally or in a virtual environment
3. Run migrations with `manage.py migrate`
4. Create a first user with `manage.py createsuperuser`
5. Start development server with `manage.py runserver`
## Updating
While intermediate updates can be skipped when updating please make sure to **read the release notes** in case some special action is required to update.
0. Before updating it is recommended to **create a backup!**
1. Stop the container using `docker-compose down`
2. Pull the latest image using `docker-compose pull`
3. Start the container again using `docker-compose up -d`
## Kubernetes
You can find a basic kubernetes setup [here](docs/k8s/). Please see the README in the folder for more detail.
## Contributing
Pull Requests and ideas are welcome, feel free to contribute in any way.
For any questions on how to work with django please refer to their excellent [documentation](https://www.djangoproject.com/start/).
### Translating
There is a [transifex project](https://www.transifex.com/django-recipes/django-cookbook/) project to enable community driven translations. If you want to contribute a new language or help maintain an already existing one feel free to create a transifex account (using the link above) and request to join the project.
It is also possible to provide the translations directly by creating a new language using `manage.py makemessages -l <language_code> -i venv`. Once finished simply open a PR with the changed files.
## License
This project is licensed under the MIT license. Even though it is not required to publish derivatives, I highly encourage pushing changes upstream and letting people profit from any work done on this project.

11
boot.sh Normal file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
source venv/bin/activate
echo "Updating database"
python manage.py migrate
python manage.py collectstatic --noinput
echo "Done"
chmod -R 755 /opt/recipes/mediafiles
exec gunicorn -b :8080 --access-logfile - --error-logfile - recipes.wsgi

View File

@@ -2,10 +2,103 @@ from django.contrib import admin
from .models import *
admin.site.register(Recipe)
class UserPreferenceAdmin(admin.ModelAdmin):
list_display = ('name', 'theme', 'nav_color', 'default_page', 'search_style')
@staticmethod
def name(obj):
return obj.user.get_user_name()
admin.site.register(UserPreference, UserPreferenceAdmin)
class StorageAdmin(admin.ModelAdmin):
list_display = ('name', 'method')
admin.site.register(Storage, StorageAdmin)
class SyncAdmin(admin.ModelAdmin):
list_display = ('storage', 'path', 'active', 'last_checked')
admin.site.register(Sync, SyncAdmin)
class SyncLogAdmin(admin.ModelAdmin):
list_display = ('sync', 'status', 'msg', 'created_at')
admin.site.register(SyncLog, SyncLogAdmin)
admin.site.register(Keyword)
admin.site.register(Sync)
admin.site.register(SyncLog)
admin.site.register(RecipeImport)
admin.site.register(Storage)
class RecipeAdmin(admin.ModelAdmin):
list_display = ('name', 'internal', 'created_by', 'storage')
@staticmethod
def created_by(obj):
return obj.created_by.get_user_name()
admin.site.register(Recipe, RecipeAdmin)
admin.site.register(Unit)
admin.site.register(Ingredient)
class RecipeIngredientAdmin(admin.ModelAdmin):
list_display = ('recipe', 'ingredient', 'amount', 'unit')
admin.site.register(RecipeIngredient, RecipeIngredientAdmin)
class CommentAdmin(admin.ModelAdmin):
list_display = ('recipe', 'name', 'created_at')
@staticmethod
def name(obj):
return obj.created_by.get_user_name()
admin.site.register(Comment, CommentAdmin)
class RecipeImportAdmin(admin.ModelAdmin):
list_display = ('name', 'storage', 'file_path')
admin.site.register(RecipeImport, RecipeImportAdmin)
class RecipeBookAdmin(admin.ModelAdmin):
list_display = ('name', 'user_name')
@staticmethod
def user_name(obj):
return obj.created_by.get_user_name()
admin.site.register(RecipeBook, RecipeBookAdmin)
class RecipeBookEntryAdmin(admin.ModelAdmin):
list_display = ('book', 'recipe')
admin.site.register(RecipeBookEntry, RecipeBookEntryAdmin)
class MealPlanAdmin(admin.ModelAdmin):
list_display = ('user', 'recipe', 'meal', 'date')
@staticmethod
def user(obj):
return obj.created_by.get_user_name()
admin.site.register(MealPlan, MealPlanAdmin)

View File

@@ -43,4 +43,12 @@ class RecipeFilter(django_filters.FilterSet):
class Meta:
model = Recipe
fields = ['name', 'keywords', 'ingredients']
fields = ['name', 'keywords', 'ingredients', 'internal']
class IngredientFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = Ingredient
fields = ['name']

View File

@@ -1,6 +1,6 @@
from dal import autocomplete
from django import forms
from django.forms import widgets
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from emoji_picker.widgets import EmojiPickerTextInput
@@ -27,12 +27,33 @@ class DateWidget(forms.DateInput):
class UserPreferenceForm(forms.ModelForm):
prefix = 'preference'
class Meta:
model = UserPreference
fields = ('theme', 'nav_color')
fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share')
help_texts = {
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!')
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.'),
'plan_share': _('Default user to share newly created meal plan entries with.'),
'show_recent': _('Show recently viewed recipes on search page.'),
}
widgets = {
'plan_share': MultiSelectWidget
}
class UserNameForm(forms.ModelForm):
prefix = 'name'
class Meta:
model = User
fields = ('first_name', 'last_name')
help_texts = {
'first_name': _('Both fields are optional. If none are given the username will be displayed instead')
}
@@ -71,11 +92,14 @@ class InternalRecipeForm(forms.ModelForm):
'waiting_time': _('Waiting time (cooking/baking) in minutes'),
}
widgets = {'keywords': MultiSelectWidget}
help_texts = {
'instructions': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
}
class ShoppingForm(forms.Form):
recipe = forms.ModelMultipleChoiceField(
queryset=Recipe.objects.all(),
queryset=Recipe.objects.filter(internal=True).all(),
widget=MultiSelectWidget
)
markdown_format = forms.BooleanField(
@@ -85,6 +109,25 @@ class ShoppingForm(forms.Form):
)
class ExportForm(forms.Form):
recipe = forms.ModelChoiceField(
queryset=Recipe.objects.filter(internal=True).all(),
widget=SelectWidget
)
image = forms.BooleanField(
help_text=_('Export Base64 encoded image?'),
required=False
)
download = forms.BooleanField(
help_text=_('Download export directly or show on page?'),
required=False
)
class ImportForm(forms.Form):
recipe = forms.CharField(widget=forms.Textarea, help_text=_('Simply paste a JSON export into this textarea and click import.'))
class UnitMergeForm(forms.Form):
prefix = 'unit'
@@ -141,6 +184,13 @@ class KeywordForm(forms.ModelForm):
widgets = {'icon': EmojiPickerTextInput}
class IngredientForm(forms.ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'recipe')
widgets = {'recipe': SelectWidget}
class StorageForm(forms.ModelForm):
username = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password'}), required=False)
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}),
@@ -197,12 +247,33 @@ class ImportRecipeForm(forms.ModelForm):
class RecipeBookForm(forms.ModelForm):
class Meta:
model = RecipeBook
fields = ('name',)
fields = ('name', 'icon', 'description', 'shared')
widgets = {'icon': EmojiPickerTextInput, 'shared': MultiSelectWidget}
class MealPlanForm(forms.ModelForm):
def clean(self):
cleaned_data = super(MealPlanForm, self).clean()
if cleaned_data['title'] == '' and cleaned_data['recipe'] is None:
raise forms.ValidationError(_('You must provide at least a recipe or a title.'))
return cleaned_data
class Meta:
model = MealPlan
fields = ('recipe', 'meal', 'note', 'date')
fields = ('recipe', 'title', 'meal', 'note', 'date', 'shared')
widgets = {'recipe': SelectWidget, 'date': DateWidget}
help_texts = {
'shared': _('You can list default users to share recipes with in the settings.'),
'note': _('You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>')
}
widgets = {'recipe': SelectWidget, 'date': DateWidget, 'shared': MultiSelectWidget}
class SuperUserForm(forms.Form):
name = forms.CharField()
password = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))
password_confirm = forms.CharField(widget=forms.TextInput(attrs={'autocomplete': 'new-password', 'type': 'password'}))

View File

@@ -0,0 +1,24 @@
import markdown
from markdown.treeprocessors import Treeprocessor
class StyleTreeprocessor(Treeprocessor):
def run_processor(self, node):
for child in node:
if child.tag == "table":
child.set("class", "table table-bordered")
if child.tag == "img":
child.set("class", "img-fluid")
self.run_processor(child)
return node
def run(self, root):
self.run_processor(root)
return root
class MarkdownFormatExtension(markdown.Extension):
def extendMarkdown(self, md, md_globals):
md.treeprocessors.register(StyleTreeprocessor(), 'StyleTreeprocessor', 10)

View File

@@ -0,0 +1,81 @@
"""A more liberal autolinker
Inspired by Django's urlize function.
Positive examples:
>>> import markdown
>>> md = markdown.Markdown(extensions=['urlize'])
>>> md.convert('http://example.com/')
u'<p><a href="http://example.com/">http://example.com/</a></p>'
>>> md.convert('go to http://example.com')
u'<p>go to <a href="http://example.com">http://example.com</a></p>'
>>> md.convert('example.com')
u'<p><a href="http://example.com">example.com</a></p>'
>>> md.convert('example.net')
u'<p><a href="http://example.net">example.net</a></p>'
>>> md.convert('www.example.us')
u'<p><a href="http://www.example.us">www.example.us</a></p>'
>>> md.convert('(www.example.us/path/?name=val)')
u'<p>(<a href="http://www.example.us/path/?name=val">www.example.us/path/?name=val</a>)</p>'
>>> md.convert('go to <http://example.com> now!')
u'<p>go to <a href="http://example.com">http://example.com</a> now!</p>'
Negative examples:
>>> md.convert('del.icio.us')
u'<p>del.icio.us</p>'
"""
import markdown
# Global Vars
URLIZE_RE = '(%s)' % '|'.join([
r'<(?:f|ht)tps?://[^>]*>',
r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
r'\bwww\.[^)<>\s]+[^.,)<>\s]',
r'[^(<\s]+\.(?:com|net|org)\b',
])
class UrlizePattern(markdown.inlinepatterns.Pattern):
""" Return a link Element given an autolink (`http://example/com`). """
def handleMatch(self, m):
url = m.group(2)
if url.startswith('<'):
url = url[1:-1]
text = url
if not url.split('://')[0] in ('http','https','ftp'):
if '@' in url and not '/' in url:
url = 'mailto:' + url
else:
url = 'http://' + url
el = markdown.util.etree.Element("a")
el.set('href', url)
el.text = markdown.util.AtomicString(text)
return el
class UrlizeExtension(markdown.Extension):
""" Urlize Extension for Python-Markdown. """
def extendMarkdown(self, md, md_globals):
""" Replace autolink with UrlizePattern """
md.inlinePatterns['autolink'] = UrlizePattern(URLIZE_RE, md)
def makeExtension(*args, **kwargs):
return UrlizeExtension(*args, **kwargs)
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@@ -0,0 +1,68 @@
"""
Source: https://djangosnippets.org/snippets/1703/
"""
from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test
from django.utils.translation import gettext as _
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy, reverse
def get_allowed_groups(groups_required):
groups_allowed = tuple(groups_required)
if 'guest' in groups_required:
groups_allowed = groups_allowed + ('user', 'admin')
if 'user' in groups_required:
groups_allowed = groups_allowed + ('admin',)
return groups_allowed
def group_required(*groups_required):
"""Requires user membership in at least one of the groups passed in."""
def in_groups(u):
groups_allowed = get_allowed_groups(groups_required)
if u.is_authenticated:
if u.is_superuser | bool(u.groups.filter(name__in=groups_allowed)):
return True
return False
return user_passes_test(in_groups, login_url='index')
class GroupRequiredMixin(object):
"""
groups_required - list of strings, required param
"""
groups_required = None
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
return HttpResponseRedirect(reverse_lazy('login'))
else:
if not request.user.is_superuser:
group_allowed = get_allowed_groups(self.groups_required)
user_groups = []
for group in request.user.groups.values_list('name', flat=True):
user_groups.append(group)
if len(set(user_groups).intersection(group_allowed)) <= 0:
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index'))
return super(GroupRequiredMixin, self).dispatch(request, *args, **kwargs)
class OwnerRequiredMixin(object):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
return HttpResponseRedirect(reverse_lazy('login'))
else:
obj = self.get_object()
if not (obj.created_by == request.user or request.user.is_superuser):
messages.add_message(request, messages.ERROR, _('You cannot interact with this object as its not owned by you!'))
return HttpResponseRedirect(reverse('index'))
return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs)

File diff suppressed because it is too large Load Diff

Binary file not shown.

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,23 @@
# Generated by Django 3.0.2 on 2020-02-19 15:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0025_userpreference_nav_color'),
]
operations = [
migrations.AddField(
model_name='recipe',
name='cors_link',
field=models.CharField(blank=True, max_length=1024, null=True),
),
migrations.AlterField(
model_name='recipe',
name='link',
field=models.CharField(blank=True, max_length=512, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.0.4 on 2020-03-17 17:31
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0026_auto_20200219_1605'),
]
operations = [
migrations.AddField(
model_name='ingredient',
name='recipe',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='cookbook.Recipe'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.0.4 on 2020-03-17 18:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0027_ingredient_recipe'),
]
operations = [
migrations.AlterField(
model_name='recipeingredient',
name='ingredient',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Ingredient'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.0.4 on 2020-03-17 18:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0028_auto_20200317_1901'),
]
operations = [
migrations.AlterField(
model_name='recipeingredient',
name='unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.Unit'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2020-03-17 18:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0029_auto_20200317_1901'),
]
operations = [
migrations.AddField(
model_name='recipeingredient',
name='note',
field=models.CharField(blank=True, max_length=64, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2020-04-07 16:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0030_recipeingredient_note'),
]
operations = [
migrations.AlterField(
model_name='keyword',
name='icon',
field=models.CharField(blank=True, max_length=16, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2020-04-13 20:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0031_auto_20200407_1841'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='default_unit',
field=models.CharField(default='g', max_length=32),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2020-04-13 20:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0032_userpreference_default_unit'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='default_page',
field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan')], default='SEARCH', max_length=64),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.0.5 on 2020-04-26 14:14
from django.db import migrations
def apply_migration(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.bulk_create([
Group(name=u'guest'),
Group(name=u'user'),
Group(name=u'admin'),
])
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0033_userpreference_default_page'),
]
operations = [
migrations.RunPython(apply_migration)
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 3.0.5 on 2020-04-27 14:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0034_auto_20200426_1614'),
]
operations = [
migrations.RenameField(
model_name='mealplan',
old_name='user',
new_name='created_by',
),
migrations.RenameField(
model_name='recipebook',
old_name='user',
new_name='created_by',
),
migrations.AlterField(
model_name='userpreference',
name='default_page',
field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan'), ('BOOKS', 'Books')], default='SEARCH', max_length=64),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.0.5 on 2020-04-27 16:00
from django.db import migrations
def apply_migration(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
User = apps.get_model('auth', 'User')
for u in User.objects.all():
if u.groups.count() < 1:
u.groups.add(Group.objects.get(name='admin'))
u.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0035_auto_20200427_1637'),
]
operations = [
migrations.RunPython(apply_migration)
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.5 on 2020-05-02 10:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0036_auto_20200427_1800'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='search_style',
field=models.CharField(choices=[('SMALL', 'Small'), ('LARGE', 'Large')], default='LARGE', max_length=64),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 3.0.5 on 2020-05-02 10:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0037_userpreference_search_style'),
]
operations = [
migrations.AddField(
model_name='recipebook',
name='description',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='recipebook',
name='icon',
field=models.CharField(blank=True, max_length=16, null=True),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.0.5 on 2020-05-02 12:04
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0038_auto_20200502_1259'),
]
operations = [
migrations.AddField(
model_name='recipebook',
name='shared',
field=models.ManyToManyField(blank=True, related_name='shared_with', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.0.5 on 2020-05-02 12:33
import annoying.fields
from django.conf import settings
from django.db import migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0039_recipebook_shared'),
]
operations = [
migrations.AlterField(
model_name='userpreference',
name='user',
field=annoying.fields.AutoOneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.0.5 on 2020-05-02 12:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0040_auto_20200502_1433'),
]
operations = [
migrations.AddField(
model_name='mealplan',
name='title',
field=models.CharField(blank=True, default='', max_length=64),
),
migrations.AlterField(
model_name='mealplan',
name='recipe',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe'),
),
]

View File

@@ -0,0 +1,27 @@
# Generated by Django 3.0.5 on 2020-05-02 14:47
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', '0041_auto_20200502_1446'),
]
operations = [
migrations.CreateModel(
name='CookLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('rating', models.IntegerField(null=True)),
('servings', models.IntegerField(default=0)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 3.0.5 on 2020-05-07 21:02
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0042_cooklog'),
]
operations = [
migrations.AddField(
model_name='mealplan',
name='shared',
field=models.ManyToManyField(blank=True, related_name='plan_share', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='userpreference',
name='plan_share',
field=models.ManyToManyField(blank=True, related_name='plan_share_default', to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 3.0.5 on 2020-05-11 10:21
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', '0043_auto_20200507_2302'),
]
operations = [
migrations.CreateModel(
name='ViewLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.0.5 on 2020-06-02 08:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0044_viewlog'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='show_recent',
field=models.BooleanField(default=True),
),
]

View File

@@ -1,8 +1,26 @@
import re
from annoying.fields import AutoOneToOneField
from django.contrib import auth
from django.contrib.auth.models import User
from django.utils.translation import gettext as _
from django.db import models
def get_user_name(self):
if not (name := f"{self.first_name} {self.last_name}") == " ":
return name
else:
return self.username
auth.models.User.add_to_class('get_user_name', get_user_name)
def get_model_name(model):
return ('_'.join(re.findall('[A-Z][^A-Z]*', model.__name__))).lower()
class UserPreference(models.Model):
# Themes
BOOTSTRAP = 'BOOTSTRAP'
@@ -24,9 +42,30 @@ class UserPreference(models.Model):
COLORS = ((PRIMARY, 'Primary'), (SECONDARY, 'Secondary'), (SUCCESS, 'Success'), (INFO, 'Info'), (WARNING, 'Warning'), (DANGER, 'Danger'), (LIGHT, 'Light'), (DARK, 'Dark'))
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
# Default Page
SEARCH = 'SEARCH'
PLAN = 'PLAN'
BOOKS = 'BOOKS'
PAGES = ((SEARCH, _('Search')), (PLAN, _('Meal-Plan')), (BOOKS, _('Books')),)
# Search Style
SMALL = 'SMALL'
LARGE = 'LARGE'
SEARCH_STYLE = ((SMALL, _('Small')), (LARGE, _('Large')),)
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY)
nav_color = models.CharField(choices=COLORS, max_length=128, default=PRIMARY)
default_unit = models.CharField(max_length=32, default='g')
default_page = models.CharField(choices=PAGES, max_length=64, default=SEARCH)
search_style = models.CharField(choices=SEARCH_STYLE, max_length=64, default=LARGE)
show_recent = models.BooleanField(default=True)
plan_share = models.ManyToManyField(User, blank=True, related_name='plan_share_default')
def __str__(self):
return str(self.user)
class Storage(models.Model):
@@ -64,17 +103,23 @@ class SyncLog(models.Model):
msg = models.TextField(default="")
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.created_at}:{self.sync} - {self.status}"
class Keyword(models.Model):
name = models.CharField(max_length=64, unique=True)
icon = models.CharField(max_length=1, blank=True, null=True)
icon = models.CharField(max_length=16, blank=True, null=True)
description = models.TextField(default="", blank=True)
created_by = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "{0} {1}".format(self.icon, self.name)
if self.icon:
return f"{self.icon} {self.name}"
else:
return f"{self.name}"
class Recipe(models.Model):
@@ -84,7 +129,8 @@ class Recipe(models.Model):
storage = models.ForeignKey(Storage, on_delete=models.PROTECT, blank=True, null=True)
file_uid = models.CharField(max_length=256, default="")
file_path = models.CharField(max_length=512, default="")
link = models.CharField(max_length=512, default="")
link = models.CharField(max_length=512, null=True, blank=True)
cors_link = models.CharField(max_length=1024, null=True, blank=True)
keywords = models.ManyToManyField(Keyword, blank=True)
working_time = models.IntegerField(default=0)
waiting_time = models.IntegerField(default=0)
@@ -96,10 +142,6 @@ class Recipe(models.Model):
def __str__(self):
return self.name
@property
def all_tags(self):
return ' '.join([(x.icon + x.name) for x in self.keywords.all()])
class Unit(models.Model):
name = models.CharField(unique=True, max_length=128)
@@ -111,16 +153,18 @@ class Unit(models.Model):
class Ingredient(models.Model):
name = models.CharField(unique=True, max_length=128)
recipe = models.ForeignKey(Recipe, null=True, blank=True, on_delete=models.SET_NULL)
def __str__(self):
return self.name
class RecipeIngredient(models.Model):
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT, null=True)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
unit = models.ForeignKey(Unit, on_delete=models.PROTECT, null=True)
ingredient = models.ForeignKey(Ingredient, on_delete=models.PROTECT)
unit = models.ForeignKey(Unit, on_delete=models.PROTECT)
amount = models.DecimalField(default=0, decimal_places=2, max_digits=16)
note = models.CharField(max_length=64, null=True, blank=True)
def __str__(self):
return str(self.amount) + ' ' + str(self.unit) + ' ' + str(self.ingredient)
@@ -150,7 +194,10 @@ class RecipeImport(models.Model):
class RecipeBook(models.Model):
name = models.CharField(max_length=128)
user = models.ForeignKey(User, on_delete=models.CASCADE)
description = models.TextField(blank=True)
icon = models.CharField(max_length=16, blank=True, null=True)
shared = models.ManyToManyField(User, blank=True, related_name='shared_with')
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.name
@@ -171,11 +218,39 @@ class MealPlan(models.Model):
OTHER = 'OTHER'
MEAL_TYPES = ((BREAKFAST, _('Breakfast')), (LUNCH, _('Lunch')), (DINNER, _('Dinner')), (OTHER, _('Other')),)
user = models.ForeignKey(User, on_delete=models.CASCADE)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, blank=True, null=True)
title = models.CharField(max_length=64, blank=True, default='')
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
shared = models.ManyToManyField(User, blank=True, related_name='plan_share')
meal = models.CharField(choices=MEAL_TYPES, max_length=128, default=BREAKFAST)
note = models.TextField(blank=True)
date = models.DateField()
def get_label(self):
if self.title:
return self.title
return str(self.recipe)
def get_meal_name(self):
meals = dict(self.MEAL_TYPES)
return meals.get(self.meal)
class CookLog(models.Model):
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)
rating = models.IntegerField(null=True)
servings = models.IntegerField(default=0)
def __str__(self):
return self.meal + ' (' + str(self.date) + ') ' + str(self.recipe)
return self.recipe.name
class ViewLog(models.Model):
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)
def __str__(self):
return self.recipe.name

View File

@@ -1,3 +1,5 @@
import base64
import io
import os
from datetime import datetime
@@ -34,7 +36,7 @@ class Dropbox(Provider):
import_count = 0
for recipe in recipes['entries']: # TODO check if has_more is set and import that as well
path = recipe['path_lower']
if not Recipe.objects.filter(file_path=path).exists() and not RecipeImport.objects.filter(
if not Recipe.objects.filter(file_path__iexact=path).exists() and not RecipeImport.objects.filter(
file_path=path).exists():
name = os.path.splitext(recipe['name'])[0]
new_recipe = RecipeImport(name=name, file_path=path, storage=monitor.storage, file_uid=recipe['id'])
@@ -88,6 +90,16 @@ class Dropbox(Provider):
response = Dropbox.create_share_link(recipe)
return response['url']
@staticmethod
def get_file(recipe):
if not recipe.link:
recipe.link = Dropbox.get_share_link(recipe)
recipe.save()
response = requests.get(recipe.link.replace('www.dropbox.', 'dl.dropboxusercontent.'))
return io.BytesIO(response.content)
@staticmethod
def rename_file(recipe, new_name):
url = "https://api.dropboxapi.com/2/files/move_v2"

View File

@@ -1,8 +1,11 @@
import base64
import io
import os
import tempfile
from datetime import datetime
import webdav3.client as wc
import requests
from io import BytesIO
from requests.auth import HTTPBasicAuth
from cookbook.models import Recipe, RecipeImport, SyncLog
@@ -14,9 +17,10 @@ class Nextcloud(Provider):
@staticmethod
def get_client(storage):
options = {
'webdav_hostname': storage.url + '/remote.php/dav/files/' + storage.username,
'webdav_hostname': storage.url,
'webdav_login': storage.username,
'webdav_password': storage.password
'webdav_password': storage.password,
'webdav_root': '/remote.php/dav/files/' + storage.username
}
return wc.Client(options)
@@ -30,7 +34,7 @@ class Nextcloud(Provider):
import_count = 0
for file in files:
path = monitor.path + '/' + file
if not Recipe.objects.filter(file_path=path).exists() and not RecipeImport.objects.filter(
if not Recipe.objects.filter(file_path__iexact=path).exists() and not RecipeImport.objects.filter(
file_path=path).exists():
name = os.path.splitext(file)[0]
new_recipe = RecipeImport(name=name, file_path=path, storage=monitor.storage)
@@ -81,6 +85,19 @@ class Nextcloud(Provider):
return Nextcloud.create_share_link(recipe)
@staticmethod
def get_file(recipe):
client = Nextcloud.get_client(recipe.storage)
tmp_file_path = tempfile.gettempdir() + '/' + recipe.name + '.pdf'
client.download_file(remote_path=recipe.file_path, local_path=tmp_file_path)
file = io.BytesIO(open(tmp_file_path, 'rb').read())
os.remove(tmp_file_path)
return file
@staticmethod
def rename_file(recipe, new_name):
client = Nextcloud.get_client(recipe.storage)

View File

@@ -11,10 +11,14 @@ class Provider:
def get_share_link(recipe):
raise Exception('Method not implemented in storage provider')
@staticmethod
def get_file(recipe):
raise Exception('Method not implemented in storage provider')
@staticmethod
def rename_file(recipe, new_name):
raise Exception('Method not implemented in storage provider')
@staticmethod
def delete_file(recipe, new_name):
def delete_file(recipe):
raise Exception('Method not implemented in storage provider')

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,721 @@
/*!
* Select2 Bootstrap Theme v0.1.0-beta.10 (https://select2.github.io/select2-bootstrap-theme)
* Copyright 2015-2017 Florian Kissling and contributors (https://github.com/select2/select2-bootstrap-theme/graphs/contributors)
* Licensed under MIT (https://github.com/select2/select2-bootstrap-theme/blob/master/LICENSE)
*/
.select2-container--bootstrap {
display: block;
/*------------------------------------* #COMMON STYLES
\*------------------------------------*/
/**
* Search field in the Select2 dropdown.
*/
/**
* No outline for all search fields - in the dropdown
* and inline in multi Select2s.
*/
/**
* Adjust Select2's choices hover and selected styles to match
* Bootstrap 3's default dropdown styles.
*
* @see http://getbootstrap.com/components/#dropdowns
*/
/**
* Clear the selection.
*/
/**
* Address disabled Select2 styles.
*
* @see https://select2.github.io/examples.html#disabled
* @see http://getbootstrap.com/css/#forms-control-disabled
*/
/*------------------------------------* #DROPDOWN
\*------------------------------------*/
/**
* Dropdown border color and box-shadow.
*/
/**
* Limit the dropdown height.
*/
/*------------------------------------* #SINGLE SELECT2
\*------------------------------------*/
/*------------------------------------* #MULTIPLE SELECT2
\*------------------------------------*/
/**
* Address Bootstrap control sizing classes
*
* 1. Reset Bootstrap defaults.
* 2. Adjust the dropdown arrow button icon position.
*
* @see http://getbootstrap.com/css/#forms-control-sizes
*/
/* 1 */
/*------------------------------------* #RTL SUPPORT
\*------------------------------------*/
}
.select2-container--bootstrap .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
color: #555555;
font-size: 14px;
outline: 0;
}
.select2-container--bootstrap .select2-selection.form-control {
border-radius: 4px;
}
.select2-container--bootstrap .select2-search--dropdown .select2-search__field {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
color: #555555;
font-size: 14px;
}
.select2-container--bootstrap .select2-search__field {
outline: 0;
/* Firefox 18- */
/**
* Firefox 19+
*
* @see http://stackoverflow.com/questions/24236240/color-for-styled-placeholder-text-is-muted-in-firefox
*/
}
.select2-container--bootstrap .select2-search__field::-webkit-input-placeholder {
color: #999;
}
.select2-container--bootstrap .select2-search__field:-moz-placeholder {
color: #999;
}
.select2-container--bootstrap .select2-search__field::-moz-placeholder {
color: #999;
opacity: 1;
}
.select2-container--bootstrap .select2-search__field:-ms-input-placeholder {
color: #999;
}
.select2-container--bootstrap .select2-results__option {
padding: 6px 12px;
/**
* Disabled results.
*
* @see https://select2.github.io/examples.html#disabled-results
*/
/**
* Hover state.
*/
/**
* Selected state.
*/
}
.select2-container--bootstrap .select2-results__option[role=group] {
padding: 0;
}
.select2-container--bootstrap .select2-results__option[aria-disabled=true] {
color: #777777;
cursor: not-allowed;
}
.select2-container--bootstrap .select2-results__option[aria-selected=true] {
background-color: #f5f5f5;
color: #262626;
}
.select2-container--bootstrap .select2-results__option--highlighted[aria-selected] {
background-color: #337ab7;
color: #fff;
}
.select2-container--bootstrap .select2-results__option .select2-results__option {
padding: 6px 12px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option {
margin-left: -12px;
padding-left: 24px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -24px;
padding-left: 36px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -36px;
padding-left: 48px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -48px;
padding-left: 60px;
}
.select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -60px;
padding-left: 72px;
}
.select2-container--bootstrap .select2-results__group {
color: #777777;
display: block;
padding: 6px 12px;
font-size: 12px;
line-height: 1.42857143;
white-space: nowrap;
}
.select2-container--bootstrap.select2-container--focus .select2-selection, .select2-container--bootstrap.select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s;
border-color: #66afe9;
}
.select2-container--bootstrap.select2-container--open {
/**
* Make the dropdown arrow point up while the dropdown is visible.
*/
/**
* Handle border radii of the container when the dropdown is showing.
*/
}
.select2-container--bootstrap.select2-container--open .select2-selection .select2-selection__arrow b {
border-color: transparent transparent #999 transparent;
border-width: 0 4px 4px 4px;
}
.select2-container--bootstrap.select2-container--open.select2-container--below .select2-selection {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-color: transparent;
}
.select2-container--bootstrap.select2-container--open.select2-container--above .select2-selection {
border-top-right-radius: 0;
border-top-left-radius: 0;
border-top-color: transparent;
}
.select2-container--bootstrap .select2-selection__clear {
color: #999;
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px;
}
.select2-container--bootstrap .select2-selection__clear:hover {
color: #333;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection {
border-color: #ccc;
-webkit-box-shadow: none;
box-shadow: none;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection,
.select2-container--bootstrap.select2-container--disabled .select2-search__field {
cursor: not-allowed;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection,
.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice {
background-color: #eeeeee;
}
.select2-container--bootstrap.select2-container--disabled .select2-selection__clear,
.select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove {
display: none;
}
.select2-container--bootstrap .select2-dropdown {
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
border-color: #66afe9;
overflow-x: hidden;
margin-top: -1px;
}
.select2-container--bootstrap .select2-dropdown--above {
-webkit-box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.175);
box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.175);
margin-top: 1px;
}
.select2-container--bootstrap .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
}
.select2-container--bootstrap .select2-selection--single {
height: 34px;
line-height: 1.42857143;
padding: 6px 24px 6px 12px;
/**
* Adjust the single Select2's dropdown arrow button appearance.
*/
}
.select2-container--bootstrap .select2-selection--single .select2-selection__arrow {
position: absolute;
bottom: 0;
right: 12px;
top: 0;
width: 4px;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
border-color: #999 transparent transparent transparent;
border-style: solid;
border-width: 4px 4px 0 4px;
height: 0;
left: 0;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__rendered {
color: #555555;
padding: 0;
}
.select2-container--bootstrap .select2-selection--single .select2-selection__placeholder {
color: #999;
}
.select2-container--bootstrap .select2-selection--multiple {
min-height: 34px;
padding: 0;
height: auto;
/**
* Make Multi Select2's choices match Bootstrap 3's default button styles.
*/
/**
* Minus 2px borders.
*/
/**
* Clear the selection.
*/
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__rendered {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: block;
line-height: 1.42857143;
list-style: none;
margin: 0;
overflow: hidden;
padding: 0;
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__placeholder {
color: #999;
float: left;
margin-top: 5px;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
color: #555555;
background: #fff;
border: 1px solid #ccc;
border-radius: 4px;
cursor: default;
float: left;
margin: 5px 0 0 6px;
padding: 0 6px;
}
.select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
background: transparent;
padding: 0 12px;
height: 32px;
line-height: 1.42857143;
margin-top: 0;
min-width: 5em;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 3px;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333;
}
.select2-container--bootstrap .select2-selection--multiple .select2-selection__clear {
margin-top: 6px;
}
.select2-container--bootstrap .select2-selection--single.input-sm,
.input-group-sm .select2-container--bootstrap .select2-selection--single,
.form-group-sm .select2-container--bootstrap .select2-selection--single {
border-radius: 3px;
font-size: 12px;
height: 30px;
line-height: 1.5;
padding: 5px 22px 5px 10px;
/* 2 */
}
.select2-container--bootstrap .select2-selection--single.input-sm .select2-selection__arrow b,
.input-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,
.form-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
margin-left: -5px;
}
.select2-container--bootstrap .select2-selection--multiple.input-sm,
.input-group-sm .select2-container--bootstrap .select2-selection--multiple,
.form-group-sm .select2-container--bootstrap .select2-selection--multiple {
min-height: 30px;
border-radius: 3px;
}
.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__choice,
.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,
.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
font-size: 12px;
line-height: 1.5;
margin: 4px 0 0 5px;
padding: 0 5px;
}
.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-search--inline .select2-search__field,
.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,
.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
padding: 0 10px;
font-size: 12px;
height: 28px;
line-height: 1.5;
}
.select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__clear,
.input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,
.form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear {
margin-top: 5px;
}
.select2-container--bootstrap .select2-selection--single.input-lg,
.input-group-lg .select2-container--bootstrap .select2-selection--single,
.form-group-lg .select2-container--bootstrap .select2-selection--single {
border-radius: 6px;
font-size: 18px;
height: 46px;
line-height: 1.3333333;
padding: 10px 31px 10px 16px;
/* 1 */
}
.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow,
.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow,
.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow {
width: 5px;
}
.select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow b,
.input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b,
.form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b {
border-width: 5px 5px 0 5px;
margin-left: -5px;
margin-left: -10px;
margin-top: -2.5px;
}
.select2-container--bootstrap .select2-selection--multiple.input-lg,
.input-group-lg .select2-container--bootstrap .select2-selection--multiple,
.form-group-lg .select2-container--bootstrap .select2-selection--multiple {
min-height: 46px;
border-radius: 6px;
}
.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__choice,
.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice,
.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice {
font-size: 18px;
line-height: 1.3333333;
border-radius: 4px;
margin: 9px 0 0 8px;
padding: 0 10px;
}
.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-search--inline .select2-search__field,
.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field,
.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field {
padding: 0 16px;
font-size: 18px;
height: 44px;
line-height: 1.3333333;
}
.select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__clear,
.input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear,
.form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear {
margin-top: 10px;
}
.select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single {
/**
* Make the dropdown arrow point up while the dropdown is visible.
*/
}
.select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #999 transparent;
border-width: 0 5px 5px 5px;
}
.input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single {
/**
* Make the dropdown arrow point up while the dropdown is visible.
*/
}
.input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #999 transparent;
border-width: 0 5px 5px 5px;
}
.select2-container--bootstrap[dir="rtl"] {
/**
* Single Select2
*
* 1. Makes sure that .select2-selection__placeholder is positioned
* correctly.
*/
/**
* Multiple Select2
*/
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single {
padding-left: 24px;
padding-right: 12px;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 0;
padding-left: 0;
text-align: right;
/* 1 */
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 12px;
right: auto;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow b {
margin-left: 0;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice,
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 0;
margin-right: 6px;
}
.select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
/*------------------------------------* #ADDITIONAL GOODIES
\*------------------------------------*/
/**
* Address Bootstrap's validation states
*
* If a Select2 widget parent has one of Bootstrap's validation state modifier
* classes, adjust Select2's border colors and focus states accordingly.
* You may apply said classes to the Select2 dropdown (body > .select2-container)
* via JavaScript match Bootstraps' to make its styles match.
*
* @see http://getbootstrap.com/css/#forms-control-validation
*/
.has-warning .select2-dropdown,
.has-warning .select2-selection {
border-color: #8a6d3b;
}
.has-warning .select2-container--focus .select2-selection,
.has-warning .select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
border-color: #66512c;
}
.has-warning.select2-drop-active {
border-color: #66512c;
}
.has-warning.select2-drop-active.select2-drop.select2-drop-above {
border-top-color: #66512c;
}
.has-error .select2-dropdown,
.has-error .select2-selection {
border-color: #a94442;
}
.has-error .select2-container--focus .select2-selection,
.has-error .select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
border-color: #843534;
}
.has-error.select2-drop-active {
border-color: #843534;
}
.has-error.select2-drop-active.select2-drop.select2-drop-above {
border-top-color: #843534;
}
.has-success .select2-dropdown,
.has-success .select2-selection {
border-color: #3c763d;
}
.has-success .select2-container--focus .select2-selection,
.has-success .select2-container--open .select2-selection {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
border-color: #2b542c;
}
.has-success.select2-drop-active {
border-color: #2b542c;
}
.has-success.select2-drop-active.select2-drop.select2-drop-above {
border-top-color: #2b542c;
}
/**
* Select2 widgets in Bootstrap Input Groups
*
* @see http://getbootstrap.com/components/#input-groups
* @see https://github.com/twbs/bootstrap/blob/master/less/input-groups.less
*/
/**
* Reset rounded corners
*/
.input-group > .select2-hidden-accessible:first-child + .select2-container--bootstrap > .selection > .select2-selection,
.input-group > .select2-hidden-accessible:first-child + .select2-container--bootstrap > .selection > .select2-selection.form-control {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
.input-group > .select2-hidden-accessible:not(:first-child) + .select2-container--bootstrap:not(:last-child) > .selection > .select2-selection,
.input-group > .select2-hidden-accessible:not(:first-child) + .select2-container--bootstrap:not(:last-child) > .selection > .select2-selection.form-control {
border-radius: 0;
}
.input-group > .select2-hidden-accessible:not(:first-child):not(:last-child) + .select2-container--bootstrap:last-child > .selection > .select2-selection,
.input-group > .select2-hidden-accessible:not(:first-child):not(:last-child) + .select2-container--bootstrap:last-child > .selection > .select2-selection.form-control {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.input-group > .select2-container--bootstrap {
display: table;
table-layout: fixed;
position: relative;
z-index: 2;
width: 100%;
margin-bottom: 0;
/**
* Adjust z-index like Bootstrap does to show the focus-box-shadow
* above appended buttons in .input-group and .form-group.
*/
/**
* Adjust alignment of Bootstrap buttons in Bootstrap Input Groups to address
* Multi Select2's height which - depending on how many elements have been selected -
* may grow taller than its initial size.
*
* @see http://getbootstrap.com/components/#input-groups
*/
}
.input-group > .select2-container--bootstrap > .selection > .select2-selection.form-control {
float: none;
}
.input-group > .select2-container--bootstrap.select2-container--open, .input-group > .select2-container--bootstrap.select2-container--focus {
z-index: 3;
}
.input-group > .select2-container--bootstrap,
.input-group > .select2-container--bootstrap .input-group-btn,
.input-group > .select2-container--bootstrap .input-group-btn .btn {
vertical-align: top;
}
/**
* Temporary fix for https://github.com/select2/select2-bootstrap-theme/issues/9
*
* Provides `!important` for certain properties of the class applied to the
* original `<select>` element to hide it.
*
* @see https://github.com/select2/select2/pull/3301
* @see https://github.com/fk/select2/commit/31830c7b32cb3d8e1b12d5b434dee40a6e753ada
*/
.form-control.select2-hidden-accessible {
position: absolute !important;
width: 1px !important;
}
/**
* Display override for inline forms
*/
@media (min-width: 768px) {
.form-inline .select2-container--bootstrap {
display: inline-block;
}
}

1
cookbook/static/css/select2.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
/* css classes needed to render markdown blockquotes */
blockquote {
background: #f9f9f9;
border-left: 4px solid #ccc;
margin: 1.5em 10px;
padding: .5em 10px;
quotes: none;
}
blockquote:before {
color: #ccc;
content: open-quote;
font-size: 4em;
line-height: .1em;
margin-right: .25em;
vertical-align: -.4em;
}
blockquote p {
display: inline;
}

7
cookbook/static/js/bootstrap.min.js vendored Normal file

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

5
cookbook/static/js/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
cookbook/static/js/select2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 40 40">
<path
d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
id="path4"
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
height="40"
width="40"
viewBox="0 0 40 40">
<rect
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
width="33.76017"
height="33.76017"
x="3.119915"
y="3.119915" />
<path
d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
</svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 40 40">
<g
transform="translate(0,-60)"
id="layer1">
<rect
width="36.460953"
height="34.805603"
x="1.7695236"
y="62.597198"
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
<g
transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
<path
d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
style="fill:#000000;fill-opacity:1;stroke:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 64 64">
<path
d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 64 64">
<path
d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
id="path604"
style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 64 64">
<path
d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
id="path2985"
style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 40 40">
</svg>

After

Width:  |  Height:  |  Size: 158 B

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 40 40">
<rect
width="36.075428"
height="31.096582"
x="1.962286"
y="4.4517088"
id="rect4"
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
<rect
width="27.96859"
height="1.5012145"
x="6.0157046"
y="10.285"
id="rect6"
style="fill:#000000;fill-opacity:1;stroke:none" />
<rect
width="27.96859"
height="0.85783684"
x="6.0157056"
y="23.21689"
id="rect8"
style="fill:#000000;fill-opacity:1;stroke:none" />
<rect
width="27.96859"
height="0.85783684"
x="5.8130345"
y="28.964394"
id="rect10"
style="fill:#000000;fill-opacity:1;stroke:none" />
<rect
width="27.96859"
height="0.85783684"
x="6.0157046"
y="17.426493"
id="rect12"
style="fill:#000000;fill-opacity:1;stroke:none" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 40 40">
<rect
width="33.76017"
height="33.76017"
x="3.119915"
y="3.119915"
style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

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