Fix after rebase

This commit is contained in:
smilerz 2021-07-29 16:38:33 -05:00
parent 89ec549dac
commit 2f91c2f86e
18 changed files with 4375 additions and 4556 deletions

View File

@ -18,6 +18,8 @@ from .models import (Comment, CookLog, Food, Ingredient, InviteLink, Keyword,
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from cookbook.managers import DICTIONARY
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
def has_add_permission(self, request, obj=None): def has_add_permission(self, request, obj=None):

View File

@ -35,10 +35,12 @@ def search_recipes(request, queryset, params):
return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes) - min(len(last_viewed_recipes), search_last_viewed):]) return queryset.filter(pk__in=last_viewed_recipes[len(last_viewed_recipes) - min(len(last_viewed_recipes), search_last_viewed):])
if search_new == 'true':
queryset = queryset.annotate( queryset = queryset.annotate(
new_recipe=Case(When( new_recipe=Case(When(created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
created_at__gte=(datetime.now() - timedelta(days=7)), then=Value(100)),
default=Value(0), )).order_by('-new_recipe', 'name') default=Value(0), )).order_by('-new_recipe', 'name')
else:
queryset = queryset.order_by('name')
search_type = search_prefs.search or 'plain' search_type = search_prefs.search or 'plain'
search_sort = None search_sort = None
@ -148,12 +150,6 @@ def search_recipes(request, queryset, params):
elif search_sort == 'rank': elif search_sort == 'rank':
queryset = queryset.order_by('-rank') queryset = queryset.order_by('-rank')
# kw = Keyword.objects.filter(recipe__in=queryset).annotate(kw_count=Count('recipe'))
# Keyword.get_annotated_list_qs(Keyword.objects.filter(id__in=[item.id for k in kw for item in k.get_ancestors_and_self()]))
# print(time.time()-start, len(queryset), len(kw))
# Keyword.get_annotated_list_qs(
# Keyword.objects.filter(recipe__in=queryset).annotate(kw_count=Count('recipe'))
# | Keyword.objects.all().filter(id__in=set([k.parent for k in Keyword.objects.filter(recipe__in=queryset).annotate(kw_count=Count('recipe'))])))
return queryset return queryset

View File

@ -27,7 +27,7 @@ def backwards(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('cookbook', '0140_userpreference_created_at'), ('cookbook', '0145_alter_userpreference_use_fractions'),
] ]
operations = [ operations = [

View File

@ -422,6 +422,7 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True) file = models.ForeignKey('UserFile', on_delete=models.PROTECT, null=True, blank=True)
show_as_header = models.BooleanField(default=True) show_as_header = models.BooleanField(default=True)
search_vector = SearchVectorField(null=True) search_vector = SearchVectorField(null=True)
step_recipe = models.ForeignKey('Recipe', default=None, blank=True, null=True, on_delete=models.PROTECT)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@ -840,42 +841,11 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_by = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self): objects = ScopedManager(space='space')
return _(self.name) space = models.ForeignKey(Space, on_delete=models.CASCADE)
@staticmethod def save(self, *args, **kwargs):
def get_name(self): if hasattr(self.file, 'file') and isinstance(self.file.file, UploadedFile) or isinstance(self.file.file, InMemoryUploadedFile):
return _(self.name) self.file.name = f'{uuid.uuid4()}' + pathlib.Path(self.file.name).suffix
self.file_size_kb = round(self.file.size / 1000)
super(UserFile, self).save(*args, **kwargs)
def allSearchFields():
return SearchFields.objects.values_list('id')
def nameSearchField():
return [SearchFields.objects.get(name='Name').id]
class SearchPreference(models.Model, PermissionModelMixin):
# Search Style (validation parsleyjs.org)
# phrase or plain or raw (websearch and trigrams are mutually exclusive)
SIMPLE = 'plain'
PHRASE = 'phrase'
WEB = 'websearch'
RAW = 'raw'
SEARCH_STYLE = (
(SIMPLE, _('Simple')),
(PHRASE, _('Phrase')),
(WEB, _('Web')),
(RAW, _('Raw'))
)
user = AutoOneToOneField(User, on_delete=models.CASCADE, primary_key=True)
search = models.CharField(choices=SEARCH_STYLE, max_length=32, default=SIMPLE)
lookup = models.BooleanField(default=False)
unaccent = models.ManyToManyField(SearchFields, related_name="unaccent_fields", blank=True, default=allSearchFields)
icontains = models.ManyToManyField(SearchFields, related_name="icontains_fields", blank=True, default=nameSearchField)
istartswith = models.ManyToManyField(SearchFields, related_name="istartswith_fields", blank=True)
trigram = models.ManyToManyField(SearchFields, related_name="trigram_fields", blank=True)
fulltext = models.ManyToManyField(SearchFields, related_name="fulltext_fields", blank=True)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -322,9 +322,17 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, StandardFilterMixin):
return super().get_queryset() return super().get_queryset()
class KeywordViewSet(viewsets.ModelViewSet, TreeMixin): class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
# TODO check if fuzzyfilter is conflicting - may also need to create 'tree filter' mixin queryset = SupermarketCategoryRelation.objects
serializer_class = SupermarketCategoryRelationSerializer
permission_classes = [CustomIsUser]
def get_queryset(self):
self.queryset = self.queryset.filter(supermarket__space=self.request.space)
return super().get_queryset()
class KeywordViewSet(viewsets.ModelViewSet, StandardFilterMixin):
queryset = Keyword.objects queryset = Keyword.objects
model = Keyword model = Keyword
serializer_class = KeywordSerializer serializer_class = KeywordSerializer
@ -439,6 +447,67 @@ class RecipePagination(PageNumberPagination):
max_page_size = 100 max_page_size = 100
# TODO move to separate class to cleanup
class RecipeSchema(AutoSchema):
def get_path_parameters(self, path, method):
if not is_list_view(path, method, self.view):
return super(RecipeSchema, 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 recipe name. In the future also fulltext search.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'keywords', "in": "query", "required": False,
"description": 'Id of keyword a recipe should have. For multiple repeat parameter.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'foods', "in": "query", "required": False,
"description": 'Id of food a recipe should have. For multiple repeat parameter.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'books', "in": "query", "required": False,
"description": 'Id of book 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.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'foods_or', "in": "query", "required": False,
"description": 'If recipe should have all (AND) or any (OR) any of the provided foods.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'books_or', "in": "query", "required": False,
"description": 'If recipe should be in all (AND) or any (OR) any of the provided books.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'internal', "in": "query", "required": False,
"description": 'true or false. If only internal recipes should be returned or not.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'random', "in": "query", "required": False,
"description": 'true or false. returns the results in randomized order.',
'schema': {'type': 'string', },
})
parameters.append({
"name": 'new', "in": "query", "required": False,
"description": 'true or false. returns new results first in search results',
'schema': {'type': 'string', },
})
return parameters
class RecipeViewSet(viewsets.ModelViewSet): class RecipeViewSet(viewsets.ModelViewSet):
queryset = Recipe.objects queryset = Recipe.objects
serializer_class = RecipeSerializer serializer_class = RecipeSerializer

View File

@ -122,11 +122,6 @@
<a :href="resolveDjangoUrl('view_settings') + '#search'">{{ $t('Advanced Search Settings') }}</a> <a :href="resolveDjangoUrl('view_settings') + '#search'">{{ $t('Advanced Search Settings') }}</a>
</div> </div>
</div> </div>
<div class="row" style="margin-top: 1vh">
<div class="col-12">
<a :href="resolveDjangoUrl('view_settings') + '#search'">{{ $t('Advanced Search Settings') }}</a>
</div>
</div>
<div class="row" style="margin-top: 1vh"> <div class="row" style="margin-top: 1vh">
<div class="col-12" style="text-align: right"> <div class="col-12" style="text-align: right">
<b-button size="sm" variant="secondary" style="margin-right:8px" <b-button size="sm" variant="secondary" style="margin-right:8px"

View File

@ -6,8 +6,7 @@
<b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src=recipe_image <b-card-img-lazy style="height: 15vh; object-fit: cover" class="" :src=recipe_image
v-bind:alt="$t('Recipe_Image')" v-bind:alt="$t('Recipe_Image')"
top></b-card-img-lazy> top></b-card-img-lazy>
<div class="card-img-overlay h-100 d-flex flex-column justify-content-right"
<div class="h-100 d-flex flex-column justify-content-right"
style="float:right; text-align: right; padding-top: 10px; padding-right: 5px"> style="float:right; text-align: right; padding-top: 10px; padding-right: 5px">
<a> <a>
<recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu> <recipe-context-menu :recipe="recipe" style="float:right" v-if="recipe !== null"></recipe-context-menu>

View File

@ -56,6 +56,7 @@
"Rating": "Rating", "Rating": "Rating",
"Close": "Close", "Close": "Close",
"Cancel": "Cancel", "Cancel": "Cancel",
"Link": "Link",
"Add": "Add", "Add": "Add",
"New": "New", "New": "New",
"Success": "Success", "Success": "Success",
@ -85,6 +86,7 @@
"or": "or", "or": "or",
"and": "and", "and": "and",
"Information": "Information", "Information": "Information",
"Advanced Search Settings": "Advanced Search Settings",
"View": "View", "View": "View",
"Recipes": "Recipes", "Recipes": "Recipes",
"Move": "Move", "Move": "Move",

View File

@ -8082,8 +8082,8 @@ export const ApiApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
async listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InlineResponse2001>> { 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<InlineResponse200>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, page, pageSize, options); const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/** /**
@ -9596,8 +9596,8 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
* @param {*} [options] Override http request option. * @param {*} [options] Override http request option.
* @throws {RequiredError} * @throws {RequiredError}
*/ */
listRecipes(query?: string, keywords?: string, foods?: string, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, page?: number, pageSize?: number, options?: any): AxiosPromise<InlineResponse2001> { 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<InlineResponse200> {
return localVarFp.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, page, pageSize, options).then((request) => request(axios, basePath)); return localVarFp.listRecipes(query, keywords, foods, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath));
}, },
/** /**
* *

File diff suppressed because it is too large Load Diff