Merge remote-tracking branch 'origin/develop' into feature/50-Linktypes

This commit is contained in:
Marcus Scholz 2021-10-02 02:47:42 +02:00
commit 4dbd8ae82d
10 changed files with 216 additions and 21 deletions

View File

@ -13,6 +13,7 @@ twine = "*"
pandoc = "*" pandoc = "*"
pylint-django = "*" pylint-django = "*"
setuptools = "*" setuptools = "*"
django-nose = "*"
[packages] [packages]
django = "*" django = "*"

View File

@ -49,7 +49,15 @@ INSTALLED_APPS = [
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles' 'django.contrib.staticfiles',
'django_nose'
]
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'--with-coverage',
'--cover-package=lostplaces',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -5,7 +5,7 @@ from django.conf import settings
settings.THUMBNAIL_ALIASES = { settings.THUMBNAIL_ALIASES = {
'': { '': {
'thumbnail': {'size': (300, 200), 'sharpen': True, 'crop': True}, 'thumbnail': {'size': (300, 200), 'sharpen': True, 'crop': True, 'upscale': True},
'hero': {'size': (700, 466), 'sharpen': True, 'crop': True}, 'hero': {'size': (700, 466), 'sharpen': True, 'crop': True},
'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False}, 'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False},
}, },

View File

@ -4,4 +4,5 @@
from django.apps import AppConfig from django.apps import AppConfig
class LostplacesAppConfig(AppConfig): class LostplacesAppConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField'
name = 'lostplaces' name = 'lostplaces'

View File

@ -82,6 +82,18 @@ class Explorer(models.Model):
choices=EXPLORER_LEVELS choices=EXPLORER_LEVELS
) )
def get_places_eligible_to_see(self):
if self.user.is_superuser:
return Place.objects.all()
return Place.objects.all().filter(level__lte=self.level) | self.places.all()
def is_eligible_to_see(self, place):
return (
self.user.is_superuser or
place.submitted_by == self or
place in self.get_places_eligible_to_see()
)
def __str__(self): def __str__(self):
return self.user.username return self.user.username

View File

@ -37,9 +37,36 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
def setUpTestData(cls): def setUpTestData(cls):
user = User.objects.create_user( user = User.objects.create_user(
username='testpeter', username='testpeter',
password='Develop123',
)
user.explorer.level = 3
user.explorer.save()
# default level should be 1, not setting required
other_user = User.objects.create_user(
username='blubberbernd',
password='Develop123' password='Develop123'
) )
superuser = User.objects.create_user(
username='toor',
password='Develop123'
)
superuser.is_superuser = True
superuser.save()
Place.objects.create(
name='Im a own place',
submitted_when=timezone.now(),
submitted_by=other_user.explorer,
location='Test %d town' % 5,
latitude=50.5 + 5/10,
longitude=7.0 - 5/10,
description='This is just a test, do not worry %d' % 5,
level=3
)
for i in range(12): for i in range(12):
place = Place.objects.create( place = Place.objects.create(
name='Im a place %d' % i, name='Im a place %d' % i,
@ -48,7 +75,8 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
location='Test %d town' % i, location='Test %d town' % i,
latitude=50.5 + i/10, latitude=50.5 + i/10,
longitude=7.0 - i/10, longitude=7.0 - i/10,
description='This is just a test, do not worry %d' % i description='This is just a test, do not worry %d' % i,
level=3
) )
place.tags.add('I a tag', 'testlocation') place.tags.add('I a tag', 'testlocation')
place.save() place.save()
@ -91,6 +119,42 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
msg='Expecting the place list to be paginated like [first] [previous] [item] at least 2 times [next] [last]' msg='Expecting the place list to be paginated like [first] [previous] [item] at least 2 times [next] [last]'
) )
def test_not_eligible_to_see_because_of_low_level(self):
self.client.login(username='blubberbernd', password='Develop123')
response = self.client.get(reverse('place_list'))
self.assertFalse(
'Im a place' in response.content.decode(),
msg='Expecting the user to not see any places'
)
def test_not_eligible_to_see_because_of_low_level_superuser(self):
self.client.login(username='toor', password='Develop123')
response = self.client.get(reverse('place_list'))
self.assertTrue(
'Im a place' in response.content.decode(),
msg='Expecting the superuser to see all places'
)
def test_not_eligible_to_see_because_of_low_level_own_place(self):
self.client.login(username='blubberbernd', password='Develop123')
response = self.client.get(reverse('place_list'))
self.assertTrue(
'Im a own place' in response.content.decode(),
msg='Expecting the user to see it\'s own places'
)
def test_eligible_to_see(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse('place_list'))
self.assertTrue(
'Im a own place' in response.content.decode(),
msg='Expecting the user to see places where their level is high enough'
)
class TestPlaceCreateView(ViewTestCase): class TestPlaceCreateView(ViewTestCase):
view = PlaceCreateView view = PlaceCreateView
@ -308,6 +372,8 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
username='testpeter', username='testpeter',
password='Develop123' password='Develop123'
) )
user.explorer.level = 3
user.explorer.save()
place = Place.objects.create( place = Place.objects.create(
name='Im a place', name='Im a place',
@ -316,11 +382,72 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
location='Testtown', location='Testtown',
latitude=50.5, latitude=50.5,
longitude=7.0, longitude=7.0,
description='This is just a test, do not worry' description='This is just a test, do not worry',
level=3
) )
place.tags.add('I a tag', 'testlocation') place.tags.add('I a tag', 'testlocation')
place.save() place.save()
other_user = User.objects.create_user(
username='blubberbernd',
password='Develop123'
)
superuser = User.objects.create_user(
username='toor',
password='Develop123'
)
superuser.is_superuser = True
superuser.save()
Place.objects.create(
name='Im a own place',
submitted_when=timezone.now(),
submitted_by=other_user.explorer,
location='Test %d town' % 5,
latitude=50.5 + 5/10,
longitude=7.0 - 5/10,
description='This is just a test, do not worry %d' % 5,
level=3
)
def test_not_eligible_to_see_because_of_low_level(self):
self.client.login(username='blubberbernd', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
self.assertFalse(
'Im a place' in response.content.decode(),
msg='Expecting the user to not see the places'
)
def test_not_eligible_to_see_because_of_low_level_superuser(self):
self.client.login(username='toor', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
self.assertTrue(
'Im a place' in response.content.decode(),
msg='Expecting the superuser to see all places'
)
def test_not_eligible_to_see_because_of_low_level_own_place(self):
self.client.login(username='blubberbernd', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
self.assertTrue(
'Im a own place' in response.content.decode(),
msg='Expecting the user to see it\'s own places'
)
def test_eligible_to_see(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
self.assertTrue(
'Im a own place' in response.content.decode(),
msg='Expecting the user to see places where their level is high enough'
)
def test_not_authenticated(self): def test_not_authenticated(self):
response = self.client.get(reverse('place_detail', kwargs={'pk': 1})) response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
self.assertHttpRedirect(response) self.assertHttpRedirect(response)

View File

@ -4,6 +4,7 @@
from django.views import View from django.views import View
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
@ -26,6 +27,7 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
permission_denied_message = _('Please login to proceed') permission_denied_message = _('Please login to proceed')
def handle_no_permission(self): def handle_no_permission(self):
if not self.request.user.is_authenticated:
messages.error(self.request, self.permission_denied_message) messages.error(self.request, self.permission_denied_message)
return super().handle_no_permission() return super().handle_no_permission()
@ -60,6 +62,23 @@ class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
messages.error(self.request, self.place_submitter_error_message) messages.error(self.request, self.place_submitter_error_message)
return False return False
class IsEligibleToSeePlaceMixin(UserPassesTestMixin):
not_eligible_to_see_message = None
def get_place(self):
pass
def test_func(self):
if not hasattr(self.request, 'user'):
return False
if self.request.user.explorer.is_eligible_to_see(self.get_place()):
return True
if self.not_eligible_to_see_message:
messages.error(self.request, self.not_eligible_to_see_message)
return False
class PlaceAssetCreateView(IsAuthenticatedMixin, SuccessMessageMixin, CreateView): class PlaceAssetCreateView(IsAuthenticatedMixin, SuccessMessageMixin, CreateView):
""" """
Abstract View for creating a place asset (i.e. PlaceImage) Abstract View for creating a place asset (i.e. PlaceImage)
@ -113,3 +132,10 @@ class PlaceAssetDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SingleOb
self.get_object().delete() self.get_object().delete()
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place_id})) return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place_id}))
class LevelCapPlaceListView(ListView):
model = Place
def get_queryset(self):
return self.request.user.explorer.get_places_eligible_to_see()

View File

@ -18,6 +18,9 @@ class MultiplePlaceImageUploadMixin:
submitted_by=submitted_by submitted_by=submitted_by
) )
place_image.save() place_image.save()
if place.hero is None:
place.hero = place.placeimages.all()[0]
place.save()
class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView): class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView):
model = PlaceImage model = PlaceImage

View File

@ -6,7 +6,6 @@ from django.db.models.functions import Lower
from django.views import View from django.views import View
from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
@ -16,16 +15,20 @@ 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 lostplaces.models import Place, PlaceImage from lostplaces.models import Place, PlaceImage
from lostplaces.views.base_views import IsAuthenticatedMixin, IsPlaceSubmitterMixin from lostplaces.views.base_views import (
IsAuthenticatedMixin,
IsPlaceSubmitterMixin,
LevelCapPlaceListView,
IsEligibleToSeePlaceMixin
)
from lostplaces.views.place_image_views import MultiplePlaceImageUploadMixin from lostplaces.views.place_image_views import MultiplePlaceImageUploadMixin
from lostplaces.forms import PlaceForm, PlaceImageForm, TagSubmitForm from lostplaces.forms import PlaceForm, PlaceImageForm, TagSubmitForm
from lostplaces.common import redirect_referer_or from lostplaces.common import redirect_referer_or
from taggit.models import Tag from taggit.models import Tag
class PlaceListView(IsAuthenticatedMixin, ListView): class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
paginate_by = 5 paginate_by = 5
model = Place
template_name = 'place/place_list.html' template_name = 'place/place_list.html'
ordering = [Lower('name')] ordering = [Lower('name')]
@ -37,9 +40,15 @@ class PlaceListView(IsAuthenticatedMixin, ListView):
} }
return context return context
class PlaceDetailView(IsAuthenticatedMixin, View): class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
not_eligible_to_see_message = _('You\'r not allowed to see this place')
def get_place(self):
return get_object_or_404(Place, pk=self.kwargs['pk'])
def get(self, request, pk): def get(self, request, pk):
place = get_object_or_404(Place, pk=pk) place = self.get_place()
context = { context = {
'place': place, 'place': place,
'mapping_config': { 'mapping_config': {
@ -129,10 +138,14 @@ class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
def get_place(self): def get_place(self):
return self.get_object() return self.get_object()
class PlaceFavoriteView(IsAuthenticatedMixin, View): class PlaceFavoriteView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
not_eligible_to_see_message = _('You\'r not allowed to favorite this place')
def get_place(self):
return get_object_or_404(Place, pk=self.kwargs['place_id'])
def get(self, request, place_id): def get(self, request, place_id):
place = get_object_or_404(Place, id=place_id) place = self.get_place()
if request.user is not None: if request.user is not None:
request.user.explorer.favorite_places.add(place) request.user.explorer.favorite_places.add(place)
request.user.explorer.save() request.user.explorer.save()
@ -149,10 +162,14 @@ class PlaceUnfavoriteView(IsAuthenticatedMixin, View):
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk})) return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
class PlaceVisitCreateView(IsAuthenticatedMixin, View): class PlaceVisitCreateView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
not_eligible_to_see_message = _('You\'r not allowed to visit this place :P (Now please stop trying out URL\'s)')
def get_place(self):
return get_object_or_404(Place, pk=self.kwargs['place_id'])
def get(self, request, place_id): def get(self, request, place_id):
place = get_object_or_404(Place, id=place_id) place = self.get_place()
if request.user is not None: if request.user is not None:
request.user.explorer.visited_places.add(place) request.user.explorer.visited_places.add(place)
request.user.explorer.save() request.user.explorer.save()

View File

@ -31,7 +31,7 @@ class SignUpView(SuccessMessageMixin, CreateView):
class HomeView(IsAuthenticatedMixin, View): class HomeView(IsAuthenticatedMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
place_list = Place.objects.all().order_by('-submitted_when')[:10] place_list = request.user.explorer.get_places_eligible_to_see()
context = { context = {
'place_list': place_list, 'place_list': place_list,
'mapping_config': { 'mapping_config': {