mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
Merge branch 'develop' into generic_modal_v2
This commit is contained in:
@@ -17,23 +17,24 @@ class CookbookConfig(AppConfig):
|
||||
'django.db.backends.postgresql']:
|
||||
import cookbook.signals # noqa
|
||||
|
||||
# when starting up run fix_tree to:
|
||||
# a) make sure that nodes are sorted when switching between sort modes
|
||||
# b) fix problems, if any, with tree consistency
|
||||
with scopes_disabled():
|
||||
try:
|
||||
from cookbook.models import Keyword, Food
|
||||
Keyword.fix_tree(fix_paths=True)
|
||||
Food.fix_tree(fix_paths=True)
|
||||
except OperationalError:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
pass # if model does not exist there is no need to fix it
|
||||
except ProgrammingError:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
pass # if migration has not been run database cannot be fixed yet
|
||||
except Exception:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
pass # dont break startup just because fix could not run, need to investigate cases when this happens
|
||||
if not settings.DISABLE_TREE_FIX_STARTUP:
|
||||
# when starting up run fix_tree to:
|
||||
# a) make sure that nodes are sorted when switching between sort modes
|
||||
# b) fix problems, if any, with tree consistency
|
||||
with scopes_disabled():
|
||||
try:
|
||||
from cookbook.models import Keyword, Food
|
||||
Keyword.fix_tree(fix_paths=True)
|
||||
Food.fix_tree(fix_paths=True)
|
||||
except OperationalError:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
pass # if model does not exist there is no need to fix it
|
||||
except ProgrammingError:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
pass # if migration has not been run database cannot be fixed yet
|
||||
except Exception:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
pass # dont break startup just because fix could not run, need to investigate cases when this happens
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
from collections import Counter
|
||||
from datetime import timedelta
|
||||
|
||||
from recipes import settings
|
||||
from django.contrib.postgres.search import (
|
||||
SearchQuery, SearchRank, TrigramSimilarity
|
||||
)
|
||||
from django.contrib.postgres.search import SearchQuery, SearchRank, TrigramSimilarity
|
||||
from django.core.cache import caches
|
||||
from django.db.models import Avg, Case, Count, Func, Max, Q, Subquery, Value, When
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone, translation
|
||||
|
||||
from cookbook.filters import RecipeFilter
|
||||
from cookbook.helper.permission_helper import has_group_permission
|
||||
from cookbook.managers import DICTIONARY
|
||||
from cookbook.models import Food, Keyword, ViewLog, SearchPreference
|
||||
from cookbook.models import Food, Keyword, Recipe, SearchPreference, ViewLog
|
||||
from recipes import settings
|
||||
|
||||
|
||||
class Round(Func):
|
||||
@@ -62,7 +62,7 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
# return queryset.annotate(last_view=Max('viewlog__pk')).annotate(new=Case(When(pk__in=last_viewed_recipes, then=('last_view')), default=Value(0))).filter(new__gt=0).order_by('-new')
|
||||
# queryset that only annotates most recent view (higher pk = lastest view)
|
||||
queryset = queryset.annotate(recent=Coalesce(Max('viewlog__pk'), Value(0)))
|
||||
queryset = queryset.annotate(recent=Coalesce(Max(Case(When(viewlog__created_by=request.user, then='viewlog__pk'))), Value(0)))
|
||||
orderby += ['-recent']
|
||||
|
||||
# TODO create setting for default ordering - most cooked, rating,
|
||||
@@ -143,9 +143,9 @@ def search_recipes(request, queryset, params):
|
||||
|
||||
# TODO add order by user settings - only do search rank and annotation if rank order is configured
|
||||
search_rank = (
|
||||
SearchRank('name_search_vector', search_query, cover_density=True)
|
||||
+ SearchRank('desc_search_vector', search_query, cover_density=True)
|
||||
+ SearchRank('steps__search_vector', search_query, cover_density=True)
|
||||
SearchRank('name_search_vector', search_query, cover_density=True)
|
||||
+ SearchRank('desc_search_vector', search_query, cover_density=True)
|
||||
+ SearchRank('steps__search_vector', search_query, cover_density=True)
|
||||
)
|
||||
queryset = queryset.filter(query_filter).annotate(rank=search_rank)
|
||||
orderby += ['-rank']
|
||||
@@ -400,3 +400,13 @@ def annotated_qs(qs, root=False, fill=False):
|
||||
if start_depth and start_depth > 0:
|
||||
info['close'] = list(range(0, prev_depth - start_depth + 1))
|
||||
return result
|
||||
|
||||
|
||||
def old_search(request):
|
||||
if has_group_permission(request.user, ('guest',)):
|
||||
params = dict(request.GET)
|
||||
params['internal'] = None
|
||||
f = RecipeFilter(params,
|
||||
queryset=Recipe.objects.filter(space=request.user.userpreference.space).all().order_by('name'),
|
||||
space=request.space)
|
||||
return f.qs
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
cookbook/locale/ro/LC_MESSAGES/django.mo
Normal file
BIN
cookbook/locale/ro/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
BIN
cookbook/locale/sl/LC_MESSAGES/django.mo
Normal file
BIN
cookbook/locale/sl/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
@@ -137,6 +137,7 @@ class UserNameSerializer(WritableNestedModelSerializer):
|
||||
|
||||
|
||||
class UserPreferenceSerializer(serializers.ModelSerializer):
|
||||
plan_share = UserNameSerializer(many=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
if validated_data['user'] != self.context['request'].user:
|
||||
@@ -620,6 +621,7 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
meal_type_name = serializers.ReadOnlyField(source='meal_type.name') # TODO deprecate once old meal plan was removed
|
||||
note_markdown = serializers.SerializerMethodField('get_note_markdown')
|
||||
servings = CustomDecimalField()
|
||||
shared = UserNameSerializer(many=True)
|
||||
|
||||
def get_note_markdown(self, obj):
|
||||
return markdown(obj.note)
|
||||
|
||||
@@ -349,6 +349,7 @@
|
||||
localStorage.setItem('SCRIPT_NAME', "{% base_path request 'script' %}")
|
||||
localStorage.setItem('BASE_PATH', "{% base_path request 'base' %}")
|
||||
localStorage.setItem('STATIC_URL', "{% base_path request 'static_base' %}")
|
||||
localStorage.setItem('DEBUG', "{% is_debug %}")
|
||||
window.addEventListener("load", () => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: "{% base_path request 'base' %}" + '/'}).then(function (reg) {
|
||||
|
||||
@@ -94,10 +94,10 @@ def recipe_last(recipe, user):
|
||||
@register.simple_tag
|
||||
def page_help(page_name):
|
||||
help_pages = {
|
||||
'edit_storage': 'https://vabene1111.github.io/recipes/features/external_recipes/',
|
||||
'view_shopping': 'https://vabene1111.github.io/recipes/features/shopping/',
|
||||
'view_import': 'https://vabene1111.github.io/recipes/features/import_export/',
|
||||
'view_export': 'https://vabene1111.github.io/recipes/features/import_export/',
|
||||
'edit_storage': 'https://docs.tandoor.dev/features/external_recipes/',
|
||||
'view_shopping': 'https://docs.tandoor.dev/features/shopping/',
|
||||
'view_import': 'https://docs.tandoor.dev/features/import_export/',
|
||||
'view_export': 'https://docs.tandoor.dev/features/import_export/',
|
||||
}
|
||||
|
||||
link = help_pages.get(page_name, '')
|
||||
|
||||
@@ -106,7 +106,7 @@ def test_add(arg, request, u1_s2, recipe_1_s1, meal_type):
|
||||
r = c.post(
|
||||
reverse(LIST_URL),
|
||||
{'recipe': {'id': recipe_1_s1.id, 'name': recipe_1_s1.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name},
|
||||
'date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test'},
|
||||
'date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test','shared':[]},
|
||||
content_type='application/json'
|
||||
)
|
||||
response = json.loads(r.content)
|
||||
|
||||
@@ -23,8 +23,8 @@ def test_list_permission(arg, request):
|
||||
|
||||
|
||||
def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 2
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 0
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 2
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 0
|
||||
|
||||
with scopes_disabled():
|
||||
recipe_1_s1.space = space_2
|
||||
@@ -32,9 +32,9 @@ def test_list_space(recipe_1_s1, u1_s1, u1_s2, space_2):
|
||||
Step.objects.update(space=Subquery(Step.objects.filter(pk=OuterRef('pk')).values('recipe__space')[:1]))
|
||||
Ingredient.objects.update(space=Subquery(Ingredient.objects.filter(pk=OuterRef('pk')).values('step__recipe__space')[:1]))
|
||||
|
||||
assert json.loads(u1_s1.get(reverse(LIST_URL)).content)['count'] == 0
|
||||
assert json.loads(u1_s2.get(reverse(LIST_URL)).content)['count'] == 2
|
||||
|
||||
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)['results']) == 0
|
||||
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)['results']) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
|
||||
@@ -18,10 +18,10 @@ def test_add(u1_s1, u2_s1):
|
||||
with scopes_disabled():
|
||||
UserPreference.objects.filter(user=auth.get_user(u1_s1)).delete()
|
||||
|
||||
r = u2_s1.post(reverse(LIST_URL), {'user': auth.get_user(u1_s1).id}, content_type='application/json')
|
||||
r = u2_s1.post(reverse(LIST_URL), {'user': auth.get_user(u1_s1).id, 'plan_share': []}, content_type='application/json')
|
||||
assert r.status_code == 404
|
||||
|
||||
r = u1_s1.post(reverse(LIST_URL), {'user': auth.get_user(u1_s1).id}, content_type='application/json')
|
||||
r = u1_s1.post(reverse(LIST_URL), {'user': auth.get_user(u1_s1).id, 'plan_share': []}, content_type='application/json')
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@ from pydoc import locate
|
||||
|
||||
from django.urls import include, path
|
||||
from django.views.generic import TemplateView
|
||||
from recipes.version import VERSION_NUMBER
|
||||
from rest_framework import routers, permissions
|
||||
from rest_framework import permissions, routers
|
||||
from rest_framework.schemas import get_schema_view
|
||||
|
||||
from cookbook.helper import dal
|
||||
from recipes.settings import DEBUG
|
||||
from recipes.version import VERSION_NUMBER
|
||||
|
||||
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
|
||||
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
|
||||
from .models import (Automation, Comment, Food, InviteLink, Keyword, MealPlan, Recipe, RecipeBook,
|
||||
RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage, Supermarket,
|
||||
SupermarketCategory, Sync, SyncLog, Unit, UserFile, get_model_name)
|
||||
from .views import api, data, delete, edit, import_export, lists, new, telegram, views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'user-name', api.UserNameViewSet, basename='username')
|
||||
@@ -68,8 +68,6 @@ urlpatterns = [
|
||||
path('history/', views.history, name='view_history'),
|
||||
path('supermarket/', views.supermarket, name='view_supermarket'),
|
||||
path('abuse/<slug:token>', views.report_share_abuse, name='view_report_share_abuse'),
|
||||
path('test/', views.test, name='view_test'),
|
||||
path('test2/', views.test2, name='view_test2'),
|
||||
|
||||
path('import/', import_export.import_recipe, name='view_import'),
|
||||
path('import-response/<int:pk>/', import_export.import_response, name='view_import_response'),
|
||||
@@ -189,3 +187,7 @@ for m in vue_models:
|
||||
f'list/{url_name}/', c, name=f'list_{py_name}'
|
||||
)
|
||||
)
|
||||
|
||||
if DEBUG:
|
||||
urlpatterns.append(path('test/', views.test, name='view_test'))
|
||||
urlpatterns.append(path('test2/', views.test2, name='view_test2'))
|
||||
|
||||
@@ -46,7 +46,9 @@ from cookbook.models import (Automation, BookmarkletImport, CookLog, Food, Impor
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
from cookbook.schemas import FilterSchema, QueryParam, QueryParamAutoSchema, TreeSchema
|
||||
|
||||
from cookbook.schemas import FilterSchema, QueryOnlySchema, RecipeSchema, TreeSchema,QueryParamAutoSchema
|
||||
|
||||
from cookbook.serializer import (AutomationSerializer, BookmarkletImportSerializer,
|
||||
CookLogSerializer, FoodSerializer, ImportLogSerializer,
|
||||
IngredientSerializer, KeywordSerializer, MealPlanSerializer,
|
||||
@@ -408,7 +410,7 @@ class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
||||
permission_classes = [CustomIsOwner]
|
||||
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=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).distinct()
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
@@ -564,7 +566,16 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
if self.request.GET.get('debug', False):
|
||||
return JsonResponse({
|
||||
'new': str(self.get_queryset().query),
|
||||
'old': str(old_search(request).query)
|
||||
})
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
# TODO write extensive tests for permissions
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return RecipeOverviewSerializer
|
||||
|
||||
@@ -13,7 +13,7 @@ from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Avg, Q, Sum
|
||||
from django.http import HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
@@ -22,16 +22,15 @@ from django_tables2 import RequestConfig
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import (CommentForm, Recipe, User,
|
||||
UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm,
|
||||
SearchPreferenceForm)
|
||||
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
|
||||
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
|
||||
ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit,
|
||||
Food, UserFile, ShareLink, SearchPreference, SearchFields)
|
||||
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
|
||||
ViewLogTable, InviteLinkTable)
|
||||
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm,
|
||||
SpaceJoinForm, User, UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm)
|
||||
from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid
|
||||
from cookbook.models import (Comment, CookLog, Food, InviteLink, Keyword, MealPlan, RecipeImport,
|
||||
SearchFields, SearchPreference, ShareLink, ShoppingList, Space, Unit,
|
||||
UserFile, ViewLog)
|
||||
from cookbook.tables import (CookLogTable, InviteLinkTable, RecipeTable, RecipeTableSmall,
|
||||
ViewLogTable)
|
||||
from cookbook.views.data import Object
|
||||
from recipes.version import BUILD_REF, VERSION_NUMBER
|
||||
|
||||
@@ -331,10 +330,10 @@ def user_settings(request):
|
||||
if not sp:
|
||||
sp = SearchPreferenceForm(user=request.user)
|
||||
fields_searched = (
|
||||
len(search_form.cleaned_data['icontains'])
|
||||
+ len(search_form.cleaned_data['istartswith'])
|
||||
+ len(search_form.cleaned_data['trigram'])
|
||||
+ len(search_form.cleaned_data['fulltext'])
|
||||
len(search_form.cleaned_data['icontains'])
|
||||
+ len(search_form.cleaned_data['istartswith'])
|
||||
+ len(search_form.cleaned_data['trigram'])
|
||||
+ len(search_form.cleaned_data['fulltext'])
|
||||
)
|
||||
if fields_searched == 0:
|
||||
search_form.add_error(None, _('You must select at least one field to search!'))
|
||||
@@ -382,7 +381,7 @@ def user_settings(request):
|
||||
if up:
|
||||
preference_form = UserPreferenceForm(instance=up, space=request.space)
|
||||
else:
|
||||
preference_form = UserPreferenceForm( space=request.space)
|
||||
preference_form = UserPreferenceForm(space=request.space)
|
||||
|
||||
fields_searched = len(sp.icontains.all()) + len(sp.istartswith.all()) + len(sp.trigram.all()) + len(
|
||||
sp.fulltext.all())
|
||||
|
||||
Reference in New Issue
Block a user