rebase and fixes

This commit is contained in:
smilerz 2021-12-15 09:39:20 -06:00
parent ee4ab41c1c
commit 453b1eb5b9
14 changed files with 46 additions and 14812 deletions

View File

@ -17,7 +17,6 @@ class SelectWidget(widgets.Select):
class MultiSelectWidget(widgets.SelectMultiple): class MultiSelectWidget(widgets.SelectMultiple):
class Media: class Media:
js = ('custom/js/form_multiselect.js',) js = ('custom/js/form_multiselect.js',)
@ -75,13 +74,10 @@ class UserPreferenceForm(forms.ModelForm):
'Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'), 'Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'),
# noqa: E501 # noqa: E501
'use_kj': _('Display nutritional energy amounts in joules instead of calories'), # noqa: E501 'use_kj': _('Display nutritional energy amounts in joules instead of calories'), # noqa: E501
'plan_share': _( 'plan_share': _('Users with whom newly created meal plans should be shared by default.'),
'Users with whom newly created meal plans should be shared by default.'), 'shopping_share': _('Users with whom to share shopping lists.'),
'shopping_share': _(
'Users with whom to share shopping lists.'),
# noqa: E501 # noqa: E501
'show_recent': _('Show recently viewed recipes on search page.'), # noqa: E501 'show_recent': _('Show recently viewed recipes on search page.'), # noqa: E501
'ingredient_decimals': _('Number of decimals to round ingredients.'), # noqa: E501 'ingredient_decimals': _('Number of decimals to round ingredients.'), # noqa: E501
'comments': _('If you want to be able to create and see comments underneath recipes.'), # noqa: E501 'comments': _('If you want to be able to create and see comments underneath recipes.'), # noqa: E501
'shopping_auto_sync': _( 'shopping_auto_sync': _(
@ -96,7 +92,6 @@ class UserPreferenceForm(forms.ModelForm):
widgets = { widgets = {
'plan_share': MultiSelectWidget, 'plan_share': MultiSelectWidget,
'shopping_share': MultiSelectWidget, 'shopping_share': MultiSelectWidget,
} }

View File

@ -205,6 +205,9 @@ 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__ == 'ShoppingList':
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)

View File

@ -142,7 +142,6 @@ class TreeModel(MP_Node):
return self.get_children().count() return self.get_children().count()
# use self.objects.get_or_create() instead # use self.objects.get_or_create() instead
@classmethod @classmethod
def add_root(self, **kwargs): def add_root(self, **kwargs):
with scopes_disabled(): with scopes_disabled():

View File

@ -4,7 +4,6 @@ from decimal import Decimal
from gettext import gettext as _ from gettext import gettext as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import transaction
from django.db.models import Avg, QuerySet, Sum from django.db.models import Avg, QuerySet, Sum
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
@ -96,7 +95,9 @@ class CustomDecimalField(serializers.Field):
class SpaceFilterSerializer(serializers.ListSerializer): class SpaceFilterSerializer(serializers.ListSerializer):
def to_representation(self, data): def to_representation(self, data):
if (type(data) == QuerySet and data.query.is_sliced) or not self.context.get('request', None): if self.context.get('request', None) is None:
return
if (type(data) == QuerySet and data.query.is_sliced):
# if query is sliced it came from api request not nested serializer # if query is sliced it came from api request not nested serializer
return super().to_representation(data) return super().to_representation(data)
if self.child.Meta.model == User: if self.child.Meta.model == User:
@ -158,7 +159,7 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
plan_share = UserNameSerializer(many=True, allow_null=True, required=False, read_only=True) plan_share = UserNameSerializer(many=True, allow_null=True, required=False, read_only=True)
# TODO decide: default inherit field values for foods are being handled via VUE client through user preference # TODO decide: default inherit field values for foods are being handled via VUE client through user preference
## should inherit field instead be set during the django model create? # should inherit field instead be set during the django model create?
def get_ignore_default(self, obj): def get_ignore_default(self, obj):
return FoodInheritFieldSerializer(Food.inherit_fields.difference(obj.space.food_inherit.all()), many=True).data return FoodInheritFieldSerializer(Food.inherit_fields.difference(obj.space.food_inherit.all()), many=True).data
@ -307,7 +308,6 @@ class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin): class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
recipe_filter = 'steps__ingredients__unit' recipe_filter = 'steps__ingredients__unit'
def create(self, validated_data): def create(self, validated_data):

View File

@ -77,16 +77,20 @@ def test_shopping_food_delete(request, arg, food):
def test_shopping_food_share(u1_s1, u2_s1, food, space_1): def test_shopping_food_share(u1_s1, u2_s1, food, space_1):
with scope(space=space_1): with scope(space=space_1):
user1 = auth.get_user(u1_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})) r = u1_s1.put(reverse(SHOPPING_FOOD_URL, args={food.id}))
r = u2_s1.put(reverse(SHOPPING_FOOD_URL, args={food.id})) r = u2_s1.put(reverse(SHOPPING_FOOD_URL, args={food2.id}))
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1 sl_1 = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)
assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1 sl_2 = json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)
assert len(sl_1) == 1
assert len(sl_2) == 1
sl_1[0]['created_by']['id'] == user1.id
sl_2[0]['created_by']['id'] == user2.id
with scopes_disabled(): with scopes_disabled():
user = auth.get_user(u1_s1) user1.userpreference.shopping_share.add(user2)
user2 = auth.get_user(u2_s1) user1.userpreference.save()
user.userpreference.shopping_share.add(user2)
user.userpreference.save()
assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1 assert len(json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 1
assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 2 assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == 2

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, SyncLog, ShoppingList from cookbook.models import RecipeBook, ShoppingList, Storage, Sync, SyncLog
LIST_URL = 'api:shoppinglist-list' LIST_URL = 'api:shoppinglist-list'
DETAIL_URL = 'api:shoppinglist-detail' DETAIL_URL = 'api:shoppinglist-detail'
@ -56,6 +56,21 @@ def test_share(obj_1, u1_s1, u2_s1, u1_s2):
assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404 assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
def test_new_share(request, obj_1, u1_s1, u2_s1, u1_s2):
assert u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
assert u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
with scopes_disabled():
user = auth.get_user(u1_s1)
user.userpreference.shopping_share.add(auth.get_user(u2_s1))
user.userpreference.save()
assert u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
assert u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 200
assert u1_s2.get(reverse(DETAIL_URL, args={obj_1.id})).status_code == 404
@pytest.mark.parametrize("arg", [ @pytest.mark.parametrize("arg", [
['a_u', 403], ['a_u', 403],
['g1_s1', 404], ['g1_s1', 404],

View File

@ -117,18 +117,3 @@ def test_delete(u1_s1, u1_s2, obj_1):
) )
assert r.status_code == 204 assert r.status_code == 204
# TODO test sharing
# TODO test completed entries still visible if today, but not yesterday
# TODO test create shopping list from recipe
# TODO test delete shopping list from recipe - include created by, shared with and not shared with
# TODO test create shopping list from food
# TODO test delete shopping list from food - include created by, shared with and not shared with
# TODO test create shopping list from mealplan
# TODO test create shopping list from recipe, excluding ingredients
# TODO test auto creating shopping list from meal plan
# TODO test excluding on-hand when auto creating shopping list
# test delay
# test completed_at when checked
# test completed_at cleared when unchecked

View File

@ -217,8 +217,3 @@ def test_recent(sle, u1_s1):
r = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?recent=1').content) r = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?recent=1').content)
assert len(r) == 10 assert len(r) == 10
assert [x['checked'] for x in r].count(False) == 9 assert [x['checked'] for x in r].count(False) == 9
# TODO test create shopping list from food
# TODO test delete shopping list from food - include created by, shared with and not shared with
# TODO test create shopping list from mealplan

View File

@ -15,7 +15,7 @@ from cookbook.tests.factories import FoodFactory, SpaceFactory, UserFactory
register(SpaceFactory, 'space_1') register(SpaceFactory, 'space_1')
register(SpaceFactory, 'space_2') register(SpaceFactory, 'space_2')
# register(FoodFactory, space=LazyFixture('space_2')) # register(FoodFactory, space=LazyFixture('space_2'))
# TODO refactor user fixtures https://stackoverflow.com/questions/40966571/how-to-create-a-field-with-a-list-of-instances-in-factory-boy # TODO refactor clients to be factories
# hack from https://github.com/raphaelm/django-scopes to disable scopes for all fixtures # hack from https://github.com/raphaelm/django-scopes to disable scopes for all fixtures
# does not work on yield fixtures as only one yield can be used per fixture (i think) # does not work on yield fixtures as only one yield can be used per fixture (i think)

View File

@ -72,9 +72,8 @@ class UserFactory(factory.django.DjangoModelFactory):
return return
if extracted: if extracted:
# TODO this doesn't work and needs saved
for prefs in extracted: for prefs in extracted:
self.userpreference[prefs] = prefs self.userpreference[prefs] = extracted[prefs]/0 # intentionally break so it can be debugged later
self.userpreference.space = self.space self.userpreference.space = self.space
self.userpreference.save() self.userpreference.save()
@ -154,7 +153,6 @@ class KeywordFactory(factory.django.DjangoModelFactory):
@register @register
class IngredientFactory(factory.django.DjangoModelFactory): class IngredientFactory(factory.django.DjangoModelFactory):
"""Ingredient factory.""" """Ingredient factory."""
# TODO add optional recipe food
food = factory.SubFactory(FoodFactory, space=factory.SelfAttribute('..space')) food = factory.SubFactory(FoodFactory, space=factory.SelfAttribute('..space'))
unit = factory.SubFactory(UnitFactory, space=factory.SelfAttribute('..space')) unit = factory.SubFactory(UnitFactory, space=factory.SelfAttribute('..space'))
amount = factory.LazyAttribute(lambda x: faker.random_int(min=1, max=10)) amount = factory.LazyAttribute(lambda x: faker.random_int(min=1, max=10))
@ -332,7 +330,7 @@ class RecipeFactory(factory.django.DjangoModelFactory):
if not create: if not create:
return return
food_recipe_count = kwargs.get('food_recipe_count', {}) # TODO - pass this food_recipe_count = kwargs.get('food_recipe_count', {})
num_steps = kwargs.get('count', 0) num_steps = kwargs.get('count', 0)
num_recipe_steps = kwargs.get('recipe_count', 0) num_recipe_steps = kwargs.get('recipe_count', 0)
if num_steps > 0: if num_steps > 0:

View File

@ -747,12 +747,13 @@ class ShoppingListViewSet(viewsets.ModelViewSet):
serializer_class = ShoppingListSerializer serializer_class = ShoppingListSerializer
permission_classes = [CustomIsOwner | CustomIsShared] permission_classes = [CustomIsOwner | CustomIsShared]
# TODO update to include settings shared user - make both work for a period of time
def get_queryset(self): def get_queryset(self):
return self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter( return self.queryset.filter(
space=self.request.space).distinct() Q(created_by=self.request.user)
| Q(shared=self.request.user)
| Q(created_by__in=list(self.request.user.get_shopping_share()))
).filter(space=self.request.space).distinct()
# TODO deprecate
def get_serializer_class(self): def get_serializer_class(self):
try: try:
autosync = self.request.query_params.get('autosync', False) autosync = self.request.query_params.get('autosync', False)

View File

@ -42,4 +42,4 @@ django-prometheus==2.1.0
django-hCaptcha==0.1.0 django-hCaptcha==0.1.0
python-ldap==3.4.0 python-ldap==3.4.0
django-auth-ldap==3.0.0 django-auth-ldap==3.0.0
factory-boy==3.2.1 pytest-factoryboy==2.1.0

14761
vue/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -89,7 +89,7 @@ module.exports = {
}, },
}, },
}, },
config.optimization.minimize(false) config.optimization.minimize(true)
) )
config.plugin("BundleTracker").use(BundleTracker, [{ relativePath: true, path: "../vue/" }]) config.plugin("BundleTracker").use(BundleTracker, [{ relativePath: true, path: "../vue/" }])