added sharing links and appropriate tests
This commit is contained in:
parent
17946c8dac
commit
dee7249347
@ -3,10 +3,14 @@ Source: https://djangosnippets.org/snippets/1703/
|
||||
"""
|
||||
from django.contrib import messages
|
||||
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.http import HttpResponseRedirect
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from cookbook.models import ShareLink
|
||||
|
||||
|
||||
def get_allowed_groups(groups_required):
|
||||
groups_allowed = tuple(groups_required)
|
||||
@ -66,3 +70,11 @@ class OwnerRequiredMixin(object):
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
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
|
||||
|
27
cookbook/migrations/0054_sharelink.py
Normal file
27
cookbook/migrations/0054_sharelink.py
Normal 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')),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,5 +1,5 @@
|
||||
import re
|
||||
|
||||
import uuid
|
||||
from annoying.fields import AutoOneToOneField
|
||||
from django.contrib import auth
|
||||
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}'
|
||||
|
||||
|
||||
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):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
@ -53,6 +53,9 @@
|
||||
data-toggle="tooltip"
|
||||
data-placement="top" title="{% trans 'Export recipe' %}"><i
|
||||
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>
|
||||
|
||||
|
@ -40,6 +40,8 @@ def markdown(value):
|
||||
|
||||
@register.simple_tag
|
||||
def recipe_rating(recipe, user):
|
||||
if not user.is_authenticated:
|
||||
return ''
|
||||
rating = recipe.cooklog_set.filter(created_by=user).aggregate(Avg('rating'))
|
||||
if rating['rating__avg']:
|
||||
|
||||
@ -57,6 +59,8 @@ def recipe_rating(recipe, user):
|
||||
|
||||
@register.simple_tag
|
||||
def recipe_last(recipe, user):
|
||||
if not user.is_authenticated:
|
||||
return ''
|
||||
last = recipe.cooklog_set.filter(created_by=user).last()
|
||||
if last:
|
||||
return last.created_at
|
||||
|
@ -11,6 +11,7 @@ class TestBase(TestCase):
|
||||
guest_client_1 = None
|
||||
guest_client_2 = None
|
||||
superuser_client = None
|
||||
anonymous_client = None
|
||||
|
||||
def create_login_user(self, name, group):
|
||||
client = Client()
|
||||
|
44
cookbook/tests/views/test_views_recipe_share.py
Normal file
44
cookbook/tests/views/test_views_recipe_share.py
Normal 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)
|
@ -30,8 +30,10 @@ urlpatterns = [
|
||||
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>/<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/internal/<int:pk>/', edit.internal_recipe_update, name='edit_internal_recipe'), # for internal use only
|
||||
|
@ -3,7 +3,7 @@ from datetime import datetime
|
||||
|
||||
from django.contrib import messages
|
||||
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.utils.translation import gettext as _
|
||||
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, \
|
||||
RecipeBookForm, MealPlanForm
|
||||
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):
|
||||
@ -36,6 +36,13 @@ class RecipeCreate(GroupRequiredMixin, CreateView):
|
||||
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):
|
||||
groups_required = ['user']
|
||||
template_name = "generic/new_template.html"
|
||||
|
@ -18,7 +18,7 @@ from django.conf import settings
|
||||
|
||||
from cookbook.filters import RecipeFilter
|
||||
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 recipes.version import *
|
||||
@ -70,9 +70,13 @@ def search(request):
|
||||
return render(request, 'index.html')
|
||||
|
||||
|
||||
@group_required('guest')
|
||||
def recipe_view(request, pk):
|
||||
def recipe_view(request, pk, share=None):
|
||||
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)
|
||||
comments = Comment.objects.filter(recipe=recipe)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user