mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-11 09:07:12 -05:00
lots of fixes and stuff
This commit is contained in:
@@ -32,41 +32,7 @@ admin.site.unregister(Group)
|
||||
@admin.action(description='Delete all data from a space')
|
||||
def delete_space_action(modeladmin, request, queryset):
|
||||
for space in queryset:
|
||||
CookLog.objects.filter(space=space).delete()
|
||||
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()
|
||||
space.save()
|
||||
|
||||
|
||||
class SpaceAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -760,6 +760,6 @@ def old_search(request):
|
||||
params = dict(request.GET)
|
||||
params['internal'] = None
|
||||
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)
|
||||
return f.qs
|
||||
|
||||
@@ -4,11 +4,12 @@ from django.db import migrations
|
||||
|
||||
|
||||
def create_default_space(apps, schema_editor):
|
||||
Space = apps.get_model('cookbook', 'Space')
|
||||
Space.objects.create(
|
||||
name='Default',
|
||||
message=''
|
||||
)
|
||||
# Space = apps.get_model('cookbook', 'Space')
|
||||
# Space.objects.create(
|
||||
# name='Default',
|
||||
# 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):
|
||||
|
||||
@@ -240,7 +240,49 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
demo = models.BooleanField(default=False)
|
||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
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):
|
||||
return self.created_by
|
||||
|
||||
@@ -551,14 +593,14 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
tree_filter = Q(space=space)
|
||||
|
||||
# remove all inherited fields from food
|
||||
Through = Food.objects.filter(tree_filter).first().inherit_fields.through
|
||||
Through.objects.all().delete()
|
||||
trough = Food.objects.filter(tree_filter).first().inherit_fields.through
|
||||
trough.objects.all().delete()
|
||||
# food is going to inherit attributes
|
||||
if len(inherit) > 0:
|
||||
# ManyToMany cannot be updated through an UPDATE operation
|
||||
for i in inherit:
|
||||
Through.objects.bulk_create([
|
||||
Through(food_id=x, foodinheritfield_id=i['id'])
|
||||
trough.objects.bulk_create([
|
||||
trough(food_id=x, foodinheritfield_id=i['id'])
|
||||
for x in Food.objects.filter(tree_filter).values_list('id', flat=True)
|
||||
])
|
||||
|
||||
|
||||
@@ -151,10 +151,27 @@ class GroupSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
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')
|
||||
recipe_count = serializers.SerializerMethodField('get_recipe_count')
|
||||
file_size_mb = serializers.SerializerMethodField('get_file_size_mb')
|
||||
food_inherit = FoodInheritFieldSerializer(many=True)
|
||||
|
||||
def get_user_count(self, obj):
|
||||
return UserSpace.objects.filter(space=obj).count()
|
||||
@@ -174,13 +191,18 @@ class SpaceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
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',)
|
||||
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):
|
||||
user = UserNameSerializer(read_only=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):
|
||||
raise ValidationError('Cannot create using this endpoint')
|
||||
|
||||
@@ -209,24 +231,6 @@ class MealTypeSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
||||
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):
|
||||
food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', many=True, allow_null=True,
|
||||
required=False, read_only=True)
|
||||
@@ -1073,7 +1077,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
|
||||
class BookmarkletImportListSerializer(serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
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)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -2,6 +2,7 @@ from decimal import Decimal
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.postgres.search import SearchVector
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
@@ -11,7 +12,7 @@ from django_scopes import scope
|
||||
from cookbook.helper.shopping_helper import RecipeShoppingEditor
|
||||
from cookbook.managers import DICTIONARY
|
||||
from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe,
|
||||
ShoppingListEntry, Step)
|
||||
ShoppingListEntry, Step, UserPreference)
|
||||
|
||||
SQLITE = True
|
||||
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'):
|
||||
return None
|
||||
return signal_func(sender, instance, **kwargs)
|
||||
|
||||
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)
|
||||
@skip_signal
|
||||
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")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@
|
||||
<a class="dropdown-item" href="{% url 'data_sync' %}"><i
|
||||
class="fas fa-sync-alt fa-fw"></i> {% trans 'External Recipes' %}</a>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
{% if user.is_superuser %}
|
||||
@@ -316,7 +316,7 @@
|
||||
{% endif %}
|
||||
{{ us.space.name }}</a>
|
||||
{% 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 %}
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
|
||||
@@ -344,7 +344,7 @@
|
||||
</div>
|
||||
</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 %}
|
||||
<div class="bg-success" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||
{{ message_of_the_day }}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<nav aria-label="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>
|
||||
</nav>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="col col-12">
|
||||
<nav aria-label="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>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "No Space" %}{% endblock %}
|
||||
{% block title %}{% trans "Overview" %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
@@ -36,6 +36,11 @@
|
||||
<h5 class="card-title"><a
|
||||
href="{% url 'view_switch_space' us.space.id %}">{{ us.space.name }}</a>
|
||||
</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
|
||||
class="text-muted">{% trans 'Owner' %}: {{ us.space.created_by }}</small>
|
||||
{% if us.space.created_by != us.user %}
|
||||
|
||||
@@ -111,8 +111,12 @@ def page_help(page_name):
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def message_of_the_day():
|
||||
return Space.objects.first().message
|
||||
def message_of_the_day(request):
|
||||
try:
|
||||
if request.space.message:
|
||||
return request.space.message
|
||||
except (AttributeError, KeyError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@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}, 'steps__recipe_count': 1}), # shopping list from recipe with StepRecipe and food 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():
|
||||
recipe.space = auth.get_user(u1_s2).userpreference.space
|
||||
recipe.space = space_2
|
||||
recipe.save()
|
||||
assert len(json.loads(
|
||||
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]
|
||||
|
||||
|
||||
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():
|
||||
user1 = auth.get_user(u1_s1)
|
||||
user2 = auth.get_user(u2_s1)
|
||||
space = user1.userpreference.space
|
||||
space = space_1
|
||||
user3 = UserFactory(space=space)
|
||||
recipe1 = RecipeFactory(created_by=user1, 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,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage,
|
||||
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.api import CustomAuthToken
|
||||
|
||||
@@ -55,15 +55,11 @@ router.register(r'view-log', api.ViewLogViewSet)
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
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('space-overview', views.space_overview, name='view_space_overview'),
|
||||
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('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('system/', views.system, name='view_system'),
|
||||
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/share-link/<int:pk>', api.share_link, name='api_share_link'),
|
||||
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'),
|
||||
# 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 = (
|
||||
Recipe, RecipeImport, Storage, RecipeBook, MealPlan, SyncLog, Sync,
|
||||
Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace
|
||||
Comment, RecipeBookEntry, ShoppingList, InviteLink, UserSpace, Space
|
||||
)
|
||||
|
||||
for m in generic_models:
|
||||
|
||||
@@ -2,6 +2,7 @@ import io
|
||||
import json
|
||||
import mimetypes
|
||||
import re
|
||||
import traceback
|
||||
import uuid
|
||||
from collections import OrderedDict
|
||||
|
||||
@@ -172,9 +173,9 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
||||
|
||||
self.queryset = (
|
||||
self.queryset
|
||||
.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
|
||||
default=Value(0))) # put exact matches at the top of the result set
|
||||
.filter(filter).order_by('-starts', Lower('name').asc())
|
||||
.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
|
||||
default=Value(0))) # put exact matches at the top of the result set
|
||||
.filter(filter).order_by('-starts', Lower('name').asc())
|
||||
)
|
||||
|
||||
updated_at = self.request.query_params.get('updated_at', None)
|
||||
@@ -388,6 +389,11 @@ class UserSpaceViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [CustomIsSpaceOwner]
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
@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):
|
||||
if recipe.storage.method == Storage.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.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.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@@ -199,3 +199,19 @@ class UserSpaceDelete(OwnerRequiredMixin, DeleteView):
|
||||
context = super(UserSpaceDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Space Membership")
|
||||
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:
|
||||
return search_v2(request)
|
||||
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()),
|
||||
space=request.space)
|
||||
if request.user.userpreference.search_style == UserPreference.LARGE:
|
||||
@@ -72,7 +72,7 @@ def search(request):
|
||||
|
||||
if request.GET == {} and request.user.userpreference.show_recent:
|
||||
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 = []
|
||||
for r in qs:
|
||||
@@ -117,20 +117,19 @@ def space_overview(request):
|
||||
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())
|
||||
|
||||
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.'))
|
||||
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():
|
||||
return HttpResponseRedirect(reverse('view_invite', args=[join_form.cleaned_data['token']]))
|
||||
else:
|
||||
if settings.SOCIAL_DEFAULT_ACCESS:
|
||||
request.user.userpreference.space = Space.objects.first()
|
||||
request.user.userpreference.save()
|
||||
request.user.groups.add(Group.objects.get(name=settings.SOCIAL_DEFAULT_GROUP))
|
||||
user_space = UserSpace.objects.create(space=Space.objects.first(), user=request.user, active=True)
|
||||
user_space.groups.add(Group.objects.filter(name=settings.SOCIAL_DEFAULT_GROUP).get())
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
if 'signup_token' in request.session:
|
||||
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.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!'))
|
||||
return HttpResponseRedirect(reverse('account_login'))
|
||||
except ValidationError as e:
|
||||
@@ -504,7 +495,7 @@ def invite_link(request, token):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
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.save()
|
||||
|
||||
@@ -526,85 +517,12 @@ def invite_link(request, token):
|
||||
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')
|
||||
def space_manage(request, space_id):
|
||||
user_space = get_object_or_404(UserSpace, space=space_id, user=request.user)
|
||||
switch_user_active_space(request.user, user_space)
|
||||
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):
|
||||
if not settings.SHARING_ABUSE:
|
||||
|
||||
Reference in New Issue
Block a user