mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-24 02:39:20 -05:00
Merge branch 'pr/865' into develop
This commit is contained in:
@@ -61,12 +61,12 @@ with scopes_disabled():
|
||||
model = Recipe
|
||||
fields = ['name', 'keywords', 'foods', 'internal']
|
||||
|
||||
class FoodFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr='icontains')
|
||||
# class FoodFilter(django_filters.FilterSet):
|
||||
# name = django_filters.CharFilter(lookup_expr='icontains')
|
||||
|
||||
class Meta:
|
||||
model = Food
|
||||
fields = ['name']
|
||||
# class Meta:
|
||||
# model = Food
|
||||
# fields = ['name']
|
||||
|
||||
class ShoppingListFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
@@ -6,12 +6,11 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scopes_disabled
|
||||
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
|
||||
from emoji_picker.widgets import EmojiPickerTextInput
|
||||
from treebeard.forms import MoveNodeForm
|
||||
from hcaptcha.fields import hCaptchaField
|
||||
|
||||
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, Storage, Sync, Unit, User,
|
||||
UserPreference, SupermarketCategory, MealType, Space,
|
||||
UserPreference, MealType, Space,
|
||||
SearchPreference)
|
||||
|
||||
|
||||
@@ -219,31 +218,31 @@ class CommentForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class KeywordForm(MoveNodeForm):
|
||||
class Meta:
|
||||
model = Keyword
|
||||
fields = ('name', 'icon', 'description')
|
||||
exclude = ('sib_order', 'parent', 'path', 'depth', 'numchild')
|
||||
widgets = {'icon': EmojiPickerTextInput}
|
||||
# class KeywordForm(MoveNodeForm):
|
||||
# class Meta:
|
||||
# model = Keyword
|
||||
# fields = ('name', 'icon', 'description')
|
||||
# exclude = ('sib_order', 'parent', 'path', 'depth', 'numchild')
|
||||
# widgets = {'icon': EmojiPickerTextInput}
|
||||
|
||||
|
||||
class FoodForm(forms.ModelForm):
|
||||
# class FoodForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
space = kwargs.pop('space')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['recipe'].queryset = Recipe.objects.filter(space=space).all()
|
||||
self.fields['supermarket_category'].queryset = SupermarketCategory.objects.filter(space=space).all()
|
||||
# def __init__(self, *args, **kwargs):
|
||||
# space = kwargs.pop('space')
|
||||
# super().__init__(*args, **kwargs)
|
||||
# self.fields['recipe'].queryset = Recipe.objects.filter(space=space).all()
|
||||
# self.fields['supermarket_category'].queryset = SupermarketCategory.objects.filter(space=space).all()
|
||||
|
||||
class Meta:
|
||||
model = Food
|
||||
fields = ('name', 'description', 'ignore_shopping', 'recipe', 'supermarket_category')
|
||||
widgets = {'recipe': SelectWidget}
|
||||
# class Meta:
|
||||
# model = Food
|
||||
# fields = ('name', 'description', 'ignore_shopping', 'recipe', 'supermarket_category')
|
||||
# widgets = {'recipe': SelectWidget}
|
||||
|
||||
field_classes = {
|
||||
'recipe': SafeModelChoiceField,
|
||||
'supermarket_category': SafeModelChoiceField,
|
||||
}
|
||||
# field_classes = {
|
||||
# 'recipe': SafeModelChoiceField,
|
||||
# 'supermarket_category': SafeModelChoiceField,
|
||||
# }
|
||||
|
||||
|
||||
class StorageForm(forms.ModelForm):
|
||||
|
||||
@@ -20,6 +20,7 @@ def search_recipes(request, queryset, params):
|
||||
search_keywords = params.getlist('keywords', [])
|
||||
search_foods = params.getlist('foods', [])
|
||||
search_books = params.getlist('books', [])
|
||||
search_units = params.get('units', None)
|
||||
|
||||
# TODO I think default behavior should be 'AND' which is how most sites operate with facet/filters based on results
|
||||
search_keywords_or = params.get('keywords_or', True)
|
||||
@@ -159,6 +160,10 @@ def search_recipes(request, queryset, params):
|
||||
else:
|
||||
for k in search_books:
|
||||
queryset = queryset.filter(recipebookentry__book__id=k)
|
||||
|
||||
# probably only useful in Unit list view, so keeping it simple
|
||||
if search_units:
|
||||
queryset = queryset.filter(steps__ingredients__unit__id=search_units)
|
||||
|
||||
if search_internal == 'true':
|
||||
queryset = queryset.filter(internal=True)
|
||||
|
||||
@@ -24,6 +24,11 @@ class RecipeSchema(AutoSchema):
|
||||
"description": 'Id of food a recipe should have. For multiple repeat parameter.',
|
||||
'schema': {'type': 'string', },
|
||||
})
|
||||
parameters.append({
|
||||
"name": 'units', "in": "query", "required": False,
|
||||
"description": 'Id of unit a recipe should have.',
|
||||
'schema': {'type': 'int', },
|
||||
})
|
||||
parameters.append({
|
||||
"name": 'books', "in": "query", "required": False,
|
||||
"description": 'Id of book a recipe should have. For multiple repeat parameter.',
|
||||
@@ -85,3 +90,18 @@ class TreeSchema(AutoSchema):
|
||||
'schema': {'type': 'int', },
|
||||
})
|
||||
return parameters
|
||||
|
||||
|
||||
class FilterSchema(AutoSchema):
|
||||
def get_path_parameters(self, path, method):
|
||||
if not is_list_view(path, method, self.view):
|
||||
return super(FilterSchema, self).get_path_parameters(path, method)
|
||||
|
||||
api_name = path.split('/')[2]
|
||||
parameters = super().get_path_parameters(path, method)
|
||||
parameters.append({
|
||||
"name": 'query', "in": "query", "required": False,
|
||||
"description": 'Query string matched against {} name.'.format(api_name),
|
||||
'schema': {'type': 'string', },
|
||||
})
|
||||
return parameters
|
||||
|
||||
@@ -118,8 +118,10 @@ class UserFileSerializer(serializers.ModelSerializer):
|
||||
except TypeError:
|
||||
current_file_size_mb = 0
|
||||
|
||||
if (validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5) > self.context[
|
||||
'request'].space.max_file_storage_mb != 0:
|
||||
if (
|
||||
(validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5)
|
||||
> self.context['request'].space.max_file_storage_mb != 0
|
||||
):
|
||||
raise ValidationError(_('You have reached your file upload limit.'))
|
||||
|
||||
def create(self, validated_data):
|
||||
@@ -241,10 +243,24 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
|
||||
|
||||
class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
image = serializers.SerializerMethodField('get_image')
|
||||
numrecipe = serializers.SerializerMethodField('count_recipes')
|
||||
|
||||
def get_image(self, obj):
|
||||
recipes = Recipe.objects.filter(steps__ingredients__unit=obj, space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
||||
|
||||
if len(recipes) != 0:
|
||||
return random.choice(recipes).image.url
|
||||
else:
|
||||
return None
|
||||
|
||||
def count_recipes(self, obj):
|
||||
return Recipe.objects.filter(steps__ingredients__unit=obj, space=obj.space).count()
|
||||
|
||||
def create(self, validated_data):
|
||||
obj, created = Unit.objects.get_or_create(name=validated_data['name'].strip(),
|
||||
space=self.context['request'].space)
|
||||
validated_data['name'] = validated_data['name'].strip()
|
||||
validated_data['space'] = self.context['request'].space
|
||||
obj, created = Unit.objects.get_or_create(**validated_data)
|
||||
return obj
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -253,8 +269,8 @@ class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Unit
|
||||
fields = ('id', 'name', 'description')
|
||||
read_only_fields = ('id',)
|
||||
fields = ('id', 'name', 'description', 'numrecipe', 'image')
|
||||
read_only_fields = ('id', 'numrecipe', 'image')
|
||||
|
||||
|
||||
class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.shake[data-v-d394ab04]{-webkit-animation:shake-data-v-d394ab04 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-d394ab04 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-d394ab04{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-d394ab04{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
@@ -1 +0,0 @@
|
||||
.shake[data-v-d394ab04]{-webkit-animation:shake-data-v-d394ab04 .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-d394ab04 .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-d394ab04{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-d394ab04{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
1
cookbook/static/vue/css/model_list_view.css
Normal file
1
cookbook/static/vue/css/model_list_view.css
Normal file
@@ -0,0 +1 @@
|
||||
.shake[data-v-021606fa]{-webkit-animation:shake-data-v-021606fa .82s cubic-bezier(.36,.07,.19,.97) both;animation:shake-data-v-021606fa .82s cubic-bezier(.36,.07,.19,.97) both;transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}@-webkit-keyframes shake-data-v-021606fa{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake-data-v-021606fa{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="css/food_list_view.css" rel="preload" as="style"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/food_list_view.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Recipes"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><div id="app"></div><script src="js/chunk-vendors.js"></script></body></html>
|
||||
1
cookbook/static/vue/js/model_list_view.js
Normal file
1
cookbook/static/vue/js/model_list_view.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="css/keyword_list_view.css" rel="preload" as="style"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/keyword_list_view.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Recipes"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><div id="app"></div><script src="js/chunk-vendors.js"></script></body></html>
|
||||
1
cookbook/static/vue/model_list_view.html
Normal file
1
cookbook/static/vue/model_list_view.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="css/model_list_view.css" rel="preload" as="style"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="js/model_list_view.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Recipes"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><div id="app"></div><script src="js/chunk-vendors.js"></script></body></html>
|
||||
@@ -52,13 +52,13 @@ class RecipeTable(tables.Table):
|
||||
)
|
||||
|
||||
|
||||
class IngredientTable(tables.Table):
|
||||
id = tables.LinkColumn('edit_food', args=[A('id')])
|
||||
# class IngredientTable(tables.Table):
|
||||
# id = tables.LinkColumn('edit_food', args=[A('id')])
|
||||
|
||||
class Meta:
|
||||
model = Keyword
|
||||
template_name = 'generic/table_template.html'
|
||||
fields = ('id', 'name')
|
||||
# class Meta:
|
||||
# model = Keyword
|
||||
# template_name = 'generic/table_template.html'
|
||||
# fields = ('id', 'name')
|
||||
|
||||
|
||||
class StorageTable(tables.Table):
|
||||
|
||||
@@ -97,6 +97,9 @@
|
||||
<a class="dropdown-item" href="{% url 'list_food' %}"><i
|
||||
class="fas fa-leaf fa-fw"></i> {% trans 'Ingredients' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'list_unit' %}"><i
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'view_supermarket' %}"><i
|
||||
class="fas fa-store-alt fa-fw"></i> {% trans 'Supermarket' %}
|
||||
</a>
|
||||
@@ -114,6 +117,28 @@
|
||||
class="fas fa-edit fa-fw"></i> {% trans 'Batch Edit' %}</a>
|
||||
</div>
|
||||
</li>
|
||||
{% comment %} should food and units be moved to the keyword dropdown and rename that?
|
||||
feels like the original intent of the organization of these menus has shifted maybe something like this?{% endcomment %}
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_food,list_unit,list_keyword,data_batch_edit' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-blender"></i> {% trans 'Gadgets' %}
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" href="{% url 'list_food' %}"><i
|
||||
class="fas fa-leaf fa-fw"></i> {% trans 'Ingredients' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'list_unit' %}"><i
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units' %}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{% url 'list_keyword' %}"><i
|
||||
class="fas fa-tags fa-fw"></i> {% trans 'Keyword' %}</a>
|
||||
{% comment %} does this funcionatliy need moved to the keyword list page?
|
||||
the Recipe Search might be better? {% endcomment %}
|
||||
<a class="dropdown-item" href="{% url 'data_batch_edit' %}"><i
|
||||
class="fas fa-edit fa-fw"></i> {% trans 'Batch Edit' %}</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown {% if request.resolver_match.url_name in 'list_storage,data_sync,list_recipe_import,list_sync_log,data_stats,edit_food,edit_storage' %}active{% endif %}">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false"><i class="fas fa-database"></i> {% trans 'Storage Data' %}
|
||||
@@ -129,8 +154,8 @@
|
||||
class="fas fa-history fa-fw"></i> {% trans 'Discovery Log' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'data_stats' %}"><i
|
||||
class="fas fa-chart-line fa-fw"></i> {% trans 'Statistics' %}</a>
|
||||
<a class="dropdown-item" href="{% url 'edit_food' %}"><i
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units & Ingredients' %}</a>
|
||||
{% comment %} <a class="dropdown-item" href="{% url 'edit_food' %}"><i
|
||||
class="fas fa-balance-scale fa-fw"></i> {% trans 'Units & Ingredients' %}</a> {% endcomment %}
|
||||
<a class="dropdown-item" href="{% url 'data_import_url' %}"><i
|
||||
class="fas fa-file-import"></i> {% trans 'Import Recipe' %}</a>
|
||||
|
||||
|
||||
@@ -766,7 +766,7 @@
|
||||
searchUnits: function (query) {
|
||||
this.units_loading = true
|
||||
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.units = response.data;
|
||||
this.units = response.data.results;
|
||||
|
||||
if (this.recipe !== undefined) {
|
||||
for (let s of this.recipe.steps) {
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
{% load render_bundle from webpack_loader %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% comment %} TODO Can this be combined with Keyword template? {% endcomment %}
|
||||
{% block title %}{% trans 'Food' %}{% endblock %}
|
||||
{% comment %} {% load l10n %} {% endcomment %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content_fluid %}
|
||||
|
||||
@@ -17,6 +16,8 @@
|
||||
|
||||
|
||||
{% block script %}
|
||||
{{ config | json_script:"model_config" }}
|
||||
|
||||
{% if debug %}
|
||||
<script src="{% url 'js_reverse' %}"></script>
|
||||
{% else %}
|
||||
@@ -27,5 +28,5 @@
|
||||
window.IMAGE_PLACEHOLDER = "{% static 'assets/recipe_no_image.svg' %}"
|
||||
</script>
|
||||
|
||||
{% render_bundle 'food_list_view' %}
|
||||
{% render_bundle 'model_list_view' %}
|
||||
{% endblock %}
|
||||
@@ -1,31 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load render_bundle from webpack_loader %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
{% comment %} TODO Can this be combined with Food template? {% endcomment %}
|
||||
{% block title %}{% trans 'Keywords' %}{% endblock %}
|
||||
|
||||
{% block content_fluid %}
|
||||
|
||||
<div id="app" >
|
||||
<keyword-list-view></keyword-list-view>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block script %}
|
||||
{% if debug %}
|
||||
<script src="{% url 'js_reverse' %}"></script>
|
||||
{% else %}
|
||||
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
<script type="application/javascript">
|
||||
window.IMAGE_PLACEHOLDER = "{% static 'assets/recipe_no_image.svg' %}"
|
||||
</script>
|
||||
|
||||
{% render_bundle 'keyword_list_view' %}
|
||||
{% endblock %}
|
||||
@@ -908,7 +908,7 @@
|
||||
searchKeywords: function (query) {
|
||||
this.keywords_loading = true
|
||||
this.$http.get("{% url 'api:keyword-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.keywords = response.data;
|
||||
this.keywords = response.data.results;
|
||||
this.keywords_loading = false
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
@@ -919,7 +919,7 @@
|
||||
searchUnits: function (query) { //TODO move to central component
|
||||
this.units_loading = true
|
||||
this.$http.get("{% url 'api:unit-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.units = response.data;
|
||||
this.units = response.data.results;
|
||||
this.units_loading = false
|
||||
}).catch((err) => {
|
||||
this.makeToast(gettext('Error'), gettext("There was an error loading a resource!") + err.bodyText, 'danger')
|
||||
@@ -928,7 +928,7 @@
|
||||
searchFoods: function (query) { //TODO move to central component
|
||||
this.foods_loading = true
|
||||
this.$http.get("{% url 'api:food-list' %}" + '?query=' + query + '&limit=10').then((response) => {
|
||||
this.foods = response.data
|
||||
this.foods = response.data.results
|
||||
this.foods_loading = false
|
||||
}).catch((err) => {
|
||||
this.makeToast(gettext('Error'), gettext("There was an error loading a resource!") + err.bodyText, 'danger')
|
||||
|
||||
@@ -273,7 +273,7 @@ def test_integrity(u1_s1, recipe_1_s1):
|
||||
)
|
||||
)
|
||||
assert r.status_code == 204
|
||||
|
||||
|
||||
with scopes_disabled():
|
||||
assert Food.objects.count() == 9
|
||||
assert Ingredient.objects.count() == 9
|
||||
@@ -327,7 +327,6 @@ def test_move(u1_s1, obj_1, obj_1_1, obj_1_1_1, obj_2, obj_3, space_1):
|
||||
assert Food.find_problems() == ([], [], [], [], [])
|
||||
|
||||
|
||||
# this seems overly long - should it be broken into smaller pieces?
|
||||
def test_merge(
|
||||
u1_s1,
|
||||
obj_1, obj_1_1, obj_1_1_1, obj_2, obj_3,
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import uuid
|
||||
|
||||
from django.contrib import auth
|
||||
from django.urls import reverse
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.models import Food, Unit
|
||||
from cookbook.models import Food, Ingredient, ShoppingList, ShoppingListEntry, Unit
|
||||
|
||||
LIST_URL = 'api:unit-list'
|
||||
DETAIL_URL = 'api:unit-detail'
|
||||
MERGE_URL = 'api:unit-merge'
|
||||
|
||||
|
||||
def random_food(space_1, u1_s1):
|
||||
return Food.objects.get_or_create(name=str(uuid.uuid4()), space=space_1)[0]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -20,6 +27,47 @@ def obj_2(space_1):
|
||||
return Unit.objects.get_or_create(name='test_2', space=space_1)[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def obj_3(space_2):
|
||||
return Unit.objects.get_or_create(name='test_3', space=space_2)[0]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def ing_1_s1(obj_1, space_1, u1_s1):
|
||||
return Ingredient.objects.create(unit=obj_1, food=random_food(space_1, u1_s1), space=space_1)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def ing_2_s1(obj_2, space_1, u1_s1):
|
||||
return Ingredient.objects.create(unit=obj_2, food=random_food(space_1, u1_s1), space=space_1)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def ing_3_s2(obj_3, space_2, u2_s2):
|
||||
return Ingredient.objects.create(unit=obj_3, food=random_food(space_2, u2_s2), space=space_2)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sle_1_s1(obj_1, u1_s1, space_1):
|
||||
e = ShoppingListEntry.objects.create(unit=obj_1, food=random_food(space_1, u1_s1))
|
||||
s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
|
||||
s.entries.add(e)
|
||||
return e
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sle_2_s1(obj_2, u1_s1, space_1):
|
||||
return ShoppingListEntry.objects.create(unit=obj_2, food=random_food(space_1, u1_s1))
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def sle_3_s2(obj_3, u2_s2, space_2):
|
||||
e = ShoppingListEntry.objects.create(unit=obj_3, food=random_food(space_2, u2_s2))
|
||||
s = ShoppingList.objects.create(created_by=auth.get_user(u2_s2), space=space_2)
|
||||
s.entries.add(e)
|
||||
return e
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
@@ -32,33 +80,31 @@ def test_list_permission(arg, request):
|
||||
|
||||
|
||||
def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 2
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 0
|
||||
|
||||
obj_1.space = space_2
|
||||
obj_1.save()
|
||||
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 1
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 1
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 1
|
||||
|
||||
|
||||
def test_list_filter(obj_1, obj_2, u1_s1):
|
||||
r = u1_s1.get(reverse(LIST_URL))
|
||||
assert r.status_code == 200
|
||||
response = json.loads(r.content)
|
||||
assert len(response) == 2
|
||||
# unit model isn't sorted - this isn't a stable test
|
||||
# assert response[0]['name'] == obj_1.name
|
||||
assert response['count'] == 2
|
||||
|
||||
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?limit=1').content)
|
||||
assert len(response) == 1
|
||||
assert response['count'] == 1
|
||||
|
||||
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?query=chicken').content)
|
||||
assert len(response) == 0
|
||||
assert response['count'] == 0
|
||||
|
||||
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?query={obj_1.name[4:]}').content)
|
||||
assert len(response) == 1
|
||||
assert response[0]['name'] == obj_1.name
|
||||
assert response['count'] == 1
|
||||
assert response['results'][0]['name'] == obj_1.name
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
@@ -148,3 +194,60 @@ def test_delete(u1_s1, u1_s2, obj_1):
|
||||
assert r.status_code == 204
|
||||
with scopes_disabled():
|
||||
assert Food.objects.count() == 0
|
||||
|
||||
|
||||
def test_merge(
|
||||
u1_s1,
|
||||
obj_1, obj_2, obj_3,
|
||||
ing_1_s1, ing_2_s1, ing_3_s2,
|
||||
sle_1_s1, sle_2_s1, sle_3_s2,
|
||||
space_1
|
||||
):
|
||||
with scopes_disabled():
|
||||
assert Unit.objects.filter(space=space_1).count() == 2
|
||||
assert obj_1.ingredient_set.count() == 1
|
||||
assert obj_2.ingredient_set.count() == 1
|
||||
assert obj_3.ingredient_set.count() == 1
|
||||
assert obj_1.shoppinglistentry_set.count() == 1
|
||||
assert obj_2.shoppinglistentry_set.count() == 1
|
||||
assert obj_3.shoppinglistentry_set.count() == 1
|
||||
|
||||
# merge Unit with ingredient/shopping list entry with another Unit, only HTTP put method should work
|
||||
url = reverse(MERGE_URL, args=[obj_1.id, obj_2.id])
|
||||
r = u1_s1.get(url)
|
||||
assert r.status_code == 405
|
||||
r = u1_s1.post(url)
|
||||
assert r.status_code == 405
|
||||
r = u1_s1.delete(url)
|
||||
assert r.status_code == 405
|
||||
r = u1_s1.put(url)
|
||||
assert r.status_code == 200
|
||||
with scopes_disabled():
|
||||
assert Unit.objects.filter(pk=obj_1.id).count() == 0
|
||||
assert obj_2.ingredient_set.count() == 2
|
||||
assert obj_3.ingredient_set.count() == 1
|
||||
|
||||
assert obj_2.shoppinglistentry_set.count() == 2
|
||||
assert obj_3.shoppinglistentry_set.count() == 1
|
||||
|
||||
# attempt to merge with non-existent parent
|
||||
r = u1_s1.put(
|
||||
reverse(MERGE_URL, args=[obj_2.id, 9999])
|
||||
)
|
||||
assert r.status_code == 404
|
||||
|
||||
# attempt to move to wrong space
|
||||
r = u1_s1.put(
|
||||
reverse(MERGE_URL, args=[obj_2.id, obj_3.id])
|
||||
)
|
||||
assert r.status_code == 404
|
||||
|
||||
# attempt to merge with self
|
||||
r = u1_s1.put(
|
||||
reverse(MERGE_URL, args=[obj_2.id, obj_2.id])
|
||||
)
|
||||
assert r.status_code == 403
|
||||
|
||||
# run diagnostic to find problems - none should be found
|
||||
with scopes_disabled():
|
||||
assert Food.find_problems() == ([], [], [], [], [])
|
||||
|
||||
@@ -10,8 +10,8 @@ from cookbook.helper import dal
|
||||
|
||||
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
|
||||
Storage, Sync, SyncLog, get_model_name)
|
||||
from .views import api, data, delete, edit, import_export, lists, trees, new, views, telegram
|
||||
Storage, Sync, SyncLog, Unit, get_model_name)
|
||||
from .views import api, data, delete, edit, import_export, lists, new, views, telegram
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'user-name', api.UserNameViewSet, basename='username')
|
||||
@@ -87,7 +87,7 @@ urlpatterns = [
|
||||
path('edit/recipe/convert/<int:pk>/', edit.convert_recipe, name='edit_convert_recipe'),
|
||||
|
||||
path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'),
|
||||
path('edit/ingredient/', edit.edit_ingredients, name='edit_food'), # TODO is this still needed?
|
||||
path('edit/ingredient/', edit.edit_ingredients, name='edit_food'), # TODO deprecate?
|
||||
|
||||
path('delete/recipe-source/<int:pk>/', delete.delete_recipe_source, name='delete_recipe_source'),
|
||||
|
||||
@@ -176,12 +176,12 @@ for m in generic_models:
|
||||
)
|
||||
)
|
||||
|
||||
tree_models = [Keyword, Food]
|
||||
for m in tree_models:
|
||||
vue_models = [Food, Keyword, Unit]
|
||||
for m in vue_models:
|
||||
py_name = get_model_name(m)
|
||||
url_name = py_name.replace('_', '-')
|
||||
|
||||
if c := getattr(trees, py_name, None):
|
||||
if c := getattr(lists, py_name, None):
|
||||
urlpatterns.append(
|
||||
path(
|
||||
f'list/{url_name}/', c, name=f'list_{py_name}'
|
||||
|
||||
@@ -48,7 +48,7 @@ from cookbook.models import (CookLog, Food, Ingredient, Keyword, MealPlan,
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
from cookbook.schemas import RecipeSchema, TreeSchema
|
||||
from cookbook.schemas import FilterSchema, RecipeSchema, TreeSchema
|
||||
from cookbook.serializer import (FoodSerializer, IngredientSerializer,
|
||||
KeywordSerializer, MealPlanSerializer,
|
||||
MealTypeSerializer, RecipeBookSerializer,
|
||||
@@ -98,6 +98,8 @@ class DefaultPagination(PageNumberPagination):
|
||||
|
||||
|
||||
class FuzzyFilterMixin(ViewSetMixin):
|
||||
schema = FilterSchema()
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(space=self.request.space)
|
||||
query = self.request.query_params.get('query', None)
|
||||
@@ -164,7 +166,12 @@ class MergeMixin(ViewSetMixin): # TODO update Units to use merge API
|
||||
if target in source.get_descendants_and_self():
|
||||
content = {'error': True, 'msg': _('Cannot merge with child object!')}
|
||||
return Response(content, status=status.HTTP_403_FORBIDDEN)
|
||||
isTree = True
|
||||
except AttributeError:
|
||||
# AttributeError probably means its not a tree, so can safely ignore
|
||||
isTree = False
|
||||
|
||||
try:
|
||||
for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]:
|
||||
linkManager = getattr(source, link.get_accessor_name())
|
||||
related = linkManager.all()
|
||||
@@ -181,9 +188,10 @@ class MergeMixin(ViewSetMixin): # TODO update Units to use merge API
|
||||
else:
|
||||
# a new scenario exists and needs to be handled
|
||||
raise NotImplementedError
|
||||
children = source.get_children().exclude(id=target.id)
|
||||
for c in children:
|
||||
c.move(target, 'sorted-child')
|
||||
if isTree:
|
||||
children = source.get_children().exclude(id=target.id)
|
||||
for c in children:
|
||||
c.move(target, 'sorted-child')
|
||||
content = {'msg': _(f'{source.name} was merged successfully with {target.name}')}
|
||||
source.delete()
|
||||
return Response(content, status=status.HTTP_200_OK)
|
||||
@@ -363,14 +371,12 @@ class KeywordViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
|
||||
class UnitViewSet(viewsets.ModelViewSet, FuzzyFilterMixin):
|
||||
class UnitViewSet(viewsets.ModelViewSet, MergeMixin, FuzzyFilterMixin):
|
||||
queryset = Unit.objects
|
||||
model = Unit
|
||||
serializer_class = UnitSerializer
|
||||
permission_classes = [CustomIsUser]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(space=self.request.space)
|
||||
return super().get_queryset()
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
|
||||
class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.views.generic import DeleteView
|
||||
from cookbook.helper.permission_helper import (GroupRequiredMixin,
|
||||
OwnerRequiredMixin,
|
||||
group_required)
|
||||
from cookbook.models import (Comment, InviteLink, Keyword, MealPlan, Recipe,
|
||||
from cookbook.models import (Comment, InviteLink, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport,
|
||||
Storage, Sync)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
@@ -73,16 +73,16 @@ class SyncDelete(GroupRequiredMixin, DeleteView):
|
||||
return context
|
||||
|
||||
|
||||
class KeywordDelete(GroupRequiredMixin, DeleteView):
|
||||
groups_required = ['user']
|
||||
template_name = "generic/delete_template.html"
|
||||
model = Keyword
|
||||
success_url = reverse_lazy('list_keyword')
|
||||
# class KeywordDelete(GroupRequiredMixin, DeleteView):
|
||||
# groups_required = ['user']
|
||||
# template_name = "generic/delete_template.html"
|
||||
# model = Keyword
|
||||
# success_url = reverse_lazy('list_keyword')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(KeywordDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Keyword")
|
||||
return context
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super(KeywordDelete, self).get_context_data(**kwargs)
|
||||
# context['title'] = _("Keyword")
|
||||
# return context
|
||||
|
||||
|
||||
class StorageDelete(GroupRequiredMixin, DeleteView):
|
||||
|
||||
@@ -10,14 +10,14 @@ from django.views.generic import UpdateView
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.forms import (CommentForm, ExternalRecipeForm, FoodForm,
|
||||
FoodMergeForm, KeywordForm, MealPlanForm,
|
||||
from cookbook.forms import (CommentForm, ExternalRecipeForm,
|
||||
FoodMergeForm, MealPlanForm,
|
||||
RecipeBookForm, StorageForm, SyncForm,
|
||||
UnitMergeForm)
|
||||
from cookbook.helper.permission_helper import (GroupRequiredMixin,
|
||||
OwnerRequiredMixin,
|
||||
group_required)
|
||||
from cookbook.models import (Comment, Food, Ingredient, Keyword, MealPlan,
|
||||
from cookbook.models import (Comment, Ingredient, MealPlan,
|
||||
MealType, Recipe, RecipeBook, RecipeImport,
|
||||
Storage, Sync, UserPreference)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
@@ -86,38 +86,38 @@ class SyncUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
|
||||
return context
|
||||
|
||||
|
||||
class KeywordUpdate(GroupRequiredMixin, UpdateView):
|
||||
groups_required = ['user']
|
||||
template_name = "generic/edit_template.html"
|
||||
model = Keyword
|
||||
form_class = KeywordForm
|
||||
# class KeywordUpdate(GroupRequiredMixin, UpdateView):
|
||||
# groups_required = ['user']
|
||||
# template_name = "generic/edit_template.html"
|
||||
# model = Keyword
|
||||
# form_class = KeywordForm
|
||||
|
||||
# TODO add msg box
|
||||
# # TODO add msg box
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('list_keyword')
|
||||
# def get_success_url(self):
|
||||
# return reverse('list_keyword')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['title'] = _("Keyword")
|
||||
return context
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context['title'] = _("Keyword")
|
||||
# return context
|
||||
|
||||
|
||||
class FoodUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
|
||||
groups_required = ['user']
|
||||
template_name = "generic/edit_template.html"
|
||||
model = Food
|
||||
form_class = FoodForm
|
||||
# class FoodUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
|
||||
# groups_required = ['user']
|
||||
# template_name = "generic/edit_template.html"
|
||||
# model = Food
|
||||
# form_class = FoodForm
|
||||
|
||||
# TODO add msg box
|
||||
# # TODO add msg box
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('edit_food', kwargs={'pk': self.object.pk})
|
||||
# def get_success_url(self):
|
||||
# return reverse('edit_food', kwargs={'pk': self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(FoodUpdate, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Food")
|
||||
return context
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super(FoodUpdate, self).get_context_data(**kwargs)
|
||||
# context['title'] = _("Food")
|
||||
# return context
|
||||
|
||||
|
||||
@group_required('admin')
|
||||
@@ -279,6 +279,7 @@ class ExternalRecipeUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
|
||||
return context
|
||||
|
||||
|
||||
# TODO deprecate
|
||||
@group_required('user')
|
||||
def edit_ingredients(request):
|
||||
if request.method == "POST":
|
||||
|
||||
@@ -5,11 +5,11 @@ from django.shortcuts import render
|
||||
from django.utils.translation import gettext as _
|
||||
from django_tables2 import RequestConfig
|
||||
|
||||
from cookbook.filters import FoodFilter, ShoppingListFilter
|
||||
from cookbook.filters import ShoppingListFilter
|
||||
from cookbook.helper.permission_helper import group_required
|
||||
from cookbook.models import (Food, InviteLink, RecipeImport,
|
||||
from cookbook.models import (InviteLink, RecipeImport,
|
||||
ShoppingList, Storage, SyncLog)
|
||||
from cookbook.tables import (ImportLogTable, IngredientTable, InviteLinkTable,
|
||||
from cookbook.tables import (ImportLogTable, InviteLinkTable,
|
||||
RecipeImportTable, ShoppingListTable, StorageTable)
|
||||
|
||||
|
||||
@@ -40,18 +40,18 @@ def recipe_import(request):
|
||||
)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def food(request):
|
||||
f = FoodFilter(request.GET, queryset=Food.objects.filter(space=request.space).all().order_by('pk'))
|
||||
# @group_required('user')
|
||||
# def food(request):
|
||||
# f = FoodFilter(request.GET, queryset=Food.objects.filter(space=request.space).all().order_by('pk'))
|
||||
|
||||
table = IngredientTable(f.qs)
|
||||
RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
# table = IngredientTable(f.qs)
|
||||
# RequestConfig(request, paginate={'per_page': 25}).configure(table)
|
||||
|
||||
return render(
|
||||
request,
|
||||
'generic/list_template.html',
|
||||
{'title': _("Ingredients"), 'table': table, 'filter': f}
|
||||
)
|
||||
# return render(
|
||||
# request,
|
||||
# 'generic/list_template.html',
|
||||
# {'title': _("Ingredients"), 'table': table, 'filter': f}
|
||||
# )
|
||||
|
||||
|
||||
@group_required('user')
|
||||
@@ -101,3 +101,52 @@ def invite_link(request):
|
||||
'table': table,
|
||||
'create_url': 'new_invite_link'
|
||||
})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def keyword(request):
|
||||
return render(
|
||||
request,
|
||||
'generic/model_template.html',
|
||||
{
|
||||
"title": _("Keywords"),
|
||||
"config": {
|
||||
'model': "KEYWORD",
|
||||
'recipe_param': 'keywords'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def food(request):
|
||||
# recipe-param is the name of the parameters used when filtering recipes by this attribute
|
||||
# model-name is the models.js name of the model, probably ALL-CAPS
|
||||
return render(
|
||||
request,
|
||||
'generic/model_template.html',
|
||||
{
|
||||
"title": _("Foods"),
|
||||
"config": {
|
||||
'model': "FOOD", # *REQUIRED* name of the model in models.js
|
||||
'recipe_param': 'foods' # *OPTIONAL* name of the listRecipes parameter if filtering on this attribute
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def unit(request):
|
||||
# recipe-param is the name of the parameters used when filtering recipes by this attribute
|
||||
# model-name is the models.js name of the model, probably ALL-CAPS
|
||||
return render(
|
||||
request,
|
||||
'generic/model_template.html',
|
||||
{
|
||||
"title": _("Units"),
|
||||
"config": {
|
||||
'model': "UNIT", # *REQUIRED* name of the model in models.js
|
||||
'recipe_param': 'units' # *OPTIONAL* name of the listRecipes parameter if filtering on this attribute
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -12,11 +12,11 @@ from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import CreateView
|
||||
|
||||
from cookbook.forms import (ImportRecipeForm, InviteLinkForm, KeywordForm,
|
||||
from cookbook.forms import (ImportRecipeForm, InviteLinkForm,
|
||||
MealPlanForm, RecipeBookForm, Storage, StorageForm)
|
||||
from cookbook.helper.permission_helper import (GroupRequiredMixin,
|
||||
group_required)
|
||||
from cookbook.models import (InviteLink, Keyword, MealPlan, MealType, Recipe,
|
||||
from cookbook.models import (InviteLink, MealPlan, MealType, Recipe,
|
||||
RecipeBook, RecipeImport, ShareLink, Step, UserPreference)
|
||||
from cookbook.views.edit import SpaceFormMixing
|
||||
|
||||
@@ -60,22 +60,22 @@ def share_link(request, pk):
|
||||
return HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': pk, 'share': link.uuid}))
|
||||
|
||||
|
||||
class KeywordCreate(GroupRequiredMixin, CreateView):
|
||||
groups_required = ['user']
|
||||
template_name = "generic/new_template.html"
|
||||
model = Keyword
|
||||
form_class = KeywordForm
|
||||
success_url = reverse_lazy('list_keyword')
|
||||
# class KeywordCreate(GroupRequiredMixin, CreateView):
|
||||
# groups_required = ['user']
|
||||
# template_name = "generic/new_template.html"
|
||||
# model = Keyword
|
||||
# form_class = KeywordForm
|
||||
# success_url = reverse_lazy('list_keyword')
|
||||
|
||||
def form_valid(self, form):
|
||||
form.cleaned_data['space'] = self.request.space
|
||||
form.save()
|
||||
return HttpResponseRedirect(reverse('list_keyword'))
|
||||
# def form_valid(self, form):
|
||||
# form.cleaned_data['space'] = self.request.space
|
||||
# form.save()
|
||||
# return HttpResponseRedirect(reverse('list_keyword'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(KeywordCreate, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Keyword")
|
||||
return context
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super(KeywordCreate, self).get_context_data(**kwargs)
|
||||
# context['title'] = _("Keyword")
|
||||
# return context
|
||||
|
||||
|
||||
class StorageCreate(GroupRequiredMixin, CreateView):
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
from cookbook.helper.permission_helper import group_required
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def keyword(request):
|
||||
return render(request, 'model/keyword_template.html', {})
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def food(request):
|
||||
return render(request, 'model/food_template.html', {})
|
||||
13
vue/package-lock.json
generated
13
vue/package-lock.json
generated
@@ -44,7 +44,7 @@
|
||||
"eslint-plugin-vue": "^7.10.0",
|
||||
"typescript": "~4.3.2",
|
||||
"vue-cli-plugin-i18n": "^2.1.1",
|
||||
"webpack-bundle-tracker": "1.1.0",
|
||||
"webpack-bundle-tracker": "1.3.0",
|
||||
"workbox-expiration": "^6.0.2",
|
||||
"workbox-navigation-preload": "^6.0.2",
|
||||
"workbox-precaching": "^6.0.2",
|
||||
@@ -14289,15 +14289,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-bundle-tracker": {
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-1.3.0.tgz",
|
||||
"integrity": "sha512-cs3QMgW5F0mE0e91X/SuEq2MUfu2LqpjFSdfINsOmNt/T4v39EESNhJ+P8d5lmbfFL6Z1z7n6LlpVCERTdDDvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
@@ -24887,14 +24887,15 @@
|
||||
}
|
||||
},
|
||||
"webpack-bundle-tracker": {
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-tracker/-/webpack-bundle-tracker-1.3.0.tgz",
|
||||
"integrity": "sha512-cs3QMgW5F0mE0e91X/SuEq2MUfu2LqpjFSdfINsOmNt/T4v39EESNhJ+P8d5lmbfFL6Z1z7n6LlpVCERTdDDvQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"strip-ansi": "^6.0.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,605 +0,0 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 d-none d-md-block">
|
||||
|
||||
</div>
|
||||
<div class="col-xl-8 col-12">
|
||||
<!-- TODO only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different component? -->
|
||||
<div class="container-fluid d-flex flex-column flex-grow-1" :class="{'vh-100' : show_split}">
|
||||
<!-- expanded options box -->
|
||||
<div class="row flex-shrink-0">
|
||||
<div class="col col-md-12">
|
||||
<b-collapse id="collapse_advanced" class="mt-2" v-model="advanced_visible">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3" style="margin-top: 1vh">
|
||||
<div class="btn btn-primary btn-block text-uppercase" @click="startAction({'action':'new'})">
|
||||
{{ this.$t('New_Keyword') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3" style="margin-top: 1vh">
|
||||
<button class="btn btn-primary btn-block text-uppercase" @click="resetSearch">
|
||||
{{ this.$t('Reset_Search') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3" style="position: relative; margin-top: 1vh">
|
||||
<b-form-checkbox v-model="show_split" name="check-button"
|
||||
class="shadow-none"
|
||||
style="position:relative;top: 50%; transform: translateY(-50%);" switch>
|
||||
{{ this.$t('show_split_screen') }}
|
||||
</b-form-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row flex-shrink-0">
|
||||
<!-- search box -->
|
||||
<div class="col col-md">
|
||||
<b-input-group class="mt-3">
|
||||
<b-input class="form-control" v-model="search_input"
|
||||
v-bind:placeholder="this.$t('Search')"></b-input>
|
||||
<b-input-group-append>
|
||||
<b-button v-b-toggle.collapse_advanced variant="primary" class="shadow-none">
|
||||
<i class="fas fa-caret-down" v-if="!advanced_visible"></i>
|
||||
<i class="fas fa-caret-up" v-if="advanced_visible"></i>
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</div>
|
||||
|
||||
<!-- split side search -->
|
||||
<div class="col col-md" v-if="show_split">
|
||||
<b-input-group class="mt-3">
|
||||
<b-input class="form-control" v-model="search_input2"
|
||||
v-bind:placeholder="this.$t('Search')"></b-input>
|
||||
</b-input-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- only show scollbars in split mode, but this doesn't interact well with infinite scroll, maybe a different componenet? -->
|
||||
<div class="row" :class="{'overflow-hidden' : show_split}" style="margin-top: 2vh">
|
||||
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
|
||||
<generic-horizontal-card v-for="kw in keywords" v-bind:key="kw.id"
|
||||
:item=kw
|
||||
item_type="Keyword"
|
||||
:draggable="true"
|
||||
:merge="true"
|
||||
:move="true"
|
||||
@item-action="startAction($event, 'left')"
|
||||
/>
|
||||
<infinite-loading
|
||||
:identifier='left'
|
||||
@infinite="infiniteHandler($event, 'left')"
|
||||
spinner="waveDots">
|
||||
<template v-slot:no-more><span/></template>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
<!-- right side keyword cards -->
|
||||
<div class="col col-md mh-100 overflow-auto " v-if="show_split">
|
||||
<generic-horizontal-card v-for="kw in keywords2" v-bind:key="kw.id"
|
||||
:item=kw
|
||||
item_type="Keyword"
|
||||
:draggable="true"
|
||||
:merge="true"
|
||||
:move="true"
|
||||
@item-action="startAction($event, 'right')"
|
||||
/>
|
||||
<infinite-loading
|
||||
:identifier='right'
|
||||
@infinite="infiniteHandler($event, 'right')"
|
||||
spinner="waveDots">
|
||||
<template v-slot:no-more><span/></template>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-md-2 d-none d-md-block">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO Modals can probably be made generic and moved to component -->
|
||||
<!-- edit modal -->
|
||||
<b-modal class="modal"
|
||||
:id="'id_modal_keyword_edit'"
|
||||
@shown="prepareEmoji"
|
||||
:title="this.$t('Edit_Keyword')"
|
||||
:ok-title="this.$t('Save')"
|
||||
:cancel-title="this.$t('Cancel')"
|
||||
@ok="saveKeyword">
|
||||
<form>
|
||||
<label for="id_keyword_name_edit">{{ this.$t('Name') }}</label>
|
||||
<input class="form-control" type="text" id="id_keyword_name_edit" v-model="this_item.name">
|
||||
<label for="id_keyword_description_edit">{{ this.$t('Description') }}</label>
|
||||
<input class="form-control" type="text" id="id_keyword_description_edit" v-model="this_item.description">
|
||||
<label for="id_keyword_icon_edit">{{ this.$t('Icon') }}</label>
|
||||
<twemoji-textarea
|
||||
id="id_keyword_icon_edit"
|
||||
ref="_edit"
|
||||
:emojiData="emojiDataAll"
|
||||
:emojiGroups="emojiGroups"
|
||||
triggerType="hover"
|
||||
recentEmojisFeat="true"
|
||||
recentEmojisStorage="local"
|
||||
@contentChanged="setIcon"
|
||||
/>
|
||||
</form>
|
||||
</b-modal>
|
||||
<!-- delete modal -->
|
||||
<b-modal class="modal"
|
||||
:id="'id_modal_keyword_delete'"
|
||||
:title="this.$t('Delete_Keyword')"
|
||||
:ok-title="this.$t('Delete')"
|
||||
:cancel-title="this.$t('Cancel')"
|
||||
@ok="delKeyword(this_item.id)">
|
||||
{{this.$t("delete_confimation", {'kw': this_item.name})}} {{this_item.name}}
|
||||
</b-modal>
|
||||
<!-- move modal -->
|
||||
<b-modal class="modal" v-if="models"
|
||||
:id="'id_modal_keyword_move'"
|
||||
:title="this.$t('Move_Keyword')"
|
||||
:ok-title="this.$t('Move')"
|
||||
:cancel-title="this.$t('Cancel')"
|
||||
@ok="moveKeyword(this_item.id, this_item.target.id)">
|
||||
{{ this.$t("move_selection", {'child': this_item.name}) }}
|
||||
<generic-multiselect
|
||||
@change="this_item.target=$event.val"
|
||||
label="name"
|
||||
:model="models.KEYWORD"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': 0,'name': $t('Root')}]"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="this.$t('Search')">
|
||||
</generic-multiselect>
|
||||
</b-modal>
|
||||
<!-- merge modal -->
|
||||
<b-modal class="modal" v-if="models"
|
||||
:id="'id_modal_keyword_merge'"
|
||||
:title="this.$t('Merge_Keyword')"
|
||||
:ok-title="this.$t('Merge')"
|
||||
:cancel-title="this.$t('Cancel')"
|
||||
@ok="mergeKeyword(this_item.id, this_item.target.id)">
|
||||
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('keyword')}) }}
|
||||
<generic-multiselect
|
||||
@change="this_item.target=$event.val"
|
||||
:model="models.KEYWORD"
|
||||
:multiple="false"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="this.$t('Search')">
|
||||
</generic-multiselect>
|
||||
</b-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import axios from "axios";
|
||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
||||
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
||||
|
||||
import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import _debounce from 'lodash/debounce'
|
||||
|
||||
import {ToastMixin} from "@/utils/utils";
|
||||
|
||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
||||
import GenericHorizontalCard from "@/components/GenericHorizontalCard";
|
||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
import InfiniteLoading from 'vue-infinite-loading';
|
||||
|
||||
// would move with modals if made generic
|
||||
import {TwemojiTextarea} from '@kevinfaguiar/vue-twemoji-picker';
|
||||
// TODO add localization
|
||||
import EmojiAllData from '@kevinfaguiar/vue-twemoji-picker/emoji-data/en/emoji-all-groups.json';
|
||||
import EmojiGroups from '@kevinfaguiar/vue-twemoji-picker/emoji-data/emoji-groups.json';
|
||||
// end move with generic modals
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
import {Models} from "@/utils/models";
|
||||
|
||||
export default {
|
||||
name: 'KeywordListView',
|
||||
mixins: [ToastMixin],
|
||||
components: {TwemojiTextarea, GenericHorizontalCard, GenericMultiselect, InfiniteLoading},
|
||||
computed: {
|
||||
// move with generic modals
|
||||
emojiDataAll() {
|
||||
return EmojiAllData;
|
||||
},
|
||||
emojiGroups() {
|
||||
return EmojiGroups;
|
||||
}
|
||||
// end move with generic modals
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keywords: [],
|
||||
keywords2: [],
|
||||
models: Models,
|
||||
show_split: false,
|
||||
search_input: '',
|
||||
search_input2: '',
|
||||
advanced_visible: false,
|
||||
right_page: 0,
|
||||
right: +new Date(),
|
||||
isDirtyRight: false,
|
||||
left_page: 0,
|
||||
left: +new Date(),
|
||||
isDirtyLeft: false,
|
||||
this_item: {
|
||||
'id': -1,
|
||||
'name': '',
|
||||
'description': '',
|
||||
'icon': '',
|
||||
'target': {
|
||||
'id': -1,
|
||||
'name': ''
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
search_input: _debounce(function() {
|
||||
this.left_page = 0
|
||||
this.keywords = []
|
||||
this.left += 1
|
||||
}, 700),
|
||||
search_input2: _debounce(function() {
|
||||
this.right_page = 0
|
||||
this.keywords2 = []
|
||||
this.right += 1
|
||||
}, 700)
|
||||
},
|
||||
methods: {
|
||||
resetSearch: function () {
|
||||
if (this.search_input !== '') {
|
||||
this.search_input = ''
|
||||
} else {
|
||||
this.left_page = 0
|
||||
this.keywords = []
|
||||
this.left += 1
|
||||
}
|
||||
if (this.search_input2 !== '') {
|
||||
this.search_input2 = ''
|
||||
} else {
|
||||
this.right_page = 0
|
||||
this.keywords2 = []
|
||||
this.right += 1
|
||||
}
|
||||
|
||||
},
|
||||
// TODO should model actions be included with the context menu? the card? a seperate mixin avaible to all?
|
||||
startAction: function(e, col) {
|
||||
let target = e.target || null
|
||||
let source = e.source || null
|
||||
|
||||
if (e.action == 'delete') {
|
||||
this.this_item = source
|
||||
this.$bvModal.show('id_modal_keyword_delete')
|
||||
} else if (e.action == 'new') {
|
||||
this.this_item = {}
|
||||
this.$bvModal.show('id_modal_keyword_edit')
|
||||
} else if (e.action == 'edit') {
|
||||
this.this_item = source
|
||||
this.$bvModal.show('id_modal_keyword_edit')
|
||||
} else if (e.action === 'move') {
|
||||
this.this_item = source
|
||||
if (target == null) {
|
||||
this.$bvModal.show('id_modal_keyword_move')
|
||||
} else {
|
||||
this.moveKeyword(source.id, target.id)
|
||||
}
|
||||
} else if (e.action === 'merge') {
|
||||
this.this_item = source
|
||||
if (target == null) {
|
||||
this.$bvModal.show('id_modal_keyword_merge')
|
||||
} else {
|
||||
this.mergeKeyword(e.source.id, e.target.id)
|
||||
}
|
||||
} else if (e.action === 'get-children') {
|
||||
if (source.show_children) {
|
||||
Vue.set(source, 'show_children', false)
|
||||
} else {
|
||||
this.this_item = source
|
||||
this.getChildren(col, source)
|
||||
}
|
||||
} else if (e.action === 'get-recipes') {
|
||||
if (source.show_recipes) {
|
||||
Vue.set(source, 'show_recipes', false)
|
||||
} else {
|
||||
this.this_item = source
|
||||
this.getRecipes(col, source)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
saveKeyword: function () {
|
||||
let apiClient = new ApiApiFactory()
|
||||
let kw = {
|
||||
name: this.this_item.name,
|
||||
description: this.this_item.description,
|
||||
icon: this.this_item.icon,
|
||||
}
|
||||
if (!this.this_item.id) { // if there is no item id assume its a new item
|
||||
apiClient.createKeyword(kw).then(result => {
|
||||
// place all new keywords at the top of the list - could sort instead
|
||||
this.keywords = [result.data].concat(this.keywords)
|
||||
// this creates a deep copy to make sure that columns stay independent
|
||||
if (this.show_split){
|
||||
this.keywords2 = [JSON.parse(JSON.stringify(result.data))].concat(this.keywords2)
|
||||
} else {
|
||||
this.keywords2 = []
|
||||
}
|
||||
this.this_item={}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.this_item = {}
|
||||
})
|
||||
} else {
|
||||
apiClient.partialUpdateKeyword(this.this_item.id, kw).then(result => {
|
||||
this.refreshCard(this.this_item.id)
|
||||
this.this_item={}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.this_item = {}
|
||||
})
|
||||
}
|
||||
},
|
||||
delKeyword: function (id) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
|
||||
apiClient.destroyKeyword(id).then(response => {
|
||||
this.destroyCard(id)
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.this_item = {}
|
||||
})
|
||||
|
||||
},
|
||||
moveKeyword: function (source_id, target_id) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.moveKeyword(String(source_id), String(target_id)).then(result => {
|
||||
if (target_id === 0) {
|
||||
let kw = this.findKeyword(this.keywords, source_id) || this.findKeyword(this.keywords2, source_id)
|
||||
kw.parent = null
|
||||
|
||||
if (this.show_split){
|
||||
this.destroyCard(source_id) // order matters, destroy old card before adding it back in at root
|
||||
|
||||
this.keywords = [kw].concat(this.keywords)
|
||||
this.keywords2 = [JSON.parse(JSON.stringify(kw))].concat(this.keywords2)
|
||||
} else {
|
||||
this.destroyCard(source_id)
|
||||
this.keywords = [kw].concat(this.keywords)
|
||||
this.keywords2 = []
|
||||
}
|
||||
} else {
|
||||
this.destroyCard(source_id)
|
||||
this.refreshCard(target_id)
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
// TODO none of the error checking works because the openapi generated functions don't throw an error?
|
||||
// or i'm capturing it incorrectly
|
||||
console.log(err)
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
mergeKeyword: function (source_id, target_id) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
apiClient.mergeKeyword(String(source_id), String(target_id)).then(result => {
|
||||
this.destroyCard(source_id)
|
||||
this.refreshCard(target_id)
|
||||
}).catch((err) => {
|
||||
console.log('Error', err)
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
// TODO: DRY the listKeyword functions (refresh, get children, infinityHandler ) can probably all be consolidated into a single function
|
||||
getChildren: function(col, kw){
|
||||
let apiClient = new ApiApiFactory()
|
||||
let parent = {}
|
||||
let query = undefined
|
||||
let page = undefined
|
||||
let root = kw.id
|
||||
let tree = undefined
|
||||
let pageSize = 200
|
||||
|
||||
apiClient.listKeywords(query, root, tree, page, pageSize).then(result => {
|
||||
if (col == 'left') {
|
||||
parent = this.findKeyword(this.keywords, kw.id)
|
||||
} else if (col == 'right'){
|
||||
parent = this.findKeyword(this.keywords2, kw.id)
|
||||
}
|
||||
if (parent) {
|
||||
Vue.set(parent, 'children', result.data.results)
|
||||
Vue.set(parent, 'show_children', true)
|
||||
Vue.set(parent, 'show_recipes', false)
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getRecipes: function(col, kw){
|
||||
let apiClient = new ApiApiFactory()
|
||||
let parent = {}
|
||||
let pageSize = 200
|
||||
let keyword = String(kw.id)
|
||||
|
||||
apiClient.listRecipes(
|
||||
undefined, keyword, undefined, undefined, undefined, undefined,
|
||||
undefined, undefined, undefined, undefined, undefined, pageSize, undefined
|
||||
).then(result => {
|
||||
if (col == 'left') {
|
||||
parent = this.findKeyword(this.keywords, kw.id)
|
||||
} else if (col == 'right'){
|
||||
parent = this.findKeyword(this.keywords2, kw.id)
|
||||
}
|
||||
if (parent) {
|
||||
Vue.set(parent, 'recipes', result.data.results)
|
||||
Vue.set(parent, 'show_recipes', true)
|
||||
Vue.set(parent, 'show_children', false)
|
||||
}
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
refreshCard: function(id){
|
||||
let target = {}
|
||||
let apiClient = new ApiApiFactory()
|
||||
let idx = undefined
|
||||
let idx2 = undefined
|
||||
apiClient.retrieveKeyword(id).then(result => {
|
||||
target = this.findKeyword(this.keywords, id) || this.findKeyword(this.keywords2, id)
|
||||
|
||||
if (target.parent) {
|
||||
let parent = this.findKeyword(this.keywords, target.parent)
|
||||
let parent2 = this.findKeyword(this.keywords2, target.parent)
|
||||
|
||||
if (parent) {
|
||||
if (parent.show_children){
|
||||
idx = parent.children.indexOf(parent.children.find(kw => kw.id === target.id))
|
||||
Vue.set(parent.children, idx, result.data)
|
||||
}
|
||||
}
|
||||
if (parent2){
|
||||
if (parent2.show_children){
|
||||
idx2 = parent2.children.indexOf(parent2.children.find(kw => kw.id === target.id))
|
||||
// deep copy to force columns to be indepedent
|
||||
Vue.set(parent2.children, idx2, JSON.parse(JSON.stringify(result.data)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
idx = this.keywords.indexOf(this.keywords.find(kw => kw.id === target.id))
|
||||
idx2 = this.keywords2.indexOf(this.keywords2.find(kw => kw.id === target.id))
|
||||
Vue.set(this.keywords, idx, result.data)
|
||||
Vue.set(this.keywords2, idx2, JSON.parse(JSON.stringify(result.data)))
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
findKeyword: function(card_list, id){
|
||||
let card_length = card_list?.length ?? 0
|
||||
if (card_length == 0) {
|
||||
return false
|
||||
}
|
||||
let cards = card_list.filter(obj => obj.id == id)
|
||||
if (cards.length == 1) {
|
||||
return cards[0]
|
||||
} else if (cards.length == 0) {
|
||||
for (const c of card_list.filter(x => x.show_children == true)) {
|
||||
cards = this.findKeyword(c.children, id)
|
||||
if (cards) {
|
||||
return cards
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('something terrible happened')
|
||||
}
|
||||
},
|
||||
// this would move with modals with mixin?
|
||||
prepareEmoji: function() {
|
||||
this.$refs._edit.addText(this.this_item.icon || '');
|
||||
this.$refs._edit.blur()
|
||||
document.getElementById('btn-emoji-default').disabled = true;
|
||||
},
|
||||
// this would move with modals with mixin?
|
||||
setIcon: function(icon) {
|
||||
this.this_item.icon = icon
|
||||
},
|
||||
infiniteHandler: function($state, col) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
let query = (col==='left') ? this.search_input : this.search_input2
|
||||
let page = (col==='left') ? this.left_page + 1 : this.right_page + 1
|
||||
let root = undefined
|
||||
let tree = undefined
|
||||
let pageSize = undefined
|
||||
|
||||
if (query === '') {
|
||||
query = undefined
|
||||
root = 0
|
||||
}
|
||||
|
||||
apiClient.listKeywords(query, root, tree, page, pageSize).then(result => {
|
||||
if (result.data.results.length){
|
||||
if (col ==='left') {
|
||||
this.left_page+=1
|
||||
this.keywords = this.keywords.concat(result.data.results)
|
||||
$state.loaded();
|
||||
if (this.keywords.length >= result.data.count) {
|
||||
$state.complete();
|
||||
}
|
||||
} else if (col ==='right') {
|
||||
this.right_page+=1
|
||||
this.keywords2 = this.keywords2.concat(result.data.results)
|
||||
$state.loaded();
|
||||
if (this.keywords2.length >= result.data.count) {
|
||||
$state.complete();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('no data returned')
|
||||
$state.complete();
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
$state.complete();
|
||||
})
|
||||
},
|
||||
destroyCard: function(id) {
|
||||
let kw = this.findKeyword(this.keywords, id)
|
||||
let kw2 = this.findKeyword(this.keywords2, id)
|
||||
let p_id = undefined
|
||||
p_id = kw?.parent ?? kw2.parent
|
||||
|
||||
if (p_id) {
|
||||
let parent = this.findKeyword(this.keywords, p_id)
|
||||
let parent2 = this.findKeyword(this.keywords2, p_id)
|
||||
if (parent){
|
||||
Vue.set(parent, 'numchild', parent.numchild - 1)
|
||||
if (parent.show_children) {
|
||||
let idx = parent.children.indexOf(parent.children.find(kw => kw.id === id))
|
||||
Vue.delete(parent.children, idx)
|
||||
}
|
||||
}
|
||||
if (parent2){
|
||||
Vue.set(parent2, 'numchild', parent2.numchild - 1)
|
||||
if (parent2.show_children) {
|
||||
let idx = parent2.children.indexOf(parent2.children.find(kw => kw.id === id))
|
||||
Vue.delete(parent2.children, idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.keywords = this.keywords.filter(kw => kw.id != id)
|
||||
this.keywords2 = this.keywords2.filter(kw => kw.id != id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,10 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import App from './KeywordListView'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
i18n,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
@@ -10,6 +10,8 @@
|
||||
@finish-action="finishAction"/>
|
||||
<generic-split-lists v-if="this_model"
|
||||
:list_name="this_model.name"
|
||||
:right_counts="right_counts"
|
||||
:left_counts="left_counts"
|
||||
@reset="resetList"
|
||||
@get-list="getItems"
|
||||
@item-action="startAction">
|
||||
@@ -19,13 +21,18 @@
|
||||
:item=i
|
||||
:item_type="this_model.name"
|
||||
:draggable="true"
|
||||
:merge="true"
|
||||
:move="true"
|
||||
:merge="this_model['merge'] !== false"
|
||||
:move="this_model['move'] !== false"
|
||||
@item-action="startAction($event, 'left')">
|
||||
<!-- foods can also be a recipe, show link to the recipe if it exists -->
|
||||
<template v-slot:upper-right>
|
||||
<b-button v-if="i.recipe" v-b-tooltip.hover :title="i.recipe.name"
|
||||
class=" btn fas fa-book-open p-0 border-0" variant="link" :href="i.recipe.url"/>
|
||||
<!-- keywords can have icons - if it exists, display it -->
|
||||
<b-button v-if="i.icon"
|
||||
class=" btn p-0 border-0" variant="link">
|
||||
{{i.icon}}
|
||||
</b-button>
|
||||
</template>
|
||||
</generic-horizontal-card>
|
||||
</template>
|
||||
@@ -34,8 +41,8 @@
|
||||
:item=i
|
||||
:item_type="this_model.name"
|
||||
:draggable="true"
|
||||
:merge="true"
|
||||
:move="true"
|
||||
:merge="this_model['merge'] !== false"
|
||||
:move="this_model['move'] !== false"
|
||||
@item-action="startAction($event, 'right')">
|
||||
<!-- foods can also be a recipe, show link to the recipe if it exists -->
|
||||
<template v-slot:upper-right>
|
||||
@@ -67,7 +74,9 @@ import GenericModalForm from "@/components/Modals/GenericModalForm";
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'FoodListView', // TODO: make generic name
|
||||
// TODO ApiGenerator doesn't capture and share error information - would be nice to share error details when available
|
||||
// or i'm capturing it incorrectly
|
||||
name: 'ModelListView',
|
||||
mixins: [CardMixin, ToastMixin, ApiMixin],
|
||||
components: {GenericHorizontalCard, GenericSplitLists, GenericModalForm},
|
||||
data() {
|
||||
@@ -75,26 +84,28 @@ export default {
|
||||
// this.Models and this.Actions inherited from ApiMixin
|
||||
items_left: [],
|
||||
items_right: [],
|
||||
load_more_left: true,
|
||||
load_more_right: true,
|
||||
right_counts: {'max': 9999, 'current': 0},
|
||||
left_counts: {'max': 9999, 'current': 0},
|
||||
this_model: undefined,
|
||||
this_action: undefined,
|
||||
this_recipe_param: undefined,
|
||||
this_item: {},
|
||||
this_target: {},
|
||||
show_modal: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.this_model = this.Models.FOOD //TODO: mounted method to calcuate
|
||||
// value is passed from lists.py
|
||||
let model_config = JSON.parse(document.getElementById('model_config').textContent)
|
||||
this.this_model = this.Models[model_config?.model]
|
||||
this.this_recipe_param = model_config?.recipe_param
|
||||
},
|
||||
methods: {
|
||||
// this.genericAPI inherited from ApiMixin
|
||||
resetList: function (e) {
|
||||
if (e.column === 'left') {
|
||||
this.items_left = []
|
||||
} else if (e.column === 'right') {
|
||||
this.items_right = []
|
||||
}
|
||||
this['items_' + e.column] = []
|
||||
this[e.column + '_counts'].max = 9999 + Math.random()
|
||||
this[e.column + '_counts'].current = 0
|
||||
},
|
||||
startAction: function (e, param) {
|
||||
let source = e?.source ?? {}
|
||||
@@ -175,28 +186,18 @@ export default {
|
||||
}
|
||||
this.clearState()
|
||||
},
|
||||
getItems: function (params, callback) {
|
||||
getItems: function (params) {
|
||||
let column = params?.column ?? 'left'
|
||||
// TODO: does this need to be a callback?
|
||||
this.genericAPI(this.this_model, this.Actions.LIST, params).then((result) => {
|
||||
if (result.data.results.length) {
|
||||
if (column === 'left') {
|
||||
// if paginated results are in result.data.results otherwise just result.data
|
||||
this.items_left = this.items_left.concat(result.data?.results ?? result.data)
|
||||
} else if (column === 'right') {
|
||||
this.items_right = this.items_right.concat(result.data?.results ?? result.data)
|
||||
}
|
||||
// are the total elements less than the length of the array? if so, stop loading
|
||||
// TODO: generalize this to handle results in result.data
|
||||
callback(result.data.count > (column === "left" ? this.items_left.length : this.items_right.length))
|
||||
this['items_' + column] = this['items_' + column].concat(result.data?.results)
|
||||
this[column + '_counts']['current'] = this['items_' + column].length
|
||||
this[column + '_counts']['max'] = result.data.count
|
||||
} else {
|
||||
callback(false) // stop loading
|
||||
this[column + '_counts']['current'] = 0
|
||||
this[column + '_counts']['max'] = 0
|
||||
console.log('no data returned')
|
||||
}
|
||||
// return true if total objects are still less than the length of the list
|
||||
// TODO this needs generalized to handle non-paginated data
|
||||
callback(result.data.count < (column === "left" ? this.items_left.length : this.items_right.length))
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
||||
@@ -208,10 +209,11 @@ export default {
|
||||
saveThis: function (thisItem) {
|
||||
if (!thisItem?.id) { // if there is no item id assume it's a new item
|
||||
this.genericAPI(this.this_model, this.Actions.CREATE, thisItem).then((result) => {
|
||||
// place all new items at the top of the list - could sort instead
|
||||
this.items_left = [result.data].concat(this.items_left)
|
||||
// look for and destroy any existing cards to prevent duplicates in the GET case of get_or_create
|
||||
// then place all new items at the top of the list - could sort instead
|
||||
this.items_left = [result.data].concat(this.destroyCard(result?.data?.id, this.items_left))
|
||||
// this creates a deep copy to make sure that columns stay independent
|
||||
this.items_right = [{...result.data}].concat(this.items_right)
|
||||
this.items_right = [{...result.data}].concat(this.destroyCard(result?.data?.id, this.items_right))
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_CREATE)
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
@@ -233,14 +235,15 @@ export default {
|
||||
this.clearState()
|
||||
return
|
||||
}
|
||||
if (source_id === undefined || target_id === undefined) {
|
||||
let item = this.findCard(source_id, this.items_left) || this.findCard(source_id, this.items_right)
|
||||
if (source_id === undefined || target_id === undefined || item?.parent == target_id) {
|
||||
this.makeToast(this.$t('Warning'), this.$t('Nothing to do'), 'warning')
|
||||
this.clearState()
|
||||
return
|
||||
}
|
||||
this.genericAPI(this.this_model, this.Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||
if (target_id === 0) {
|
||||
let item = this.findCard(source_id, this.items_left) || this.findCard(source_id, this.items_right)
|
||||
|
||||
this.items_left = [item].concat(this.destroyCard(source_id, this.items_left)) // order matters, destroy old card before adding it back in at root
|
||||
this.items_right = [...[item]].concat(this.destroyCard(source_id, this.items_right)) // order matters, destroy old card before adding it back in at root
|
||||
item.parent = null
|
||||
@@ -252,8 +255,6 @@ export default {
|
||||
// TODO make standard toast
|
||||
this.makeToast(this.$t('Success'), 'Succesfully moved resource', 'success')
|
||||
}).catch((err) => {
|
||||
// TODO none of the error checking works because the openapi generated functions don't throw an error?
|
||||
// or i'm capturing it incorrectly
|
||||
console.log(err)
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
})
|
||||
@@ -292,7 +293,7 @@ export default {
|
||||
'pageSize': 200
|
||||
}
|
||||
this.genericAPI(this.this_model, this.Actions.LIST, options).then((result) => {
|
||||
parent = this.findCard(item.id, col === 'left' ? this.items_left : this.items_right)
|
||||
parent = this.findCard(item.id, this['items_' + col])
|
||||
if (parent) {
|
||||
Vue.set(parent, 'children', result.data.results)
|
||||
Vue.set(parent, 'show_children', true)
|
||||
@@ -303,15 +304,14 @@ export default {
|
||||
this.makeToast(this.$t('Error'), err.bodyText, 'danger')
|
||||
})
|
||||
},
|
||||
getRecipes: function (col, food) {
|
||||
getRecipes: function (col, item) {
|
||||
let parent = {}
|
||||
// TODO: make this generic
|
||||
let options = {
|
||||
'foods': food.id,
|
||||
'pageSize': 200
|
||||
}
|
||||
let options = {'pageSize': 200}
|
||||
options[this.this_recipe_param] = item.id
|
||||
|
||||
this.genericAPI(this.Models.RECIPE, this.Actions.LIST, options).then((result) => {
|
||||
parent = this.findCard(food.id, col === 'left' ? this.items_left : this.items_right)
|
||||
parent = this.findCard(item.id, this['items_' + col])
|
||||
if (parent) {
|
||||
Vue.set(parent, 'recipes', result.data.results)
|
||||
Vue.set(parent, 'show_recipes', true)
|
||||
@@ -1,5 +1,5 @@
|
||||
import Vue from 'vue'
|
||||
import App from './FoodListView'
|
||||
import App from './ModelListView'
|
||||
import i18n from '@/i18n'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
@@ -396,6 +396,7 @@ export default {
|
||||
this.settings.search_input,
|
||||
this.settings.search_keywords,
|
||||
this.settings.search_foods,
|
||||
undefined,
|
||||
this.settings.search_books.map(function (A) {
|
||||
return A["id"];
|
||||
}),
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<h5 class="m-0 mt-1 text-truncate">{{ item[title] }}</h5>
|
||||
<div class= "m-0 text-truncate">{{ item[subtitle] }}</div>
|
||||
<div class="mt-auto mb-1 d-flex flex-row justify-content-end">
|
||||
<div v-if="item[child_count] !=0" class="mx-2 btn btn-link btn-sm"
|
||||
<div v-if="item[child_count]" class="mx-2 btn btn-link btn-sm"
|
||||
style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':item})">
|
||||
<div v-if="!item.show_children">{{ item[child_count] }} {{ item_type }}</div>
|
||||
<div v-else>{{ text.hide_children }}</div>
|
||||
@@ -112,6 +112,7 @@ export default {
|
||||
recipes: {type: String, default: 'recipes'},
|
||||
move: {type: Boolean, default: false},
|
||||
merge: {type: Boolean, default: false},
|
||||
tree: {type: Boolean, default: false},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
|
||||
<!-- only show scollbars in split mode -->
|
||||
<!-- weird behavior when switching to split mode, infinite scoll doesn't trigger if
|
||||
<!-- TODO: weird behavior when switching to split mode, infinite scoll doesn't trigger if
|
||||
bottom of page is in viewport can trigger by scrolling page (not column) up -->
|
||||
<div class="row" :class="{'overflow-hidden' : show_split}">
|
||||
<div class="col col-md" :class="{'mh-100 overflow-auto' : show_split}">
|
||||
@@ -74,6 +74,7 @@
|
||||
@infinite="infiniteHandler($event, 'left')"
|
||||
spinner="waveDots">
|
||||
<template v-slot:no-more><span/></template>
|
||||
<template v-slot:no-results><span>{{$t('No_Results')}}</span></template>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
<!-- right side cards -->
|
||||
@@ -84,6 +85,7 @@
|
||||
@infinite="infiniteHandler($event, 'right')"
|
||||
spinner="waveDots">
|
||||
<template v-slot:no-more><span/></template>
|
||||
<template v-slot:no-results><span>{{$t('No_Results')}}</span></template>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,18 +98,20 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue' // maybe not needed?
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
import _debounce from 'lodash/debounce'
|
||||
import InfiniteLoading from 'vue-infinite-loading';
|
||||
|
||||
export default {
|
||||
// TODO: this should be simplified into a Generic Infinitely Scrolling List and added as two components when split lists desired
|
||||
name: 'GenericSplitLists',
|
||||
components: {InfiniteLoading},
|
||||
props: {
|
||||
list_name: {type: String, default: 'Blank List'}, // TODO update translations to handle plural translations
|
||||
left_list: {type:Array, default(){return []}},
|
||||
left_list: {type: Array, default(){return []}},
|
||||
left_counts: {type: Object},
|
||||
right_list: {type:Array, default(){return []}},
|
||||
right_counts: {type: Object},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -117,6 +121,8 @@ export default {
|
||||
search_left: '',
|
||||
right_page: 0,
|
||||
left_page: 0,
|
||||
right_state: undefined,
|
||||
left_state: undefined,
|
||||
right: +new Date(),
|
||||
left: +new Date(),
|
||||
text: {
|
||||
@@ -143,11 +149,45 @@ export default {
|
||||
this.$emit('reset', {'column':'right'})
|
||||
this.right += 1
|
||||
}, 700),
|
||||
right_counts: {
|
||||
deep: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (newVal.current > 0) {
|
||||
this.right_state.loaded()
|
||||
}
|
||||
if (newVal.current >= newVal.max) {
|
||||
this.right_state.complete()
|
||||
}
|
||||
}
|
||||
},
|
||||
left_counts: {
|
||||
deep: true,
|
||||
handler(newVal, oldVal) {
|
||||
if (newVal.current > 0) {
|
||||
this.left_state.loaded()
|
||||
}
|
||||
if (newVal.current >= newVal.max) {
|
||||
this.left_state.complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch: function () {
|
||||
this.search_right = ''
|
||||
this.search_left = ''
|
||||
if (this.search_right == '') {
|
||||
this.right_page = 0
|
||||
this.right += 1
|
||||
this.$emit('reset', {'column':'right'})
|
||||
} else {
|
||||
this.search_right = ''
|
||||
}
|
||||
if (this.search_left == '') {
|
||||
this.left_page = 0
|
||||
this.left += 1
|
||||
this.$emit('reset', {'column':'left'})
|
||||
} else {
|
||||
this.search_left = ''
|
||||
}
|
||||
},
|
||||
infiniteHandler: function($state, col) {
|
||||
let params = {
|
||||
@@ -155,16 +195,9 @@ export default {
|
||||
'page': (col==='left') ? this.left_page + 1 : this.right_page + 1,
|
||||
'column': col
|
||||
}
|
||||
// TODO: change this to be an emit and watch a prop to determine if loaded or complete
|
||||
new Promise((callback) => this.$emit('get-list', params, callback)).then((result) => {
|
||||
this[col+'_page'] += 1
|
||||
$state.loaded();
|
||||
if (!result) { // callback needs to return true if handler should continue loading more data
|
||||
$state.complete();
|
||||
}
|
||||
}).catch(() => {
|
||||
$state.complete();
|
||||
})
|
||||
this[col+'_state'] = $state
|
||||
this.$emit('get-list', params)
|
||||
this[col+'_page'] += 1
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
<template>
|
||||
<div row>
|
||||
<b-card no-body d-flex flex-column :class="{'border border-primary' : over, 'shake': isError}"
|
||||
refs="keywordCard"
|
||||
style="height: 10vh;" :style="{'cursor:grab' : draggable}"
|
||||
@dragover.prevent
|
||||
@dragenter.prevent
|
||||
:draggable="draggable"
|
||||
@dragstart="handleDragStart($event)"
|
||||
@dragenter="handleDragEnter($event)"
|
||||
@dragleave="handleDragLeave($event)"
|
||||
@drop="handleDragDrop($event)">
|
||||
<b-row no-gutters style="height:inherit;">
|
||||
<b-col no-gutters md="3" style="height:inherit;">
|
||||
<b-card-img-lazy style="object-fit: cover; height: 10vh;" :src="keyword_image" v-bind:alt="$t('Recipe_Image')"></b-card-img-lazy>
|
||||
</b-col>
|
||||
<b-col no-gutters md="9" style="height:inherit;">
|
||||
<b-card-body class="m-0 py-0" style="height:inherit;">
|
||||
<b-card-text class=" h-100 my-0 d-flex flex-column" style="text-overflow: ellipsis">
|
||||
<h5 class="m-0 mt-1 text-truncate">{{ keyword.name }}</h5>
|
||||
<div class= "m-0 text-truncate">{{ keyword.description }}</div>
|
||||
<div class="mt-auto mb-1 d-flex flex-row justify-content-end">
|
||||
<div v-if="keyword.numchild !=0" class="mx-2 btn btn-link btn-sm"
|
||||
style="z-index: 800;" v-on:click="$emit('item-action',{'action':'get-children','source':keyword})">
|
||||
<div v-if="!keyword.expanded">{{keyword.numchild}} {{$t('Keywords')}}</div>
|
||||
<div v-else>{{$t('Hide Keywords')}}</div>
|
||||
</div>
|
||||
<div class="mx-2 btn btn-link btn-sm" style="z-index: 800;"
|
||||
v-on:click="$emit('item-action',{'action':'get-recipes','source':keyword})">
|
||||
<div v-if="!keyword.show_recipes">{{keyword.numrecipe}} {{$t('Recipes')}}</div>
|
||||
<div v-else>{{$t('Hide Recipes')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
</b-col>
|
||||
<div class="card-img-overlay justify-content-right h-25 m-0 p-0 text-right">
|
||||
<generic-context-menu class="p-0"
|
||||
:show_merge="true"
|
||||
:show_move="true"
|
||||
@item-action="$emit('item-action', {'action': $event, 'source': keyword})">
|
||||
</generic-context-menu>
|
||||
</div>
|
||||
</b-row>
|
||||
</b-card>
|
||||
<!-- recursively add child keywords -->
|
||||
<div class="row" v-if="keyword.expanded">
|
||||
<div class="col-md-11 offset-md-1">
|
||||
<keyword-card v-for="child in keyword.children"
|
||||
:keyword="child"
|
||||
v-bind:key="child.id"
|
||||
draggable="true"
|
||||
@item-action="$emit('item-action', $event)">
|
||||
</keyword-card>
|
||||
</div>
|
||||
</div>
|
||||
<!-- conditionally view recipes -->
|
||||
<div class="row" v-if="keyword.show_recipes">
|
||||
<div class="col-md-11 offset-md-1">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));grid-gap: 1rem;">
|
||||
<recipe-card v-for="r in keyword.recipes"
|
||||
v-bind:key="r.id"
|
||||
:recipe="r">
|
||||
</recipe-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- this should be made a generic component, would also require mixin for functions that generate the popup and put in parent container-->
|
||||
<b-list-group ref="tooltip" variant="light" v-show="show_menu" v-on-clickaway="closeMenu" style="z-index:999; cursor:pointer">
|
||||
<b-list-group-item action v-on:click="$emit('item-action',{'action': 'move', 'target': keyword, 'source': source}); closeMenu()">
|
||||
<i class="fas fa-expand-arrows-alt fa-fw"></i> {{$t('Move')}}: {{$t('move_confirmation', {'child': source.name,'parent':keyword.name})}}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item action v-on:click="$emit('item-action',{'action': 'merge', 'target': keyword, 'source': source}); closeMenu()">
|
||||
<i class="fas fa-compress-arrows-alt fa-fw"></i> {{$t('Merge')}}: {{ $t('merge_confirmation', {'source': source.name,'target':keyword.name}) }}
|
||||
</b-list-group-item>
|
||||
<b-list-group-item action v-on:click="closeMenu()">
|
||||
<i class="fas fa-times fa-fw"></i> {{$t('Cancel')}}
|
||||
</b-list-group-item>
|
||||
</b-list-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GenericContextMenu from "@/components/GenericContextMenu";
|
||||
import RecipeCard from "@/components/RecipeCard";
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { createPopper } from '@popperjs/core';
|
||||
|
||||
export default {
|
||||
name: "KeywordCard",
|
||||
components: { GenericContextMenu, RecipeCard },
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
keyword: Object,
|
||||
draggable: {type: Boolean, default: false}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyword_image: '',
|
||||
over: false,
|
||||
show_menu: false,
|
||||
dragMenu: undefined,
|
||||
isError: false,
|
||||
source: {},
|
||||
target: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.keyword == null || this.keyword.image == null) {
|
||||
this.keyword_image = window.IMAGE_PLACEHOLDER
|
||||
} else {
|
||||
this.keyword_image = this.keyword.image
|
||||
}
|
||||
this.dragMenu = this.$refs.tooltip
|
||||
},
|
||||
methods: {
|
||||
handleDragStart: function(e) {
|
||||
this.isError = false
|
||||
e.dataTransfer.setData('source', JSON.stringify(this.keyword))
|
||||
},
|
||||
handleDragEnter: function(e) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget) && e.relatedTarget != null) {
|
||||
this.over = true
|
||||
}
|
||||
},
|
||||
handleDragLeave: function(e) {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) {
|
||||
this.over = false
|
||||
}
|
||||
},
|
||||
handleDragDrop: function(e) {
|
||||
let source = JSON.parse(e.dataTransfer.getData('source'))
|
||||
if (source.id != this.keyword.id){
|
||||
this.source = source
|
||||
let menuLocation = {getBoundingClientRect: this.generateLocation(e.clientX, e.clientY),}
|
||||
this.show_menu = true
|
||||
let popper = createPopper(
|
||||
menuLocation,
|
||||
this.dragMenu,
|
||||
{
|
||||
placement: 'bottom-start',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
rootBoundary: 'document',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: ['bottom-end', 'top-start', 'top-end', 'left-start', 'right-start'],
|
||||
rootBoundary: 'document',
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
popper.update()
|
||||
this.over = false
|
||||
this.$emit({'action': 'drop', 'target': this.keyword, 'source': this.source})
|
||||
} else {
|
||||
this.isError = true
|
||||
}
|
||||
},
|
||||
generateLocation: function (x = 0, y = 0) {
|
||||
return () => ({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: y,
|
||||
right: x,
|
||||
bottom: y,
|
||||
left: x,
|
||||
});
|
||||
},
|
||||
closeMenu: function(){
|
||||
this.show_menu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shake {
|
||||
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%,
|
||||
90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
|
||||
20%,
|
||||
80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
|
||||
30%,
|
||||
50%,
|
||||
70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%,
|
||||
60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
71
vue/src/components/Modals/EmojiInput.vue
Normal file
71
vue/src/components/Modals/EmojiInput.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-group
|
||||
v-bind:label="label"
|
||||
class="mb-3">
|
||||
<twemoji-textarea
|
||||
:ref="'_edit_' + id"
|
||||
:initialContent="value"
|
||||
:emojiData="emojiDataAll"
|
||||
:emojiGroups="emojiGroups"
|
||||
triggerType="hover"
|
||||
recentEmojisFeat="true"
|
||||
recentEmojisStorage="local"
|
||||
@contentChanged="setIcon"
|
||||
/>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {TwemojiTextarea} from '@kevinfaguiar/vue-twemoji-picker';
|
||||
// TODO add localization
|
||||
import EmojiAllData from '@kevinfaguiar/vue-twemoji-picker/emoji-data/en/emoji-all-groups.json';
|
||||
import EmojiGroups from '@kevinfaguiar/vue-twemoji-picker/emoji-data/emoji-groups.json';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'EmojiInput',
|
||||
components: {TwemojiTextarea},
|
||||
props: {
|
||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||
label: {type: String, default: ''},
|
||||
value: {type: String, default: ''},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
new_value: undefined,
|
||||
id: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// modelName() {
|
||||
// return this?.model?.name ?? this.$t('Search')
|
||||
// },
|
||||
emojiDataAll() {
|
||||
return EmojiAllData;
|
||||
},
|
||||
emojiGroups() {
|
||||
return EmojiGroups;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'new_value': function () {
|
||||
this.$root.$emit('change', this.field, this.new_value ?? null)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.id = this._uid
|
||||
},
|
||||
methods: {
|
||||
prepareEmoji: function() {
|
||||
this.$refs['_edit_' + this.id].addText(this.this_item.icon || '');
|
||||
this.$refs['_edit_' + this.id].blur()
|
||||
document.getElementById('btn-emoji-default').disabled = true;
|
||||
},
|
||||
setIcon: function(icon) {
|
||||
this.new_value = icon
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -12,7 +12,6 @@
|
||||
:model="listModel(f.list)"
|
||||
:sticky_options="f.sticky_options || undefined"
|
||||
@change="storeValue"/> <!-- TODO add ability to create new items associated with lookup -->
|
||||
<!-- TODO: add emoji field -->
|
||||
<!-- TODO: add multi-selection input list -->
|
||||
<checkbox-input v-if="f.type=='checkbox'"
|
||||
:label="f.label"
|
||||
@@ -23,6 +22,11 @@
|
||||
:value="f.value"
|
||||
:field="f.field"
|
||||
:placeholder="f.placeholder"/>
|
||||
<emoji-input v-if="f.type=='emoji'"
|
||||
:label="f.label"
|
||||
:value="f.value"
|
||||
:field="f.field"
|
||||
@change="storeValue"/>
|
||||
</div>
|
||||
|
||||
<template v-slot:modal-footer>
|
||||
@@ -43,10 +47,11 @@ import {Models} from "@/utils/models";
|
||||
import CheckboxInput from "@/components/Modals/CheckboxInput";
|
||||
import LookupInput from "@/components/Modals/LookupInput";
|
||||
import TextInput from "@/components/Modals/TextInput";
|
||||
import EmojiInput from "@/components/Modals/EmojiInput";
|
||||
|
||||
export default {
|
||||
name: 'GenericModalForm',
|
||||
components: {CheckboxInput, LookupInput, TextInput},
|
||||
components: {CheckboxInput, LookupInput, TextInput, EmojiInput},
|
||||
props: {
|
||||
model: {required: true, type: Object, default: function() {}},
|
||||
action: {required: true, type: Object, default: function() {}},
|
||||
|
||||
@@ -47,10 +47,5 @@ export default {
|
||||
this.$root.$emit('change', this.field, this.new_value?.id ?? null)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
Button: function(e) {
|
||||
this.$bvModal.show('modal')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -94,6 +94,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// TODO: convert this to genericAPI
|
||||
clickUrl: function () {
|
||||
if (this.recipe !== null) {
|
||||
return resolveDjangoUrl('view_recipe', this.recipe.id)
|
||||
|
||||
@@ -121,5 +121,9 @@
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"Recipe": "Recipe",
|
||||
"tree_root": "Root of Tree"
|
||||
"tree_root": "Root of Tree",
|
||||
"Icon": "Icon",
|
||||
"Unit": "Unit",
|
||||
"No_Results": "No Results",
|
||||
"New_Unit": "New Unit"
|
||||
}
|
||||
|
||||
@@ -108,10 +108,58 @@ export class Models {
|
||||
static KEYWORD = {
|
||||
'name': i18n.t('Keyword'), // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||
'apiName': 'Keyword',
|
||||
'model_type': this.TREE
|
||||
'model_type': this.TREE,
|
||||
'create': {
|
||||
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||
'params': [['name', 'description', 'icon']],
|
||||
'form': {
|
||||
'name': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'name',
|
||||
'label': i18n.t('Name'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'description': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'description',
|
||||
'label': i18n.t('Description'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'icon': {
|
||||
'form_field': true,
|
||||
'type': 'emoji',
|
||||
'field': 'icon',
|
||||
'label': i18n.t('Icon')
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
static UNIT = {
|
||||
'name': i18n.t('Unit'),
|
||||
'apiName': 'Unit',
|
||||
'create': {
|
||||
'params': [['name', 'description']],
|
||||
'form': {
|
||||
'name': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'name',
|
||||
'label': i18n.t('Name'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'description': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'description',
|
||||
'label': i18n.t('Description'),
|
||||
'placeholder': ''
|
||||
}
|
||||
}
|
||||
},
|
||||
'move': false
|
||||
}
|
||||
static UNIT = {}
|
||||
static RECIPE = {}
|
||||
static SHOPPING_LIST = {}
|
||||
static RECIPE_BOOK = {
|
||||
'name': i18n.t('Recipe_Book'),
|
||||
@@ -126,7 +174,7 @@ export class Models {
|
||||
'name': i18n.t('Recipe'),
|
||||
'apiName': 'Recipe',
|
||||
'list': {
|
||||
'params': ['query', 'keywords', 'foods', 'books', 'keywordsOr', 'foodsOr', 'booksOr', 'internal', 'random', '_new', 'page', 'pageSize', 'options'],
|
||||
'params': ['query', 'keywords', 'foods', 'units', 'books', 'keywordsOr', 'foodsOr', 'booksOr', 'internal', 'random', '_new', 'page', 'pageSize', 'options'],
|
||||
'config': {
|
||||
'foods': {'type':'string'},
|
||||
'keywords': {'type': 'string'},
|
||||
|
||||
@@ -414,10 +414,10 @@ export interface InlineResponse2001 {
|
||||
previous?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Food>}
|
||||
* @type {Array<Unit>}
|
||||
* @memberof InlineResponse2001
|
||||
*/
|
||||
results?: Array<Food>;
|
||||
results?: Array<Unit>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -445,10 +445,10 @@ export interface InlineResponse2002 {
|
||||
previous?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {Array<RecipeOverview>}
|
||||
* @type {Array<Food>}
|
||||
* @memberof InlineResponse2002
|
||||
*/
|
||||
results?: Array<RecipeOverview>;
|
||||
results?: Array<Food>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -476,10 +476,10 @@ export interface InlineResponse2003 {
|
||||
previous?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {Array<ViewLog>}
|
||||
* @type {Array<RecipeOverview>}
|
||||
* @memberof InlineResponse2003
|
||||
*/
|
||||
results?: Array<ViewLog>;
|
||||
results?: Array<RecipeOverview>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -507,73 +507,11 @@ export interface InlineResponse2004 {
|
||||
previous?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {Array<CookLog>}
|
||||
* @type {Array<SupermarketCategoryRelation>}
|
||||
* @memberof InlineResponse2004
|
||||
*/
|
||||
results?: Array<CookLog>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface InlineResponse2005
|
||||
*/
|
||||
export interface InlineResponse2005 {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof InlineResponse2005
|
||||
*/
|
||||
count?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof InlineResponse2005
|
||||
*/
|
||||
next?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof InlineResponse2005
|
||||
*/
|
||||
previous?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {Array<SupermarketCategoryRelation>}
|
||||
* @memberof InlineResponse2005
|
||||
*/
|
||||
results?: Array<SupermarketCategoryRelation>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface InlineResponse2006
|
||||
*/
|
||||
export interface InlineResponse2006 {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof InlineResponse2006
|
||||
*/
|
||||
count?: number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof InlineResponse2006
|
||||
*/
|
||||
next?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof InlineResponse2006
|
||||
*/
|
||||
previous?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {Array<ImportLog>}
|
||||
* @memberof InlineResponse2006
|
||||
*/
|
||||
results?: Array<ImportLog>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -1992,6 +1930,18 @@ export interface StepUnit {
|
||||
* @memberof StepUnit
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StepUnit
|
||||
*/
|
||||
numrecipe?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StepUnit
|
||||
*/
|
||||
image?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -2238,6 +2188,18 @@ export interface Unit {
|
||||
* @memberof Unit
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Unit
|
||||
*/
|
||||
numrecipe?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Unit
|
||||
*/
|
||||
image?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -4118,12 +4080,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listCookLogs: async (page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
listCookLogs: async (options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/cook-log/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@@ -4136,14 +4096,6 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
|
||||
if (pageSize !== undefined) {
|
||||
localVarQueryParameter['page_size'] = pageSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
@@ -4211,12 +4163,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listImportLogs: async (page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
listImportLogs: async (options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/import-log/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@@ -4229,14 +4179,6 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
|
||||
if (pageSize !== undefined) {
|
||||
localVarQueryParameter['page_size'] = pageSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
@@ -4452,6 +4394,7 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
* @param {string} [query] Query string matched (fuzzy) against recipe name. In the future also fulltext search.
|
||||
* @param {string} [keywords] Id of keyword a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [foods] Id of food a recipe should have. For multiple repeat parameter.
|
||||
* @param {number} [units] Id of unit a recipe should have.
|
||||
* @param {string} [books] Id of book a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [keywordsOr] If recipe should have all (AND) or any (OR) of the provided keywords.
|
||||
* @param {string} [foodsOr] If recipe should have all (AND) or any (OR) any of the provided foods.
|
||||
@@ -4464,7 +4407,7 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listRecipes: async (query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
listRecipes: async (query?: string, keywords?: string, foods?: string, units?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/recipe/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@@ -4489,6 +4432,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
localVarQueryParameter['foods'] = foods;
|
||||
}
|
||||
|
||||
if (units !== undefined) {
|
||||
localVarQueryParameter['units'] = units;
|
||||
}
|
||||
|
||||
if (books !== undefined) {
|
||||
localVarQueryParameter['books'] = books;
|
||||
}
|
||||
@@ -4838,10 +4785,13 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} [query] Query string matched against unit name.
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listUnits: async (options: any = {}): Promise<RequestArgs> => {
|
||||
listUnits: async (query?: string, page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/unit/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@@ -4854,6 +4804,18 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
if (query !== undefined) {
|
||||
localVarQueryParameter['query'] = query;
|
||||
}
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
|
||||
if (pageSize !== undefined) {
|
||||
localVarQueryParameter['page_size'] = pageSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
@@ -4954,12 +4916,10 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listViewLogs: async (page?: number, pageSize?: number, options: any = {}): Promise<RequestArgs> => {
|
||||
listViewLogs: async (options: any = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/view-log/`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@@ -4972,14 +4932,6 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
|
||||
if (pageSize !== undefined) {
|
||||
localVarQueryParameter['page_size'] = pageSize;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
@@ -5073,6 +5025,47 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this unit.
|
||||
* @param {string} target
|
||||
* @param {Unit} [unit]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
mergeUnit: async (id: string, target: string, unit?: Unit, options: any = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('mergeUnit', 'id', id)
|
||||
// verify required parameter 'target' is not null or undefined
|
||||
assertParamExists('mergeUnit', 'target', target)
|
||||
const localVarPath = `/api/unit/{id}/merge/{target}/`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)))
|
||||
.replace(`{${"target"}}`, encodeURIComponent(String(target)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(unit, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this food.
|
||||
@@ -8348,13 +8341,11 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listCookLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2004>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listCookLogs(page, pageSize, options);
|
||||
async listCookLogs(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<CookLog>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listCookLogs(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@@ -8367,19 +8358,17 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listFoods(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2001>> {
|
||||
async listFoods(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2002>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listFoods(query, root, tree, page, pageSize, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listImportLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2006>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listImportLogs(page, pageSize, options);
|
||||
async listImportLogs(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ImportLog>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listImportLogs(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@@ -8446,6 +8435,7 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
* @param {string} [query] Query string matched (fuzzy) against recipe name. In the future also fulltext search.
|
||||
* @param {string} [keywords] Id of keyword a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [foods] Id of food a recipe should have. For multiple repeat parameter.
|
||||
* @param {number} [units] Id of unit a recipe should have.
|
||||
* @param {string} [books] Id of book a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [keywordsOr] If recipe should have all (AND) or any (OR) of the provided keywords.
|
||||
* @param {string} [foodsOr] If recipe should have all (AND) or any (OR) any of the provided foods.
|
||||
@@ -8458,8 +8448,8 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2002>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options);
|
||||
async listRecipes(query?: string, keywords?: string, foods?: string, units?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2003>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, units, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@@ -8514,7 +8504,7 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2005>> {
|
||||
async listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2004>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listSupermarketCategoryRelations(page, pageSize, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
@@ -8556,11 +8546,14 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} [query] Query string matched against unit name.
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listUnits(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<Unit>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listUnits(options);
|
||||
async listUnits(query?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2001>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listUnits(query, page, pageSize, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@@ -8592,13 +8585,11 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listViewLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2003>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listViewLogs(page, pageSize, options);
|
||||
async listViewLogs(options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ViewLog>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listViewLogs(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@@ -8625,6 +8616,18 @@ export const ApiApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.mergeKeyword(id, target, keyword, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this unit.
|
||||
* @param {string} target
|
||||
* @param {Unit} [unit]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async mergeUnit(id: string, target: string, unit?: Unit, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Unit>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.mergeUnit(id, target, unit, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this food.
|
||||
@@ -9908,13 +9911,11 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listCookLogs(page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2004> {
|
||||
return localVarFp.listCookLogs(page, pageSize, options).then((request) => request(axios, basePath));
|
||||
listCookLogs(options?: any): AxiosPromise<Array<CookLog>> {
|
||||
return localVarFp.listCookLogs(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@@ -9926,18 +9927,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listFoods(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2001> {
|
||||
listFoods(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2002> {
|
||||
return localVarFp.listFoods(query, root, tree, page, pageSize, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listImportLogs(page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2006> {
|
||||
return localVarFp.listImportLogs(page, pageSize, options).then((request) => request(axios, basePath));
|
||||
listImportLogs(options?: any): AxiosPromise<Array<ImportLog>> {
|
||||
return localVarFp.listImportLogs(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@@ -9997,6 +9996,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
* @param {string} [query] Query string matched (fuzzy) against recipe name. In the future also fulltext search.
|
||||
* @param {string} [keywords] Id of keyword a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [foods] Id of food a recipe should have. For multiple repeat parameter.
|
||||
* @param {number} [units] Id of unit a recipe should have.
|
||||
* @param {string} [books] Id of book a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [keywordsOr] If recipe should have all (AND) or any (OR) of the provided keywords.
|
||||
* @param {string} [foodsOr] If recipe should have all (AND) or any (OR) any of the provided foods.
|
||||
@@ -10009,8 +10009,8 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2002> {
|
||||
return localVarFp.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath));
|
||||
listRecipes(query?: string, keywords?: string, foods?: string, units?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2003> {
|
||||
return localVarFp.listRecipes(query, keywords, foods, units, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@@ -10059,7 +10059,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2005> {
|
||||
listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2004> {
|
||||
return localVarFp.listSupermarketCategoryRelations(page, pageSize, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
@@ -10096,11 +10096,14 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} [query] Query string matched against unit name.
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listUnits(options?: any): AxiosPromise<Array<Unit>> {
|
||||
return localVarFp.listUnits(options).then((request) => request(axios, basePath));
|
||||
listUnits(query?: string, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2001> {
|
||||
return localVarFp.listUnits(query, page, pageSize, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@@ -10128,13 +10131,11 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listViewLogs(page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2003> {
|
||||
return localVarFp.listViewLogs(page, pageSize, options).then((request) => request(axios, basePath));
|
||||
listViewLogs(options?: any): AxiosPromise<Array<ViewLog>> {
|
||||
return localVarFp.listViewLogs(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
@@ -10158,6 +10159,17 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
|
||||
mergeKeyword(id: string, target: string, keyword?: Keyword, options?: any): AxiosPromise<Keyword> {
|
||||
return localVarFp.mergeKeyword(id, target, keyword, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this unit.
|
||||
* @param {string} target
|
||||
* @param {Unit} [unit]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
mergeUnit(id: string, target: string, unit?: Unit, options?: any): AxiosPromise<Unit> {
|
||||
return localVarFp.mergeUnit(id, target, unit, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this food.
|
||||
@@ -11465,14 +11477,12 @@ export class ApiApi extends BaseAPI {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listCookLogs(page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listCookLogs(page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
public listCookLogs(options?: any) {
|
||||
return ApiApiFp(this.configuration).listCookLogs(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11492,14 +11502,12 @@ export class ApiApi extends BaseAPI {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listImportLogs(page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listImportLogs(page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
public listImportLogs(options?: any) {
|
||||
return ApiApiFp(this.configuration).listImportLogs(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11572,6 +11580,7 @@ export class ApiApi extends BaseAPI {
|
||||
* @param {string} [query] Query string matched (fuzzy) against recipe name. In the future also fulltext search.
|
||||
* @param {string} [keywords] Id of keyword a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [foods] Id of food a recipe should have. For multiple repeat parameter.
|
||||
* @param {number} [units] Id of unit a recipe should have.
|
||||
* @param {string} [books] Id of book a recipe should have. For multiple repeat parameter.
|
||||
* @param {string} [keywordsOr] If recipe should have all (AND) or any (OR) of the provided keywords.
|
||||
* @param {string} [foodsOr] If recipe should have all (AND) or any (OR) any of the provided foods.
|
||||
@@ -11585,8 +11594,8 @@ export class ApiApi extends BaseAPI {
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
public listRecipes(query?: string, keywords?: string, foods?: string, units?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listRecipes(query, keywords, foods, units, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11693,12 +11702,15 @@ export class ApiApi extends BaseAPI {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} [query] Query string matched against unit name.
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listUnits(options?: any) {
|
||||
return ApiApiFp(this.configuration).listUnits(options).then((request) => request(this.axios, this.basePath));
|
||||
public listUnits(query?: string, page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listUnits(query, page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11733,14 +11745,12 @@ export class ApiApi extends BaseAPI {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} [page] A page number within the paginated result set.
|
||||
* @param {number} [pageSize] Number of results to return per page.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public listViewLogs(page?: number, pageSize?: number, options?: any) {
|
||||
return ApiApiFp(this.configuration).listViewLogs(page, pageSize, options).then((request) => request(this.axios, this.basePath));
|
||||
public listViewLogs(options?: any) {
|
||||
return ApiApiFp(this.configuration).listViewLogs(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11769,6 +11779,19 @@ export class ApiApi extends BaseAPI {
|
||||
return ApiApiFp(this.configuration).mergeKeyword(id, target, keyword, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this unit.
|
||||
* @param {string} target
|
||||
* @param {Unit} [unit]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof ApiApi
|
||||
*/
|
||||
public mergeUnit(id: string, target: string, unit?: Unit, options?: any) {
|
||||
return ApiApiFp(this.configuration).mergeUnit(id, target, unit, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id A unique integer value identifying this food.
|
||||
|
||||
@@ -25,12 +25,8 @@ const pages = {
|
||||
entry: './src/apps/UserFileView/main.js',
|
||||
chunks: ['chunk-vendors']
|
||||
},
|
||||
'keyword_list_view': {
|
||||
entry: './src/apps/KeywordListView/main.js',
|
||||
chunks: ['chunk-vendors']
|
||||
},
|
||||
'food_list_view': {
|
||||
entry: './src/apps/FoodListView/main.js',
|
||||
'model_list_view': {
|
||||
entry: './src/apps/ModelListView/main.js',
|
||||
chunks: ['chunk-vendors']
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user