diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index fc62d7417..3f361af0c 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -4,7 +4,8 @@ from datetime import date, timedelta from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector, TrigramSimilarity from django.core.cache import caches -from django.db.models import Avg, Case, Count, F, Func, Max, OuterRef, Q, Subquery, Sum, Value, When +from django.db.models import (Avg, Case, Count, Exists, F, Func, Max, OuterRef, Q, Subquery, Sum, + Value, When) from django.db.models.functions import Coalesce, Lower, Substr from django.utils import timezone, translation from django.utils.translation import gettext as _ @@ -525,16 +526,18 @@ class RecipeSearch(): @ staticmethod def __children_substitute_filter(shopping_users=None): children_onhand_subquery = Food.objects.filter( - path__startswith=Substr(OuterRef('path'), 1, Food.steplen*OuterRef('depth')), + path__startswith=OuterRef('path'), depth__gt=OuterRef('depth'), onhand_users__in=shopping_users - ).annotate(child_onhand=Coalesce(Func('pk', function='Count'), 0)).values('child_onhand') + ) return Food.objects.exclude( # list of foods that are onhand and children of: foods that are not onhand and are set to use children as substitutes Q(onhand_users__in=shopping_users) | Q(ignore_shopping=True, recipe__isnull=True) | Q(substitute__onhand_users__in=shopping_users) - ).exclude(depth=1, numchild=0).filter(substitute_children=True - ).annotate(child_onhand=Coalesce(Subquery(children_onhand_subquery), 0)).exclude(child_onhand=0) + ).exclude(depth=1, numchild=0 + ).filter(substitute_children=True + ).annotate(child_onhand_count=Exists(children_onhand_subquery) + ).filter(child_onhand_count=True) @ staticmethod def __sibling_substitute_filter(shopping_users=None): @@ -542,13 +545,15 @@ class RecipeSearch(): path__startswith=Substr(OuterRef('path'), 1, Food.steplen*(OuterRef('depth')-1)), depth=OuterRef('depth'), onhand_users__in=shopping_users - ).annotate(sibling_onhand=Coalesce(Func('pk', function='Count'), 0)).values('sibling_onhand') + ) return Food.objects.exclude( # list of foods that are onhand and siblings of: foods that are not onhand and are set to use siblings as substitutes Q(onhand_users__in=shopping_users) | Q(ignore_shopping=True, recipe__isnull=True) | Q(substitute__onhand_users__in=shopping_users) - ).exclude(depth=1, numchild=0).filter(substitute_siblings=True - ).annotate(sibling_onhand=Coalesce(Subquery(sibling_onhand_subquery), 0)).exclude(sibling_onhand=0) + ).exclude(depth=1, numchild=0 + ).filter(substitute_siblings=True + ).annotate(sibling_onhand=Exists(sibling_onhand_subquery) + ).filter(sibling_onhand=True) class RecipeFacet(): diff --git a/docs/system/backup.md b/docs/system/backup.md index b265eeb9c..20e865f9f 100644 --- a/docs/system/backup.md +++ b/docs/system/backup.md @@ -25,4 +25,25 @@ They can be found in the mediafiles mounted directory (depending on your install To create a backup of those files simply copy them elsewhere. Do it the other way around for restoring. -The filenames consist of `_`. In case you screw up really badly this can help restore data. \ No newline at end of file +The filenames consist of `_`. In case you screw up really badly this can help restore data. + +## Manual backup from docker build +The standard docker build of tandoor uses postgresql as the back end database. This can be backed up using a function called "dumpall". This generates a .SQL file containing a list of commands for a postgresql server to use to rebuild your database. You will also need to back up the media files separately. + +Making a full copy of the docker directory can work as a back up, but only if you know you will be using the same hardware, os, and postgresql version upon restore. If not, then the different version of postgresql won't be compatible with the existing tables. +You can back up from docker even when the tandoor container is failing, so long as the postgresql database has started successfully. + +the following commands assume that your docker-compose files are in a folder called "docker". replace "docker_db_recipes_1" with the name of your db container. The commands also assume you use a backup name of pgdump.sql. It's a good idea to include a date in this filename, so that successive backups do not get deleted. +To back up: +``` +sudo docker exec -t docker_db_recipes_1 pg_dumpall -U djangouser > pgdump.sql + +``` + +To restore: +``` +cat pgdump.sql | sudo docker exec -i docker_db_recipes_1 psql postgres -U djangouser + +``` +This connects to the postgres table instead of the actual dgangodb table, as the import function needs to delete the table, which can't be dropped off you're connected to it. + diff --git a/requirements.txt b/requirements.txt index c9a149c2f..5004d80f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ drf-writable-nested==0.6.3 bleach==5.0.1 bleach-allowlist==1.0.3 gunicorn==20.1.0 -lxml==4.9.0 +lxml==4.9.1 Markdown==3.3.7 Pillow==9.1.1 psycopg2-binary==2.9.3 diff --git a/vue/package.json b/vue/package.json index 25d228106..e51a30fdc 100644 --- a/vue/package.json +++ b/vue/package.json @@ -22,12 +22,13 @@ "html2pdf.js": "^0.10.1", "lodash": "^4.17.21", "mavon-editor": "^2.10.4", - "moment": "^2.29.1", + "moment": "^2.29.4", "prismjs": "^1.27.0", "vue": "^2.6.14", "vue-class-component": "^7.2.3", "vue-click-outside": "^1.1.0", "vue-clickaway": "^2.2.2", + "vue-clipboard2": "^0.3.3", "vue-cookies": "^1.8.1", "vue-i18n": "^8.26.8", "vue-infinite-loading": "^2.4.5", @@ -69,7 +70,8 @@ "env": { "node": true }, - "extends": [ "plugin:vue/vue3-essential", + "extends": [ + "plugin:vue/vue3-essential", "eslint:recommended", "@vue/typescript" ], diff --git a/vue/src/apps/SpaceManageView/SpaceManageView.vue b/vue/src/apps/SpaceManageView/SpaceManageView.vue index 442602dba..e98bab27d 100644 --- a/vue/src/apps/SpaceManageView/SpaceManageView.vue +++ b/vue/src/apps/SpaceManageView/SpaceManageView.vue @@ -190,6 +190,9 @@ import {ApiApiFactory} from "@/utils/openapi/api.ts" import GenericMultiselect from "@/components/GenericMultiselect"; import GenericModalForm from "@/components/Modals/GenericModalForm"; import axios from "axios"; +import VueClipboard from 'vue-clipboard2' + +Vue.use(VueClipboard) Vue.use(BootstrapVue) @@ -229,7 +232,7 @@ export default { if (link) { content = localStorage.BASE_PATH + this.resolveDjangoUrl('view_invite', inviteLink.uuid) } - navigator.clipboard.writeText(content) + this.$copyText(content) }, loadInviteLinks: function () { let apiFactory = new ApiApiFactory() diff --git a/vue/yarn.lock b/vue/yarn.lock index a50405af1..14e34b9ad 100644 --- a/vue/yarn.lock +++ b/vue/yarn.lock @@ -4043,6 +4043,15 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +clipboard@^2.0.0: + version "2.0.11" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5" + integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + clipboardy@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290" @@ -4770,6 +4779,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -6146,6 +6160,13 @@ globby@^11.0.2, globby@^11.0.3, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw== + dependencies: + delegate "^3.1.2" + got@^8.3.1: version "8.3.2" resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" @@ -7880,10 +7901,10 @@ module-alias@^2.2.2: resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0" integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q== -moment@^2.29.1: - version "2.29.3" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" - integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== +moment@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== move-concurrently@^1.0.1: version "1.0.1" @@ -9633,6 +9654,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA== + selfsigned@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.1.tgz#8b2df7fa56bf014d19b6007655fff209c0ef0a56" @@ -10567,6 +10593,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -11058,6 +11089,13 @@ vue-clickaway@^2.2.2: dependencies: loose-envify "^1.2.0" +vue-clipboard2@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/vue-clipboard2/-/vue-clipboard2-0.3.3.tgz#331fec85f9d4f175eb0d4feaef4d77338562af36" + integrity sha512-aNWXIL2DKgJyY/1OOeITwAQz1fHaCIGvUFHf9h8UcoQBG5a74MkdhS/xqoYe7DNZdQmZRL+TAdIbtUs9OyVjbw== + dependencies: + clipboard "^2.0.0" + vue-codemod@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/vue-codemod/-/vue-codemod-0.0.5.tgz#679b3a7f5b053feba1abde907fd70f961a398470"