Merge remote-tracking branch 'origin/develop' into feature/50-Linktypes
This commit is contained in:
		
							
								
								
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							@@ -13,6 +13,7 @@ twine = "*"
 | 
			
		||||
pandoc = "*"
 | 
			
		||||
pylint-django = "*"
 | 
			
		||||
setuptools = "*"
 | 
			
		||||
django-nose = "*"
 | 
			
		||||
 | 
			
		||||
[packages]
 | 
			
		||||
django = "*"
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,15 @@ INSTALLED_APPS = [
 | 
			
		||||
    'django.contrib.contenttypes',
 | 
			
		||||
    'django.contrib.sessions',
 | 
			
		||||
    'django.contrib.messages',
 | 
			
		||||
    'django.contrib.staticfiles'
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    'django_nose'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
 | 
			
		||||
 | 
			
		||||
NOSE_ARGS = [
 | 
			
		||||
    '--with-coverage',
 | 
			
		||||
    '--cover-package=lostplaces',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ from django.conf import settings
 | 
			
		||||
 | 
			
		||||
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},
 | 
			
		||||
        'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False},
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -4,4 +4,5 @@
 | 
			
		||||
from django.apps import AppConfig
 | 
			
		||||
 | 
			
		||||
class LostplacesAppConfig(AppConfig):
 | 
			
		||||
    default_auto_field = 'django.db.models.AutoField'
 | 
			
		||||
    name = 'lostplaces'
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,18 @@ class Explorer(models.Model):
 | 
			
		||||
        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):
 | 
			
		||||
        return self.user.username
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,36 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        user = User.objects.create_user(
 | 
			
		||||
            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'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
            place = Place.objects.create(
 | 
			
		||||
                name='Im a place %d' % i,
 | 
			
		||||
@@ -48,7 +75,8 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
                location='Test %d town' % i,
 | 
			
		||||
                latitude=50.5 + 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.save()
 | 
			
		||||
@@ -90,6 +118,42 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
            ),
 | 
			
		||||
            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):
 | 
			
		||||
    view = PlaceCreateView
 | 
			
		||||
@@ -308,6 +372,8 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
            username='testpeter',
 | 
			
		||||
            password='Develop123'
 | 
			
		||||
        )
 | 
			
		||||
        user.explorer.level = 3
 | 
			
		||||
        user.explorer.save()
 | 
			
		||||
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
@@ -316,11 +382,72 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
            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.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):
 | 
			
		||||
        response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
 | 
			
		||||
        self.assertHttpRedirect(response)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.generic.edit import CreateView
 | 
			
		||||
from django.views.generic.detail import SingleObjectMixin
 | 
			
		||||
from django.views.generic import ListView
 | 
			
		||||
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
 | 
			
		||||
@@ -26,7 +27,8 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
 | 
			
		||||
    permission_denied_message = _('Please login to proceed')
 | 
			
		||||
 | 
			
		||||
    def handle_no_permission(self):
 | 
			
		||||
        messages.error(self.request, self.permission_denied_message)
 | 
			
		||||
        if not self.request.user.is_authenticated:
 | 
			
		||||
            messages.error(self.request, self.permission_denied_message)
 | 
			
		||||
        return super().handle_no_permission()
 | 
			
		||||
 | 
			
		||||
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
 | 
			
		||||
@@ -60,6 +62,23 @@ class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
 | 
			
		||||
            messages.error(self.request, self.place_submitter_error_message)
 | 
			
		||||
        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):
 | 
			
		||||
    """
 | 
			
		||||
    Abstract View for creating a place asset (i.e. PlaceImage) 
 | 
			
		||||
@@ -113,3 +132,10 @@ class PlaceAssetDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SingleOb
 | 
			
		||||
        self.get_object().delete()
 | 
			
		||||
        messages.success(self.request, self.success_message)
 | 
			
		||||
        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,7 +18,10 @@ class MultiplePlaceImageUploadMixin:
 | 
			
		||||
                    submitted_by=submitted_by
 | 
			
		||||
                )
 | 
			
		||||
                place_image.save()
 | 
			
		||||
            
 | 
			
		||||
            if place.hero is None:
 | 
			
		||||
                place.hero = place.placeimages.all()[0]
 | 
			
		||||
                place.save()
 | 
			
		||||
 | 
			
		||||
class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView):
 | 
			
		||||
    model = PlaceImage
 | 
			
		||||
    form_class = PlaceImageForm
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ from django.db.models.functions import Lower
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
 | 
			
		||||
from django.views.generic.detail import SingleObjectMixin
 | 
			
		||||
from django.views.generic import ListView
 | 
			
		||||
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
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 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.forms import PlaceForm, PlaceImageForm, TagSubmitForm
 | 
			
		||||
from lostplaces.common import redirect_referer_or
 | 
			
		||||
 | 
			
		||||
from taggit.models import Tag
 | 
			
		||||
 | 
			
		||||
class PlaceListView(IsAuthenticatedMixin, ListView):
 | 
			
		||||
class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
 | 
			
		||||
    paginate_by = 5
 | 
			
		||||
    model = Place
 | 
			
		||||
    template_name = 'place/place_list.html'
 | 
			
		||||
    ordering = [Lower('name')]
 | 
			
		||||
 | 
			
		||||
@@ -37,9 +40,15 @@ class PlaceListView(IsAuthenticatedMixin, ListView):
 | 
			
		||||
        }
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
class PlaceDetailView(IsAuthenticatedMixin, View):
 | 
			
		||||
    def get(self, request, pk):
 | 
			
		||||
        place = get_object_or_404(Place, pk=pk)
 | 
			
		||||
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):        
 | 
			
		||||
        place = self.get_place()
 | 
			
		||||
 | 
			
		||||
        context = {
 | 
			
		||||
            'place': place,
 | 
			
		||||
            'mapping_config': {
 | 
			
		||||
@@ -129,10 +138,14 @@ class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
 | 
			
		||||
    def get_place(self):
 | 
			
		||||
        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):
 | 
			
		||||
        place = get_object_or_404(Place, id=place_id)
 | 
			
		||||
        place = self.get_place()
 | 
			
		||||
        if request.user is not None:
 | 
			
		||||
            request.user.explorer.favorite_places.add(place)
 | 
			
		||||
            request.user.explorer.save()
 | 
			
		||||
@@ -140,7 +153,7 @@ class PlaceFavoriteView(IsAuthenticatedMixin, View):
 | 
			
		||||
        return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
 | 
			
		||||
            
 | 
			
		||||
class PlaceUnfavoriteView(IsAuthenticatedMixin, View):
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def get(self, request, place_id):
 | 
			
		||||
        place = get_object_or_404(Place, id=place_id)
 | 
			
		||||
        if request.user is not None:
 | 
			
		||||
@@ -149,10 +162,14 @@ class PlaceUnfavoriteView(IsAuthenticatedMixin, View):
 | 
			
		||||
 | 
			
		||||
        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):
 | 
			
		||||
        place = get_object_or_404(Place, id=place_id)
 | 
			
		||||
        place = self.get_place()
 | 
			
		||||
        if request.user is not None:
 | 
			
		||||
            request.user.explorer.visited_places.add(place)
 | 
			
		||||
            request.user.explorer.save()
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ class SignUpView(SuccessMessageMixin, CreateView):
 | 
			
		||||
 | 
			
		||||
class HomeView(IsAuthenticatedMixin, View):
 | 
			
		||||
    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 = {
 | 
			
		||||
            'place_list': place_list,
 | 
			
		||||
            'mapping_config': {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user