Merge pull request #2630 from smilerz/imports_cleanup

Imports cleanup
This commit is contained in:
vabene1111 2023-10-05 18:55:32 +02:00 committed by GitHub
commit bc63ba6713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 207 additions and 777 deletions

View File

@ -10,13 +10,13 @@ from treebeard.forms import movenodeform_factory
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from .models import (Automation, BookmarkletImport, Comment, CookLog, Food, FoodInheritField, from .models import (BookmarkletImport, Comment, CookLog, Food, ImportLog, Ingredient, InviteLink,
ImportLog, Ingredient, InviteLink, Keyword, MealPlan, MealType, Keyword, MealPlan, MealType, NutritionInformation, Property, PropertyType,
NutritionInformation, Property, PropertyType, Recipe, RecipeBook, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, ShoppingList, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Supermarket, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, TelegramBot, TelegramBot, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog) ViewLog)
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):

View File

@ -1,11 +1,10 @@
import datetime import datetime
from gettext import gettext as _
from django.conf import settings
from allauth.account.adapter import DefaultAccountAdapter from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.core.cache import caches from django.core.cache import caches
from gettext import gettext as _
from cookbook.models import InviteLink from cookbook.models import InviteLink
@ -17,7 +16,8 @@ class AllAuthCustomAdapter(DefaultAccountAdapter):
Whether to allow sign-ups. Whether to allow sign-ups.
""" """
signup_token = False signup_token = False
if 'signup_token' in request.session and InviteLink.objects.filter(valid_until__gte=datetime.datetime.today(), used_by=None, uuid=request.session['signup_token']).exists(): if 'signup_token' in request.session and InviteLink.objects.filter(
valid_until__gte=datetime.datetime.today(), used_by=None, uuid=request.session['signup_token']).exists():
signup_token = True signup_token = True
if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP and not signup_token: if request.resolver_match.view_name == 'account_signup' and not settings.ENABLE_SIGNUP and not signup_token:
@ -35,7 +35,7 @@ class AllAuthCustomAdapter(DefaultAccountAdapter):
if c == default: if c == default:
try: try:
super(AllAuthCustomAdapter, self).send_mail(template_prefix, email, context) super(AllAuthCustomAdapter, self).send_mail(template_prefix, email, context)
except Exception: # dont fail signup just because confirmation mail could not be send except Exception: # dont fail signup just because confirmation mail could not be send
pass pass
else: else:
messages.add_message(self.request, messages.ERROR, _('In order to prevent spam, the requested email was not send. Please wait a few minutes and try again.')) messages.add_message(self.request, messages.ERROR, _('In order to prevent spam, the requested email was not send. Please wait a few minutes and try again.'))

View File

@ -1,6 +1,3 @@
import cookbook.helper.dal
from cookbook.helper.AllAuthCustomAdapter import AllAuthCustomAdapter
__all__ = [ __all__ = [
'dal', 'dal',
] ]

View File

@ -1,8 +1,7 @@
import os import os
import sys from io import BytesIO
from PIL import Image from PIL import Image
from io import BytesIO
def rescale_image_jpeg(image_object, base_width=1020): def rescale_image_jpeg(image_object, base_width=1020):

View File

@ -1,6 +1,5 @@
from django.db.models import Q from cookbook.models import (Food, FoodProperty, Property, PropertyType, Supermarket,
SupermarketCategory, SupermarketCategoryRelation, Unit, UnitConversion)
from cookbook.models import Unit, SupermarketCategory, Property, PropertyType, Supermarket, SupermarketCategoryRelation, Food, Automation, UnitConversion, FoodProperty
class OpenDataImporter: class OpenDataImporter:
@ -33,7 +32,8 @@ class OpenDataImporter:
)) ))
if self.update_existing: if self.update_existing:
return Unit.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('name', 'plural_name', 'base_unit', 'open_data_slug'), unique_fields=('space', 'name',)) return Unit.objects.bulk_create(insert_list, update_conflicts=True, update_fields=(
'name', 'plural_name', 'base_unit', 'open_data_slug'), unique_fields=('space', 'name',))
else: else:
return Unit.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',)) return Unit.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',))
@ -116,12 +116,6 @@ class OpenDataImporter:
self._update_slug_cache(Unit, 'unit') self._update_slug_cache(Unit, 'unit')
self._update_slug_cache(PropertyType, 'property') self._update_slug_cache(PropertyType, 'property')
# pref_unit_key = 'preferred_unit_metric'
# pref_shopping_unit_key = 'preferred_packaging_unit_metric'
# if not self.use_metric:
# pref_unit_key = 'preferred_unit_imperial'
# pref_shopping_unit_key = 'preferred_packaging_unit_imperial'
insert_list = [] insert_list = []
update_list = [] update_list = []
update_field_list = [] update_field_list = []
@ -130,8 +124,6 @@ class OpenDataImporter:
insert_list.append({'data': { insert_list.append({'data': {
'name': self.data[datatype][k]['name'], 'name': self.data[datatype][k]['name'],
'plural_name': self.data[datatype][k]['plural_name'] if self.data[datatype][k]['plural_name'] != '' else None, 'plural_name': self.data[datatype][k]['plural_name'] if self.data[datatype][k]['plural_name'] != '' else None,
# 'preferred_unit_id': self.slug_id_cache['unit'][self.data[datatype][k][pref_unit_key]],
# 'preferred_shopping_unit_id': self.slug_id_cache['unit'][self.data[datatype][k][pref_shopping_unit_key]],
'supermarket_category_id': self.slug_id_cache['category'][self.data[datatype][k]['store_category']], 'supermarket_category_id': self.slug_id_cache['category'][self.data[datatype][k]['store_category']],
'fdc_id': self.data[datatype][k]['fdc_id'] if self.data[datatype][k]['fdc_id'] != '' else None, 'fdc_id': self.data[datatype][k]['fdc_id'] if self.data[datatype][k]['fdc_id'] != '' else None,
'open_data_slug': k, 'open_data_slug': k,
@ -149,8 +141,6 @@ class OpenDataImporter:
id=existing_food_id, id=existing_food_id,
name=self.data[datatype][k]['name'], name=self.data[datatype][k]['name'],
plural_name=self.data[datatype][k]['plural_name'] if self.data[datatype][k]['plural_name'] != '' else None, plural_name=self.data[datatype][k]['plural_name'] if self.data[datatype][k]['plural_name'] != '' else None,
# preferred_unit_id=self.slug_id_cache['unit'][self.data[datatype][k][pref_unit_key]],
# preferred_shopping_unit_id=self.slug_id_cache['unit'][self.data[datatype][k][pref_shopping_unit_key]],
supermarket_category_id=self.slug_id_cache['category'][self.data[datatype][k]['store_category']], supermarket_category_id=self.slug_id_cache['category'][self.data[datatype][k]['store_category']],
fdc_id=self.data[datatype][k]['fdc_id'] if self.data[datatype][k]['fdc_id'] != '' else None, fdc_id=self.data[datatype][k]['fdc_id'] if self.data[datatype][k]['fdc_id'] != '' else None,
open_data_slug=k, open_data_slug=k,
@ -166,7 +156,7 @@ class OpenDataImporter:
self._update_slug_cache(Food, 'food') self._update_slug_cache(Food, 'food')
food_property_list = [] food_property_list = []
alias_list = [] # alias_list = []
for k in list(self.data[datatype].keys()): for k in list(self.data[datatype].keys()):
for fp in self.data[datatype][k]['properties']['type_values']: for fp in self.data[datatype][k]['properties']['type_values']:
@ -180,15 +170,6 @@ class OpenDataImporter:
)) ))
except KeyError: except KeyError:
print(str(k) + ' is not in self.slug_id_cache["food"]') print(str(k) + ' is not in self.slug_id_cache["food"]')
# for a in self.data[datatype][k]['alias']:
# alias_list.append(Automation(
# param_1=a,
# param_2=self.data[datatype][k]['name'],
# space=self.request.space,
# created_by=self.request.user,
# ))
Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type',)) Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type',))
@ -198,7 +179,6 @@ class OpenDataImporter:
FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',)) FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',))
# Automation.objects.bulk_create(alias_list, ignore_conflicts=True, unique_fields=('space', 'param_1', 'param_2',))
return insert_list + update_list return insert_list + update_list
def import_conversion(self): def import_conversion(self):
@ -206,7 +186,7 @@ class OpenDataImporter:
insert_list = [] insert_list = []
for k in list(self.data[datatype].keys()): for k in list(self.data[datatype].keys()):
# try catch here because somettimes key "k" is not set for he food cache # try catch here because sometimes key "k" is not set for he food cache
try: try:
insert_list.append(UnitConversion( insert_list.append(UnitConversion(
base_amount=self.data[datatype][k]['base_amount'], base_amount=self.data[datatype][k]['base_amount'],

View File

@ -4,16 +4,16 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ValidationError, ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from oauth2_provider.contrib.rest_framework import TokenHasScope, TokenHasReadWriteScope from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
from oauth2_provider.models import AccessToken from oauth2_provider.models import AccessToken
from rest_framework import permissions from rest_framework import permissions
from rest_framework.permissions import SAFE_METHODS from rest_framework.permissions import SAFE_METHODS
from cookbook.models import ShareLink, Recipe, UserSpace from cookbook.models import Recipe, ShareLink, UserSpace
def get_allowed_groups(groups_required): def get_allowed_groups(groups_required):
@ -255,9 +255,6 @@ class CustomIsShared(permissions.BasePermission):
return request.user.is_authenticated return request.user.is_authenticated
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
# # temporary hack to make old shopping list work with new shopping list
# if obj.__class__.__name__ in ['ShoppingList', 'ShoppingListEntry']:
# return is_object_shared(request.user, obj) or obj.created_by in list(request.user.get_shopping_share())
return is_object_shared(request.user, obj) return is_object_shared(request.user, obj)
@ -322,7 +319,8 @@ class CustomRecipePermission(permissions.BasePermission):
def has_permission(self, request, view): # user is either at least a guest or a share link is given and the request is safe def has_permission(self, request, view): # user is either at least a guest or a share link is given and the request is safe
share = request.query_params.get('share', None) share = request.query_params.get('share', None)
return ((has_group_permission(request.user, ['guest']) and request.method in SAFE_METHODS) or has_group_permission(request.user, ['user'])) or (share and request.method in SAFE_METHODS and 'pk' in view.kwargs) return ((has_group_permission(request.user, ['guest']) and request.method in SAFE_METHODS) or has_group_permission(
request.user, ['user'])) or (share and request.method in SAFE_METHODS and 'pk' in view.kwargs)
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
share = request.query_params.get('share', None) share = request.query_params.get('share', None)
@ -332,7 +330,8 @@ class CustomRecipePermission(permissions.BasePermission):
if obj.private: if obj.private:
return ((obj.created_by == request.user) or (request.user in obj.shared.all())) and obj.space == request.space return ((obj.created_by == request.user) or (request.user in obj.shared.all())) and obj.space == request.space
else: else:
return ((has_group_permission(request.user, ['guest']) and request.method in SAFE_METHODS) or has_group_permission(request.user, ['user'])) and obj.space == request.space return ((has_group_permission(request.user, ['guest']) and request.method in SAFE_METHODS)
or has_group_permission(request.user, ['user'])) and obj.space == request.space
class CustomUserPermission(permissions.BasePermission): class CustomUserPermission(permissions.BasePermission):
@ -361,7 +360,7 @@ class CustomTokenHasScope(TokenHasScope):
""" """
def has_permission(self, request, view): def has_permission(self, request, view):
if type(request.auth) == AccessToken: if isinstance(request.auth, AccessToken):
return super().has_permission(request, view) return super().has_permission(request, view)
else: else:
return request.user.is_authenticated return request.user.is_authenticated
@ -375,7 +374,7 @@ class CustomTokenHasReadWriteScope(TokenHasReadWriteScope):
""" """
def has_permission(self, request, view): def has_permission(self, request, view):
if type(request.auth) == AccessToken: if isinstance(request.auth, AccessToken):
return super().has_permission(request, view) return super().has_permission(request, view)
else: else:
return True return True

View File

@ -2,7 +2,7 @@ from django.core.cache import caches
from cookbook.helper.cache_helper import CacheHelper from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.unit_conversion_helper import UnitConversionHelper from cookbook.helper.unit_conversion_helper import UnitConversionHelper
from cookbook.models import PropertyType, Unit, Food, Property, Recipe, Step from cookbook.models import PropertyType
class FoodPropertyHelper: class FoodPropertyHelper:
@ -31,10 +31,12 @@ class FoodPropertyHelper:
if not property_types: if not property_types:
property_types = PropertyType.objects.filter(space=self.space).all() property_types = PropertyType.objects.filter(space=self.space).all()
caches['default'].set(CacheHelper(self.space).PROPERTY_TYPE_CACHE_KEY, property_types, 60 * 60) # cache is cleared on property type save signal so long duration is fine # cache is cleared on property type save signal so long duration is fine
caches['default'].set(CacheHelper(self.space).PROPERTY_TYPE_CACHE_KEY, property_types, 60 * 60)
for fpt in property_types: for fpt in property_types:
computed_properties[fpt.id] = {'id': fpt.id, 'name': fpt.name, 'description': fpt.description, 'unit': fpt.unit, 'order': fpt.order, 'food_values': {}, 'total_value': 0, 'missing_value': False} computed_properties[fpt.id] = {'id': fpt.id, 'name': fpt.name, 'description': fpt.description,
'unit': fpt.unit, 'order': fpt.order, 'food_values': {}, 'total_value': 0, 'missing_value': False}
uch = UnitConversionHelper(self.space) uch = UnitConversionHelper(self.space)
@ -53,7 +55,8 @@ class FoodPropertyHelper:
if c.unit == i.food.properties_food_unit: if c.unit == i.food.properties_food_unit:
found_property = True found_property = True
computed_properties[pt.id]['total_value'] += (c.amount / i.food.properties_food_amount) * p.property_amount computed_properties[pt.id]['total_value'] += (c.amount / i.food.properties_food_amount) * p.property_amount
computed_properties[pt.id]['food_values'] = self.add_or_create(computed_properties[p.property_type.id]['food_values'], c.food.id, (c.amount / i.food.properties_food_amount) * p.property_amount, c.food) computed_properties[pt.id]['food_values'] = self.add_or_create(
computed_properties[p.property_type.id]['food_values'], c.food.id, (c.amount / i.food.properties_food_amount) * p.property_amount, c.food)
if not found_property: if not found_property:
computed_properties[pt.id]['missing_value'] = True computed_properties[pt.id]['missing_value'] = True
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': i.food.name, 'value': 0} computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': i.food.name, 'value': 0}

View File

@ -14,7 +14,6 @@ from cookbook.models import (CookLog, CustomFilter, Food, Keyword, Recipe, Searc
from recipes import settings from recipes import settings
# TODO create extensive tests to make sure ORs ANDs and various filters, sorting, etc work as expected
# TODO consider creating a simpleListRecipe API that only includes minimum of recipe info and minimal filtering # TODO consider creating a simpleListRecipe API that only includes minimum of recipe info and minimal filtering
class RecipeSearch(): class RecipeSearch():
_postgres = settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql'] _postgres = settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']
@ -162,7 +161,7 @@ class RecipeSearch():
else: else:
order = [] order = []
# TODO add userpreference for default sort order and replace '-favorite' # TODO add userpreference for default sort order and replace '-favorite'
default_order = ['-name'] default_order = ['name']
# recent and new_recipe are always first; they float a few recipes to the top # recent and new_recipe are always first; they float a few recipes to the top
if self._num_recent: if self._num_recent:
order += ['-recent'] order += ['-recent']
@ -580,218 +579,3 @@ class RecipeSearch():
.annotate(sibling_onhand=Exists(sibling_onhand_subquery)) .annotate(sibling_onhand=Exists(sibling_onhand_subquery))
.filter(sibling_onhand=True) .filter(sibling_onhand=True)
) )
# class RecipeFacet():
# class CacheEmpty(Exception):
# pass
# def __init__(self, request, queryset=None, hash_key=None, cache_timeout=3600):
# if hash_key is None and queryset is None:
# raise ValueError(_("One of queryset or hash_key must be provided"))
# self._request = request
# self._queryset = queryset
# self.hash_key = hash_key or str(hash(self._queryset.query))
# self._SEARCH_CACHE_KEY = f"recipes_filter_{self.hash_key}"
# self._cache_timeout = cache_timeout
# self._cache = caches['default'].get(self._SEARCH_CACHE_KEY, {})
# if self._cache is None and self._queryset is None:
# raise self.CacheEmpty("No queryset provided and cache empty")
# self.Keywords = self._cache.get('Keywords', None)
# self.Foods = self._cache.get('Foods', None)
# self.Books = self._cache.get('Books', None)
# self.Ratings = self._cache.get('Ratings', None)
# # TODO Move Recent to recipe annotation/serializer: requrires change in RecipeSearch(), RecipeSearchView.vue and serializer
# self.Recent = self._cache.get('Recent', None)
# if self._queryset is not None:
# self._recipe_list = list(
# self._queryset.values_list('id', flat=True))
# self._search_params = {
# 'keyword_list': self._request.query_params.getlist('keywords', []),
# 'food_list': self._request.query_params.getlist('foods', []),
# 'book_list': self._request.query_params.getlist('book', []),
# 'search_keywords_or': str2bool(self._request.query_params.get('keywords_or', True)),
# 'search_foods_or': str2bool(self._request.query_params.get('foods_or', True)),
# 'search_books_or': str2bool(self._request.query_params.get('books_or', True)),
# 'space': self._request.space,
# }
# elif self.hash_key is not None:
# self._recipe_list = self._cache.get('recipe_list', [])
# self._search_params = {
# 'keyword_list': self._cache.get('keyword_list', None),
# 'food_list': self._cache.get('food_list', None),
# 'book_list': self._cache.get('book_list', None),
# 'search_keywords_or': self._cache.get('search_keywords_or', None),
# 'search_foods_or': self._cache.get('search_foods_or', None),
# 'search_books_or': self._cache.get('search_books_or', None),
# 'space': self._cache.get('space', None),
# }
# self._cache = {
# **self._search_params,
# 'recipe_list': self._recipe_list,
# 'Ratings': self.Ratings,
# 'Recent': self.Recent,
# 'Keywords': self.Keywords,
# 'Foods': self.Foods,
# 'Books': self.Books
# }
# caches['default'].set(self._SEARCH_CACHE_KEY,
# self._cache, self._cache_timeout)
# def get_facets(self, from_cache=False):
# if from_cache:
# return {
# 'cache_key': self.hash_key or '',
# 'Ratings': self.Ratings or {},
# 'Recent': self.Recent or [],
# 'Keywords': self.Keywords or [],
# 'Foods': self.Foods or [],
# 'Books': self.Books or []
# }
# return {
# 'cache_key': self.hash_key,
# 'Ratings': self.get_ratings(),
# 'Recent': self.get_recent(),
# 'Keywords': self.get_keywords(),
# 'Foods': self.get_foods(),
# 'Books': self.get_books()
# }
# def set_cache(self, key, value):
# self._cache = {**self._cache, key: value}
# caches['default'].set(
# self._SEARCH_CACHE_KEY,
# self._cache,
# self._cache_timeout
# )
# def get_books(self):
# if self.Books is None:
# self.Books = []
# return self.Books
# def get_keywords(self):
# if self.Keywords is None:
# if self._search_params['search_keywords_or']:
# keywords = Keyword.objects.filter(
# space=self._request.space).distinct()
# else:
# keywords = Keyword.objects.filter(Q(recipe__in=self._recipe_list) | Q(
# depth=1)).filter(space=self._request.space).distinct()
# # set keywords to root objects only
# keywords = self._keyword_queryset(keywords)
# self.Keywords = [{**x, 'children': None}
# if x['numchild'] > 0 else x for x in list(keywords)]
# self.set_cache('Keywords', self.Keywords)
# return self.Keywords
# def get_foods(self):
# if self.Foods is None:
# # # if using an OR search, will annotate all keywords, otherwise, just those that appear in results
# if self._search_params['search_foods_or']:
# foods = Food.objects.filter(
# space=self._request.space).distinct()
# else:
# foods = Food.objects.filter(Q(ingredient__step__recipe__in=self._recipe_list) | Q(
# depth=1)).filter(space=self._request.space).distinct()
# # set keywords to root objects only
# foods = self._food_queryset(foods)
# self.Foods = [{**x, 'children': None}
# if x['numchild'] > 0 else x for x in list(foods)]
# self.set_cache('Foods', self.Foods)
# return self.Foods
# def get_ratings(self):
# if self.Ratings is None:
# if not self._request.space.demo and self._request.space.show_facet_count:
# if self._queryset is None:
# self._queryset = Recipe.objects.filter(
# id__in=self._recipe_list)
# rating_qs = self._queryset.annotate(rating=Round(Avg(Case(When(
# cooklog__created_by=self._request.user, then='cooklog__rating'), default=Value(0)))))
# self.Ratings = dict(Counter(r.rating for r in rating_qs))
# else:
# self.Rating = {}
# self.set_cache('Ratings', self.Ratings)
# return self.Ratings
# def get_recent(self):
# if self.Recent is None:
# # TODO make days of recent recipe a setting
# recent_recipes = ViewLog.objects.filter(created_by=self._request.user, space=self._request.space, created_at__gte=timezone.now() - timedelta(days=14)
# ).values_list('recipe__pk', flat=True)
# self.Recent = list(recent_recipes)
# self.set_cache('Recent', self.Recent)
# return self.Recent
# def add_food_children(self, id):
# try:
# food = Food.objects.get(id=id)
# nodes = food.get_ancestors()
# except Food.DoesNotExist:
# return self.get_facets()
# foods = self._food_queryset(food.get_children(), food)
# deep_search = self.Foods
# for node in nodes:
# index = next((i for i, x in enumerate(
# deep_search) if x["id"] == node.id), None)
# deep_search = deep_search[index]['children']
# index = next((i for i, x in enumerate(
# deep_search) if x["id"] == food.id), None)
# deep_search[index]['children'] = [
# {**x, 'children': None} if x['numchild'] > 0 else x for x in list(foods)]
# self.set_cache('Foods', self.Foods)
# return self.get_facets()
# def add_keyword_children(self, id):
# try:
# keyword = Keyword.objects.get(id=id)
# nodes = keyword.get_ancestors()
# except Keyword.DoesNotExist:
# return self.get_facets()
# keywords = self._keyword_queryset(keyword.get_children(), keyword)
# deep_search = self.Keywords
# for node in nodes:
# index = next((i for i, x in enumerate(
# deep_search) if x["id"] == node.id), None)
# deep_search = deep_search[index]['children']
# index = next((i for i, x in enumerate(deep_search)
# if x["id"] == keyword.id), None)
# deep_search[index]['children'] = [
# {**x, 'children': None} if x['numchild'] > 0 else x for x in list(keywords)]
# self.set_cache('Keywords', self.Keywords)
# return self.get_facets()
# def _recipe_count_queryset(self, field, depth=1, steplen=4):
# return Recipe.objects.filter(**{f'{field}__path__startswith': OuterRef('path'), f'{field}__depth__gte': depth}, id__in=self._recipe_list, space=self._request.space
# ).annotate(count=Coalesce(Func('pk', function='Count'), 0)).values('count')
# def _keyword_queryset(self, queryset, keyword=None):
# depth = getattr(keyword, 'depth', 0) + 1
# steplen = depth * Keyword.steplen
# if not self._request.space.demo and self._request.space.show_facet_count:
# return queryset.annotate(count=Coalesce(Subquery(self._recipe_count_queryset('keywords', depth, steplen)), 0)
# ).filter(depth=depth, count__gt=0
# ).values('id', 'name', 'count', 'numchild').order_by(Lower('name').asc())[:200]
# else:
# return queryset.filter(depth=depth).values('id', 'name', 'numchild').order_by(Lower('name').asc())
# def _food_queryset(self, queryset, food=None):
# depth = getattr(food, 'depth', 0) + 1
# steplen = depth * Food.steplen
# if not self._request.space.demo and self._request.space.show_facet_count:
# return queryset.annotate(count=Coalesce(Subquery(self._recipe_count_queryset('steps__ingredients__food', depth, steplen)), 0)
# ).filter(depth__lte=depth, count__gt=0
# ).values('id', 'name', 'count', 'numchild').order_by(Lower('name').asc())[:200]
# else:
# return queryset.filter(depth__lte=depth).values('id', 'name', 'numchild').order_by(Lower('name').asc())

View File

@ -408,16 +408,6 @@ def parse_time(recipe_time):
def parse_keywords(keyword_json, request): def parse_keywords(keyword_json, request):
keywords = [] keywords = []
automation_engine = AutomationEngine(request) automation_engine = AutomationEngine(request)
# keyword_aliases = {}
# retrieve keyword automation cache if it exists, otherwise build from database
# KEYWORD_CACHE_KEY = f'automation_keyword_alias_{space.pk}'
# if c := caches['default'].get(KEYWORD_CACHE_KEY, None):
# keyword_aliases = c
# caches['default'].touch(KEYWORD_CACHE_KEY, 30)
# else:
# for a in Automation.objects.filter(space=space, disabled=False, type=Automation.KEYWORD_ALIAS).only('param_1', 'param_2').order_by('order').all():
# keyword_aliases[a.param_1.lower()] = a.param_2
# caches['default'].set(KEYWORD_CACHE_KEY, keyword_aliases, 30)
# keywords as list # keywords as list
for kw in keyword_json: for kw in keyword_json:
@ -425,11 +415,6 @@ def parse_keywords(keyword_json, request):
# if alias exists use that instead # if alias exists use that instead
if len(kw) != 0: if len(kw) != 0:
# if keyword_aliases:
# try:
# kw = keyword_aliases[kw.lower()]
# except KeyError:
# pass
automation_engine.apply_keyword_automation(kw) automation_engine.apply_keyword_automation(kw)
if k := Keyword.objects.filter(name=kw, space=request.space).first(): if k := Keyword.objects.filter(name=kw, space=request.space).first():
keywords.append({'label': str(k), 'name': k.name, 'id': k.id}) keywords.append({'label': str(k), 'name': k.name, 'id': k.id})

View File

@ -1,8 +1,6 @@
from django.urls import reverse from django.urls import reverse
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from oauth2_provider.contrib.rest_framework import OAuth2Authentication from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from rest_framework.authentication import TokenAuthentication
from rest_framework.authtoken.models import Token
from rest_framework.exceptions import AuthenticationFailed from rest_framework.exceptions import AuthenticationFailed
from cookbook.views import views from cookbook.views import views
@ -50,7 +48,6 @@ class ScopeMiddleware:
return views.no_groups(request) return views.no_groups(request)
request.space = user_space.space request.space = user_space.space
# with scopes_disabled():
with scope(space=request.space): with scope(space=request.space):
return self.get_response(request) return self.get_response(request)
else: else:

View File

@ -198,120 +198,3 @@ class RecipeShoppingEditor():
to_delete = self._shopping_list_recipe.entries.exclude(ingredient__in=ingredients) to_delete = self._shopping_list_recipe.entries.exclude(ingredient__in=ingredients)
ShoppingListEntry.objects.filter(id__in=to_delete).delete() ShoppingListEntry.objects.filter(id__in=to_delete).delete()
self._shopping_list_recipe = self.get_shopping_list_recipe(self.id, self.created_by, self.space) self._shopping_list_recipe = self.get_shopping_list_recipe(self.id, self.created_by, self.space)
# # TODO refactor as class
# def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None, ingredients=None, created_by=None, space=None, append=False):
# """
# Creates ShoppingListRecipe and associated ShoppingListEntrys from a recipe or a meal plan with a recipe
# :param list_recipe: Modify an existing ShoppingListRecipe
# :param recipe: Recipe to use as list of ingredients. One of [recipe, mealplan] are required
# :param mealplan: alternatively use a mealplan recipe as source of ingredients
# :param servings: Optional: Number of servings to use to scale shoppinglist. If servings = 0 an existing recipe list will be deleted
# :param ingredients: Ingredients, list of ingredient IDs to include on the shopping list. When not provided all ingredients will be used
# :param append: If False will remove any entries not included with ingredients, when True will append ingredients to the shopping list
# """
# r = recipe or getattr(mealplan, 'recipe', None) or getattr(list_recipe, 'recipe', None)
# if not r:
# raise ValueError(_("You must supply a recipe or mealplan"))
# created_by = created_by or getattr(ShoppingListEntry.objects.filter(list_recipe=list_recipe).first(), 'created_by', None)
# if not created_by:
# raise ValueError(_("You must supply a created_by"))
# try:
# servings = float(servings)
# except (ValueError, TypeError):
# servings = getattr(mealplan, 'servings', 1.0)
# servings_factor = servings / r.servings
# shared_users = list(created_by.get_shopping_share())
# shared_users.append(created_by)
# if list_recipe:
# created = False
# else:
# list_recipe = ShoppingListRecipe.objects.create(recipe=r, mealplan=mealplan, servings=servings)
# created = True
# related_step_ing = []
# if servings == 0 and not created:
# list_recipe.delete()
# return []
# elif ingredients:
# ingredients = Ingredient.objects.filter(pk__in=ingredients, space=space)
# else:
# ingredients = Ingredient.objects.filter(step__recipe=r, food__ignore_shopping=False, space=space)
# if exclude_onhand := created_by.userpreference.mealplan_autoexclude_onhand:
# ingredients = ingredients.exclude(food__onhand_users__id__in=[x.id for x in shared_users])
# if related := created_by.userpreference.mealplan_autoinclude_related:
# # TODO: add levels of related recipes (related recipes of related recipes) to use when auto-adding mealplans
# related_recipes = r.get_related_recipes()
# for x in related_recipes:
# # related recipe is a Step serving size is driven by recipe serving size
# # TODO once/if Steps can have a serving size this needs to be refactored
# if exclude_onhand:
# # if steps are used more than once in a recipe or subrecipe - I don' think this results in the desired behavior
# related_step_ing += Ingredient.objects.filter(step__recipe=x, space=space).exclude(food__onhand_users__id__in=[x.id for x in shared_users]).values_list('id', flat=True)
# else:
# related_step_ing += Ingredient.objects.filter(step__recipe=x, space=space).values_list('id', flat=True)
# x_ing = []
# if ingredients.filter(food__recipe=x).exists():
# for ing in ingredients.filter(food__recipe=x):
# if exclude_onhand:
# x_ing = Ingredient.objects.filter(step__recipe=x, food__ignore_shopping=False, space=space).exclude(food__onhand_users__id__in=[x.id for x in shared_users])
# else:
# x_ing = Ingredient.objects.filter(step__recipe=x, food__ignore_shopping=False, space=space).exclude(food__ignore_shopping=True)
# for i in [x for x in x_ing]:
# ShoppingListEntry.objects.create(
# list_recipe=list_recipe,
# food=i.food,
# unit=i.unit,
# ingredient=i,
# amount=i.amount * Decimal(servings_factor),
# created_by=created_by,
# space=space,
# )
# # dont' add food to the shopping list that are actually recipes that will be added as ingredients
# ingredients = ingredients.exclude(food__recipe=x)
# add_ingredients = list(ingredients.values_list('id', flat=True)) + related_step_ing
# if not append:
# existing_list = ShoppingListEntry.objects.filter(list_recipe=list_recipe)
# # delete shopping list entries not included in ingredients
# existing_list.exclude(ingredient__in=ingredients).delete()
# # add shopping list entries that did not previously exist
# add_ingredients = set(add_ingredients) - set(existing_list.values_list('ingredient__id', flat=True))
# add_ingredients = Ingredient.objects.filter(id__in=add_ingredients, space=space)
# # if servings have changed, update the ShoppingListRecipe and existing Entries
# if servings <= 0:
# servings = 1
# if not created and list_recipe.servings != servings:
# update_ingredients = set(ingredients.values_list('id', flat=True)) - set(add_ingredients.values_list('id', flat=True))
# list_recipe.servings = servings
# list_recipe.save()
# for sle in ShoppingListEntry.objects.filter(list_recipe=list_recipe, ingredient__id__in=update_ingredients):
# sle.amount = sle.ingredient.amount * Decimal(servings_factor)
# sle.save()
# # add any missing Entries
# for i in [x for x in add_ingredients if x.food]:
# ShoppingListEntry.objects.create(
# list_recipe=list_recipe,
# food=i.food,
# unit=i.unit,
# ingredient=i,
# amount=i.amount * Decimal(servings_factor),
# created_by=created_by,
# space=space,
# )
# # return all shopping list items
# return list_recipe

View File

@ -20,7 +20,6 @@ class CookBookApp(Integration):
def get_recipe_from_file(self, file): def get_recipe_from_file(self, file):
recipe_html = file.getvalue().decode("utf-8") recipe_html = file.getvalue().decode("utf-8")
# recipe_json, recipe_tree, html_data, images = get_recipe_from_source(recipe_html, 'CookBookApp', self.request)
scrape = text_scraper(text=recipe_html) scrape = text_scraper(text=recipe_html)
recipe_json = get_from_scraper(scrape, self.request) recipe_json = get_from_scraper(scrape, self.request)
images = list(dict.fromkeys(get_images_from_soup(scrape.soup, None))) images = list(dict.fromkeys(get_images_from_soup(scrape.soup, None)))
@ -32,7 +31,7 @@ class CookBookApp(Integration):
try: try:
recipe.servings = re.findall('([0-9])+', recipe_json['recipeYield'])[0] recipe.servings = re.findall('([0-9])+', recipe_json['recipeYield'])[0]
except Exception as e: except Exception:
pass pass
try: try:

View File

@ -1,17 +1,12 @@
import base64
import json
from io import BytesIO from io import BytesIO
from gettext import gettext as _
import requests import requests
import validators import validators
from lxml import etree
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_url_import import parse_servings, parse_time, parse_servings_text from cookbook.helper.recipe_url_import import parse_servings, parse_servings_text, parse_time
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Ingredient, Keyword, Recipe, Step from cookbook.models import Ingredient, Recipe, Step
class Cookmate(Integration): class Cookmate(Integration):

View File

@ -1,4 +1,3 @@
import re
from io import BytesIO from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
@ -26,12 +25,13 @@ class CopyMeThat(Integration):
except AttributeError: except AttributeError:
source = None source = None
recipe = Recipe.objects.create(name=file.find("div", {"id": "name"}).text.strip()[:128], source_url=source, created_by=self.request.user, internal=True, space=self.request.space, ) recipe = Recipe.objects.create(name=file.find("div", {"id": "name"}).text.strip(
)[:128], source_url=source, created_by=self.request.user, internal=True, space=self.request.space, )
for category in file.find_all("span", {"class": "recipeCategory"}): for category in file.find_all("span", {"class": "recipeCategory"}):
keyword, created = Keyword.objects.get_or_create(name=category.text, space=self.request.space) keyword, created = Keyword.objects.get_or_create(name=category.text, space=self.request.space)
recipe.keywords.add(keyword) recipe.keywords.add(keyword)
try: try:
recipe.servings = parse_servings(file.find("a", {"id": "recipeYield"}).text.strip()) recipe.servings = parse_servings(file.find("a", {"id": "recipeYield"}).text.strip())
recipe.working_time = iso_duration_to_minutes(file.find("span", {"meta": "prepTime"}).text.strip()) recipe.working_time = iso_duration_to_minutes(file.find("span", {"meta": "prepTime"}).text.strip())
@ -61,7 +61,14 @@ class CopyMeThat(Integration):
if not isinstance(ingredient, Tag) or not ingredient.text.strip() or "recipeIngredient_spacer" in ingredient['class']: if not isinstance(ingredient, Tag) or not ingredient.text.strip() or "recipeIngredient_spacer" in ingredient['class']:
continue continue
if any(x in ingredient['class'] for x in ["recipeIngredient_subheader", "recipeIngredient_note"]): if any(x in ingredient['class'] for x in ["recipeIngredient_subheader", "recipeIngredient_note"]):
step.ingredients.add(Ingredient.objects.create(is_header=True, note=ingredient.text.strip()[:256], original_text=ingredient.text.strip(), space=self.request.space, )) step.ingredients.add(
Ingredient.objects.create(
is_header=True,
note=ingredient.text.strip()[
:256],
original_text=ingredient.text.strip(),
space=self.request.space,
))
else: else:
amount, unit, food, note = ingredient_parser.parse(ingredient.text.strip()) amount, unit, food, note = ingredient_parser.parse(ingredient.text.strip())
f = ingredient_parser.get_food(food) f = ingredient_parser.get_food(food)
@ -78,7 +85,7 @@ class CopyMeThat(Integration):
step.save() step.save()
recipe.steps.add(step) recipe.steps.add(step)
step = Step.objects.create(instruction='', space=self.request.space, ) step = Step.objects.create(instruction='', space=self.request.space, )
step.name = instruction.text.strip()[:128] step.name = instruction.text.strip()[:128]
else: else:
step.instruction += instruction.text.strip() + ' \n\n' step.instruction += instruction.text.strip() + ' \n\n'

View File

@ -22,7 +22,7 @@ class Default(Integration):
if images: if images:
try: try:
self.import_recipe_image(recipe, BytesIO(recipe_zip.read(images[0])), filetype=get_filetype(images[0])) self.import_recipe_image(recipe, BytesIO(recipe_zip.read(images[0])), filetype=get_filetype(images[0]))
except AttributeError as e: except AttributeError:
traceback.print_exc() traceback.print_exc()
return recipe return recipe

View File

@ -2,13 +2,14 @@ import json
import re import re
from io import BytesIO, StringIO from io import BytesIO, StringIO
from zipfile import ZipFile from zipfile import ZipFile
from PIL import Image from PIL import Image
from cookbook.helper.image_processing import get_filetype from cookbook.helper.image_processing import get_filetype
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_url_import import iso_duration_to_minutes from cookbook.helper.recipe_url_import import iso_duration_to_minutes
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Ingredient, Keyword, Recipe, Step, NutritionInformation from cookbook.models import Ingredient, Keyword, NutritionInformation, Recipe, Step
class NextcloudCookbook(Integration): class NextcloudCookbook(Integration):
@ -51,7 +52,6 @@ class NextcloudCookbook(Integration):
ingredients_added = False ingredients_added = False
for s in recipe_json['recipeInstructions']: for s in recipe_json['recipeInstructions']:
instruction_text = ''
if 'text' in s: if 'text' in s:
step = Step.objects.create( step = Step.objects.create(
instruction=s['text'], name=s['name'], space=self.request.space, show_ingredients_table=self.request.user.userpreference.show_step_ingredients, instruction=s['text'], name=s['name'], space=self.request.space, show_ingredients_table=self.request.user.userpreference.show_step_ingredients,
@ -91,7 +91,7 @@ class NextcloudCookbook(Integration):
if nutrition != {}: if nutrition != {}:
recipe.nutrition = NutritionInformation.objects.create(**nutrition, space=self.request.space) recipe.nutrition = NutritionInformation.objects.create(**nutrition, space=self.request.space)
recipe.save() recipe.save()
except Exception as e: except Exception:
pass pass
for f in self.files: for f in self.files:

View File

@ -1,9 +1,11 @@
import json import json
from django.utils.translation import gettext as _
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Ingredient, Recipe, Step, Keyword, Comment, CookLog from cookbook.models import Comment, CookLog, Ingredient, Keyword, Recipe, Step
from django.utils.translation import gettext as _
class OpenEats(Integration): class OpenEats(Integration):
@ -25,16 +27,16 @@ class OpenEats(Integration):
if file["source"] != '': if file["source"] != '':
instructions += '\n' + _('Recipe source:') + f'[{file["source"]}]({file["source"]})' instructions += '\n' + _('Recipe source:') + f'[{file["source"]}]({file["source"]})'
cuisine_keyword, created = Keyword.objects.get_or_create(name="Cuisine", space=self.request.space) cuisine_keyword, created = Keyword.objects.get_or_create(name="Cuisine", space=self.request.space)
if file["cuisine"] != '': if file["cuisine"] != '':
keyword, created = Keyword.objects.get_or_create(name=file["cuisine"].strip(), space=self.request.space) keyword, created = Keyword.objects.get_or_create(name=file["cuisine"].strip(), space=self.request.space)
if created: if created:
keyword.move(cuisine_keyword, pos="last-child") keyword.move(cuisine_keyword, pos="last-child")
recipe.keywords.add(keyword) recipe.keywords.add(keyword)
course_keyword, created = Keyword.objects.get_or_create(name="Course", space=self.request.space) course_keyword, created = Keyword.objects.get_or_create(name="Course", space=self.request.space)
if file["course"] != '': if file["course"] != '':
keyword, created = Keyword.objects.get_or_create(name=file["course"].strip(), space=self.request.space) keyword, created = Keyword.objects.get_or_create(name=file["course"].strip(), space=self.request.space)
if created: if created:
keyword.move(course_keyword, pos="last-child") keyword.move(course_keyword, pos="last-child")
recipe.keywords.add(keyword) recipe.keywords.add(keyword)

View File

@ -90,7 +90,7 @@ class Paprika(Integration):
if validators.url(url, public=True): if validators.url(url, public=True):
response = requests.get(url) response = requests.get(url)
self.import_recipe_image(recipe, BytesIO(response.content)) self.import_recipe_image(recipe, BytesIO(response.content))
except: except Exception:
if recipe_json.get("photo_data", None): if recipe_json.get("photo_data", None):
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])), filetype='.jpeg') self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_json['photo_data'])), filetype='.jpeg')

View File

@ -1,21 +1,11 @@
import json
from io import BytesIO
from re import match
from zipfile import ZipFile
import asyncio import asyncio
from pyppeteer import launch
from rest_framework.renderers import JSONRenderer
from cookbook.helper.image_processing import get_filetype
from cookbook.integration.integration import Integration
from cookbook.serializer import RecipeExportSerializer
from cookbook.models import ExportLog
from asgiref.sync import sync_to_async
import django.core.management.commands.runserver as runserver import django.core.management.commands.runserver as runserver
import logging from asgiref.sync import sync_to_async
from pyppeteer import launch
from cookbook.integration.integration import Integration
class PDFexport(Integration): class PDFexport(Integration):
@ -42,7 +32,6 @@ class PDFexport(Integration):
} }
} }
files = [] files = []
for recipe in recipes: for recipe in recipes:
@ -50,20 +39,18 @@ class PDFexport(Integration):
await page.emulateMedia('print') await page.emulateMedia('print')
await page.setCookie(cookies) await page.setCookie(cookies)
await page.goto('http://'+cmd.default_addr+':'+cmd.default_port+'/view/recipe/'+str(recipe.id), {'waitUntil': 'domcontentloaded'}) await page.goto('http://' + cmd.default_addr + ':' + cmd.default_port + '/view/recipe/' + str(recipe.id), {'waitUntil': 'domcontentloaded'})
await page.waitForSelector('#printReady'); await page.waitForSelector('#printReady')
files.append([recipe.name + '.pdf', await page.pdf(options)]) files.append([recipe.name + '.pdf', await page.pdf(options)])
await page.close(); await page.close()
el.exported_recipes += 1 el.exported_recipes += 1
el.msg += self.get_recipe_processed_msg(recipe) el.msg += self.get_recipe_processed_msg(recipe)
await sync_to_async(el.save, thread_sensitive=True)() await sync_to_async(el.save, thread_sensitive=True)()
await browser.close() await browser.close()
return files return files
def get_files_from_recipes(self, recipes, el, cookie): def get_files_from_recipes(self, recipes, el, cookie):
return asyncio.run(self.get_files_from_recipes_async(recipes, el, cookie)) return asyncio.run(self.get_files_from_recipes_async(recipes, el, cookie))

View File

@ -2,12 +2,10 @@ import base64
from io import BytesIO from io import BytesIO
from xml import etree from xml import etree
from lxml import etree
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_url_import import parse_time, parse_servings, parse_servings_text from cookbook.helper.recipe_url_import import parse_servings, parse_servings_text
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Ingredient, Recipe, Step, Keyword from cookbook.models import Ingredient, Keyword, Recipe, Step
class Rezeptsuitede(Integration): class Rezeptsuitede(Integration):
@ -61,14 +59,14 @@ class Rezeptsuitede(Integration):
try: try:
k, created = Keyword.objects.get_or_create(name=recipe_xml.find('head').find('cat').text.strip(), space=self.request.space) k, created = Keyword.objects.get_or_create(name=recipe_xml.find('head').find('cat').text.strip(), space=self.request.space)
recipe.keywords.add(k) recipe.keywords.add(k)
except Exception as e: except Exception:
pass pass
recipe.save() recipe.save()
try: try:
self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_xml.find('head').find('picbin').text)), filetype='.jpeg') self.import_recipe_image(recipe, BytesIO(base64.b64decode(recipe_xml.find('head').find('picbin').text)), filetype='.jpeg')
except: except BaseException:
pass pass
return recipe return recipe

View File

@ -60,8 +60,8 @@ class RezKonv(Integration):
def split_recipe_file(self, file): def split_recipe_file(self, file):
recipe_list = [] recipe_list = []
current_recipe = '' current_recipe = ''
encoding_list = ['windows-1250', # TODO build algorithm to try trough encodings and fail if none work, use for all importers
'latin-1'] # TODO build algorithm to try trough encodings and fail if none work, use for all importers # encoding_list = ['windows-1250', 'latin-1']
encoding = 'windows-1250' encoding = 'windows-1250'
for fl in file.readlines(): for fl in file.readlines():
try: try:

View File

@ -59,11 +59,11 @@ class Saffron(Integration):
def get_file_from_recipe(self, recipe): def get_file_from_recipe(self, recipe):
data = "Title: "+recipe.name if recipe.name else ""+"\n" data = "Title: " + recipe.name if recipe.name else "" + "\n"
data += "Description: "+recipe.description if recipe.description else ""+"\n" data += "Description: " + recipe.description if recipe.description else "" + "\n"
data += "Source: \n" data += "Source: \n"
data += "Original URL: \n" data += "Original URL: \n"
data += "Yield: "+str(recipe.servings)+"\n" data += "Yield: " + str(recipe.servings) + "\n"
data += "Cookbook: \n" data += "Cookbook: \n"
data += "Section: \n" data += "Section: \n"
data += "Image: \n" data += "Image: \n"
@ -78,13 +78,13 @@ class Saffron(Integration):
data += "Ingredients: \n" data += "Ingredients: \n"
for ingredient in recipeIngredient: for ingredient in recipeIngredient:
data += ingredient+"\n" data += ingredient + "\n"
data += "Instructions: \n" data += "Instructions: \n"
for instruction in recipeInstructions: for instruction in recipeInstructions:
data += instruction+"\n" data += instruction + "\n"
return recipe.name+'.txt', data return recipe.name + '.txt', data
def get_files_from_recipes(self, recipes, el, cookie): def get_files_from_recipes(self, recipes, el, cookie):
files = [] files = []

View File

@ -34,35 +34,14 @@ class RecipeSearchManager(models.Manager):
+ SearchVector(StringAgg('steps__ingredients__food__name__unaccent', delimiter=' '), weight='B', config=language) + SearchVector(StringAgg('steps__ingredients__food__name__unaccent', delimiter=' '), weight='B', config=language)
+ SearchVector(StringAgg('keywords__name__unaccent', delimiter=' '), weight='B', config=language)) + SearchVector(StringAgg('keywords__name__unaccent', delimiter=' '), weight='B', config=language))
search_rank = SearchRank(search_vectors, search_query) search_rank = SearchRank(search_vectors, search_query)
# USING TRIGRAM BREAKS WEB SEARCH
# ADDING MULTIPLE TRIGRAMS CREATES DUPLICATE RESULTS
# DISTINCT NOT COMPAITBLE WITH ANNOTATE
# trigram_name = (TrigramSimilarity('name', search_text))
# trigram_description = (TrigramSimilarity('description', search_text))
# trigram_food = (TrigramSimilarity('steps__ingredients__food__name', search_text))
# trigram_keyword = (TrigramSimilarity('keywords__name', search_text))
# adding additional trigrams created duplicates
# + TrigramSimilarity('description', search_text)
# + TrigramSimilarity('steps__ingredients__food__name', search_text)
# + TrigramSimilarity('keywords__name', search_text)
return ( return (
self.get_queryset() self.get_queryset()
.annotate( .annotate(
search=search_vectors, search=search_vectors,
rank=search_rank, rank=search_rank,
# trigram=trigram_name+trigram_description+trigram_food+trigram_keyword
# trigram_name=trigram_name,
# trigram_description=trigram_description,
# trigram_food=trigram_food,
# trigram_keyword=trigram_keyword
) )
.filter( .filter(
Q(search=search_query) Q(search=search_query)
# | Q(trigram_name__gt=0.1)
# | Q(name__icontains=search_text)
# | Q(trigram_name__gt=0.2)
# | Q(trigram_description__gt=0.2)
# | Q(trigram_food__gt=0.2)
# | Q(trigram_keyword__gt=0.2)
) )
.order_by('-rank')) .order_by('-rank'))

View File

@ -1294,7 +1294,7 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio
def is_image(self): def is_image(self):
try: try:
img = Image.open(self.file.file.file) Image.open(self.file.file.file)
return True return True
except Exception: except Exception:
return False return False

View File

@ -67,17 +67,3 @@ class FilterSchema(AutoSchema):
'schema': {'type': 'string', }, 'schema': {'type': 'string', },
}) })
return parameters 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

@ -208,7 +208,7 @@ class UserFileSerializer(serializers.ModelSerializer):
def get_preview_link(self, obj): def get_preview_link(self, obj):
try: try:
img = Image.open(obj.file.file.file) Image.open(obj.file.file.file)
return self.context['request'].build_absolute_uri(obj.file.url) return self.context['request'].build_absolute_uri(obj.file.url)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
@ -256,7 +256,7 @@ class UserFileViewSerializer(serializers.ModelSerializer):
def get_preview_link(self, obj): def get_preview_link(self, obj):
try: try:
img = Image.open(obj.file.file.file) Image.open(obj.file.file.file)
return self.context['request'].build_absolute_uri(obj.file.url) return self.context['request'].build_absolute_uri(obj.file.url)
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
@ -570,7 +570,6 @@ class FoodSimpleSerializer(serializers.ModelSerializer):
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin, OpenDataModelMixin): class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedRecipeMixin, OpenDataModelMixin):
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False) supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
recipe = RecipeSimpleSerializer(allow_null=True, required=False) recipe = RecipeSimpleSerializer(allow_null=True, required=False)
# shopping = serializers.SerializerMethodField('get_shopping_status')
shopping = serializers.ReadOnlyField(source='shopping_status') shopping = serializers.ReadOnlyField(source='shopping_status')
inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False) inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False)
child_inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False) child_inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False)
@ -609,9 +608,6 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
filter |= Q(path__startswith=obj.path, depth__gt=obj.depth) filter |= Q(path__startswith=obj.path, depth__gt=obj.depth)
return Food.objects.filter(filter).filter(onhand_users__id__in=shared_users).exists() return Food.objects.filter(filter).filter(onhand_users__id__in=shared_users).exists()
# def get_shopping_status(self, obj):
# return ShoppingListEntry.objects.filter(space=obj.space, food=obj, checked=False).count() > 0
def create(self, validated_data): def create(self, validated_data):
name = validated_data['name'].strip() name = validated_data['name'].strip()

View File

@ -1,4 +1,3 @@
from decimal import Decimal
from functools import wraps from functools import wraps
from django.conf import settings from django.conf import settings
@ -13,8 +12,8 @@ from django_scopes import scope, scopes_disabled
from cookbook.helper.cache_helper import CacheHelper from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.shopping_helper import RecipeShoppingEditor from cookbook.helper.shopping_helper import RecipeShoppingEditor
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe, from cookbook.models import (Food, MealPlan, PropertyType, Recipe, SearchFields, SearchPreference,
ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields, Unit, PropertyType) Step, Unit, UserPreference)
SQLITE = True SQLITE = True
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',

View File

@ -3,8 +3,7 @@ from django.utils.html import format_html
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_tables2.utils import A from django_tables2.utils import A
from .models import (CookLog, InviteLink, Recipe, RecipeImport, from .models import CookLog, InviteLink, RecipeImport, Storage, Sync, SyncLog, ViewLog
Storage, Sync, SyncLog, ViewLog)
class StorageTable(tables.Table): class StorageTable(tables.Table):

View File

@ -3,19 +3,19 @@ from gettext import gettext as _
import bleach import bleach
import markdown as md import markdown as md
from django_scopes import ScopeError
from markdown.extensions.tables import TableExtension
from django import template from django import template
from django.db.models import Avg from django.db.models import Avg
from django.templatetags.static import static from django.templatetags.static import static
from django.urls import NoReverseMatch, reverse from django.urls import NoReverseMatch, reverse
from django_scopes import ScopeError
from markdown.extensions.tables import TableExtension
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from cookbook.helper.mdx_attributes import MarkdownFormatExtension from cookbook.helper.mdx_attributes import MarkdownFormatExtension
from cookbook.helper.mdx_urlize import UrlizeExtension from cookbook.helper.mdx_urlize import UrlizeExtension
from cookbook.models import Space, get_model_name from cookbook.models import get_model_name
from recipes import settings from recipes import settings
from recipes.settings import STATIC_URL, PLUGINS from recipes.settings import PLUGINS, STATIC_URL
register = template.Library() register = template.Library()
@ -69,8 +69,8 @@ def markdown(value):
"a": ["href", "alt", "title"], "a": ["href", "alt", "title"],
} }
parsed_md = parsed_md[3:] # remove outer paragraph parsed_md = parsed_md[3:] # remove outer paragraph
parsed_md = parsed_md[:len(parsed_md)-4] parsed_md = parsed_md[:len(parsed_md) - 4]
return bleach.clean(parsed_md, tags, markdown_attrs) return bleach.clean(parsed_md, tags, markdown_attrs)
@ -144,6 +144,7 @@ def is_debug():
def markdown_link(): def markdown_link():
return f"{_('You can use markdown to format this field. See the ')}<a target='_blank' href='{reverse('docs_markdown')}'>{_('docs here')}</a>" return f"{_('You can use markdown to format this field. See the ')}<a target='_blank' href='{reverse('docs_markdown')}'>{_('docs here')}</a>"
@register.simple_tag @register.simple_tag
def plugin_dropdown_nav_templates(): def plugin_dropdown_nav_templates():
templates = [] templates = []
@ -152,6 +153,7 @@ def plugin_dropdown_nav_templates():
templates.append(p['nav_dropdown']) templates.append(p['nav_dropdown'])
return templates return templates
@register.simple_tag @register.simple_tag
def plugin_main_nav_templates(): def plugin_main_nav_templates():
templates = [] templates = []
@ -199,7 +201,8 @@ def base_path(request, path_type):
@register.simple_tag @register.simple_tag
def user_prefs(request): def user_prefs(request):
from cookbook.serializer import UserPreferenceSerializer # putting it with imports caused circular execution from cookbook.serializer import \
UserPreferenceSerializer # putting it with imports caused circular execution
try: try:
return UserPreferenceSerializer(request.user.userpreference, context={'request': request}).data return UserPreferenceSerializer(request.user.userpreference, context={'request': request}).data
except AttributeError: except AttributeError:

View File

@ -1,6 +1,7 @@
from cookbook.models import UserPreference
from django import template from django import template
from django.templatetags.static import static from django.templatetags.static import static
from cookbook.models import UserPreference
from recipes.settings import STICKY_NAV_PREF_DEFAULT from recipes.settings import STICKY_NAV_PREF_DEFAULT
register = template.Library() register = template.Library()
@ -41,4 +42,4 @@ def sticky_nav(request):
(request.user.is_authenticated and request.user.userpreference.sticky_navbar): # noqa: E501 (request.user.is_authenticated and request.user.userpreference.sticky_navbar): # noqa: E501
return 'position: sticky; top: 0; left: 0; z-index: 1000;' return 'position: sticky; top: 0; left: 0; z-index: 1000;'
else: else:
return '' return ''

View File

@ -7,8 +7,6 @@ from django.utils import timezone
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from oauth2_provider.models import AccessToken from oauth2_provider.models import AccessToken
from cookbook.models import ViewLog
LIST_URL = 'api:accesstoken-list' LIST_URL = 'api:accesstoken-list'
DETAIL_URL = 'api:accesstoken-detail' DETAIL_URL = 'api:accesstoken-detail'

View File

@ -35,11 +35,6 @@ register(FoodFactory, 'obj_3', space=LazyFixture('space_2'))
register(SupermarketCategoryFactory, 'cat_1', space=LazyFixture('space_1')) register(SupermarketCategoryFactory, 'cat_1', space=LazyFixture('space_1'))
# @pytest.fixture
# def true():
# return True
@pytest.fixture @pytest.fixture
def false(): def false():
return False return False

View File

@ -1,13 +1,3 @@
# test create
# test create units
# test amounts
# test create wrong space
# test sharing
# test delete
# test delete checked (nothing should happen)
# test delete not shared (nothing happens)
# test delete shared
import json import json
import pytest import pytest
@ -15,7 +5,6 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from cookbook.models import Food, ShoppingListEntry
from cookbook.tests.factories import FoodFactory from cookbook.tests.factories import FoodFactory
SHOPPING_LIST_URL = 'api:shoppinglistentry-list' SHOPPING_LIST_URL = 'api:shoppinglistentry-list'
@ -80,8 +69,8 @@ def test_shopping_food_share(u1_s1, u2_s1, food, space_1):
user1 = auth.get_user(u1_s1) user1 = auth.get_user(u1_s1)
user2 = auth.get_user(u2_s1) user2 = auth.get_user(u2_s1)
food2 = FoodFactory(space=space_1) food2 = FoodFactory(space=space_1)
r = u1_s1.put(reverse(SHOPPING_FOOD_URL, args={food.id})) u1_s1.put(reverse(SHOPPING_FOOD_URL, args={food.id}))
r = u2_s1.put(reverse(SHOPPING_FOOD_URL, args={food2.id})) u2_s1.put(reverse(SHOPPING_FOOD_URL, args={food2.id}))
sl_1 = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) sl_1 = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)
sl_2 = json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content) sl_2 = json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)
assert len(sl_1) == 1 assert len(sl_1) == 1

View File

@ -2,13 +2,10 @@ import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.models import AnonymousUser
from django.db.models import OuterRef, Subquery
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.helper.permission_helper import switch_user_active_space from cookbook.models import InviteLink
from cookbook.models import Ingredient, Step, InviteLink, UserSpace
LIST_URL = 'api:invitelink-list' LIST_URL = 'api:invitelink-list'
DETAIL_URL = 'api:invitelink-detail' DETAIL_URL = 'api:invitelink-detail'

View File

@ -25,6 +25,7 @@ if (Keyword.node_order_by):
else: else:
node_location = 'last-child' node_location = 'last-child'
@pytest.fixture() @pytest.fixture()
def obj_1(space_1): def obj_1(space_1):
return Keyword.objects.get_or_create(name='test_1', space=space_1)[0] return Keyword.objects.get_or_create(name='test_1', space=space_1)[0]

View File

@ -6,7 +6,7 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from cookbook.models import Food, MealPlan, MealType from cookbook.models import MealPlan, MealType
from cookbook.tests.factories import RecipeFactory from cookbook.tests.factories import RecipeFactory
LIST_URL = 'api:mealplan-list' LIST_URL = 'api:mealplan-list'
@ -149,7 +149,7 @@ def test_add_with_shopping(u1_s1, meal_type):
space = meal_type.space space = meal_type.space
with scope(space=space): with scope(space=space):
recipe = RecipeFactory.create(space=space) recipe = RecipeFactory.create(space=space)
r = u1_s1.post( u1_s1.post(
reverse(LIST_URL), reverse(LIST_URL),
{'recipe': {'id': recipe.id, 'name': recipe.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name}, {'recipe': {'id': recipe.id, 'name': recipe.name, 'keywords': []}, 'meal_type': {'id': meal_type.id, 'name': meal_type.name},
'from_date': (datetime.now()).strftime("%Y-%m-%d"), 'to_date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test', 'shared': [], 'addshopping': True}, 'from_date': (datetime.now()).strftime("%Y-%m-%d"), 'to_date': (datetime.now()).strftime("%Y-%m-%d"), 'servings': 1, 'title': 'test', 'shared': [], 'addshopping': True},

View File

@ -5,7 +5,7 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import Food, MealType from cookbook.models import MealType
LIST_URL = 'api:mealtype-list' LIST_URL = 'api:mealtype-list'
DETAIL_URL = 'api:mealtype-detail' DETAIL_URL = 'api:mealtype-detail'

View File

@ -1,11 +1,10 @@
import json import json
import pytest import pytest
from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import Food, MealType, PropertyType, Property from cookbook.models import MealType, Property, PropertyType
LIST_URL = 'api:property-list' LIST_URL = 'api:property-list'
DETAIL_URL = 'api:property-detail' DETAIL_URL = 'api:property-detail'

View File

@ -1,11 +1,10 @@
import json import json
import pytest import pytest
from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import Food, MealType, PropertyType from cookbook.models import MealType, PropertyType
LIST_URL = 'api:propertytype-list' LIST_URL = 'api:propertytype-list'
DETAIL_URL = 'api:propertytype-detail' DETAIL_URL = 'api:propertytype-detail'

View File

@ -67,7 +67,8 @@ def test_share_permission(recipe_1_s1, u1_s1, u1_s2, u2_s1, a_u):
share = ShareLink.objects.filter(recipe=recipe_1_s1).first() share = ShareLink.objects.filter(recipe=recipe_1_s1).first()
assert a_u.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200 assert a_u.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200
assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200 assert u1_s1.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 200
assert u1_s2.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 404 # TODO fix in https://github.com/TandoorRecipes/recipes/issues/1238 # TODO fix in https://github.com/TandoorRecipes/recipes/issues/1238
assert u1_s2.get(reverse(DETAIL_URL, args=[recipe_1_s1.pk]) + f'?share={share.uuid}').status_code == 404
recipe_1_s1.created_by = auth.get_user(u1_s1) recipe_1_s1.created_by = auth.get_user(u1_s1)
recipe_1_s1.private = True recipe_1_s1.private = True
@ -100,7 +101,6 @@ def test_update(arg, request, recipe_1_s1):
j, j,
content_type='application/json' content_type='application/json'
) )
response = json.loads(r.content)
assert r.status_code == arg[1] assert r.status_code == arg[1]
if r.status_code == 200: if r.status_code == 200:
validate_recipe(j, json.loads(r.content)) validate_recipe(j, json.loads(r.content))

View File

@ -48,8 +48,6 @@ def test_list_filter(obj_1, obj_2, u1_s1):
assert r.status_code == 200 assert r.status_code == 200
response = json.loads(r.content) response = json.loads(r.content)
assert len(response) == 2 assert len(response) == 2
# RecipeBooks model is unsorted - this isn't a reliable test
# assert response[0]['name'] == obj_1.name
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?limit=1').content) response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?limit=1').content)
assert len(response) == 1 assert len(response) == 1

View File

@ -1,11 +1,9 @@
import json import json
import factory
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pytest_factoryboy import LazyFixture, register
from cookbook.tests.factories import RecipeFactory from cookbook.tests.factories import RecipeFactory
@ -18,7 +16,7 @@ def recipe(request, space_1, u1_s1):
params = request.param # request.param is a magic variable params = request.param # request.param is a magic variable
except AttributeError: except AttributeError:
params = {} params = {}
step_recipe = params.get('steps__count', 1) # step_recipe = params.get('steps__count', 1) # TODO add tests for step recipes
steps__recipe_count = params.get('steps__recipe_count', 0) steps__recipe_count = params.get('steps__recipe_count', 0)
steps__food_recipe_count = params.get('steps__food_recipe_count', {}) steps__food_recipe_count = params.get('steps__food_recipe_count', {})
created_by = params.get('created_by', auth.get_user(u1_s1)) created_by = params.get('created_by', auth.get_user(u1_s1))
@ -26,6 +24,7 @@ def recipe(request, space_1, u1_s1):
return RecipeFactory.create( return RecipeFactory.create(
steps__recipe_count=steps__recipe_count, steps__recipe_count=steps__recipe_count,
steps__food_recipe_count=steps__food_recipe_count, steps__food_recipe_count=steps__food_recipe_count,
# steps__step_recipe=step_recipe, # TODO add tests for step recipes
created_by=created_by, created_by=created_by,
space=space_1, space=space_1,
) )
@ -56,7 +55,7 @@ def test_get_related_recipes(request, arg, recipe, related_count, u1_s1, space_2
({'steps__food_recipe_count': {'step': 0, 'count': 1}}), # shopping list from recipe with food recipe ({'steps__food_recipe_count': {'step': 0, 'count': 1}}), # shopping list from recipe with food recipe
({'steps__food_recipe_count': {'step': 0, 'count': 1}, 'steps__recipe_count': 1}), # shopping list from recipe with StepRecipe and food recipe ({'steps__food_recipe_count': {'step': 0, 'count': 1}, 'steps__recipe_count': 1}), # shopping list from recipe with StepRecipe and food recipe
], indirect=['recipe']) ], indirect=['recipe'])
def test_related_mixed_space(request, recipe, u1_s2, space_2): def test_related_mixed_space(request, recipe, u1_s2, space_2):
with scopes_disabled(): with scopes_disabled():
recipe.space = space_2 recipe.space = space_2
recipe.save() recipe.save()

View File

@ -5,7 +5,7 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import RecipeBook, ShoppingList, Storage, Sync, SyncLog from cookbook.models import ShoppingList
LIST_URL = 'api:shoppinglist-list' LIST_URL = 'api:shoppinglist-list'
DETAIL_URL = 'api:shoppinglist-detail' DETAIL_URL = 'api:shoppinglist-detail'

View File

@ -2,7 +2,6 @@ import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.forms import model_to_dict
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled

View File

@ -1,14 +1,11 @@
import json import json
from datetime import timedelta from datetime import timedelta
import factory
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.forms import model_to_dict
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pytest_factoryboy import LazyFixture, register
from cookbook.models import ShoppingListEntry from cookbook.models import ShoppingListEntry
from cookbook.tests.factories import ShoppingListEntryFactory from cookbook.tests.factories import ShoppingListEntryFactory
@ -46,7 +43,7 @@ def test_list_permission(arg, request):
assert c.get(reverse(LIST_URL)).status_code == arg[1] assert c.get(reverse(LIST_URL)).status_code == arg[1]
def test_list_space(sle, u1_s1, u1_s2, space_2): def test_list_space(sle, u1_s1, u1_s2, space_2):
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 10 assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 10
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
@ -169,7 +166,7 @@ def test_sharing(request, shared, count, sle_2, sle, u1_s1):
assert len(r) == count assert len(r) == count
# count unchecked entries # count unchecked entries
if not x.status_code == 404: if not x.status_code == 404:
count = count-1 count = count - 1
assert [x['checked'] for x in r].count(False) == count assert [x['checked'] for x in r].count(False) == count
# test shared user can delete # test shared user can delete
x = shared_client.delete( x = shared_client.delete(
@ -182,13 +179,12 @@ def test_sharing(request, shared, count, sle_2, sle, u1_s1):
assert len(r) == count assert len(r) == count
# count unchecked entries # count unchecked entries
if not x.status_code == 404: if not x.status_code == 404:
count = count-1 count = count - 1
assert [x['checked'] for x in r].count(False) == count assert [x['checked'] for x in r].count(False) == count
def test_completed(sle, u1_s1): def test_completed(sle, u1_s1):
# check 1 entry # check 1 entry
#
u1_s1.patch( u1_s1.patch(
reverse(DETAIL_URL, args={sle[0].id}), reverse(DETAIL_URL, args={sle[0].id}),
{'checked': True}, {'checked': True},
@ -199,7 +195,7 @@ def test_completed(sle, u1_s1):
# count unchecked entries # count unchecked entries
assert [x['checked'] for x in r].count(False) == 9 assert [x['checked'] for x in r].count(False) == 9
# confirm completed_at is populated # confirm completed_at is populated
assert [(x['completed_at'] != None) for x in r if x['checked']].count(True) == 1 assert [(x['completed_at'] is not None) for x in r if x['checked']].count(True) == 1
assert len(json.loads(u1_s1.get(f'{reverse(LIST_URL)}?checked=0').content)) == 9 assert len(json.loads(u1_s1.get(f'{reverse(LIST_URL)}?checked=0').content)) == 9
assert len(json.loads(u1_s1.get(f'{reverse(LIST_URL)}?checked=1').content)) == 1 assert len(json.loads(u1_s1.get(f'{reverse(LIST_URL)}?checked=1').content)) == 1
@ -213,7 +209,7 @@ def test_completed(sle, u1_s1):
r = json.loads(u1_s1.get(reverse(LIST_URL)).content) r = json.loads(u1_s1.get(reverse(LIST_URL)).content)
assert [x['checked'] for x in r].count(False) == 10 assert [x['checked'] for x in r].count(False) == 10
# confirm completed_at value cleared # confirm completed_at value cleared
assert [(x['completed_at'] != None) for x in r if x['checked']].count(True) == 0 assert [(x['completed_at'] is not None) for x in r if x['checked']].count(True) == 0
def test_recent(sle, u1_s1): def test_recent(sle, u1_s1):

View File

@ -2,11 +2,10 @@ import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.forms import model_to_dict
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import RecipeBook, Storage, Sync, SyncLog, ShoppingList, ShoppingListEntry, Food, ShoppingListRecipe from cookbook.models import ShoppingList, ShoppingListRecipe
LIST_URL = 'api:shoppinglistrecipe-list' LIST_URL = 'api:shoppinglistrecipe-list'
DETAIL_URL = 'api:shoppinglistrecipe-detail' DETAIL_URL = 'api:shoppinglistrecipe-detail'
@ -21,7 +20,7 @@ def obj_1(space_1, u1_s1, recipe_1_s1):
@pytest.fixture @pytest.fixture
def obj_2(space_1, u1_s1,recipe_1_s1): def obj_2(space_1, u1_s1, recipe_1_s1):
r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1) r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1)
s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, ) s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, )
s.recipes.add(r) s.recipes.add(r)

View File

@ -246,8 +246,6 @@ def test_shopping_recipe_mixed_authors(u1_s1, u2_s1, space_1):
@pytest.mark.parametrize("recipe", [{'steps__ingredients__header': 1}], indirect=['recipe']) @pytest.mark.parametrize("recipe", [{'steps__ingredients__header': 1}], indirect=['recipe'])
def test_shopping_with_header_ingredient(u1_s1, recipe): def test_shopping_with_header_ingredient(u1_s1, recipe):
# with scope(space=recipe.space):
# recipe.step_set.first().ingredient_set.add(IngredientFactory(ingredients__header=1))
u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id})) u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}))
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 10 assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 10
assert len(json.loads( assert len(json.loads(

View File

@ -2,12 +2,9 @@ import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.db.models import OuterRef, Subquery
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import Ingredient, Step
LIST_URL = 'api:space-list' LIST_URL = 'api:space-list'
DETAIL_URL = 'api:space-detail' DETAIL_URL = 'api:space-detail'

View File

@ -5,7 +5,7 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import RecipeBook, Storage, Sync from cookbook.models import Storage
LIST_URL = 'api:storage-list' LIST_URL = 'api:storage-list'
DETAIL_URL = 'api:storage-detail' DETAIL_URL = 'api:storage-detail'

View File

@ -1,11 +1,10 @@
import json import json
import pytest import pytest
from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import RecipeBook, Supermarket from cookbook.models import Supermarket
LIST_URL = 'api:supermarket-list' LIST_URL = 'api:supermarket-list'
DETAIL_URL = 'api:supermarket-detail' DETAIL_URL = 'api:supermarket-detail'
@ -48,7 +47,6 @@ def test_list_filter(obj_1, obj_2, u1_s1):
assert r.status_code == 200 assert r.status_code == 200
response = json.loads(r.content) response = json.loads(r.content)
assert len(response) == 2 assert len(response) == 2
# assert response[0]['name'] == obj_1.name # assuming an order when it's not always valid
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?limit=1').content) response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?limit=1').content)
assert len(response) == 1 assert len(response) == 1

View File

@ -5,7 +5,7 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import RecipeBook, Storage, Sync from cookbook.models import Storage, Sync
LIST_URL = 'api:sync-list' LIST_URL = 'api:sync-list'
DETAIL_URL = 'api:sync-detail' DETAIL_URL = 'api:sync-detail'

View File

@ -5,7 +5,7 @@ from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import Food, MealType, UnitConversion from cookbook.models import MealType, UnitConversion
from cookbook.tests.conftest import get_random_food, get_random_unit from cookbook.tests.conftest import get_random_food, get_random_unit
LIST_URL = 'api:unitconversion-list' LIST_URL = 'api:unitconversion-list'

View File

@ -1,7 +1,6 @@
import json import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.urls import reverse from django.urls import reverse
@ -77,7 +76,7 @@ def test_user_retrieve(arg, request, u1_s1):
assert r.status_code == arg[1] assert r.status_code == arg[1]
def test_user_update(u1_s1, u2_s1,u1_s2): def test_user_update(u1_s1, u2_s1, u1_s2):
# can update own user # can update own user
r = u1_s1.patch( r = u1_s1.patch(
reverse( reverse(

View File

@ -120,7 +120,6 @@ def test_default_inherit_fields(u1_s1, u1_s2, space_1, space_2):
r = u1_s1.get( r = u1_s1.get(
reverse(DETAIL_URL, args={auth.get_user(u1_s1).id}), reverse(DETAIL_URL, args={auth.get_user(u1_s1).id}),
) )
#assert len([x['field'] for x in json.loads(r.content)['food_inherit_default']]) == 0
# inherit all possible fields # inherit all possible fields
with scope(space=space_1): with scope(space=space_1):

View File

@ -2,12 +2,9 @@ import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.db.models import OuterRef, Subquery
from django.urls import reverse from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import Ingredient, Step
LIST_URL = 'api:userspace-list' LIST_URL = 'api:userspace-list'
DETAIL_URL = 'api:userspace-detail' DETAIL_URL = 'api:userspace-detail'

View File

@ -36,18 +36,6 @@ def enable_db_access_for_all_tests(db):
pass pass
# @pytest.fixture()
# def space_1():
# with scopes_disabled():
# return Space.objects.get_or_create(name='space_1')[0]
# @pytest.fixture()
# def space_2():
# with scopes_disabled():
# return Space.objects.get_or_create(name='space_2')[0]
# ---------------------- OBJECT FIXTURES --------------------- # ---------------------- OBJECT FIXTURES ---------------------
def get_random_recipe(space_1, u1_s1): def get_random_recipe(space_1, u1_s1):
@ -124,7 +112,7 @@ def validate_recipe(expected, recipe):
# file and url are metadata not related to the recipe # file and url are metadata not related to the recipe
[expected.pop(k) for k in ['file', 'url'] if k in expected] [expected.pop(k) for k in ['file', 'url'] if k in expected]
# if a key is a list remove it to deal with later # if a key is a list remove it to deal with later
lists = [k for k, v in expected.items() if type(v) == list] lists = [k for k, v in expected.items() if isinstance(v, list)]
for k in lists: for k in lists:
expected_lists[k] = expected.pop(k) expected_lists[k] = expected.pop(k)
target_lists[k] = recipe.pop(k) target_lists[k] = recipe.pop(k)
@ -157,7 +145,7 @@ def dict_compare(d1, d2, details=False):
d1_keys = set(d1.keys()) d1_keys = set(d1.keys())
d2_keys = set(d2.keys()) d2_keys = set(d2.keys())
shared = d1_keys.intersection(d2_keys) shared = d1_keys.intersection(d2_keys)
sub_dicts = [i for i, j in d1.items() if type(j) == dict] sub_dicts = [i for i, j in d1.items() if isinstance(j, dict)]
not_dicts = shared - set(sub_dicts) not_dicts = shared - set(sub_dicts)
added = d1_keys - d2_keys added = d1_keys - d2_keys
removed = d2_keys - d1_keys removed = d2_keys - d1_keys

View File

@ -1,4 +1,5 @@
import inspect
from datetime import date from datetime import date
from decimal import Decimal from decimal import Decimal
@ -11,12 +12,6 @@ from pytest_factoryboy import register
from cookbook.models import UserSpace from cookbook.models import UserSpace
# this code will run immediately prior to creating the model object useful when you want a reverse relationship
# log = factory.RelatedFactory(
# UserLogFactory,
# factory_related_name='user',
# action=models.UserLog.ACTION_CREATE,
# )
faker = FakerFactory.create() faker = FakerFactory.create()
@ -96,7 +91,6 @@ class SupermarketCategoryFactory(factory.django.DjangoModelFactory):
django_get_or_create = ('name', 'space',) django_get_or_create = ('name', 'space',)
# @factory.django.mute_signals(post_save)
@register @register
class FoodFactory(factory.django.DjangoModelFactory): class FoodFactory(factory.django.DjangoModelFactory):
"""Food factory.""" """Food factory."""
@ -451,17 +445,6 @@ class RecipeFactory(factory.django.DjangoModelFactory):
for step in extracted: for step in extracted:
self.steps.add(step) self.steps.add(step)
# image = models.ImageField(upload_to='recipes/', blank=True, null=True) #TODO test recipe image api https://factoryboy.readthedocs.io/en/stable/orms.html#factory.django.ImageField
# storage = models.ForeignKey(
# Storage, on_delete=models.PROTECT, blank=True, null=True
# )
# file_uid = models.CharField(max_length=256, default="", blank=True)
# file_path = models.CharField(max_length=512, default="", blank=True)
# link = models.CharField(max_length=512, null=True, blank=True)
# cors_link = models.CharField(max_length=1024, null=True, blank=True)
# nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
# updated_at = models.DateTimeField(auto_now=True)
class Meta: class Meta:
model = 'cookbook.Recipe' model = 'cookbook.Recipe'

View File

@ -2235,41 +2235,41 @@ THE_SPRUCE_EATS = {
"image": "https://www.thespruceeats.com/thmb/X_emapo3nNw6ASJctdNpYycYFtM=/940x0/filters:no_upscale():max_bytes(150000):strip_icc()/creamy-potato-soup-with-ham-3059797-stovetop-step-12-99dc3bf1962c4e26a2d225ee3c25ecad.jpg", "image": "https://www.thespruceeats.com/thmb/X_emapo3nNw6ASJctdNpYycYFtM=/940x0/filters:no_upscale():max_bytes(150000):strip_icc()/creamy-potato-soup-with-ham-3059797-stovetop-step-12-99dc3bf1962c4e26a2d225ee3c25ecad.jpg",
"source_url": "https://www.thespruceeats.com/creamy-potato-soup-with-ham-3059797", "source_url": "https://www.thespruceeats.com/creamy-potato-soup-with-ham-3059797",
"keywords": [ "keywords": [
{
"label": "soup",
"name": "soup",
"id": 18,
},
{ {
"label": "lunch", "label": "soup",
"name": "lunch", "name": "soup",
}, "id": 18,
},
{ {
"label": "entree", "label": "lunch",
"name": "entree", "name": "lunch",
}, },
{ {
"label": "side dish", "label": "entree",
"name": "side dish", "name": "entree",
"id": 14, },
},
{ {
"label": "www.thespruceeats.com", "label": "side dish",
"name": "www.thespruceeats.com", "name": "side dish",
}, "id": 14,
},
{ {
"label": "southern", "label": "www.thespruceeats.com",
"name": "southern", "name": "www.thespruceeats.com",
"id": 27, },
},
{ {
"label": "cheddar", "label": "southern",
"name": "cheddar", "name": "southern",
}, "id": 27,
},
{ {
"label": "dinner", "label": "cheddar",
"name": "dinner", "name": "cheddar",
} },
{
"label": "dinner",
"name": "dinner",
}
], ],
"steps": [ "steps": [
{ {

View File

@ -1,11 +1,12 @@
from decimal import Decimal
from django.contrib import auth from django.contrib import auth
from django.core.cache import caches from django.core.cache import caches
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from decimal import Decimal
from cookbook.helper.cache_helper import CacheHelper from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.property_helper import FoodPropertyHelper from cookbook.helper.property_helper import FoodPropertyHelper
from cookbook.models import Unit, Food, PropertyType, Property, Recipe, Step, UnitConversion, Property from cookbook.models import Food, Property, PropertyType, Recipe, Step, Unit, UnitConversion
def test_food_property(space_1, space_2, u1_s1): def test_food_property(space_1, space_2, u1_s1):
@ -125,5 +126,3 @@ def test_food_property(space_1, space_2, u1_s1):
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2) property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_fat.id not in property_values assert property_fat.id not in property_values

View File

@ -1,6 +1,5 @@
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.urls import reverse
from django_scopes import scope from django_scopes import scope
from cookbook.helper.recipe_search import RecipeSearch from cookbook.helper.recipe_search import RecipeSearch

View File

@ -1,13 +1,10 @@
import pytest
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.urls import reverse
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.forms import ImportExportBase from cookbook.helper.permission_helper import (has_group_permission, is_object_owner,
from cookbook.helper.ingredient_parser import IngredientParser is_space_owner, switch_user_active_space)
from cookbook.helper.permission_helper import has_group_permission, is_space_owner, switch_user_active_space, is_object_owner from cookbook.models import Food, RecipeBook, RecipeBookEntry, Space, UserSpace
from cookbook.models import ExportLog, UserSpace, Food, Space, Comment, RecipeBook, RecipeBookEntry
def test_has_group_permission(u1_s1, a_u, space_2): def test_has_group_permission(u1_s1, a_u, space_2):

View File

@ -1,7 +1,4 @@
import pytest import pytest
from cookbook.models import Recipe
from django.contrib import auth
from django.urls import reverse from django.urls import reverse
@ -28,7 +25,6 @@ def test_external_link_permission(arg, request, ext_recipe_1_s1):
['u1_s2', 404], ['u1_s2', 404],
['a1_s2', 404], ['a1_s2', 404],
]) ])
def test_external_link_permission(arg, request, ext_recipe_1_s1): def test_external_file_permission(arg, request, ext_recipe_1_s1):
c = request.getfixturevalue(arg[0]) c = request.getfixturevalue(arg[0])
assert c.get(reverse('api_get_recipe_file', args=[ext_recipe_1_s1.pk])).status_code == arg[1] assert c.get(reverse('api_get_recipe_file', args=[ext_recipe_1_s1.pk])).status_code == arg[1]

View File

@ -9,10 +9,10 @@ from cookbook.helper import dal
from cookbook.version_info import TANDOOR_VERSION from cookbook.version_info import TANDOOR_VERSION
from recipes.settings import DEBUG, PLUGINS from recipes.settings import DEBUG, PLUGINS
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, PropertyType,
PropertyType, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Space, Step,
Space, Step, Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UnitConversion,
UnitConversion, UserFile, UserSpace, get_model_name) UserFile, UserSpace, get_model_name)
from .views import api, data, delete, edit, import_export, lists, new, telegram, views from .views import api, data, delete, edit, import_export, lists, new, telegram, views
from .views.api import CustomAuthToken, ImportOpenData from .views.api import CustomAuthToken, ImportOpenData

View File

@ -1490,7 +1490,6 @@ class ImportOpenData(APIView):
# TODO validate data # TODO validate data
print(request.data) print(request.data)
selected_version = request.data['selected_version'] selected_version = request.data['selected_version']
selected_datatypes = request.data['selected_datatypes']
update_existing = str2bool(request.data['update_existing']) update_existing = str2bool(request.data['update_existing'])
use_metric = str2bool(request.data['use_metric']) use_metric = str2bool(request.data['use_metric'])

View File

@ -10,12 +10,11 @@ from django.utils.translation import gettext as _
from django.utils.translation import ngettext from django.utils.translation import ngettext
from django_tables2 import RequestConfig from django_tables2 import RequestConfig
from oauth2_provider.models import AccessToken from oauth2_provider.models import AccessToken
from rest_framework.authtoken.models import Token
from cookbook.forms import BatchEditForm, SyncForm from cookbook.forms import BatchEditForm, SyncForm
from cookbook.helper.permission_helper import group_required, has_group_permission, above_space_limit from cookbook.helper.permission_helper import (above_space_limit, group_required,
from cookbook.models import (Comment, Food, Keyword, Recipe, RecipeImport, Sync, has_group_permission)
Unit, UserPreference, BookmarkletImport) from cookbook.models import BookmarkletImport, Recipe, RecipeImport, Sync
from cookbook.tables import SyncTable from cookbook.tables import SyncTable
from recipes import settings from recipes import settings
@ -119,11 +118,19 @@ def import_url(request):
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
if (api_token := AccessToken.objects.filter(user=request.user, scope='bookmarklet').first()) is None: if (api_token := AccessToken.objects.filter(user=request.user, scope='bookmarklet').first()) is None:
api_token = AccessToken.objects.create(user=request.user, scope='bookmarklet', expires=(timezone.now() + timezone.timedelta(days=365*10)), token=f'tda_{str(uuid.uuid4()).replace("-","_")}') api_token = AccessToken.objects.create(
user=request.user,
scope='bookmarklet',
expires=(
timezone.now() +
timezone.timedelta(
days=365 *
10)),
token=f'tda_{str(uuid.uuid4()).replace("-","_")}')
bookmarklet_import_id = -1 bookmarklet_import_id = -1
if 'id' in request.GET: if 'id' in request.GET:
if bookmarklet_import := BookmarkletImport.objects.filter(id=request.GET['id']).first(): if bookmarklet_import := BookmarkletImport.objects.filter(id=request.GET['id']).first():
bookmarklet_import_id = bookmarklet_import.pk bookmarklet_import_id = bookmarklet_import.pk
return render(request, 'url_import.html', {'api_token': api_token, 'bookmarklet_import_id': bookmarklet_import_id}) return render(request, 'url_import.html', {'api_token': api_token, 'bookmarklet_import_id': bookmarklet_import_id})

View File

@ -9,7 +9,7 @@ from django.views.generic import DeleteView
from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required
from cookbook.models import (Comment, InviteLink, MealPlan, Recipe, RecipeBook, RecipeBookEntry, from cookbook.models import (Comment, InviteLink, MealPlan, Recipe, RecipeBook, RecipeBookEntry,
RecipeImport, Storage, Sync, UserSpace, Space) RecipeImport, Space, Storage, Sync, UserSpace)
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud from cookbook.provider.nextcloud import Nextcloud
@ -99,18 +99,6 @@ class SyncDelete(GroupRequiredMixin, DeleteView):
return context return context
# 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
class StorageDelete(GroupRequiredMixin, DeleteView): class StorageDelete(GroupRequiredMixin, DeleteView):
groups_required = ['admin'] groups_required = ['admin']
template_name = "generic/delete_template.html" template_name = "generic/delete_template.html"

View File

@ -9,9 +9,9 @@ from django.views.generic import UpdateView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm from cookbook.forms import CommentForm, ExternalRecipeForm, StorageForm, SyncForm
from cookbook.helper.permission_helper import GroupRequiredMixin, OwnerRequiredMixin, group_required, above_space_limit from cookbook.helper.permission_helper import (GroupRequiredMixin, OwnerRequiredMixin,
from cookbook.models import (Comment, MealPlan, MealType, Recipe, RecipeImport, Storage, Sync, above_space_limit, group_required)
UserPreference) from cookbook.models import Comment, Recipe, RecipeImport, Storage, Sync
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud from cookbook.provider.nextcloud import Nextcloud
@ -74,40 +74,6 @@ class SyncUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
return context return context
# class KeywordUpdate(GroupRequiredMixin, UpdateView):
# groups_required = ['user']
# template_name = "generic/edit_template.html"
# model = Keyword
# form_class = KeywordForm
# # TODO add msg box
# 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
# class FoodUpdate(GroupRequiredMixin, UpdateView, SpaceFormMixing):
# groups_required = ['user']
# template_name = "generic/edit_template.html"
# model = Food
# form_class = FoodForm
# # TODO add msg box
# 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
@group_required('admin') @group_required('admin')
def edit_storage(request, pk): def edit_storage(request, pk):
instance = get_object_or_404(Storage, pk=pk, space=request.space) instance = get_object_or_404(Storage, pk=pk, space=request.space)

View File

@ -7,8 +7,7 @@ from django_tables2 import RequestConfig
from cookbook.helper.permission_helper import group_required from cookbook.helper.permission_helper import group_required
from cookbook.models import InviteLink, RecipeImport, Storage, SyncLog, UserFile from cookbook.models import InviteLink, RecipeImport, Storage, SyncLog, UserFile
from cookbook.tables import (ImportLogTable, InviteLinkTable, RecipeImportTable, from cookbook.tables import ImportLogTable, InviteLinkTable, RecipeImportTable, StorageTable
StorageTable)
@group_required('admin') @group_required('admin')
@ -196,7 +195,7 @@ def custom_filter(request):
def user_file(request): def user_file(request):
try: try:
current_file_size_mb = UserFile.objects.filter(space=request.space).aggregate(Sum('file_size_kb'))[ current_file_size_mb = UserFile.objects.filter(space=request.space).aggregate(Sum('file_size_kb'))[
'file_size_kb__sum'] / 1000 'file_size_kb__sum'] / 1000
except TypeError: except TypeError:
current_file_size_mb = 0 current_file_size_mb = 0

View File

@ -1,22 +1,14 @@
import re
from datetime import datetime, timedelta
from html import escape
from smtplib import SMTPException
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Group
from django.core.mail import BadHeaderError, send_mail
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import CreateView from django.views.generic import CreateView
from cookbook.forms import ImportRecipeForm, InviteLinkForm, Storage, StorageForm from cookbook.forms import ImportRecipeForm, Storage, StorageForm
from cookbook.helper.permission_helper import GroupRequiredMixin, group_required, above_space_limit from cookbook.helper.permission_helper import GroupRequiredMixin, above_space_limit, group_required
from cookbook.models import (InviteLink, MealPlan, MealType, Recipe, RecipeBook, RecipeImport, from cookbook.models import Recipe, RecipeImport, ShareLink, Step
ShareLink, Step, UserPreference, UserSpace)
from cookbook.views.edit import SpaceFormMixing
from recipes import settings from recipes import settings
@ -56,24 +48,6 @@ def share_link(request, pk):
return HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': pk, 'share': link.uuid})) 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')
# 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
class StorageCreate(GroupRequiredMixin, CreateView): class StorageCreate(GroupRequiredMixin, CreateView):
groups_required = ['admin'] groups_required = ['admin']
template_name = "generic/new_template.html" template_name = "generic/new_template.html"
@ -133,5 +107,3 @@ def create_new_external_recipe(request, import_id):
) )
return render(request, 'forms/edit_import_recipe.html', {'form': form}) return render(request, 'forms/edit_import_recipe.html', {'form': form})

View File

@ -2,14 +2,13 @@ import json
import traceback import traceback
import requests import requests
from django.db.models import Q
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import group_required from cookbook.helper.permission_helper import group_required
from cookbook.models import ShoppingList, ShoppingListEntry, TelegramBot from cookbook.models import ShoppingListEntry, TelegramBot
@group_required('user') @group_required('user')
@ -21,7 +20,8 @@ def setup_bot(request, pk):
create_response = requests.get(f'https://api.telegram.org/bot{bot.token}/setWebhook?url={hook_url}') create_response = requests.get(f'https://api.telegram.org/bot{bot.token}/setWebhook?url={hook_url}')
info_response = requests.get(f'https://api.telegram.org/bot{bot.token}/getWebhookInfo') info_response = requests.get(f'https://api.telegram.org/bot{bot.token}/getWebhookInfo')
return JsonResponse({'hook_url': hook_url, 'create_response': json.loads(create_response.content.decode()), 'info_response': json.loads(info_response.content.decode())}, json_dumps_params={'indent': 4}) return JsonResponse({'hook_url': hook_url, 'create_response': json.loads(create_response.content.decode()),
'info_response': json.loads(info_response.content.decode())}, json_dumps_params={'indent': 4})
@group_required('user') @group_required('user')
@ -31,7 +31,8 @@ def remove_bot(request, pk):
remove_response = requests.get(f'https://api.telegram.org/bot{bot.token}/deleteWebhook') remove_response = requests.get(f'https://api.telegram.org/bot{bot.token}/deleteWebhook')
info_response = requests.get(f'https://api.telegram.org/bot{bot.token}/getWebhookInfo') info_response = requests.get(f'https://api.telegram.org/bot{bot.token}/getWebhookInfo')
return JsonResponse({'remove_response': json.loads(remove_response.content.decode()), 'info_response': json.loads(info_response.content.decode())}, json_dumps_params={'indent': 4}) return JsonResponse({'remove_response': json.loads(remove_response.content.decode()),
'info_response': json.loads(info_response.content.decode())}, json_dumps_params={'indent': 4})
@csrf_exempt @csrf_exempt

View File

@ -1,4 +1,3 @@
import time
from os import getenv from os import getenv
from django.conf import settings from django.conf import settings
@ -31,12 +30,12 @@ def terminal_width():
s = struct.pack('HHHH', 0, 0, 0, 0) s = struct.pack('HHHH', 0, 0, 0, 0)
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
width = struct.unpack('HHHH', x)[1] width = struct.unpack('HHHH', x)[1]
except: except Exception:
pass pass
if width <= 0: if width <= 0:
try: try:
width = int(getenv['COLUMNS']) width = int(getenv['COLUMNS'])
except: except Exception:
pass pass
if width <= 0: if width <= 0:
width = 80 width = 80

View File

@ -52,7 +52,7 @@ for p in settings.PLUGINS:
if settings.DEBUG: if settings.DEBUG:
print(e.msg) print(e.msg)
print(f'ERROR failed loading plugin <{p["name"]}> urls, did you forget creating urls.py in your plugin?') print(f'ERROR failed loading plugin <{p["name"]}> urls, did you forget creating urls.py in your plugin?')
except Exception as e: except Exception:
if settings.DEBUG: if settings.DEBUG:
print(f'ERROR failed loading urls for plugin <{p["name"]}>') print(f'ERROR failed loading urls for plugin <{p["name"]}>')
traceback.format_exc() traceback.format_exc()

View File

@ -3,6 +3,7 @@
"target": "es5", "target": "es5",
"module": "esnext", "module": "esnext",
"strict": true, "strict": true,
"allowJs": true,
"jsx": "preserve", "jsx": "preserve",
"importHelpers": true, "importHelpers": true,
"moduleResolution": "node", "moduleResolution": "node",
@ -32,8 +33,9 @@
"src/**/*.tsx", "src/**/*.tsx",
"src/**/*.vue", "src/**/*.vue",
"tests/**/*.ts", "tests/**/*.ts",
"tests/**/*.tsx" "tests/**/*.tsx",
, "src/directives/OutsideClick.js" ], "src/directives/OutsideClick.js"
],
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ]