Merge remote-tracking branch 'origin/develop' into feature/50-Linktypes
This commit is contained in:
commit
4dbd8ae82d
1
Pipfile
1
Pipfile
@ -13,6 +13,7 @@ twine = "*"
|
|||||||
pandoc = "*"
|
pandoc = "*"
|
||||||
pylint-django = "*"
|
pylint-django = "*"
|
||||||
setuptools = "*"
|
setuptools = "*"
|
||||||
|
django-nose = "*"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
django = "*"
|
django = "*"
|
||||||
|
@ -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 = [
|
||||||
|
@ -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},
|
||||||
},
|
},
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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': {
|
||||||
|
Loading…
Reference in New Issue
Block a user