diff --git a/cookbook/forms.py b/cookbook/forms.py index 99a9d254..8b4ca607 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -479,10 +479,11 @@ class SearchPreferenceForm(forms.ModelForm): class Meta: model = SearchPreference - fields = ('search', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext') + fields = ('search', 'lookup', 'unaccent', 'icontains', 'istartswith', 'trigram', 'fulltext') help_texts = { 'search': _('Select type method of search. Click here for full desciption of choices.'), + 'lookup': _('Use fuzzy matching on units, keywords and ingredients when editing and importing recipes.'), 'unaccent': _('Fields to search ignoring accents. Selecting this option can improve or degrade search quality depending on language'), 'icontains': _("Fields to search for partial matches. (e.g. searching for 'Pie' will return 'pie' and 'piece' and 'soapie')"), 'istartswith': _("Fields to search for beginning of word matches. (e.g. searching for 'sa' will return 'salad' and 'sandwich')"), @@ -492,6 +493,7 @@ class SearchPreferenceForm(forms.ModelForm): labels = { 'search': _('Search Method'), + 'lookup': _('Fuzzy Lookups'), 'unaccent': _('Ignore Accent'), 'icontains': _("Partial Match"), 'istartswith': _("Starts Wtih"), @@ -506,4 +508,4 @@ class SearchPreferenceForm(forms.ModelForm): 'istartswith': MultiSelectWidget, 'trigram': MultiSelectWidget, 'fulltext': MultiSelectWidget, - } + } diff --git a/cookbook/migrations/0124_build_full_text_index.py b/cookbook/migrations/0124_build_full_text_index.py index 658538fd..dc49bf69 100644 --- a/cookbook/migrations/0124_build_full_text_index.py +++ b/cookbook/migrations/0124_build_full_text_index.py @@ -95,7 +95,8 @@ class Migration(migrations.Migration): name='SearchPreference', fields=[ ('user', annoying.fields.AutoOneToOneField(on_delete=deletion.CASCADE, primary_key=True, serialize=False, to='auth.user')), - ('search', models.CharField(choices=[('plain', 'Plain'), ('phrase', 'Phrase'), ('websearch', 'Web'), ('raw', 'Raw')], default='plain', max_length=32)), + ('search', models.CharField(choices=[('plain', 'Simple'), ('phrase', 'Phrase'), ('websearch', 'Web'), ('raw', 'Raw')], default='plain', max_length=32)), + ('lookup', models.BooleanField(default=False)), ('fulltext', models.ManyToManyField(blank=True, related_name='fulltext_fields', to='cookbook.SearchFields')), ('icontains', models.ManyToManyField(blank=True, default=nameSearchField, related_name='icontains_fields', to='cookbook.SearchFields')), ('istartswith', models.ManyToManyField(blank=True, related_name='istartswith_fields', to='cookbook.SearchFields')), diff --git a/cookbook/models.py b/cookbook/models.py index 1246eeb0..e8b405c7 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -760,6 +760,7 @@ class SearchPreference(models.Model, PermissionModelMixin): user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True) search = models.CharField(choices=SEARCH_STYLE, max_length=32, default=SIMPLE) + lookup = models.BooleanField(default=False) unaccent = models.ManyToManyField(SearchFields, related_name="unaccent_fields", blank=True, default=allSearchFields) icontains = models.ManyToManyField(SearchFields, related_name="icontains_fields", blank=True, default=nameSearchField) istartswith = models.ManyToManyField(SearchFields, related_name="istartswith_fields", blank=True) diff --git a/cookbook/static/vue/js/recipe_search_view.js b/cookbook/static/vue/js/recipe_search_view.js index fa63c35f..639532a1 100644 --- a/cookbook/static/vue/js/recipe_search_view.js +++ b/cookbook/static/vue/js/recipe_search_view.js @@ -1 +1 @@ -(function(e){function t(t){for(var r,a,s=t[0],c=t[1],u=t[2],p=0,h=[];pnew Date(Date.now()-6048e5)?n("b-badge",{attrs:{pill:"",variant:"success"}},[e._v(e._s(e.$t("New")))]):e._e()]:[e._v(e._s(e.meal_plan.note))]],2)],1),void 0!==e.footer_text?n("b-card-footer",[n("i",{class:e.footer_icon}),e._v(" "+e._s(e.footer_text)+" ")]):e._e()],1)},j=[],O=n("fc0d"),v=n("81d5"),g={name:"RecipeCard",mixins:[b["b"]],components:{Keywords:v["a"],RecipeContextMenu:O["a"]},props:{recipe:Object,meal_plan:Object,footer_text:String,footer_icon:String},data:function(){return{recipe_image:""}},mounted:function(){null==this.recipe||null===this.recipe.image?this.recipe_image=window.IMAGE_PLACEHOLDER:this.recipe_image=this.recipe.image},methods:{clickUrl:function(){return null!==this.recipe?Object(b["g"])("view_recipe",this.recipe.id):Object(b["g"])("view_plan_entry",this.meal_plan.id)}}},m=g,y=n("2877"),P=Object(y["a"])(m,f,j,!1,null,"6fcb509c",null),S=P.exports,w=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("multiselect",{attrs:{options:e.objects,"close-on-select":!0,"clear-on-select":!0,"hide-selected":!0,"preserve-search":!0,placeholder:e.placeholder,label:e.label,"track-by":"id",multiple:!0,loading:e.loading},on:{"search-change":e.search,input:e.selectionChanged},model:{value:e.selected_objects,callback:function(t){e.selected_objects=t},expression:"selected_objects"}})},U=[],k=(n("ac1f"),n("841c"),n("8e5f")),R=n.n(k),L={name:"GenericMultiselect",components:{Multiselect:R.a},data:function(){return{loading:!1,objects:[],selected_objects:[]}},props:{placeholder:String,search_function:String,label:String,parent_variable:String,initial_selection:Array},watch:{initial_selection:function(e,t){this.selected_objects=e}},mounted:function(){this.search("")},methods:{search:function(e){var t=this,n=new l["a"];n[this.search_function]({query:{query:e,limit:10}}).then((function(e){t.objects=e.data}))},selectionChanged:function(){this.$emit("change",{var:this.parent_variable,val:this.selected_objects})}}},_=L,C=Object(y["a"])(_,w,U,!1,null,"20923c58",null),I=C.exports;r["default"].use(h.a),r["default"].use(a["a"]);var E={name:"RecipeSearchView",mixins:[b["b"]],components:{GenericMultiselect:I,RecipeCard:S},data:function(){return{recipes:[],meal_plans:[],last_viewed_recipes:[],search_input:"",search_internal:!1,search_keywords:[],search_foods:[],search_books:[],settings:{search_keywords_or:!0,search_foods_or:!0,search_books_or:!0,advanced_search_visible:!1,show_meal_plan:!0,recently_viewed:5},pagination_count:0,pagination_page:1}},mounted:function(){this.$nextTick((function(){this.$cookies.isKey("search_settings")&&(console.log("loaded cookie settings",this.$cookies.get("search_settings")),this.settings=this.$cookies.get("search_settings")),this.loadMealPlan(),this.loadRecentlyViewed()})),this.refreshData()},watch:{settings:{handler:function(){this.$cookies.set("search_settings",this.settings,-1)},deep:!0},"settings.show_meal_plan":function(){this.loadMealPlan()},"settings.recently_viewed":function(){this.loadRecentlyViewed()},search_input:d()((function(){this.refreshData()}),300)},methods:{refreshData:function(){var e=this,t=new l["a"];t.listRecipes(this.search_input,this.search_keywords.map((function(e){return e["id"]})),this.search_foods.map((function(e){return e["id"]})),this.search_books.map((function(e){return e["id"]})),this.settings.search_keywords_or,this.settings.search_foods_or,this.settings.search_books_or,this.search_internal,void 0,this.pagination_page).then((function(t){e.recipes=t.data.results,e.pagination_count=t.data.count}))},loadMealPlan:function(){var e=this,t=new l["a"];this.settings.show_meal_plan?t.listMealPlans({query:{from_date:c()().format("YYYY-MM-DD"),to_date:c()().format("YYYY-MM-DD")}}).then((function(t){e.meal_plans=t.data})):this.meal_plans=[]},loadRecentlyViewed:function(){var e=this,t=new l["a"];this.settings.recently_viewed>0?t.listRecipes({options:{query:{last_viewed:this.settings.recently_viewed}}}).then((function(t){e.last_viewed_recipes=t.data.results})):this.last_viewed_recipes=[]},genericSelectChanged:function(e){this[e.var]=e.val,this.refreshData()},resetSearch:function(){this.search_input="",this.search_internal=!1,this.search_keywords=[],this.search_foods=[],this.search_books=[],this.refreshData()},pageChange:function(e){this.pagination_page=e,this.refreshData()}}},T=E,x=(n("60bc"),Object(y["a"])(T,i,o,!1,null,null,null)),B=x.exports,q=n("9225");r["default"].config.productionTip=!1,new r["default"]({i18n:q["a"],render:function(e){return e(B)}}).$mount("#app")},"6ce2":function(e){e.exports=JSON.parse('{"Import":"Import","import_running":"Import läuft, bitte warten!","Import_finished":"Import fertig","View_Recipes":"Rezepte Ansehen","Information":"Information","all_fields_optional":"Alle Felder sind optional und können leer gelassen werden.","convert_internal":"Zu internem Rezept wandeln","Log_Recipe_Cooking":"Kochen protokollieren","External_Recipe_Image":"Externes Rezept Bild","Add_to_Book":"Zu Buch hinzufügen","Add_to_Shopping":"Zu Einkaufsliste hinzufügen","Add_to_Plan":"Zu Plan hinzufügen","Step_start_time":"Schritt Startzeit","Select_Book":"Buch wählen","Recipe_Image":"Rezept Bild","Log_Cooking":"Kochen protokollieren","Proteins":"Proteine","Fats":"Fette","Carbohydrates":"Kohlenhydrate","Calories":"Kalorien","Nutrition":"Nährwerte","Keywords":"Stichwörter","Books":"Bücher","show_only_internal":"Nur interne Rezepte anzeigen","Ingredients":"Zutaten","min":"Min","Servings":"Portionen","Waiting":"Wartezeit","Preparation":"Zubereitung","Edit":"Bearbeiten","Open":"Öffnen","Save":"Speichern","Step":"Schritt","Search":"Suchen","Print":"Drucken","New_Recipe":"Neues Rezept","Url_Import":"URL Import","Reset_Search":"Suche zurücksetzen","or":"oder","and":"und","Recently_Viewed":"Kürzlich angesehen","External":"Extern","Settings":"Einstellungen","Meal_Plan":"Speiseplan","Date":"Datum","Share":"Teilen","Export":"Exportieren","Rating":"Bewertung","Close":"Schließen","Add":"Hinzufügen"}')},"7c15":function(e,t,n){"use strict";n.d(t,"c",(function(){return a})),n.d(t,"d",(function(){return s})),n.d(t,"b",(function(){return c})),n.d(t,"a",(function(){return u}));var r=n("bc3a"),i=n.n(r),o=n("fa7d");function a(e){var t=Object(o["g"])("api:recipe-detail",e);return void 0!==window.SHARE_UID&&(t+="?share="+window.SHARE_UID),i.a.get(t).then((function(e){return e.data})).catch((function(e){d(e,"There was an error loading a resource!","danger")}))}function s(e){return i.a.post(Object(o["g"])("api:cooklog-list"),e).then((function(e){Object(o["f"])("Saved","Cook Log entry saved!","success")})).catch((function(e){d(e,"There was an error creating a resource!","danger")}))}function c(e){return i.a.get(Object(o["g"])("api:recipebook-list")+"?query="+e).then((function(e){return e.data})).catch((function(e){}))}function u(e){return i.a.post(Object(o["g"])("api:recipebookentry-list"),e).then((function(e){Object(o["f"])("Saved","Recipe Book entry saved!","success")})).catch((function(e){d(e,"There was an error creating a resource!","danger")}))}function d(e,t){if("response"in e){console.log(e.response);var n="statusText"in e.response?e.response.statusText:Object(o["e"])("Error");t+="\n\n"+JSON.stringify(e.response.data),Object(o["f"])(n,t,"danger")}else Object(o["f"])("Error",t,"danger"),console.log(e)}i.a.defaults.xsrfCookieName="csrftoken",i.a.defaults.xsrfHeaderName="X-CSRFTOKEN"},"81d5":function(e,t,n){"use strict";var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.recipe.keywords.length>0?n("div",e._l(e.recipe.keywords,(function(t){return n("small",{key:t.id,staticStyle:{padding:"2px"}},[n("b-badge",{attrs:{pill:"",variant:"light"}},[e._v(e._s(t.label))])],1)})),0):e._e()},i=[],o={name:"Keywords",props:{recipe:Object}},a=o,s=n("2877"),c=Object(s["a"])(a,r,i,!1,null,null,null);t["a"]=c.exports},9225:function(e,t,n){"use strict";n("159b"),n("d3b7"),n("ddb0"),n("ac1f"),n("466d");var r=n("a026"),i=n("a925");function o(){var e=n("49f8"),t={};return e.keys().forEach((function(n){var r=n.match(/([A-Za-z0-9-_]+)\./i);if(r&&r.length>1){var i=r[1];t[i]=e(n)}})),t}r["default"].use(i["a"]),t["a"]=new i["a"]({locale:Object({NODE_ENV:"production",BASE_URL:""}).VUE_APP_I18N_LOCALE||"en",fallbackLocale:Object({NODE_ENV:"production",BASE_URL:""}).VUE_APP_I18N_FALLBACK_LOCALE||"en",messages:o()})},a625:function(e){e.exports=JSON.parse('{"import_running":"Er wordt geïmporteerd, even geduld!","all_fields_optional":"Alle velden zijn optioneel en kunnen leeg gelaten worden.","convert_internal":"Zet om naar intern recept","Log_Recipe_Cooking":"Log Bereiding","External_Recipe_Image":"Externe Afbeelding Recept","Add_to_Book":"Voeg toe aan Boek","Add_to_Shopping":"Voeg toe aan Boodschappenlijst","Add_to_Plan":"Voeg toe aan Plan","Step_start_time":"Starttijd stap","Select_Book":"Selecteer Boek","Recipe_Image":"Afbeelding Recept","Import_finished":"Importeren gereed","View_Recipes":"Bekijk Recepten","Log_Cooking":"Log Bereiding","Proteins":"Eiwitten","Fats":"Vetten","Carbohydrates":"Koolhydraten","Calories":"Calorieën","Nutrition":"Voedingswaarde","Date":"Datum","Share":"Deel","Export":"Exporteren","Rating":"Beoordeling","Close":"Sluiten","Add":"Voeg toe","Ingredients":"Ingrediënten","min":"min","Servings":"Porties","Waiting":"Wachten","Preparation":"Bereiding","Edit":"Bewerken","Open":"Open","Save":"Opslaan","Step":"Stap","Search":"Zoeken","Import":"Importeer","Print":"Afdrukken","Information":"Informatie","Keywords":"Etiketten","Books":"Boeken","show_only_internal":"Toon alleen interne recepten","New_Recipe":"Nieuw Recept","Url_Import":"Importeer URL","Reset_Search":"Zoeken resetten","or":"of","and":"en","Recently_Viewed":"Recent bekeken","External":"Externe","Settings":"Instellingen","Meal_Plan":"Maaltijdplan","New":"Nieuw"}')},d76c:function(e,t,n){"use strict";var r=function(){var e=this,t=e.$createElement;e._self._c;return e._m(0)},i=[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"row"},[n("div",{staticClass:"col",staticStyle:{"text-align":"center"}},[n("i",{staticClass:"fas fa-spinner fa-spin fa-10x"})])])}],o={name:"LoadingSpinner",props:{recipe:Object}},a=o,s=n("2877"),c=Object(s["a"])(a,r,i,!1,null,null,null);t["a"]=c.exports},edd4:function(e){e.exports=JSON.parse('{"import_running":"Import running, please wait!","all_fields_optional":"All fields are optional and can be left empty.","convert_internal":"Convert to internal recipe","show_only_internal":"Show only internal recipes","Log_Recipe_Cooking":"Log Recipe Cooking","External_Recipe_Image":"External Recipe Image","Add_to_Book":"Add to Book","Add_to_Shopping":"Add to Shopping","Add_to_Plan":"Add to Plan","Step_start_time":"Step start time","Meal_Plan":"Meal Plan","Select_Book":"Select Book","Recipe_Image":"Recipe Image","Import_finished":"Import finished","View_Recipes":"View Recipes","Log_Cooking":"Log Cooking","New_Recipe":"New Recipe","Url_Import":"Url Import","Reset_Search":"Reset Search","Recently_Viewed":"Recently Viewed","Keywords":"Keywords","Books":"Books","Proteins":"Proteins","Fats":"Fats","Carbohydrates":"Carbohydrates","Calories":"Calories","Nutrition":"Nutrition","Date":"Date","Share":"Share","Export":"Export","Rating":"Rating","Close":"Close","Add":"Add","New":"New","Ingredients":"Ingredients","min":"min","Servings":"Servings","Waiting":"Waiting","Preparation":"Preparation","External":"External","Edit":"Edit","Open":"Open","Save":"Save","Step":"Step","Search":"Search","Import":"Import","Print":"Print","Settings":"Settings","or":"or","and":"and","Information":"Information","Advanced Search Settings":"Advanced Search Settings"}')},fa7d:function(e,t,n){"use strict";n.d(t,"c",(function(){return o})),n.d(t,"f",(function(){return a})),n.d(t,"a",(function(){return s})),n.d(t,"e",(function(){return c})),n.d(t,"b",(function(){return u})),n.d(t,"g",(function(){return d})),n.d(t,"d",(function(){return h}));n("99af");var r=n("59e4");function i(e,t,n){var r=Math.floor(e),i=1,o=r+1,a=1;if(e!==r)while(i<=t&&a<=t){var s=(r+o)/(i+a);if(e===s){i+a<=t?(i+=a,r+=o,a=t+1):i>a?a=t+1:i=t+1;break}et&&(i=a,r=o),!n)return[0,r,i];var c=Math.floor(r/i);return[c,r-c*i,i]}var o={methods:{makeToast:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;return a(e,t,n)}}};function a(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,i=new r["a"];i.$bvToast.toast(t,{title:e,variant:n,toaster:"b-toaster-top-center",solid:!0})}var s={methods:{_:function(e){return c(e)}}};function c(e){return window.gettext(e)}var u={methods:{resolveDjangoUrl:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return d(e,t)}}};function d(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return null!==t?window.Urls[e](t):window.Urls[e]()}function p(e){return window.USER_PREF[e]}function h(e,t){if(p("use_fractions")){var n="",r=i(e*t,9,!0);return r[0]>0&&(n+=r[0]),r[1]>0&&(n+=" ".concat(r[1],"").concat(r[2],"")),n}return b(e*t)}function b(e){var t=p("user_fractions")?p("user_fractions"):2;return+(Math.round(e+"e+".concat(t))+"e-".concat(t))}},fc0d:function(e,t,n){"use strict";var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("div",{staticClass:"dropdown"},[e._m(0),n("div",{staticClass:"dropdown-menu dropdown-menu-right",attrs:{"aria-labelledby":"dropdownMenuLink"}},[n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("edit_recipe",e.recipe.id)}},[n("i",{staticClass:"fas fa-pencil-alt fa-fw"}),e._v(" "+e._s(e.$t("Edit")))]),e.recipe.internal?e._e():n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("edit_convert_recipe",e.recipe.id)}},[n("i",{staticClass:"fas fa-exchange-alt fa-fw"}),e._v(" "+e._s(e.$t("convert_internal")))]),n("button",{staticClass:"dropdown-item",on:{click:function(t){return e.$bvModal.show("id_modal_add_book")}}},[n("i",{staticClass:"fas fa-bookmark fa-fw"}),e._v(" "+e._s(e.$t("Add_to_Book"))+" ")]),e.recipe.internal?n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("view_shopping")+"?r=["+e.recipe.id+","+e.servings_value+"]",target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-shopping-cart fa-fw"}),e._v(" "+e._s(e.$t("Add_to_Shopping"))+" ")]):e._e(),n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("new_meal_plan")+"?recipe="+e.recipe.id,target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-calendar fa-fw"}),e._v(" "+e._s(e.$t("Add_to_Plan"))+" ")]),n("button",{staticClass:"dropdown-item",on:{click:function(t){return e.$bvModal.show("id_modal_cook_log")}}},[n("i",{staticClass:"fas fa-clipboard-list fa-fw"}),e._v(" "+e._s(e.$t("Log_Cooking"))+" ")]),n("button",{staticClass:"dropdown-item",attrs:{onclick:"window.print()"}},[n("i",{staticClass:"fas fa-print fa-fw"}),e._v(" "+e._s(e.$t("Print"))+" ")]),n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("view_export")+"?r="+e.recipe.id,target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-file-export fa-fw"}),e._v(" "+e._s(e.$t("Export")))]),e.recipe.internal?n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("new_share_link",e.recipe.id),target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-share-alt fa-fw"}),e._v(" "+e._s(e.$t("Share")))]):e._e()])]),n("cook-log",{attrs:{recipe:e.recipe}})],1)},i=[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("a",{staticClass:"btn shadow-none",attrs:{href:"#",role:"button",id:"dropdownMenuLink","data-toggle":"dropdown","aria-haspopup":"true","aria-expanded":"false"}},[n("i",{staticClass:"fas fa-ellipsis-v"})])}],o=(n("a9e3"),n("fa7d")),a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("b-modal",{staticClass:"modal",attrs:{id:"id_modal_cook_log",title:e.$t("Log_Recipe_Cooking"),"ok-title":e.$t("Save"),"cancel-title":e.$t("Close")},on:{ok:function(t){return e.logCook()}}},[n("p",[e._v(e._s(e.$t("all_fields_optional")))]),n("form",[n("label",{attrs:{for:"id_log_servings"}},[e._v(e._s(e.$t("Servings")))]),n("input",{directives:[{name:"model",rawName:"v-model",value:e.logObject.servings,expression:"logObject.servings"}],staticClass:"form-control",attrs:{type:"number",id:"id_log_servings"},domProps:{value:e.logObject.servings},on:{input:function(t){t.target.composing||e.$set(e.logObject,"servings",t.target.value)}}}),n("label",{staticStyle:{"margin-top":"2vh"}},[e._v(e._s(e.$t("Rating"))+" - "),n("span",{attrs:{id:"id_rating_show"}},[e._v(e._s(e.logObject.rating)+"/5")])]),n("b-form-rating",{model:{value:e.logObject.rating,callback:function(t){e.$set(e.logObject,"rating",t)},expression:"logObject.rating"}}),n("label",{staticStyle:{"margin-top":"2vh"},attrs:{for:"id_date"}},[e._v(e._s(e.$t("Date")))]),n("input",{directives:[{name:"model",rawName:"v-model",value:e.logObject.created_at,expression:"logObject.created_at"}],staticClass:"form-control",attrs:{type:"datetime-local",id:"id_date"},domProps:{value:e.logObject.created_at},on:{input:function(t){t.target.composing||e.$set(e.logObject,"created_at",t.target.value)}}})],1)])],1)},s=[],c=n("c1df"),u=n.n(c),d=n("a026"),p=n("5f5b"),h=n("7c15");d["default"].prototype.moment=u.a,d["default"].use(p["a"]);var b={name:"CookLog",props:{recipe:Object},data:function(){return{logObject:{recipe:this.recipe.id,servings:0,rating:0,created_at:u()().format("yyyy-MM-DDTHH:mm")}}},methods:{logCook:function(){Object(h["d"])(this.logObject)}}},l=b,f=n("2877"),j=Object(f["a"])(l,a,s,!1,null,null,null),O=j.exports,v={name:"RecipeContextMenu",mixins:[o["b"]],components:{CookLog:O},data:function(){return{servings_value:0}},props:{recipe:Object,servings:{type:Number,default:-1}},mounted:function(){this.servings_value=-1===this.servings?this.recipe.servings:this.servings}},g=v,m=Object(f["a"])(g,r,i,!1,null,null,null);t["a"]=m.exports}}); \ No newline at end of file +(function(e){function t(t){for(var r,a,s=t[0],c=t[1],u=t[2],p=0,h=[];pnew Date(Date.now()-6048e5)?n("b-badge",{attrs:{pill:"",variant:"success"}},[e._v(e._s(e.$t("New")))]):e._e()]:[e._v(e._s(e.meal_plan.note))]],2)],1),void 0!==e.footer_text?n("b-card-footer",[n("i",{class:e.footer_icon}),e._v(" "+e._s(e.footer_text)+" ")]):e._e()],1)},j=[],O=n("fc0d"),v=n("81d5"),g={name:"RecipeCard",mixins:[b["b"]],components:{Keywords:v["a"],RecipeContextMenu:O["a"]},props:{recipe:Object,meal_plan:Object,footer_text:String,footer_icon:String},data:function(){return{recipe_image:""}},mounted:function(){null==this.recipe||null===this.recipe.image?this.recipe_image=window.IMAGE_PLACEHOLDER:this.recipe_image=this.recipe.image},methods:{clickUrl:function(){return null!==this.recipe?Object(b["g"])("view_recipe",this.recipe.id):Object(b["g"])("view_plan_entry",this.meal_plan.id)}}},m=g,y=n("2877"),P=Object(y["a"])(m,f,j,!1,null,"6fcb509c",null),S=P.exports,w=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("multiselect",{attrs:{options:e.objects,"close-on-select":!0,"clear-on-select":!0,"hide-selected":!0,"preserve-search":!0,placeholder:e.placeholder,label:e.label,"track-by":"id",multiple:!0,loading:e.loading},on:{"search-change":e.search,input:e.selectionChanged},model:{value:e.selected_objects,callback:function(t){e.selected_objects=t},expression:"selected_objects"}})},U=[],k=(n("ac1f"),n("841c"),n("8e5f")),R=n.n(k),L={name:"GenericMultiselect",components:{Multiselect:R.a},data:function(){return{loading:!1,objects:[],selected_objects:[]}},props:{placeholder:String,search_function:String,label:String,parent_variable:String,initial_selection:Array},watch:{initial_selection:function(e,t){this.selected_objects=e}},mounted:function(){this.search("")},methods:{search:function(e){var t=this,n=new l["a"];n[this.search_function]({query:{query:e,limit:10}}).then((function(e){t.objects=e.data}))},selectionChanged:function(){this.$emit("change",{var:this.parent_variable,val:this.selected_objects})}}},_=L,C=Object(y["a"])(_,w,U,!1,null,"20923c58",null),I=C.exports;r["default"].use(h.a),r["default"].use(a["a"]);var E={name:"RecipeSearchView",mixins:[b["b"]],components:{GenericMultiselect:I,RecipeCard:S},data:function(){return{recipes:[],meal_plans:[],last_viewed_recipes:[],search_input:"",search_internal:!1,search_keywords:[],search_foods:[],search_books:[],settings:{search_keywords_or:!0,search_foods_or:!0,search_books_or:!0,advanced_search_visible:!1,show_meal_plan:!0,recently_viewed:5},pagination_count:0,pagination_page:1}},mounted:function(){this.$nextTick((function(){this.$cookies.isKey("search_settings")&&(console.log("loaded cookie settings",this.$cookies.get("search_settings")),this.settings=this.$cookies.get("search_settings")),this.loadMealPlan(),this.loadRecentlyViewed()})),this.refreshData()},watch:{settings:{handler:function(){this.$cookies.set("search_settings",this.settings,-1)},deep:!0},"settings.show_meal_plan":function(){this.loadMealPlan()},"settings.recently_viewed":function(){this.loadRecentlyViewed()},search_input:d()((function(){this.refreshData()}),300)},methods:{refreshData:function(){var e=this,t=new l["a"];t.listRecipes(this.search_input,this.search_keywords.map((function(e){return e["id"]})),this.search_foods.map((function(e){return e["id"]})),this.search_books.map((function(e){return e["id"]})),this.settings.search_keywords_or,this.settings.search_foods_or,this.settings.search_books_or,this.search_internal,void 0,this.pagination_page).then((function(t){e.recipes=t.data.results,e.pagination_count=t.data.count}))},loadMealPlan:function(){var e=this,t=new l["a"];this.settings.show_meal_plan?t.listMealPlans({query:{from_date:c()().format("YYYY-MM-DD"),to_date:c()().format("YYYY-MM-DD")}}).then((function(t){e.meal_plans=t.data})):this.meal_plans=[]},loadRecentlyViewed:function(){var e=this,t=new l["a"];this.settings.recently_viewed>0?t.listRecipes({options:{query:{last_viewed:this.settings.recently_viewed}}}).then((function(t){e.last_viewed_recipes=t.data.results})):this.last_viewed_recipes=[]},genericSelectChanged:function(e){this[e.var]=e.val,this.refreshData()},resetSearch:function(){this.search_input="",this.search_internal=!1,this.search_keywords=[],this.search_foods=[],this.search_books=[],this.refreshData()},pageChange:function(e){this.pagination_page=e,this.refreshData()}}},T=E,x=(n("60bc"),Object(y["a"])(T,i,o,!1,null,null,null)),B=x.exports,q=n("9225");r["default"].config.productionTip=!1,new r["default"]({i18n:q["a"],render:function(e){return e(B)}}).$mount("#app")},"6ce2":function(e){e.exports=JSON.parse('{"Import":"Import","import_running":"Import läuft, bitte warten!","Import_finished":"Import fertig","View_Recipes":"Rezepte Ansehen","Information":"Information","all_fields_optional":"Alle Felder sind optional und können leer gelassen werden.","convert_internal":"Zu internem Rezept wandeln","Log_Recipe_Cooking":"Kochen protokollieren","External_Recipe_Image":"Externes Rezept Bild","Add_to_Book":"Zu Buch hinzufügen","Add_to_Shopping":"Zu Einkaufsliste hinzufügen","Add_to_Plan":"Zu Plan hinzufügen","Step_start_time":"Schritt Startzeit","Select_Book":"Buch wählen","Recipe_Image":"Rezept Bild","Log_Cooking":"Kochen protokollieren","Proteins":"Proteine","Fats":"Fette","Carbohydrates":"Kohlenhydrate","Calories":"Kalorien","Nutrition":"Nährwerte","Keywords":"Stichwörter","Books":"Bücher","show_only_internal":"Nur interne Rezepte anzeigen","Ingredients":"Zutaten","min":"Min","Servings":"Portionen","Waiting":"Wartezeit","Preparation":"Zubereitung","Edit":"Bearbeiten","Open":"Öffnen","Save":"Speichern","Step":"Schritt","Search":"Suchen","Print":"Drucken","New_Recipe":"Neues Rezept","Url_Import":"URL Import","Reset_Search":"Suche zurücksetzen","or":"oder","and":"und","Recently_Viewed":"Kürzlich angesehen","External":"Extern","Settings":"Einstellungen","Meal_Plan":"Speiseplan","Date":"Datum","Share":"Teilen","Export":"Exportieren","Rating":"Bewertung","Close":"Schließen","Add":"Hinzufügen"}')},"7c15":function(e,t,n){"use strict";n.d(t,"c",(function(){return a})),n.d(t,"d",(function(){return s})),n.d(t,"b",(function(){return c})),n.d(t,"a",(function(){return u}));var r=n("bc3a"),i=n.n(r),o=n("fa7d");function a(e){var t=Object(o["g"])("api:recipe-detail",e);return void 0!==window.SHARE_UID&&(t+="?share="+window.SHARE_UID),i.a.get(t).then((function(e){return e.data})).catch((function(e){d(e,"There was an error loading a resource!","danger")}))}function s(e){return i.a.post(Object(o["g"])("api:cooklog-list"),e).then((function(e){Object(o["f"])("Saved","Cook Log entry saved!","success")})).catch((function(e){d(e,"There was an error creating a resource!","danger")}))}function c(e){return i.a.get(Object(o["g"])("api:recipebook-list")+"?query="+e).then((function(e){return e.data})).catch((function(e){}))}function u(e){return i.a.post(Object(o["g"])("api:recipebookentry-list"),e).then((function(e){Object(o["f"])("Saved","Recipe Book entry saved!","success")})).catch((function(e){d(e,"There was an error creating a resource!","danger")}))}function d(e,t){if("response"in e){console.log(e.response);var n="statusText"in e.response?e.response.statusText:Object(o["e"])("Error");t+="\n\n"+JSON.stringify(e.response.data),Object(o["f"])(n,t,"danger")}else Object(o["f"])("Error",t,"danger"),console.log(e)}i.a.defaults.xsrfCookieName="csrftoken",i.a.defaults.xsrfHeaderName="X-CSRFTOKEN"},"81d5":function(e,t,n){"use strict";var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return e.recipe.keywords.length>0?n("div",e._l(e.recipe.keywords,(function(t){return n("small",{key:t.id,staticStyle:{padding:"2px"}},[n("b-badge",{attrs:{pill:"",variant:"light"}},[e._v(e._s(t.label))])],1)})),0):e._e()},i=[],o={name:"Keywords",props:{recipe:Object}},a=o,s=n("2877"),c=Object(s["a"])(a,r,i,!1,null,null,null);t["a"]=c.exports},9225:function(e,t,n){"use strict";n("159b"),n("d3b7"),n("ddb0"),n("ac1f"),n("466d");var r=n("a026"),i=n("a925");function o(){var e=n("49f8"),t={};return e.keys().forEach((function(n){var r=n.match(/([A-Za-z0-9-_]+)\./i);if(r&&r.length>1){var i=r[1];t[i]=e(n)}})),t}r["default"].use(i["a"]),t["a"]=new i["a"]({locale:Object({NODE_ENV:"production",BASE_URL:""}).VUE_APP_I18N_LOCALE||"en",fallbackLocale:Object({NODE_ENV:"production",BASE_URL:""}).VUE_APP_I18N_FALLBACK_LOCALE||"en",messages:o()})},a625:function(e){e.exports=JSON.parse('{"import_running":"Er wordt geïmporteerd, even geduld!","all_fields_optional":"Alle velden zijn optioneel en kunnen leeg gelaten worden.","convert_internal":"Zet om naar intern recept","Log_Recipe_Cooking":"Log Bereiding","External_Recipe_Image":"Externe Afbeelding Recept","Add_to_Book":"Voeg toe aan Boek","Add_to_Shopping":"Voeg toe aan Boodschappenlijst","Add_to_Plan":"Voeg toe aan Plan","Step_start_time":"Starttijd stap","Select_Book":"Selecteer Boek","Recipe_Image":"Afbeelding Recept","Import_finished":"Importeren gereed","View_Recipes":"Bekijk Recepten","Log_Cooking":"Log Bereiding","Proteins":"Eiwitten","Fats":"Vetten","Carbohydrates":"Koolhydraten","Calories":"Calorieën","Nutrition":"Voedingswaarde","Date":"Datum","Share":"Deel","Export":"Exporteren","Rating":"Beoordeling","Close":"Sluiten","Add":"Voeg toe","Ingredients":"Ingrediënten","min":"min","Servings":"Porties","Waiting":"Wachten","Preparation":"Bereiding","Edit":"Bewerken","Open":"Open","Save":"Opslaan","Step":"Stap","Search":"Zoeken","Import":"Importeer","Print":"Afdrukken","Information":"Informatie","Keywords":"Etiketten","Books":"Boeken","show_only_internal":"Toon alleen interne recepten","New_Recipe":"Nieuw Recept","Url_Import":"Importeer URL","Reset_Search":"Zoeken resetten","or":"of","and":"en","Recently_Viewed":"Recent bekeken","External":"Externe","Settings":"Instellingen","Meal_Plan":"Maaltijdplan","New":"Nieuw"}')},d76c:function(e,t,n){"use strict";var r=function(){var e=this,t=e.$createElement;e._self._c;return e._m(0)},i=[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",{staticClass:"row"},[n("div",{staticClass:"col",staticStyle:{"text-align":"center"}},[n("i",{staticClass:"fas fa-spinner fa-spin fa-10x"})])])}],o={name:"LoadingSpinner",props:{recipe:Object}},a=o,s=n("2877"),c=Object(s["a"])(a,r,i,!1,null,null,null);t["a"]=c.exports},edd4:function(e){e.exports=JSON.parse('{"import_running":"Import running, please wait!","all_fields_optional":"All fields are optional and can be left empty.","convert_internal":"Convert to internal recipe","show_only_internal":"Show only internal recipes","Log_Recipe_Cooking":"Log Recipe Cooking","External_Recipe_Image":"External Recipe Image","Add_to_Book":"Add to Book","Add_to_Shopping":"Add to Shopping","Add_to_Plan":"Add to Plan","Step_start_time":"Step start time","Meal_Plan":"Meal Plan","Select_Book":"Select Book","Recipe_Image":"Recipe Image","Import_finished":"Import finished","View_Recipes":"View Recipes","Log_Cooking":"Log Cooking","New_Recipe":"New Recipe","Url_Import":"Url Import","Reset_Search":"Reset Search","Recently_Viewed":"Recently Viewed","Keywords":"Keywords","Books":"Books","Proteins":"Proteins","Fats":"Fats","Carbohydrates":"Carbohydrates","Calories":"Calories","Nutrition":"Nutrition","Date":"Date","Share":"Share","Export":"Export","Rating":"Rating","Close":"Close","Add":"Add","New":"New","Ingredients":"Ingredients","min":"min","Servings":"Servings","Waiting":"Waiting","Preparation":"Preparation","External":"External","Edit":"Edit","Open":"Open","Save":"Save","Step":"Step","Search":"Search","Import":"Import","Print":"Print","Settings":"Settings","or":"or","and":"and","Information":"Information","Advanced Search Settings":"Advanced Search Settings"}')},fa7d:function(e,t,n){"use strict";n.d(t,"c",(function(){return o})),n.d(t,"f",(function(){return a})),n.d(t,"a",(function(){return s})),n.d(t,"e",(function(){return c})),n.d(t,"b",(function(){return u})),n.d(t,"g",(function(){return d})),n.d(t,"d",(function(){return h}));n("99af");var r=n("59e4");function i(e,t,n){var r=Math.floor(e),i=1,o=r+1,a=1;if(e!==r)while(i<=t&&a<=t){var s=(r+o)/(i+a);if(e===s){i+a<=t?(i+=a,r+=o,a=t+1):i>a?a=t+1:i=t+1;break}et&&(i=a,r=o),!n)return[0,r,i];var c=Math.floor(r/i);return[c,r-c*i,i]}var o={methods:{makeToast:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;return a(e,t,n)}}};function a(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,i=new r["a"];i.$bvToast.toast(t,{title:e,variant:n,toaster:"b-toaster-top-center",solid:!0})}var s={methods:{_:function(e){return c(e)}}};function c(e){return window.gettext(e)}var u={methods:{resolveDjangoUrl:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return d(e,t)}}};function d(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return null!==t?window.Urls[e](t):window.Urls[e]()}function p(e){return window.USER_PREF[e]}function h(e,t){if(p("use_fractions")){var n="",r=i(e*t,9,!0);return r[0]>0&&(n+=r[0]),r[1]>0&&(n+=" ".concat(r[1],"").concat(r[2],"")),n}return b(e*t)}function b(e){var t=p("user_fractions")?p("user_fractions"):2;return+(Math.round(e+"e+".concat(t))+"e-".concat(t))}},fc0d:function(e,t,n){"use strict";var r=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("div",{staticClass:"dropdown"},[e._m(0),n("div",{staticClass:"dropdown-menu dropdown-menu-right",attrs:{"aria-labelledby":"dropdownMenuLink"}},[n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("edit_recipe",e.recipe.id)}},[n("i",{staticClass:"fas fa-pencil-alt fa-fw"}),e._v(" "+e._s(e.$t("Edit")))]),e.recipe.internal?e._e():n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("edit_convert_recipe",e.recipe.id)}},[n("i",{staticClass:"fas fa-exchange-alt fa-fw"}),e._v(" "+e._s(e.$t("convert_internal")))]),n("button",{staticClass:"dropdown-item",on:{click:function(t){return e.$bvModal.show("id_modal_add_book")}}},[n("i",{staticClass:"fas fa-bookmark fa-fw"}),e._v(" "+e._s(e.$t("Add_to_Book"))+" ")]),e.recipe.internal?n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("view_shopping")+"?r=["+e.recipe.id+","+e.servings_value+"]",target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-shopping-cart fa-fw"}),e._v(" "+e._s(e.$t("Add_to_Shopping"))+" ")]):e._e(),n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("new_meal_plan")+"?recipe="+e.recipe.id,target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-calendar fa-fw"}),e._v(" "+e._s(e.$t("Add_to_Plan"))+" ")]),n("button",{staticClass:"dropdown-item",on:{click:function(t){return e.$bvModal.show("id_modal_cook_log")}}},[n("i",{staticClass:"fas fa-clipboard-list fa-fw"}),e._v(" "+e._s(e.$t("Log_Cooking"))+" ")]),n("button",{staticClass:"dropdown-item",attrs:{onclick:"window.print()"}},[n("i",{staticClass:"fas fa-print fa-fw"}),e._v(" "+e._s(e.$t("Print"))+" ")]),n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("view_export")+"?r="+e.recipe.id,target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-file-export fa-fw"}),e._v(" "+e._s(e.$t("Export")))]),e.recipe.internal?n("a",{staticClass:"dropdown-item",attrs:{href:e.resolveDjangoUrl("new_share_link",e.recipe.id),target:"_blank",rel:"noopener noreferrer"}},[n("i",{staticClass:"fas fa-share-alt fa-fw"}),e._v(" "+e._s(e.$t("Share")))]):e._e()])]),n("cook-log",{attrs:{recipe:e.recipe}})],1)},i=[function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("a",{staticClass:"btn shadow-none",attrs:{href:"#",role:"button",id:"dropdownMenuLink","data-toggle":"dropdown","aria-haspopup":"true","aria-expanded":"false"}},[n("i",{staticClass:"fas fa-ellipsis-v"})])}],o=(n("a9e3"),n("fa7d")),a=function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("b-modal",{staticClass:"modal",attrs:{id:"id_modal_cook_log",title:e.$t("Log_Recipe_Cooking"),"ok-title":e.$t("Save"),"cancel-title":e.$t("Close")},on:{ok:function(t){return e.logCook()}}},[n("p",[e._v(e._s(e.$t("all_fields_optional")))]),n("form",[n("label",{attrs:{for:"id_log_servings"}},[e._v(e._s(e.$t("Servings")))]),n("input",{directives:[{name:"model",rawName:"v-model",value:e.logObject.servings,expression:"logObject.servings"}],staticClass:"form-control",attrs:{type:"number",id:"id_log_servings"},domProps:{value:e.logObject.servings},on:{input:function(t){t.target.composing||e.$set(e.logObject,"servings",t.target.value)}}}),n("label",{staticStyle:{"margin-top":"2vh"}},[e._v(e._s(e.$t("Rating"))+" - "),n("span",{attrs:{id:"id_rating_show"}},[e._v(e._s(e.logObject.rating)+"/5")])]),n("b-form-rating",{model:{value:e.logObject.rating,callback:function(t){e.$set(e.logObject,"rating",t)},expression:"logObject.rating"}}),n("label",{staticStyle:{"margin-top":"2vh"},attrs:{for:"id_date"}},[e._v(e._s(e.$t("Date")))]),n("input",{directives:[{name:"model",rawName:"v-model",value:e.logObject.created_at,expression:"logObject.created_at"}],staticClass:"form-control",attrs:{type:"datetime-local",id:"id_date"},domProps:{value:e.logObject.created_at},on:{input:function(t){t.target.composing||e.$set(e.logObject,"created_at",t.target.value)}}})],1)])],1)},s=[],c=n("c1df"),u=n.n(c),d=n("a026"),p=n("5f5b"),h=n("7c15");d["default"].prototype.moment=u.a,d["default"].use(p["a"]);var b={name:"CookLog",props:{recipe:Object},data:function(){return{logObject:{recipe:this.recipe.id,servings:0,rating:0,created_at:u()().format("yyyy-MM-DDTHH:mm")}}},methods:{logCook:function(){Object(h["d"])(this.logObject)}}},l=b,f=n("2877"),j=Object(f["a"])(l,a,s,!1,null,null,null),O=j.exports,v={name:"RecipeContextMenu",mixins:[o["b"]],components:{CookLog:O},data:function(){return{servings_value:0}},props:{recipe:Object,servings:{type:Number,default:-1}},mounted:function(){this.servings_value=-1===this.servings?this.recipe.servings:this.servings}},g=v,m=Object(f["a"])(g,r,i,!1,null,null,null);t["a"]=m.exports}}); \ No newline at end of file diff --git a/cookbook/templates/settings.html b/cookbook/templates/settings.html index 45e66844..7ca10608 100644 --- a/cookbook/templates/settings.html +++ b/cookbook/templates/settings.html @@ -171,5 +171,15 @@ token.select(); document.execCommand("copy"); } + // Javascript to enable link to tab + var hash = location.hash.replace(/^#/, ''); // ^ means starting, meaning only match the first hash + if (hash) { + $('.nav-tabs a[href="#' + hash + '"]').tab('show'); + } + + // Change hash for page-reload + $('.nav-tabs a').on('shown.bs.tab', function (e) { + window.location.hash = e.target.hash; + }) {% endblock %} \ No newline at end of file diff --git a/cookbook/views/api.py b/cookbook/views/api.py index cc9c0b50..c510e421 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -9,6 +9,7 @@ from annoying.decorators import ajax_request from annoying.functions import get_object_or_None from django.contrib import messages from django.contrib.auth.models import User +from django.contrib.postgres.search import TrigramSimilarity from django.core.exceptions import FieldError, ValidationError from django.core.files import File from django.db.models import Q @@ -88,6 +89,38 @@ class StandardFilterMixin(ViewSetMixin): return queryset +class FuzzyFilterMixin(ViewSetMixin): + + def get_queryset(self): + queryset = self.queryset + query = self.request.query_params.get('query', None) + fuzzy = self.request.user.searchpreference.lookup + + if query is not None or query != '': + if fuzzy: + queryset = queryset.annotate(trigram=TrigramSimilarity('name', query)).filter(trigram__gt=0.2).order_by("-trigram") + else: + # TODO have this check unaccent search settings? + queryset = queryset.filter(name__icontains=query) + + updated_at = self.request.query_params.get('updated_at', None) + if updated_at is not None: + try: + queryset = queryset.filter(updated_at__gte=updated_at) + except FieldError: + pass + except ValidationError: + raise APIException(_('Parameter updated_at incorrectly formatted')) + + limit = self.request.query_params.get('limit', None) + random = self.request.query_params.get('random', False) + if limit is not None: + if random: + queryset = queryset.order_by("?") + queryset = queryset[:int(limit)] + return queryset + + class UserNameViewSet(viewsets.ReadOnlyModelViewSet): """ list: @@ -159,27 +192,7 @@ class SupermarketViewSet(viewsets.ModelViewSet, StandardFilterMixin): return super().get_queryset() -class SupermarketCategoryViewSet(viewsets.ModelViewSet, StandardFilterMixin): - queryset = SupermarketCategory.objects - serializer_class = SupermarketCategorySerializer - permission_classes = [CustomIsUser] - - def get_queryset(self): - self.queryset = self.queryset.filter(space=self.request.space) - return super().get_queryset() - - -class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin): - queryset = SupermarketCategoryRelation.objects - serializer_class = SupermarketCategoryRelationSerializer - permission_classes = [CustomIsUser] - - def get_queryset(self): - self.queryset = self.queryset.filter(supermarket__space=self.request.space) - return super().get_queryset() - - -class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin): +class KeywordViewSet(viewsets.ModelViewSet, FuzzyFilterMixin): """ list: optional parameters @@ -197,7 +210,7 @@ class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin): return super().get_queryset() -class UnitViewSet(viewsets.ModelViewSet, StandardFilterMixin): +class UnitViewSet(viewsets.ModelViewSet, FuzzyFilterMixin): queryset = Unit.objects serializer_class = UnitSerializer permission_classes = [CustomIsUser] @@ -207,7 +220,7 @@ class UnitViewSet(viewsets.ModelViewSet, StandardFilterMixin): return super().get_queryset() -class FoodViewSet(viewsets.ModelViewSet, StandardFilterMixin): +class FoodViewSet(viewsets.ModelViewSet, FuzzyFilterMixin): queryset = Food.objects serializer_class = FoodSerializer permission_classes = [CustomIsUser] diff --git a/cookbook/views/views.py b/cookbook/views/views.py index 34fcbfb1..3ab96bec 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -34,7 +34,6 @@ from cookbook.models import (Comment, CookLog, InviteLink, MealPlan, from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall, ViewLogTable, InviteLinkTable) from cookbook.views.data import Object -from recipes import settings from recipes.settings import DEMO from recipes.version import BUILD_REF, VERSION_NUMBER @@ -374,6 +373,7 @@ def user_settings(request): search_error = True else: sp.search = search_form.cleaned_data['search'] + sp.lookup = search_form.cleaned_data['lookup'] sp.unaccent.set(search_form.cleaned_data['unaccent']) sp.icontains.set(search_form.cleaned_data['icontains']) sp.istartswith.set(search_form.cleaned_data['istartswith']) @@ -398,6 +398,7 @@ def user_settings(request): # these fields require postgress - just disable them if postgress isn't available if not settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']: search_form.fields['search'].disabled = True + search_form.fields['lookup'].disabled = True search_form.fields['trigram'].disabled = True search_form.fields['fulltext'].disabled = True diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index 986cfd73..78e42a4f 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -119,7 +119,7 @@