lots of fixes and stuff
This commit is contained in:
parent
07f78bb7b8
commit
e2b887b449
@ -32,41 +32,7 @@ admin.site.unregister(Group)
|
|||||||
@admin.action(description='Delete all data from a space')
|
@admin.action(description='Delete all data from a space')
|
||||||
def delete_space_action(modeladmin, request, queryset):
|
def delete_space_action(modeladmin, request, queryset):
|
||||||
for space in queryset:
|
for space in queryset:
|
||||||
CookLog.objects.filter(space=space).delete()
|
space.save()
|
||||||
ViewLog.objects.filter(space=space).delete()
|
|
||||||
ImportLog.objects.filter(space=space).delete()
|
|
||||||
BookmarkletImport.objects.filter(space=space).delete()
|
|
||||||
|
|
||||||
Comment.objects.filter(recipe__space=space).delete()
|
|
||||||
Keyword.objects.filter(space=space).delete()
|
|
||||||
Ingredient.objects.filter(space=space).delete()
|
|
||||||
Food.objects.filter(space=space).delete()
|
|
||||||
Unit.objects.filter(space=space).delete()
|
|
||||||
Step.objects.filter(space=space).delete()
|
|
||||||
NutritionInformation.objects.filter(space=space).delete()
|
|
||||||
RecipeBookEntry.objects.filter(book__space=space).delete()
|
|
||||||
RecipeBook.objects.filter(space=space).delete()
|
|
||||||
MealType.objects.filter(space=space).delete()
|
|
||||||
MealPlan.objects.filter(space=space).delete()
|
|
||||||
ShareLink.objects.filter(space=space).delete()
|
|
||||||
Recipe.objects.filter(space=space).delete()
|
|
||||||
|
|
||||||
RecipeImport.objects.filter(space=space).delete()
|
|
||||||
SyncLog.objects.filter(sync__space=space).delete()
|
|
||||||
Sync.objects.filter(space=space).delete()
|
|
||||||
Storage.objects.filter(space=space).delete()
|
|
||||||
|
|
||||||
ShoppingListEntry.objects.filter(shoppinglist__space=space).delete()
|
|
||||||
ShoppingListRecipe.objects.filter(shoppinglist__space=space).delete()
|
|
||||||
ShoppingList.objects.filter(space=space).delete()
|
|
||||||
|
|
||||||
SupermarketCategoryRelation.objects.filter(supermarket__space=space).delete()
|
|
||||||
SupermarketCategory.objects.filter(space=space).delete()
|
|
||||||
Supermarket.objects.filter(space=space).delete()
|
|
||||||
|
|
||||||
InviteLink.objects.filter(space=space).delete()
|
|
||||||
UserFile.objects.filter(space=space).delete()
|
|
||||||
Automation.objects.filter(space=space).delete()
|
|
||||||
|
|
||||||
|
|
||||||
class SpaceAdmin(admin.ModelAdmin):
|
class SpaceAdmin(admin.ModelAdmin):
|
||||||
|
@ -760,6 +760,6 @@ def old_search(request):
|
|||||||
params = dict(request.GET)
|
params = dict(request.GET)
|
||||||
params['internal'] = None
|
params['internal'] = None
|
||||||
f = RecipeFilter(params,
|
f = RecipeFilter(params,
|
||||||
queryset=Recipe.objects.filter(space=request.user.userpreference.space).all().order_by(Lower('name').asc()),
|
queryset=Recipe.objects.filter(space=request.space).all().order_by(Lower('name').asc()),
|
||||||
space=request.space)
|
space=request.space)
|
||||||
return f.qs
|
return f.qs
|
||||||
|
@ -4,11 +4,12 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
def create_default_space(apps, schema_editor):
|
def create_default_space(apps, schema_editor):
|
||||||
Space = apps.get_model('cookbook', 'Space')
|
# Space = apps.get_model('cookbook', 'Space')
|
||||||
Space.objects.create(
|
# Space.objects.create(
|
||||||
name='Default',
|
# name='Default',
|
||||||
message=''
|
# message=''
|
||||||
)
|
# )
|
||||||
|
pass # Beginning with the multi space tenancy version (~something around 1.3) a default space is no longer needed as the first user can create it after setup
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -240,7 +240,49 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
|||||||
demo = models.BooleanField(default=False)
|
demo = models.BooleanField(default=False)
|
||||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||||
show_facet_count = models.BooleanField(default=False)
|
show_facet_count = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def safe_delete(self):
|
||||||
|
"""
|
||||||
|
Safely deletes a space by deleting all objects belonging to the space first and then deleting the space itself
|
||||||
|
"""
|
||||||
|
CookLog.objects.filter(space=self).delete()
|
||||||
|
ViewLog.objects.filter(space=self).delete()
|
||||||
|
ImportLog.objects.filter(space=self).delete()
|
||||||
|
BookmarkletImport.objects.filter(space=self).delete()
|
||||||
|
CustomFilter.objects.filter(space=self).delete()
|
||||||
|
|
||||||
|
Comment.objects.filter(recipe__space=self).delete()
|
||||||
|
Keyword.objects.filter(space=self).delete()
|
||||||
|
Ingredient.objects.filter(space=self).delete()
|
||||||
|
Food.objects.filter(space=self).delete()
|
||||||
|
Unit.objects.filter(space=self).delete()
|
||||||
|
Step.objects.filter(space=self).delete()
|
||||||
|
NutritionInformation.objects.filter(space=self).delete()
|
||||||
|
RecipeBookEntry.objects.filter(book__space=self).delete()
|
||||||
|
RecipeBook.objects.filter(space=self).delete()
|
||||||
|
MealType.objects.filter(space=self).delete()
|
||||||
|
MealPlan.objects.filter(space=self).delete()
|
||||||
|
ShareLink.objects.filter(space=self).delete()
|
||||||
|
Recipe.objects.filter(space=self).delete()
|
||||||
|
|
||||||
|
RecipeImport.objects.filter(space=self).delete()
|
||||||
|
SyncLog.objects.filter(sync__space=self).delete()
|
||||||
|
Sync.objects.filter(space=self).delete()
|
||||||
|
Storage.objects.filter(space=self).delete()
|
||||||
|
|
||||||
|
ShoppingListEntry.objects.filter(shoppinglist__space=self).delete()
|
||||||
|
ShoppingListRecipe.objects.filter(shoppinglist__space=self).delete()
|
||||||
|
ShoppingList.objects.filter(space=self).delete()
|
||||||
|
|
||||||
|
SupermarketCategoryRelation.objects.filter(supermarket__space=self).delete()
|
||||||
|
SupermarketCategory.objects.filter(space=self).delete()
|
||||||
|
Supermarket.objects.filter(space=self).delete()
|
||||||
|
|
||||||
|
InviteLink.objects.filter(space=self).delete()
|
||||||
|
UserFile.objects.filter(space=self).delete()
|
||||||
|
Automation.objects.filter(space=self).delete()
|
||||||
|
self.delete()
|
||||||
|
|
||||||
def get_owner(self):
|
def get_owner(self):
|
||||||
return self.created_by
|
return self.created_by
|
||||||
|
|
||||||
@ -551,14 +593,14 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
|||||||
tree_filter = Q(space=space)
|
tree_filter = Q(space=space)
|
||||||
|
|
||||||
# remove all inherited fields from food
|
# remove all inherited fields from food
|
||||||
Through = Food.objects.filter(tree_filter).first().inherit_fields.through
|
trough = Food.objects.filter(tree_filter).first().inherit_fields.through
|
||||||
Through.objects.all().delete()
|
trough.objects.all().delete()
|
||||||
# food is going to inherit attributes
|
# food is going to inherit attributes
|
||||||
if len(inherit) > 0:
|
if len(inherit) > 0:
|
||||||
# ManyToMany cannot be updated through an UPDATE operation
|
# ManyToMany cannot be updated through an UPDATE operation
|
||||||
for i in inherit:
|
for i in inherit:
|
||||||
Through.objects.bulk_create([
|
trough.objects.bulk_create([
|
||||||
Through(food_id=x, foodinheritfield_id=i['id'])
|
trough(food_id=x, foodinheritfield_id=i['id'])
|
||||||
for x in Food.objects.filter(tree_filter).values_list('id', flat=True)
|
for x in Food.objects.filter(tree_filter).values_list('id', flat=True)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -151,10 +151,27 @@ class GroupSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
|||||||
fields = ('id', 'name')
|
fields = ('id', 'name')
|
||||||
|
|
||||||
|
|
||||||
class SpaceSerializer(serializers.ModelSerializer):
|
class FoodInheritFieldSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||||
|
name = serializers.CharField(allow_null=True, allow_blank=True, required=False)
|
||||||
|
field = serializers.CharField(allow_null=True, allow_blank=True, required=False)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
raise ValidationError('Cannot create using this endpoint')
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
return instance
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FoodInheritField
|
||||||
|
fields = ('id', 'name', 'field',)
|
||||||
|
read_only_fields = ['id']
|
||||||
|
|
||||||
|
|
||||||
|
class SpaceSerializer(WritableNestedModelSerializer):
|
||||||
user_count = serializers.SerializerMethodField('get_user_count')
|
user_count = serializers.SerializerMethodField('get_user_count')
|
||||||
recipe_count = serializers.SerializerMethodField('get_recipe_count')
|
recipe_count = serializers.SerializerMethodField('get_recipe_count')
|
||||||
file_size_mb = serializers.SerializerMethodField('get_file_size_mb')
|
file_size_mb = serializers.SerializerMethodField('get_file_size_mb')
|
||||||
|
food_inherit = FoodInheritFieldSerializer(many=True)
|
||||||
|
|
||||||
def get_user_count(self, obj):
|
def get_user_count(self, obj):
|
||||||
return UserSpace.objects.filter(space=obj).count()
|
return UserSpace.objects.filter(space=obj).count()
|
||||||
@ -174,13 +191,18 @@ class SpaceSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Space
|
model = Space
|
||||||
fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',)
|
fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',)
|
||||||
read_only_fields = ('id', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',)
|
read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',)
|
||||||
|
|
||||||
|
|
||||||
class UserSpaceSerializer(WritableNestedModelSerializer):
|
class UserSpaceSerializer(WritableNestedModelSerializer):
|
||||||
user = UserNameSerializer(read_only=True)
|
user = UserNameSerializer(read_only=True)
|
||||||
groups = GroupSerializer(many=True)
|
groups = GroupSerializer(many=True)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
if self.instance.user == self.context['request'].space.created_by: # cant change space owner permission
|
||||||
|
raise serializers.ValidationError(_('Cannot modify Space owner permission.'))
|
||||||
|
return super().validate(data)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
raise ValidationError('Cannot create using this endpoint')
|
raise ValidationError('Cannot create using this endpoint')
|
||||||
|
|
||||||
@ -209,24 +231,6 @@ class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
|||||||
read_only_fields = ('created_by',)
|
read_only_fields = ('created_by',)
|
||||||
|
|
||||||
|
|
||||||
class FoodInheritFieldSerializer(WritableNestedModelSerializer):
|
|
||||||
name = serializers.CharField(allow_null=True, allow_blank=True, required=False)
|
|
||||||
field = serializers.CharField(allow_null=True, allow_blank=True, required=False)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
# don't allow writing to FoodInheritField via API
|
|
||||||
return FoodInheritField.objects.get(**validated_data)
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
# don't allow writing to FoodInheritField via API
|
|
||||||
return FoodInheritField.objects.get(**validated_data)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = FoodInheritField
|
|
||||||
fields = ('id', 'name', 'field',)
|
|
||||||
read_only_fields = ['id']
|
|
||||||
|
|
||||||
|
|
||||||
class UserPreferenceSerializer(WritableNestedModelSerializer):
|
class UserPreferenceSerializer(WritableNestedModelSerializer):
|
||||||
food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', many=True, allow_null=True,
|
food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', many=True, allow_null=True,
|
||||||
required=False, read_only=True)
|
required=False, read_only=True)
|
||||||
@ -1073,7 +1077,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
|
|||||||
class BookmarkletImportListSerializer(serializers.ModelSerializer):
|
class BookmarkletImportListSerializer(serializers.ModelSerializer):
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['created_by'] = self.context['request'].user
|
validated_data['created_by'] = self.context['request'].user
|
||||||
validated_data['space'] = self.context['request'].user.userpreference.space
|
validated_data['space'] = self.context['request'].space
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -2,6 +2,7 @@ from decimal import Decimal
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.postgres.search import SearchVector
|
from django.contrib.postgres.search import SearchVector
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
@ -11,7 +12,7 @@ from django_scopes import scope
|
|||||||
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, FoodInheritField, Ingredient, MealPlan, Recipe,
|
||||||
ShoppingListEntry, Step)
|
ShoppingListEntry, Step, 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',
|
||||||
@ -28,9 +29,17 @@ def skip_signal(signal_func):
|
|||||||
if hasattr(instance, 'skip_signal'):
|
if hasattr(instance, 'skip_signal'):
|
||||||
return None
|
return None
|
||||||
return signal_func(sender, instance, **kwargs)
|
return signal_func(sender, instance, **kwargs)
|
||||||
|
|
||||||
return _decorator
|
return _decorator
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=User)
|
||||||
|
@skip_signal
|
||||||
|
def update_recipe_search_vector(sender, instance=None, created=False, **kwargs):
|
||||||
|
if created:
|
||||||
|
UserPreference.objects.get_or_create(user=instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Recipe)
|
@receiver(post_save, sender=Recipe)
|
||||||
@skip_signal
|
@skip_signal
|
||||||
def update_recipe_search_vector(sender, instance=None, created=False, **kwargs):
|
def update_recipe_search_vector(sender, instance=None, created=False, **kwargs):
|
||||||
@ -131,5 +140,3 @@ def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs
|
|||||||
print("MEAL_AUTO_ADD Created SLR")
|
print("MEAL_AUTO_ADD Created SLR")
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@
|
|||||||
<a class="dropdown-item" href="{% url 'data_sync' %}"><i
|
<a class="dropdown-item" href="{% url 'data_sync' %}"><i
|
||||||
class="fas fa-sync-alt fa-fw"></i> {% trans 'External Recipes' %}</a>
|
class="fas fa-sync-alt fa-fw"></i> {% trans 'External Recipes' %}</a>
|
||||||
{% if request.user == request.space.created_by %}
|
{% if request.user == request.space.created_by %}
|
||||||
<a class="dropdown-item" href="{% url 'view_space' %}"><i
|
<a class="dropdown-item" href="{% url 'view_space_manage' request.space.pk %}"><i
|
||||||
class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a>
|
class="fas fa-server fa-fw"></i> {% trans 'Space Settings' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_superuser %}
|
{% if user.is_superuser %}
|
||||||
@ -316,7 +316,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{{ us.space.name }}</a>
|
{{ us.space.name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<a class="dropdown-item" href="{% url 'view_space_overview' %}"><i class="fas fa-plus fa-fw"></i> {% trans 'Create New' %}</a>
|
<a class="dropdown-item" href="{% url 'view_space_overview' %}"><i class="fas fa-list"></i> {% trans 'Overview' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
|
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
|
||||||
@ -344,7 +344,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{% message_of_the_day as message_of_the_day %}
|
{% message_of_the_day request as message_of_the_day %}
|
||||||
{% if message_of_the_day %}
|
{% if message_of_the_day %}
|
||||||
<div class="bg-success" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
<div class="bg-success" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||||
{{ message_of_the_day }}
|
{{ message_of_the_day }}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'view_space' %}">{% trans 'Space Settings' %}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'view_space_manage' request.space.pk %}">{% trans 'Space Settings' %}</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'view_space' %}">{% trans 'Space Settings' %}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'view_space_manage' request.space.pk %}">{% trans 'Space Settings' %}</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "No Space" %}{% endblock %}
|
{% block title %}{% trans "Overview" %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -36,6 +36,11 @@
|
|||||||
<h5 class="card-title"><a
|
<h5 class="card-title"><a
|
||||||
href="{% url 'view_switch_space' us.space.id %}">{{ us.space.name }}</a>
|
href="{% url 'view_switch_space' us.space.id %}">{{ us.space.name }}</a>
|
||||||
</h5>
|
</h5>
|
||||||
|
{# {% if us.active %}#}
|
||||||
|
{# <i class="far fa-dot-circle fa-fw"></i>#}
|
||||||
|
{# {% else %}#}
|
||||||
|
{# <i class="far fa-circle fa-fw"></i>#}
|
||||||
|
{# {% endif %}#}
|
||||||
<p class="card-text"><small
|
<p class="card-text"><small
|
||||||
class="text-muted">{% trans 'Owner' %}: {{ us.space.created_by }}</small>
|
class="text-muted">{% trans 'Owner' %}: {{ us.space.created_by }}</small>
|
||||||
{% if us.space.created_by != us.user %}
|
{% if us.space.created_by != us.user %}
|
||||||
|
@ -111,8 +111,12 @@ def page_help(page_name):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def message_of_the_day():
|
def message_of_the_day(request):
|
||||||
return Space.objects.first().message
|
try:
|
||||||
|
if request.space.message:
|
||||||
|
return request.space.message
|
||||||
|
except (AttributeError, KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
|
@ -56,9 +56,9 @@ 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):
|
def test_related_mixed_space(request, recipe, u1_s2, space_2):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
recipe.space = auth.get_user(u1_s2).userpreference.space
|
recipe.space = space_2
|
||||||
recipe.save()
|
recipe.save()
|
||||||
assert len(json.loads(
|
assert len(json.loads(
|
||||||
u1_s2.get(
|
u1_s2.get(
|
||||||
|
@ -204,11 +204,11 @@ def test_shopping_recipe_userpreference(recipe, sle_count, use_mealplan, user2):
|
|||||||
assert len(json.loads(user2.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count[1]
|
assert len(json.loads(user2.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count[1]
|
||||||
|
|
||||||
|
|
||||||
def test_shopping_recipe_mixed_authors(u1_s1, u2_s1):
|
def test_shopping_recipe_mixed_authors(u1_s1, u2_s1,space_1):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
user1 = auth.get_user(u1_s1)
|
user1 = auth.get_user(u1_s1)
|
||||||
user2 = auth.get_user(u2_s1)
|
user2 = auth.get_user(u2_s1)
|
||||||
space = user1.userpreference.space
|
space = space_1
|
||||||
user3 = UserFactory(space=space)
|
user3 = UserFactory(space=space)
|
||||||
recipe1 = RecipeFactory(created_by=user1, space=space)
|
recipe1 = RecipeFactory(created_by=user1, space=space)
|
||||||
recipe2 = RecipeFactory(created_by=user2, space=space)
|
recipe2 = RecipeFactory(created_by=user2, space=space)
|
||||||
|
@ -12,7 +12,7 @@ from recipes.version import VERSION_NUMBER
|
|||||||
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe,
|
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage,
|
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage,
|
||||||
Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UserFile,
|
Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UserFile,
|
||||||
get_model_name, UserSpace)
|
get_model_name, UserSpace, Space)
|
||||||
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
|
from .views.api import CustomAuthToken
|
||||||
|
|
||||||
@ -55,15 +55,11 @@ router.register(r'view-log', api.ViewLogViewSet)
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
path('setup/', views.setup, name='view_setup'),
|
path('setup/', views.setup, name='view_setup'),
|
||||||
path('space/', views.space, name='view_space'),
|
|
||||||
path('space/member/<int:user_id>/<int:space_id>/<slug:group>', views.space_change_member,
|
|
||||||
name='change_space_member'),
|
|
||||||
path('no-group', views.no_groups, name='view_no_group'),
|
path('no-group', views.no_groups, name='view_no_group'),
|
||||||
path('space-overview', views.space_overview, name='view_space_overview'),
|
path('space-overview', views.space_overview, name='view_space_overview'),
|
||||||
path('space-manage/<int:space_id>', views.space_manage, name='view_space_manage'),
|
path('space-manage/<int:space_id>', views.space_manage, name='view_space_manage'),
|
||||||
path('switch-space/<int:space_id>', views.switch_space, name='view_switch_space'),
|
path('switch-space/<int:space_id>', views.switch_space, name='view_switch_space'),
|
||||||
path('no-perm', views.no_perm, name='view_no_perm'),
|
path('no-perm', views.no_perm, name='view_no_perm'),
|
||||||
path('signup/<slug:token>', views.signup, name='view_signup'), # TODO deprecated with 0.16.2 remove at some point
|
|
||||||
path('invite/<slug:token>', views.invite_link, name='view_invite'),
|
path('invite/<slug:token>', views.invite_link, name='view_invite'),
|
||||||
path('system/', views.system, name='view_system'),
|
path('system/', views.system, name='view_system'),
|
||||||
path('search/', views.search, name='view_search'),
|
path('search/', views.search, name='view_search'),
|
||||||
@ -120,6 +116,7 @@ urlpatterns = [
|
|||||||
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),
|
path('api/ingredient-from-string/', api.ingredient_from_string, name='api_ingredient_from_string'),
|
||||||
path('api/share-link/<int:pk>', api.share_link, name='api_share_link'),
|
path('api/share-link/<int:pk>', api.share_link, name='api_share_link'),
|
||||||
path('api/get_facets/', api.get_facets, name='api_get_facets'),
|
path('api/get_facets/', api.get_facets, name='api_get_facets'),
|
||||||
|
path('api/reset-food-inheritance/', api.reset_food_inheritance, name='api_reset_food_inheritance'),
|
||||||
|
|
||||||
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
||||||
# TODO is this deprecated? not yet, some old forms remain, could likely be changed to generic API endpoints
|
# TODO is this deprecated? not yet, some old forms remain, could likely be changed to generic API endpoints
|
||||||
@ -151,7 +148,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
generic_models = (
|
generic_models = (
|
||||||
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
||||||
Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace
|
Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace, Space
|
||||||
)
|
)
|
||||||
|
|
||||||
for m in generic_models:
|
for m in generic_models:
|
||||||
|
@ -2,6 +2,7 @@ import io
|
|||||||
import json
|
import json
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
@ -172,9 +173,9 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
|||||||
|
|
||||||
self.queryset = (
|
self.queryset = (
|
||||||
self.queryset
|
self.queryset
|
||||||
.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
|
.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
|
||||||
default=Value(0))) # put exact matches at the top of the result set
|
default=Value(0))) # put exact matches at the top of the result set
|
||||||
.filter(filter).order_by('-starts', Lower('name').asc())
|
.filter(filter).order_by('-starts', Lower('name').asc())
|
||||||
)
|
)
|
||||||
|
|
||||||
updated_at = self.request.query_params.get('updated_at', None)
|
updated_at = self.request.query_params.get('updated_at', None)
|
||||||
@ -388,6 +389,11 @@ class UserSpaceViewSet(viewsets.ModelViewSet):
|
|||||||
permission_classes = [CustomIsSpaceOwner]
|
permission_classes = [CustomIsSpaceOwner]
|
||||||
http_method_names = ['get', 'patch', 'put', 'delete']
|
http_method_names = ['get', 'patch', 'put', 'delete']
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
if request.space.created_by == UserSpace.objects.get(pk=kwargs['pk']).user:
|
||||||
|
raise APIException('Cannot delete Space owner permission.')
|
||||||
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.queryset.filter(space=self.request.space)
|
return self.queryset.filter(space=self.request.space)
|
||||||
|
|
||||||
@ -1156,6 +1162,22 @@ def recipe_from_source(request):
|
|||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
# @schema(AutoSchema()) #TODO add proper schema
|
||||||
|
@permission_classes([CustomIsAdmin])
|
||||||
|
# TODO add rate limiting
|
||||||
|
def reset_food_inheritance(request):
|
||||||
|
"""
|
||||||
|
function to reset inheritance from api, see food method for docs
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
Food.reset_inheritance(space=request.space)
|
||||||
|
return Response({'message': 'success', }, status=status.HTTP_200_OK)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
def get_recipe_provider(recipe):
|
def get_recipe_provider(recipe):
|
||||||
if recipe.storage.method == Storage.DROPBOX:
|
if recipe.storage.method == Storage.DROPBOX:
|
||||||
return Dropbox
|
return Dropbox
|
||||||
|
@ -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)
|
RecipeImport, Storage, Sync, UserSpace, Space)
|
||||||
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
|
||||||
@ -199,3 +199,19 @@ class UserSpaceDelete(OwnerRequiredMixin, DeleteView):
|
|||||||
context = super(UserSpaceDelete, self).get_context_data(**kwargs)
|
context = super(UserSpaceDelete, self).get_context_data(**kwargs)
|
||||||
context['title'] = _("Space Membership")
|
context['title'] = _("Space Membership")
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class SpaceDelete(OwnerRequiredMixin, DeleteView):
|
||||||
|
template_name = "generic/delete_template.html"
|
||||||
|
model = Space
|
||||||
|
success_url = reverse_lazy('view_space_overview')
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
self.object.safe_delete()
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(SpaceDelete, self).get_context_data(**kwargs)
|
||||||
|
context['title'] = _("Space")
|
||||||
|
return context
|
||||||
|
@ -61,7 +61,7 @@ def search(request):
|
|||||||
if request.user.userpreference.search_style == UserPreference.NEW:
|
if request.user.userpreference.search_style == UserPreference.NEW:
|
||||||
return search_v2(request)
|
return search_v2(request)
|
||||||
f = RecipeFilter(request.GET,
|
f = RecipeFilter(request.GET,
|
||||||
queryset=Recipe.objects.filter(space=request.user.userpreference.space).all().order_by(
|
queryset=Recipe.objects.filter(space=request.space).all().order_by(
|
||||||
Lower('name').asc()),
|
Lower('name').asc()),
|
||||||
space=request.space)
|
space=request.space)
|
||||||
if request.user.userpreference.search_style == UserPreference.LARGE:
|
if request.user.userpreference.search_style == UserPreference.LARGE:
|
||||||
@ -72,7 +72,7 @@ def search(request):
|
|||||||
|
|
||||||
if request.GET == {} and request.user.userpreference.show_recent:
|
if request.GET == {} and request.user.userpreference.show_recent:
|
||||||
qs = Recipe.objects.filter(viewlog__created_by=request.user).filter(
|
qs = Recipe.objects.filter(viewlog__created_by=request.user).filter(
|
||||||
space=request.user.userpreference.space).order_by('-viewlog__created_at').all()
|
space=request.space).order_by('-viewlog__created_at').all()
|
||||||
|
|
||||||
recent_list = []
|
recent_list = []
|
||||||
for r in qs:
|
for r in qs:
|
||||||
@ -117,20 +117,19 @@ def space_overview(request):
|
|||||||
allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING,
|
allow_sharing=settings.SPACE_DEFAULT_ALLOW_SHARING,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_space = UserSpace.objects.create(space=created_space, user=request.user, active=True)
|
user_space = UserSpace.objects.create(space=created_space, user=request.user, active=False)
|
||||||
user_space.groups.add(Group.objects.filter(name='admin').get())
|
user_space.groups.add(Group.objects.filter(name='admin').get())
|
||||||
|
|
||||||
messages.add_message(request, messages.SUCCESS,
|
messages.add_message(request, messages.SUCCESS,
|
||||||
_('You have successfully created your own recipe space. Start by adding some recipes or invite other people to join you.'))
|
_('You have successfully created your own recipe space. Start by adding some recipes or invite other people to join you.'))
|
||||||
return HttpResponseRedirect(reverse('view_switch_space', args=[user_space.pk]))
|
return HttpResponseRedirect(reverse('view_switch_space', args=[user_space.space.pk]))
|
||||||
|
|
||||||
if join_form.is_valid():
|
if join_form.is_valid():
|
||||||
return HttpResponseRedirect(reverse('view_invite', args=[join_form.cleaned_data['token']]))
|
return HttpResponseRedirect(reverse('view_invite', args=[join_form.cleaned_data['token']]))
|
||||||
else:
|
else:
|
||||||
if settings.SOCIAL_DEFAULT_ACCESS:
|
if settings.SOCIAL_DEFAULT_ACCESS:
|
||||||
request.user.userpreference.space = Space.objects.first()
|
user_space = UserSpace.objects.create(space=Space.objects.first(), user=request.user, active=True)
|
||||||
request.user.userpreference.save()
|
user_space.groups.add(Group.objects.filter(name=settings.SOCIAL_DEFAULT_GROUP).get())
|
||||||
request.user.groups.add(Group.objects.get(name=settings.SOCIAL_DEFAULT_GROUP))
|
|
||||||
return HttpResponseRedirect(reverse('index'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
if 'signup_token' in request.session:
|
if 'signup_token' in request.session:
|
||||||
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
return HttpResponseRedirect(reverse('view_invite', args=[request.session.pop('signup_token', '')]))
|
||||||
@ -476,14 +475,6 @@ def setup(request):
|
|||||||
user.set_password(form.cleaned_data['password'])
|
user.set_password(form.cleaned_data['password'])
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
user.groups.add(Group.objects.get(name='admin'))
|
|
||||||
|
|
||||||
user.userpreference.space = Space.objects.first()
|
|
||||||
user.userpreference.save()
|
|
||||||
|
|
||||||
for x in Space.objects.all():
|
|
||||||
x.created_by = user
|
|
||||||
x.save()
|
|
||||||
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
|
messages.add_message(request, messages.SUCCESS, _('User has been created, please login!'))
|
||||||
return HttpResponseRedirect(reverse('account_login'))
|
return HttpResponseRedirect(reverse('account_login'))
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
@ -504,7 +495,7 @@ def invite_link(request, token):
|
|||||||
return HttpResponseRedirect(reverse('index'))
|
return HttpResponseRedirect(reverse('index'))
|
||||||
|
|
||||||
if link := InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, uuid=token).first():
|
if link := InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, uuid=token).first():
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated and not request.user.userspace_set.filter(space=link.space).exists():
|
||||||
link.used_by = request.user
|
link.used_by = request.user
|
||||||
link.save()
|
link.save()
|
||||||
|
|
||||||
@ -526,85 +517,12 @@ def invite_link(request, token):
|
|||||||
return HttpResponseRedirect(reverse('view_space_overview'))
|
return HttpResponseRedirect(reverse('view_space_overview'))
|
||||||
|
|
||||||
|
|
||||||
# TODO deprecated with 0.16.2 remove at some point
|
|
||||||
def signup(request, token):
|
|
||||||
return HttpResponseRedirect(reverse('view_invite', args=[token]))
|
|
||||||
|
|
||||||
|
|
||||||
@group_required('admin')
|
@group_required('admin')
|
||||||
def space_manage(request, space_id):
|
def space_manage(request, space_id):
|
||||||
user_space = get_object_or_404(UserSpace, space=space_id, user=request.user)
|
user_space = get_object_or_404(UserSpace, space=space_id, user=request.user)
|
||||||
switch_user_active_space(request.user, user_space)
|
switch_user_active_space(request.user, user_space)
|
||||||
return render(request, 'space_manage.html', {})
|
return render(request, 'space_manage.html', {})
|
||||||
|
|
||||||
@group_required('admin')
|
|
||||||
def space(request):
|
|
||||||
space_users = UserSpace.objects.filter(space=request.space).all()
|
|
||||||
|
|
||||||
counts = Object()
|
|
||||||
counts.recipes = Recipe.objects.filter(space=request.space).count()
|
|
||||||
counts.keywords = Keyword.objects.filter(space=request.space).count()
|
|
||||||
counts.recipe_import = RecipeImport.objects.filter(space=request.space).count()
|
|
||||||
counts.units = Unit.objects.filter(space=request.space).count()
|
|
||||||
counts.ingredients = Food.objects.filter(space=request.space).count()
|
|
||||||
counts.comments = Comment.objects.filter(recipe__space=request.space).count()
|
|
||||||
|
|
||||||
counts.recipes_internal = Recipe.objects.filter(internal=True, space=request.space).count()
|
|
||||||
counts.recipes_external = counts.recipes - counts.recipes_internal
|
|
||||||
|
|
||||||
counts.recipes_no_keyword = Recipe.objects.filter(keywords=None, space=request.space).count()
|
|
||||||
|
|
||||||
invite_links = InviteLinkTable(
|
|
||||||
InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=request.space).all())
|
|
||||||
RequestConfig(request, paginate={'per_page': 25}).configure(invite_links)
|
|
||||||
|
|
||||||
space_form = SpacePreferenceForm(instance=request.space)
|
|
||||||
|
|
||||||
space_form.base_fields['food_inherit'].queryset = Food.inheritable_fields
|
|
||||||
if request.method == "POST" and 'space_form' in request.POST:
|
|
||||||
form = SpacePreferenceForm(request.POST, prefix='space')
|
|
||||||
if form.is_valid():
|
|
||||||
request.space.food_inherit.set(form.cleaned_data['food_inherit'])
|
|
||||||
request.space.show_facet_count = form.cleaned_data['show_facet_count']
|
|
||||||
request.space.save()
|
|
||||||
if form.cleaned_data['reset_food_inherit']:
|
|
||||||
Food.reset_inheritance(space=request.space)
|
|
||||||
|
|
||||||
return render(request, 'space.html', {
|
|
||||||
'space_users': space_users,
|
|
||||||
'counts': counts,
|
|
||||||
'invite_links': invite_links,
|
|
||||||
'space_form': space_form
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# TODO super hacky and quick solution, safe but needs rework
|
|
||||||
# TODO move group settings to space to prevent permissions from one space to move to another
|
|
||||||
@group_required('admin')
|
|
||||||
def space_change_member(request, user_id, space_id, group):
|
|
||||||
m_space = get_object_or_404(Space, pk=space_id)
|
|
||||||
m_user = get_object_or_404(User, pk=user_id)
|
|
||||||
if request.user == m_space.created_by and m_user != m_space.created_by:
|
|
||||||
if m_user.userpreference.space == m_space:
|
|
||||||
if group == 'admin':
|
|
||||||
m_user.groups.clear()
|
|
||||||
m_user.groups.add(Group.objects.get(name='admin'))
|
|
||||||
return HttpResponseRedirect(reverse('view_space'))
|
|
||||||
if group == 'user':
|
|
||||||
m_user.groups.clear()
|
|
||||||
m_user.groups.add(Group.objects.get(name='user'))
|
|
||||||
return HttpResponseRedirect(reverse('view_space'))
|
|
||||||
if group == 'guest':
|
|
||||||
m_user.groups.clear()
|
|
||||||
m_user.groups.add(Group.objects.get(name='guest'))
|
|
||||||
return HttpResponseRedirect(reverse('view_space'))
|
|
||||||
if group == 'remove':
|
|
||||||
m_user.groups.clear()
|
|
||||||
m_user.userpreference.space = None
|
|
||||||
m_user.userpreference.save()
|
|
||||||
return HttpResponseRedirect(reverse('view_space'))
|
|
||||||
return HttpResponseRedirect(reverse('view_space'))
|
|
||||||
|
|
||||||
|
|
||||||
def report_share_abuse(request, token):
|
def report_share_abuse(request, token):
|
||||||
if not settings.SHARING_ABUSE:
|
if not settings.SHARING_ABUSE:
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div v-if="space !== undefined">
|
<div v-if="space !== undefined">
|
||||||
<h6><i class="fas fa-book"></i> {{ $t('Recipes') }}</h6>
|
<h6><i class="fas fa-book"></i> {{ $t('Recipes') }}</h6>
|
||||||
<b-progress height="1.5rem" :max="space.max_recipes" variant="success" :striped="true">
|
<b-progress height="1.5rem" :max="space.max_recipes" variant="success" :striped="true">
|
||||||
<b-progress-bar :value="space.recipe_count">
|
<b-progress-bar :value="space.recipe_count" class="text-dark font-weight-bold">
|
||||||
{{ space.recipe_count }} /
|
{{ space.recipe_count }} /
|
||||||
<template v-if="space.max_recipes === 0">∞</template>
|
<template v-if="space.max_recipes === 0">∞</template>
|
||||||
<template v-else>{{ space.max_recipes }}</template>
|
<template v-else>{{ space.max_recipes }}</template>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<h6 class="mt-2"><i class="fas fa-users"></i> {{ $t('Users') }}</h6>
|
<h6 class="mt-2"><i class="fas fa-users"></i> {{ $t('Users') }}</h6>
|
||||||
<b-progress height="1.5rem" :max="space.max_users" variant="success" :striped="true">
|
<b-progress height="1.5rem" :max="space.max_users" variant="success" :striped="true">
|
||||||
<b-progress-bar :value="space.user_count">
|
<b-progress-bar :value="space.user_count" class="text-dark font-weight-bold">
|
||||||
{{ space.user_count }} /
|
{{ space.user_count }} /
|
||||||
<template v-if="space.max_users === 0">∞</template>
|
<template v-if="space.max_users === 0">∞</template>
|
||||||
<template v-else>{{ space.max_users }}</template>
|
<template v-else>{{ space.max_users }}</template>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<h6 class="mt-2"><i class="fas fa-file"></i> {{ $t('Files') }}</h6>
|
<h6 class="mt-2"><i class="fas fa-file"></i> {{ $t('Files') }}</h6>
|
||||||
<b-progress height="1.5rem" :max="space.max_file_storage_mb" variant="success" :striped="true">
|
<b-progress height="1.5rem" :max="space.max_file_storage_mb" variant="success" :striped="true">
|
||||||
<b-progress-bar :value="space.file_size_mb">
|
<b-progress-bar :value="space.file_size_mb" class="text-dark font-weight-bold">
|
||||||
{{ space.file_size_mb }} /
|
{{ space.file_size_mb }} /
|
||||||
<template v-if="space.max_file_storage_mb === 0">∞</template>
|
<template v-if="space.max_file_storage_mb === 0">∞</template>
|
||||||
<template v-else>{{ space.max_file_storage_mb }}</template>
|
<template v-else>{{ space.max_file_storage_mb }}</template>
|
||||||
@ -85,7 +85,7 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tr v-for="il in invite_links" :key="il.id">
|
<tr v-for="il in active_invite_links" :key="il.id">
|
||||||
<td>{{ il.id }}</td>
|
<td>{{ il.id }}</td>
|
||||||
<td>{{ il.email }}</td>
|
<td>{{ il.email }}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -134,6 +134,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4" v-if="space !== undefined">
|
||||||
|
<div class="col col-12">
|
||||||
|
<h4 class="mt-2"><i class="fas fa-cogs"></i> {{ $t('Settings') }}</h4>
|
||||||
|
|
||||||
|
<label>{{ $t('Message') }}</label>
|
||||||
|
<b-form-textarea v-model="space.message"></b-form-textarea>
|
||||||
|
|
||||||
|
<b-form-checkbox v-model="space.show_facet_count"> Facet Count</b-form-checkbox>
|
||||||
|
<span class="text-muted small">{{ $t('facet_count_info') }}</span><br/>
|
||||||
|
|
||||||
|
<label>{{ $t('FoodInherit') }}</label>
|
||||||
|
<generic-multiselect :initial_selection="space.food_inherit"
|
||||||
|
:model="Models.FOOD_INHERIT_FIELDS"
|
||||||
|
@change="space.food_inherit = $event.val;">
|
||||||
|
</generic-multiselect>
|
||||||
|
<span class="text-muted small">{{ $t('food_inherit_info') }}</span><br/>
|
||||||
|
|
||||||
|
<a class="btn btn-success" @click="updateSpace()">{{ $t('Update') }}</a><br/>
|
||||||
|
<a class="btn btn-warning mt-1" @click="resetInheritance()">{{ $t('reset_food_inheritance') }}</a><br/>
|
||||||
|
<span class="text-muted small">{{ $t('reset_food_inheritance_info') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col col-12">
|
||||||
|
<h4 class="mt-2"><i class="fas fa-trash"></i> {{ $t('Delete') }}</h4>
|
||||||
|
|
||||||
|
{{ $t('warning_space_delete') }}
|
||||||
|
<br/>
|
||||||
|
<a class="btn btn-danger" :href="resolveDjangoUrl('delete_space', ACTIVE_SPACE_ID)">{{
|
||||||
|
$t('Delete')
|
||||||
|
}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|
||||||
<generic-modal-form :model="Models.INVITE_LINK" :action="Actions.CREATE" :show="show_invite_create"
|
<generic-modal-form :model="Models.INVITE_LINK" :action="Actions.CREATE" :show="show_invite_create"
|
||||||
@finish-action="show_invite_create = false; loadInviteLinks()"/>
|
@finish-action="show_invite_create = false; loadInviteLinks()"/>
|
||||||
|
|
||||||
@ -146,11 +184,12 @@ import {BootstrapVue} from "bootstrap-vue"
|
|||||||
|
|
||||||
import "bootstrap-vue/dist/bootstrap-vue.css"
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
|
||||||
import {ApiMixin, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils"
|
import {ApiMixin, resolveDjangoUrl, ResolveUrlMixin, StandardToasts, ToastMixin} from "@/utils/utils"
|
||||||
|
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api.ts"
|
import {ApiApiFactory} from "@/utils/openapi/api.ts"
|
||||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||||
import GenericModalForm from "@/components/Modals/GenericModalForm";
|
import GenericModalForm from "@/components/Modals/GenericModalForm";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
@ -160,17 +199,23 @@ export default {
|
|||||||
components: {GenericMultiselect, GenericModalForm},
|
components: {GenericMultiselect, GenericModalForm},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
ACTIVE_SPACE_ID: window.ACTIVE_SPACE_ID,
|
||||||
space: undefined,
|
space: undefined,
|
||||||
user_spaces: [],
|
user_spaces: [],
|
||||||
invite_links: [],
|
invite_links: [],
|
||||||
show_invite_create: false
|
show_invite_create: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
active_invite_links: function () {
|
||||||
|
return this.invite_links.filter(il => il.used_by === null)
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
|
|
||||||
let apiFactory = new ApiApiFactory()
|
let apiFactory = new ApiApiFactory()
|
||||||
apiFactory.retrieveSpace(window.ACTIVE_SPACE_ID).then(r => {
|
apiFactory.retrieveSpace(this.ACTIVE_SPACE_ID).then(r => {
|
||||||
this.space = r.data
|
this.space = r.data
|
||||||
})
|
})
|
||||||
apiFactory.listUserSpaces().then(r => {
|
apiFactory.listUserSpaces().then(r => {
|
||||||
@ -192,6 +237,14 @@ export default {
|
|||||||
this.invite_links = r.data
|
this.invite_links = r.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
updateSpace: function () {
|
||||||
|
let apiFactory = new ApiApiFactory()
|
||||||
|
apiFactory.partialUpdateSpace(this.ACTIVE_SPACE_ID, this.space).then(r => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||||
|
}).catch(err => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
|
})
|
||||||
|
},
|
||||||
updateUserSpace: function (userSpace) {
|
updateUserSpace: function (userSpace) {
|
||||||
let apiFactory = new ApiApiFactory()
|
let apiFactory = new ApiApiFactory()
|
||||||
apiFactory.partialUpdateUserSpace(userSpace.id, userSpace).then(r => {
|
apiFactory.partialUpdateUserSpace(userSpace.id, userSpace).then(r => {
|
||||||
@ -219,7 +272,13 @@ export default {
|
|||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
resetInheritance: function () {
|
||||||
|
axios.get(resolveDjangoUrl('api_reset_food_inheritance')).then(r => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||||
|
}).catch(err => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
"success_moving_resource": "Successfully moved a resource!",
|
"success_moving_resource": "Successfully moved a resource!",
|
||||||
"success_merging_resource": "Successfully merged a resource!",
|
"success_merging_resource": "Successfully merged a resource!",
|
||||||
"file_upload_disabled": "File upload is not enabled for your space.",
|
"file_upload_disabled": "File upload is not enabled for your space.",
|
||||||
|
"warning_space_delete": "You can delete your space including all recipes, shopping lists, meal plans and whatever else you have created. This cannot be undone! Are you sure you want to do this ?",
|
||||||
|
"food_inherit_info": "Fields on food that should be inherited by default.",
|
||||||
|
"facet_count_info": "Show recipe counts on search filters.",
|
||||||
"step_time_minutes": "Step time in minutes",
|
"step_time_minutes": "Step time in minutes",
|
||||||
"confirm_delete": "Are you sure you want to delete this {object}?",
|
"confirm_delete": "Are you sure you want to delete this {object}?",
|
||||||
"import_running": "Import running, please wait!",
|
"import_running": "Import running, please wait!",
|
||||||
@ -344,6 +347,7 @@
|
|||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"Website": "Website",
|
"Website": "Website",
|
||||||
"App": "App",
|
"App": "App",
|
||||||
|
"Message": "Message",
|
||||||
"Bookmarklet": "Bookmarklet",
|
"Bookmarklet": "Bookmarklet",
|
||||||
"click_image_import": "Click the image you want to import for this recipe",
|
"click_image_import": "Click the image you want to import for this recipe",
|
||||||
"no_more_images_found": "No additional images found on Website.",
|
"no_more_images_found": "No additional images found on Website.",
|
||||||
@ -355,7 +359,9 @@
|
|||||||
"search_create_help_text": "Create a new recipe directly in Tandoor.",
|
"search_create_help_text": "Create a new recipe directly in Tandoor.",
|
||||||
"warning_duplicate_filter": "Warning: Due to technical limitations having multiple filters of the same combination (and/or/not) might yield unexpected results.",
|
"warning_duplicate_filter": "Warning: Due to technical limitations having multiple filters of the same combination (and/or/not) might yield unexpected results.",
|
||||||
"reset_children": "Reset Child Inheritance",
|
"reset_children": "Reset Child Inheritance",
|
||||||
"reset_children_help": "Overwrite all children with values from inherited fields. Inheritted fields of children will be set to Inherit Fields unless Children Inherit Fields is set.",
|
"reset_children_help": "Overwrite all children with values from inherited fields. Inherited fields of children will be set to Inherit Fields unless Children Inherit Fields is set.",
|
||||||
|
"reset_food_inheritance": "Reset Inheritance",
|
||||||
|
"reset_food_inheritance_info": "Reset all foods to default inherited fields and their parent values.",
|
||||||
"substitute_help": "Substitutes are considered when searching for recipes that can be made with onhand ingredients.",
|
"substitute_help": "Substitutes are considered when searching for recipes that can be made with onhand ingredients.",
|
||||||
"substitute_siblings_help": "All food that share a parent of this food are considered substitutes.",
|
"substitute_siblings_help": "All food that share a parent of this food are considered substitutes.",
|
||||||
"substitute_children_help": "All food that are children of this food are considered substitutes.",
|
"substitute_children_help": "All food that are children of this food are considered substitutes.",
|
||||||
@ -364,7 +370,7 @@
|
|||||||
"SubstituteOnHand": "You have a substitute on hand.",
|
"SubstituteOnHand": "You have a substitute on hand.",
|
||||||
"ChildInheritFields": "Children Inherit Fields",
|
"ChildInheritFields": "Children Inherit Fields",
|
||||||
"ChildInheritFields_help": "Children will inherit these fields by default.",
|
"ChildInheritFields_help": "Children will inherit these fields by default.",
|
||||||
"InheritFields_help": "The values of these fields will be inheritted from parent (Exception: blank shopping categories are not inheritted)",
|
"InheritFields_help": "The values of these fields will be inherited from parent (Exception: blank shopping categories are not inherited)",
|
||||||
"last_viewed": "Last Viewed",
|
"last_viewed": "Last Viewed",
|
||||||
"created_on": "Created On",
|
"created_on": "Created On",
|
||||||
"updatedon": "Updated On",
|
"updatedon": "Updated On",
|
||||||
|
Loading…
Reference in New Issue
Block a user