added sharing links and appropriate tests

This commit is contained in:
vabene1111 2020-06-16 11:23:58 +02:00
parent 17946c8dac
commit dee7249347
10 changed files with 118 additions and 7 deletions

View File

@ -3,10 +3,14 @@ Source: https://djangosnippets.org/snippets/1703/
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from cookbook.models import ShareLink
def get_allowed_groups(groups_required): def get_allowed_groups(groups_required):
groups_allowed = tuple(groups_required) groups_allowed = tuple(groups_required)
@ -66,3 +70,11 @@ class OwnerRequiredMixin(object):
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs) return super(OwnerRequiredMixin, self).dispatch(request, *args, **kwargs)
def share_link_valid(recipe, share):
print(share, recipe)
try:
return True if ShareLink.objects.filter(recipe=recipe, uuid=share).exists() else False
except ValidationError:
return False

View File

@ -0,0 +1,27 @@
# Generated by Django 3.0.7 on 2020-06-16 08:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0053_auto_20200611_2217'),
]
operations = [
migrations.CreateModel(
name='ShareLink',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('uuid', models.UUIDField(default=uuid.UUID('dbbf5150-0795-4305-b9bd-3952dfa2264b'))),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
],
),
]

View File

@ -1,5 +1,5 @@
import re import re
import uuid
from annoying.fields import AutoOneToOneField from annoying.fields import AutoOneToOneField
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -242,6 +242,13 @@ class MealPlan(models.Model):
return f'{self.get_label()} - {self.date} - {self.meal_type.name}' return f'{self.get_label()} - {self.date} - {self.meal_type.name}'
class ShareLink(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
uuid = models.UUIDField(default=uuid.uuid4())
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class CookLog(models.Model): class CookLog(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_by = models.ForeignKey(User, on_delete=models.CASCADE)

View File

@ -53,6 +53,9 @@
data-toggle="tooltip" data-toggle="tooltip"
data-placement="top" title="{% trans 'Export recipe' %}"><i data-placement="top" title="{% trans 'Export recipe' %}"><i
class="fas fa-file-export"></i></a> class="fas fa-file-export"></i></a>
<a class="btn btn-primary" href="{% url 'new_share_link' recipe.pk %}" target="_blank" rel="noopener noreferrer"
data-toggle="tooltip"
data-placement="top" title="{% trans 'Create share link' %}"><i class="fas fa-share-alt"></i></a>
</div> </div>
</div> </div>

View File

@ -40,6 +40,8 @@ def markdown(value):
@register.simple_tag @register.simple_tag
def recipe_rating(recipe, user): def recipe_rating(recipe, user):
if not user.is_authenticated:
return ''
rating = recipe.cooklog_set.filter(created_by=user).aggregate(Avg('rating')) rating = recipe.cooklog_set.filter(created_by=user).aggregate(Avg('rating'))
if rating['rating__avg']: if rating['rating__avg']:
@ -57,6 +59,8 @@ def recipe_rating(recipe, user):
@register.simple_tag @register.simple_tag
def recipe_last(recipe, user): def recipe_last(recipe, user):
if not user.is_authenticated:
return ''
last = recipe.cooklog_set.filter(created_by=user).last() last = recipe.cooklog_set.filter(created_by=user).last()
if last: if last:
return last.created_at return last.created_at

View File

@ -11,6 +11,7 @@ class TestBase(TestCase):
guest_client_1 = None guest_client_1 = None
guest_client_2 = None guest_client_2 = None
superuser_client = None superuser_client = None
anonymous_client = None
def create_login_user(self, name, group): def create_login_user(self, name, group):
client = Client() client = Client()

View File

@ -0,0 +1,44 @@
import uuid
from django.contrib import auth
from django.urls import reverse
from cookbook.helper.permission_helper import share_link_valid
from cookbook.models import Recipe, ShareLink
from cookbook.tests.views.test_views import TestViews
class TestViewsGeneral(TestViews):
def test_share(self):
internal_recipe = Recipe.objects.create(
name='Test',
internal=True,
created_by=auth.get_user(self.user_client_1)
)
url = reverse('view_recipe', kwargs={'pk': internal_recipe.pk})
r = self.user_client_1.get(url)
self.assertEqual(r.status_code, 200)
r = self.anonymous_client.get(url)
self.assertEqual(r.status_code, 302)
url = reverse('new_share_link', kwargs={'pk': internal_recipe.pk})
r = self.user_client_1.get(url)
self.assertEqual(r.status_code, 302)
share = ShareLink.objects.filter(recipe=internal_recipe).first()
self.assertIsNotNone(share)
self.assertTrue(share_link_valid(internal_recipe, share.uuid))
url = reverse('view_recipe', kwargs={'pk': internal_recipe.pk, 'share': share.uuid})
r = self.anonymous_client.get(url)
self.assertEqual(r.status_code, 200)
url = reverse('view_recipe', kwargs={'pk': (internal_recipe.pk + 1), 'share': share.uuid})
r = self.anonymous_client.get(url)
self.assertEqual(r.status_code, 404)
url = reverse('view_recipe', kwargs={'pk': internal_recipe.pk, 'share': uuid.uuid4()})
r = self.anonymous_client.get(url)
self.assertEqual(r.status_code, 302)

View File

@ -30,8 +30,10 @@ urlpatterns = [
path('export/', import_export.export_recipe, name='view_export'), path('export/', import_export.export_recipe, name='view_export'),
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'), path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
path('view/recipe/<int:pk>/<slug:share>', views.recipe_view, name='view_recipe'),
path('new/recipe_import/<int:import_id>/', new.create_new_external_recipe, name='new_recipe_import'), path('new/recipe-import/<int:import_id>/', new.create_new_external_recipe, name='new_recipe_import'),
path('new/share-link/<int:pk>/', new.share_link, name='new_share_link'),
path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'), path('edit/recipe/<int:pk>/', edit.switch_recipe, name='edit_recipe'),
path('edit/recipe/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'), # for internal use only path('edit/recipe/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'), # for internal use only

View File

@ -3,7 +3,7 @@ from datetime import datetime
from django.contrib import messages from django.contrib import messages
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import render, redirect from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import CreateView from django.views.generic import CreateView
@ -11,7 +11,7 @@ from django.views.generic import CreateView
from cookbook.forms import ImportRecipeForm, RecipeImport, KeywordForm, Storage, StorageForm, InternalRecipeForm, \ from cookbook.forms import ImportRecipeForm, RecipeImport, KeywordForm, Storage, StorageForm, InternalRecipeForm, \
RecipeBookForm, MealPlanForm RecipeBookForm, MealPlanForm
from cookbook.helper.permission_helper import GroupRequiredMixin, group_required from cookbook.helper.permission_helper import GroupRequiredMixin, group_required
from cookbook.models import Keyword, Recipe, RecipeBook, MealPlan from cookbook.models import Keyword, Recipe, RecipeBook, MealPlan, ShareLink
class RecipeCreate(GroupRequiredMixin, CreateView): class RecipeCreate(GroupRequiredMixin, CreateView):
@ -36,6 +36,13 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
return context return context
@group_required('user')
def share_link(request, pk):
recipe = get_object_or_404(Recipe, pk=pk)
link = ShareLink.objects.create(recipe=recipe, created_by=request.user)
return HttpResponseRedirect(reverse('view_recipe', kwargs={'pk': pk, 'share': link.uuid}))
class KeywordCreate(GroupRequiredMixin, CreateView): class KeywordCreate(GroupRequiredMixin, CreateView):
groups_required = ['user'] groups_required = ['user']
template_name = "generic/new_template.html" template_name = "generic/new_template.html"

View File

@ -18,7 +18,7 @@ from django.conf import settings
from cookbook.filters import RecipeFilter from cookbook.filters import RecipeFilter
from cookbook.forms import * from cookbook.forms import *
from cookbook.helper.permission_helper import group_required from cookbook.helper.permission_helper import group_required, share_link_valid
from cookbook.tables import RecipeTable, RecipeTableSmall, CookLogTable, ViewLogTable from cookbook.tables import RecipeTable, RecipeTableSmall, CookLogTable, ViewLogTable
from recipes.version import * from recipes.version import *
@ -70,9 +70,13 @@ def search(request):
return render(request, 'index.html') return render(request, 'index.html')
@group_required('guest') def recipe_view(request, pk, share=None):
def recipe_view(request, pk):
recipe = get_object_or_404(Recipe, pk=pk) recipe = get_object_or_404(Recipe, pk=pk)
if not request.user.is_authenticated and not share_link_valid(recipe, share):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('index'))
ingredients = RecipeIngredient.objects.filter(recipe=recipe) ingredients = RecipeIngredient.objects.filter(recipe=recipe)
comments = Comment.objects.filter(recipe=recipe) comments = Comment.objects.filter(recipe=recipe)