fixed book sharing and added step page

This commit is contained in:
vabene1111
2021-11-14 15:33:25 +01:00
parent 7c4cd02dfa
commit 695cab29a1
10 changed files with 274 additions and 166 deletions

View File

@@ -38,6 +38,7 @@ def search_recipes(request, queryset, params):
search_keywords = params.getlist('keywords', [])
search_foods = params.getlist('foods', [])
search_books = params.getlist('books', [])
search_steps = params.getlist('steps', [])
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
@@ -191,6 +192,10 @@ def search_recipes(request, queryset, params):
if search_units:
queryset = queryset.filter(steps__ingredients__unit__id=search_units)
# probably only useful in Unit list view, so keeping it simple
if search_steps:
queryset = queryset.filter(steps__id__in=search_steps)
if search_internal:
queryset = queryset.filter(internal=True)

View File

@@ -39,6 +39,11 @@ class RecipeSchema(AutoSchema):
"description": 'Id of book a recipe should have. For multiple repeat parameter.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'steps', "in": "query", "required": False,
"description": 'Id of a step a recipe should have. For multiple repeat parameter.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'keywords_or', "in": "query", "required": False,
"description": 'If recipe should have all (AND) or any (OR) of the provided keywords.',
@@ -86,7 +91,8 @@ class TreeSchema(AutoSchema):
})
parameters.append({
"name": 'root', "in": "query", "required": False,
"description": 'Return first level children of {obj} with ID [int]. Integer 0 will return root {obj}s.'.format(obj=api_name),
"description": 'Return first level children of {obj} with ID [int]. Integer 0 will return root {obj}s.'.format(
obj=api_name),
'schema': {'type': 'int', },
})
parameters.append({
@@ -110,3 +116,17 @@ class FilterSchema(AutoSchema):
'schema': {'type': 'string', },
})
return parameters
class QueryOnlySchema(AutoSchema):
def get_path_parameters(self, path, method):
if not is_list_view(path, method, self.view):
return super(QueryOnlySchema, self).get_path_parameters(path, method)
parameters = super().get_path_parameters(path, method)
parameters.append({
"name": 'query', "in": "query", "required": False,
"description": 'Query string matched (fuzzy) against object name.',
'schema': {'type': 'string', },
})
return parameters

View File

@@ -34,7 +34,8 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
api_serializer = None
# extended values are computationally expensive and not needed in normal circumstances
try:
if bool(int(self.context['request'].query_params.get('extended', False))) and self.__class__ == api_serializer:
if bool(int(
self.context['request'].query_params.get('extended', False))) and self.__class__ == api_serializer:
return fields
except AttributeError:
pass
@@ -49,11 +50,13 @@ class ExtendedRecipeMixin(serializers.ModelSerializer):
def get_image(self, obj):
# TODO add caching
recipes = Recipe.objects.filter(**{self.recipe_filter: obj}, space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
recipes = Recipe.objects.filter(**{self.recipe_filter: obj}, space=obj.space).exclude(
image__isnull=True).exclude(image__exact='')
try:
if recipes.count() == 0 and obj.has_children():
obj__in = self.recipe_filter + '__in'
recipes = Recipe.objects.filter(**{obj__in: obj.get_descendants()}, space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
recipes = Recipe.objects.filter(**{obj__in: obj.get_descendants()}, space=obj.space).exclude(
image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree
except AttributeError:
# probably not a tree
pass
@@ -404,7 +407,10 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
class Meta:
model = Food
fields = ('id', 'name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category', 'image', 'parent', 'numchild', 'numrecipe')
fields = (
'id', 'name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category', 'image', 'parent',
'numchild',
'numrecipe')
read_only_fields = ('id', 'numchild', 'parent', 'image')
@@ -425,12 +431,13 @@ class IngredientSerializer(WritableNestedModelSerializer):
)
class StepSerializer(WritableNestedModelSerializer):
class StepSerializer(WritableNestedModelSerializer, ExtendedRecipeMixin):
ingredients = IngredientSerializer(many=True)
ingredients_markdown = serializers.SerializerMethodField('get_ingredients_markdown')
ingredients_vue = serializers.SerializerMethodField('get_ingredients_vue')
file = UserFileViewSerializer(allow_null=True, required=False)
step_recipe_data = serializers.SerializerMethodField('get_step_recipe_data')
recipe_filter = 'steps'
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
@@ -442,6 +449,9 @@ class StepSerializer(WritableNestedModelSerializer):
def get_ingredients_markdown(self, obj):
return obj.get_instruction_render()
def get_step_recipes(self, obj):
return list(obj.recipe_set.values_list('id', flat=True).all())
def get_step_recipe_data(self, obj):
# check if root type is recipe to prevent infinite recursion
# can be improved later to allow multi level embedding
@@ -452,7 +462,7 @@ class StepSerializer(WritableNestedModelSerializer):
model = Step
fields = (
'id', 'name', 'type', 'instruction', 'ingredients', 'ingredients_markdown',
'ingredients_vue', 'time', 'order', 'show_as_header', 'file', 'step_recipe', 'step_recipe_data'
'ingredients_vue', 'time', 'order', 'show_as_header', 'file', 'step_recipe', 'step_recipe_data', 'numrecipe'
)

View File

@@ -10,7 +10,8 @@ from cookbook.helper import dal
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name, Automation, UserFile)
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name, Automation,
UserFile, Step)
from .views import api, data, delete, edit, import_export, lists, new, views, telegram
router = routers.DefaultRouter()
@@ -177,7 +178,7 @@ for m in generic_models:
)
)
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile]
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile, Step]
for m in vue_models:
py_name = get_model_name(m)
url_name = py_name.replace('_', '-')

View File

@@ -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 FilterSchema, RecipeSchema, TreeSchema
from cookbook.schemas import FilterSchema, RecipeSchema, TreeSchema, QueryOnlySchema
from cookbook.serializer import (FoodSerializer, IngredientSerializer,
KeywordSerializer, MealPlanSerializer,
MealTypeSerializer, RecipeBookSerializer,
@@ -410,7 +410,7 @@ class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
permission_classes = [CustomIsOwner]
def get_queryset(self):
self.queryset = self.queryset.filter(created_by=self.request.user).filter(space=self.request.space)
self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space)
return super().get_queryset()
@@ -498,9 +498,16 @@ class StepViewSet(viewsets.ModelViewSet):
queryset = Step.objects
serializer_class = StepSerializer
permission_classes = [CustomIsUser]
pagination_class = DefaultPagination
schema = QueryOnlySchema()
def get_queryset(self):
return self.queryset.filter(recipe__space=self.request.space)
queryset = self.queryset.filter(recipe__space=self.request.space)
query = self.request.query_params.get('query', None)
if query is not None:
queryset = queryset.filter(Q(name__icontains=query) | Q(recipe__name__icontains=query))
return queryset
class RecipePagination(PageNumberPagination):

View File

@@ -221,6 +221,23 @@ def user_file(request):
)
@group_required('user')
def step(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": _("Steps"),
"config": {
'model': "STEP", # *REQUIRED* name of the model in models.js
'recipe_param': 'steps',
}
}
)
@group_required('user')
def shopping_list_new(request):
# recipe-param is the name of the parameters used when filtering recipes by this attribute