11 Commits

Author SHA1 Message Date
Leonhard Strohmidel
724c26c926 #64 TDD: Tests for draft view 2022-09-25 16:14:32 +02:00
Leonhard Strohmidel
06d68380c9 Testing explorer views 2022-09-25 16:14:11 +02:00
Leonhard Strohmidel
f06b6bdae9 Fixing PlaceImage tests 2022-09-25 16:13:19 +02:00
Leonhard Strohmidel
7a7c06882a #64 Testing place modes 2022-09-25 16:12:14 +02:00
Leonhard Strohmidel
c9d83dfc2c #64 place mode and filtering of place modes in list views 2022-09-24 12:10:19 +02:00
Leonhard Strohmidel
49301afe51 Adapting django version 2022-09-20 12:00:27 +02:00
Leonhard Strohmidel
624878624f Testing place level and level acccuracy 2022-09-20 12:00:09 +02:00
Leonhard Strohmidel
a2ee323fa4 Removing Expireable from PlaceVote 2022-09-20 11:59:51 +02:00
Leonhard Strohmidel
86c9de3213 Fixing average latlon function 2022-09-20 11:58:20 +02:00
Leonhard Strohmidel
8597e53599 #63 Replacing vote expiration with vote accuarcy 2022-09-20 09:56:23 +02:00
Leonhard Strohmidel
d213b51a59 Fixing Typo 2022-09-20 09:53:00 +02:00
18 changed files with 634 additions and 53 deletions

View File

@@ -1,6 +1,6 @@
# lostplaces-backend # lostplaces-backend
lostplaces-backend is a django (3.x) based webproject. It once wants to become a software which allows a group of urban explorers to manage, document and share the locations of lost places while not exposing too much / any information to the public. lostplaces-backend is a django (4.x) based webproject. It once wants to become a software which allows a group of urban explorers to manage, document and share the locations of lost places while not exposing too much / any information to the public.
The software is currently in early development status, neither scope, datamodel(s) nor features are finalized yet. Therefore we would not recommend to download or install this piece of software anywhere - except your local django dev server. The software is currently in early development status, neither scope, datamodel(s) nor features are finalized yet. Therefore we would not recommend to download or install this piece of software anywhere - except your local django dev server.

View File

@@ -69,7 +69,7 @@ class PlaceForm(forms.ModelForm):
class Meta: class Meta:
model = Place model = Place
fields = '__all__' fields = '__all__'
exclude = ['submitted_by', 'level'] exclude = ['submitted_by', 'level', 'mode']
widgets = { widgets = {
'hero': widgets.SelectContent() 'hero': widgets.SelectContent()
} }
@@ -88,6 +88,11 @@ class PlaceForm(forms.ModelForm):
widget=forms.NumberInput(attrs={'min':-180,'max': 180,'type': 'number', 'step': 'any'}) widget=forms.NumberInput(attrs={'min':-180,'max': 180,'type': 'number', 'step': 'any'})
) )
draft = forms.BooleanField(
label=_('Save Place as draft'),
required=False
)
class PlaceImageForm(forms.ModelForm): class PlaceImageForm(forms.ModelForm):
class Meta: class Meta:
model = PlaceImage model = PlaceImage

View File

@@ -82,9 +82,25 @@ class Explorer(models.Model):
choices=EXPLORER_LEVELS choices=EXPLORER_LEVELS
) )
def get_place_list_to_display(self):
'''
Gets the list of places to show on the homepage
and the list views
'''
if self.user.is_superuser:
return Place.objects.filter(mode='live')
return Place.objects.filter(
level__lte=self.level,
mode='live'
) | Place.objects.filter(
submitted_by=self,
mode='live'
)
def get_places_eligible_to_see(self): def get_places_eligible_to_see(self):
if self.user.is_superuser: if self.user.is_superuser:
return Place.objects.all() return Place.objects.filter(mode='live')
return Place.objects.all().filter(level__lte=self.level) | self.places.all() return Place.objects.all().filter(level__lte=self.level) | self.places.all()
def is_eligible_to_see(self, place): def is_eligible_to_see(self, place):

View File

@@ -1,11 +1,13 @@
import os import os
from math import floor import datetime
from math import ceil
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_delete, pre_save from django.db.models.signals import post_delete, pre_save
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.utils import timezone
from django.conf import settings from django.conf import settings
from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable
@@ -21,6 +23,13 @@ PLACE_LEVELS = (
(5, 'Time Capsule') (5, 'Time Capsule')
) )
PLACE_MODES = (
('live', 'live'),
('draft', 'draft'),
('review', 'review'),
('archive', 'archive')
)
class Place(Submittable, Taggable, Mapable): class Place(Submittable, Taggable, Mapable):
""" """
Place defines a lost place (location, name, description etc.). Place defines a lost place (location, name, description etc.).
@@ -48,6 +57,12 @@ class Place(Submittable, Taggable, Mapable):
choices=PLACE_LEVELS choices=PLACE_LEVELS
) )
mode = models.TextField(
default='live',
choices=PLACE_MODES,
verbose_name=_('Mode of Place Editing')
)
def get_hero_image(self): def get_hero_image(self):
if self.hero: if self.hero:
return self.hero return self.hero
@@ -76,24 +91,22 @@ class Place(Submittable, Taggable, Mapable):
# Get center position of LP-geocoordinates. # Get center position of LP-geocoordinates.
def average_latlon(cls, place_list): def average_latlon(cls, place_list):
amount = len(place_list) amount = len(place_list)
# Init fill values to prevent None
# China Corner in Münster
# Where I almost always eat lunch
# (Does'nt help losing wheight, tho)
longitude = 7.6295628132604385
latitude = 51.961922091398904
if amount > 0: if amount > 0:
latitude = 0
longitude = 0
for place in place_list: for place in place_list:
longitude += place.longitude longitude += place.longitude
latitude += place.latitude latitude += place.latitude
return {'latitude':latitude / amount, 'longitude': longitude / amount} return {'latitude': latitude / amount, 'longitude': longitude / amount}
else:
return {'latitude': latitude, 'longitude': longitude} # Location of China Corner in Münster
# Where I almost always eat lunch
# (Does'nt help losing wheight, tho)
return {'latitude': 51.961922091398904, 'longitude': 7.6295628132604385}
def calculate_place_level(self): def calculate_place_level(self):
self.remove_expired_votes()
if self.placevotings.count() == 0: if self.placevotings.count() == 0:
self.level = 5 self.level = 5
self.save() self.save()
@@ -104,13 +117,22 @@ class Place(Submittable, Taggable, Mapable):
for vote in self.placevotings.all(): for vote in self.placevotings.all():
level += vote.vote level += vote.vote
self.level = floor(level / self.placevotings.count()) self.level = round(level / self.placevotings.count())
self.save() self.save()
def remove_expired_votes(self): def calculate_voting_accuracy(self):
place_age = timezone.now() - self.submitted_when;
accuaries = [];
for vote in self.placevotings.all(): for vote in self.placevotings.all():
if vote.is_expired: vote_age = timezone.now() - vote.submitted_when;
vote.delete() accuracy = 100 - (100 / (place_age / vote_age))
accuaries.append(accuracy)
if len(accuaries) > 0:
return ceil(sum(accuaries) / len(accuaries))
else:
return 0
def __str__(self): def __str__(self):
return self.name return self.name
@@ -210,7 +232,7 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
old_file.delete(save=False) old_file.delete(save=False)
class PlaceVoting(PlaceAsset, Expireable): class PlaceVoting(PlaceAsset):
vote = models.IntegerField(choices=PLACE_LEVELS) vote = models.IntegerField(choices=PLACE_LEVELS)
def get_human_readable_level(self): def get_human_readable_level(self):

View File

@@ -14,7 +14,7 @@
<div class="LP-PlaceTeaser__Meta"> <div class="LP-PlaceTeaser__Meta">
<div class="LP-PlaceTeaser__Info"> <div class="LP-PlaceTeaser__Info">
<span class="LP-PlaceTeaser__Title"> <span class="LP-PlaceTeaser__Title">
<h1 class="LP-Headline LP-Headline--teaser">{{place.name|truncatechars:19}}</h1> <h1 class="LP-Headline LP-Headline--teaser">{{place.name}}</h1>
</span> </span>
<span class="LP-PlaceTeaser__Detail"> <span class="LP-PlaceTeaser__Detail">
<p class="LP-Paragraph">{{place.location|truncatechars:25}}</p> <p class="LP-Paragraph">{{place.location|truncatechars:25}}</p>

View File

@@ -28,12 +28,7 @@
</div> </div>
<div class="LP-Voting__Expiration"> <div class="LP-Voting__Expiration">
<span class="LP-Voting__InfoLabel">Your vote expires on</span> The accuracy of the voting is {{voting.accuracy}}%
<span class="LP-Voting__Date">
<time datetime="{{voting.expires_when|date:'Y-m-d'}}">
{{voting.users_vote.expires_when|date:'d.m.Y'}}
</time>
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -39,8 +39,10 @@
</div> </div>
</div> </div>
{% translate 'Create' as action %} {% translate 'Create' as action %}
<div class="LP-Form__Composition LP-Form__Composition--buttons"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
{% include 'partials/form/inputField.html' with field=place_form.draft %}
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %} {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
</div> </div>
</fieldset> </fieldset>

View File

@@ -23,7 +23,15 @@
{% block maincontent %} {% block maincontent %}
<article class="LP-PlaceDetail"> <article class="LP-PlaceDetail">
<header class="LP-PlaceDetail__Header"> <header class="LP-PlaceDetail__Header">
<h1 class="LP-Headline">{{ place.name }} {% include 'partials/icons/place_favorite.html' %} {% include 'partials/icons/place_visited.html' %}</h1> <h1 class="LP-Headline">
{{ place.name }}
{% include 'partials/icons/place_favorite.html' %}
{% include 'partials/icons/place_visited.html' %}
{% if user.is_superuser %}
<a class="LP-Link" href="{{'/admin/lostplaces/place/'|addstr:place.id }}" target="_blank"><span class="LP-Link__Text">{% translate 'view place in admin panel' %}</span></a>
{% endif %}
</h1>
{% if place.get_hero_image %} {% if place.get_hero_image %}
<div class="LP-PlaceDetail__Image"> <div class="LP-PlaceDetail__Image">
{% include '../partials/image.html' with source_url=place.get_hero_image.filename.hero.url link_url="#image"|addstr:place.get_hero_index_in_queryset %} {% include '../partials/image.html' with source_url=place.get_hero_image.filename.hero.url link_url="#image"|addstr:place.get_hero_index_in_queryset %}

View File

@@ -14,7 +14,7 @@
</div> </div>
<div class="LP-Form__Composition LP-Form__Composition--buttons"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER %} {% include 'partials/form/submit.html' with referer=request.META.HTTP_REFERER %}
</div> </div>
</fieldset> </fieldset>

View File

@@ -42,21 +42,25 @@ class PlaceImageTestCase(ModelTestCase):
if not os.path.isdir(settings.MEDIA_ROOT): if not os.path.isdir(settings.MEDIA_ROOT):
os.mkdir(settings.MEDIA_ROOT) os.mkdir(settings.MEDIA_ROOT)
images_dir = os.path.join(
settings.MEDIA_ROOT,
settings.RELATIVE_THUMBNAIL_PATH,
)
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
if not os.path.isfile(os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg')): if not os.path.isfile(os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg')):
shutil.copyfile( shutil.copyfile(
os.path.join(current_dir, 'im_a_image.jpeg'), os.path.join(current_dir, 'im_a_image.jpeg'),
os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg') os.path.join(images_dir, 'im_a_image_copy.jpeg')
) )
shutil.copyfile( shutil.copyfile(
os.path.join(current_dir, 'im_a_image.jpeg'), os.path.join(current_dir, 'im_a_image.jpeg'),
os.path.join(settings.MEDIA_ROOT, 'im_a_image_changed.jpeg') os.path.join(images_dir, 'im_a_image_changed.jpeg')
) )
PlaceImage.objects.create( PlaceImage.objects.create(
description='Im a description', description='Im a description',
filename=os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg'), filename=os.path.join(images_dir, 'im_a_image_copy.jpeg'),
place=place, place=place,
submitted_when=timezone.now(), submitted_when=timezone.now(),
submitted_by=user.explorer submitted_by=user.explorer

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import datetime
from django.test import TestCase from django.test import TestCase
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils import timezone from django.utils import timezone
from lostplaces.models import Place from lostplaces.models import Place, PlaceVoting
from lostplaces.tests.models import ModelTestCase from lostplaces.tests.models import ModelTestCase
class PlaceTestCase(ModelTestCase): class PlaceTestCase(ModelTestCase):
@@ -106,12 +107,12 @@ class PlaceTestCase(ModelTestCase):
an empty list an empty list
''' '''
avg_latlon = Place.average_latlon([]) avg_latlon = Place.average_latlon([])
self.assertEqual(avg_latlon['latitude'], 0, self.assertEqual(avg_latlon['latitude'], 51.961922091398904,
msg='%s: (no places) average latitude missmatch' % ( msg='%s: (no places) average latitude missmatch' % (
self.model.__name__ self.model.__name__
) )
) )
self.assertEqual(avg_latlon['longitude'], 0, self.assertEqual(avg_latlon['longitude'], 7.6295628132604385,
msg='%s: (no places) average longitude missmatch' % ( msg='%s: (no places) average longitude missmatch' % (
self.model.__name__ self.model.__name__
) )
@@ -124,3 +125,169 @@ class PlaceTestCase(ModelTestCase):
self.model.__name__ self.model.__name__
) )
) )
def test_level_calculation(self):
explorer = self.place.submitted_by
PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote=5
)
PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote=2
)
PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote=4
)
self.place.calculate_place_level()
self.assertEqual(
4,
self.place.level,
msg='Expecting the place level to be 4'
)
def test_level_calculation_no_votes(self):
self.place.calculate_place_level()
self.assertEqual(
5,
self.place.level,
msg='Expecting the default place level to be 5'
)
def test_level_mid_accuracy(self):
explorer = self.place.submitted_by
six_month_ago = datetime.timedelta(days=180)
self.place.submitted_when = timezone.now() - six_month_ago
votings = [
{
'date': timezone.now() - datetime.timedelta(days=170),
'vote': 5
},
{
'date': timezone.now() - datetime.timedelta(days=23),
'vote': 2
},
{
'date': timezone.now() - datetime.timedelta(days=1),
'vote': 4
}
]
for vote in votings:
voting = PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote= vote['vote']
)
voting.submitted_when = vote['date']
voting.save()
self.assertEqual(
65,
self.place.calculate_voting_accuracy()
)
def test_level_high_accuracy(self):
explorer = self.place.submitted_by
six_month_ago = datetime.timedelta(days=180)
self.place.submitted_when = timezone.now() - six_month_ago
votings = [
{
'date': timezone.now() - datetime.timedelta(days=9),
'vote': 5
},
{
'date': timezone.now() - datetime.timedelta(days=14),
'vote': 2
},
{
'date': timezone.now() - datetime.timedelta(days=0),
'vote': 4
}
]
for vote in votings:
voting = PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote= vote['vote']
)
voting.submitted_when = vote['date']
voting.save()
self.assertEqual(
96,
self.place.calculate_voting_accuracy()
)
def test_level_low_accuracy(self):
explorer = self.place.submitted_by
six_month_ago = datetime.timedelta(days=180)
self.place.submitted_when = timezone.now() - six_month_ago
votings = [
{
'date': timezone.now() - datetime.timedelta(days=177),
'vote': 5
},
{
'date': timezone.now() - datetime.timedelta(days=150),
'vote': 2
},
{
'date': timezone.now() - datetime.timedelta(days=100),
'vote': 4
}
]
for vote in votings:
voting = PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote= vote['vote']
)
voting.submitted_when = vote['date']
voting.save()
self.assertEqual(
21,
self.place.calculate_voting_accuracy()
)
def test_level_accuracy_zero_timedelta(self):
explorer = self.place.submitted_by
six_month_ago = datetime.timedelta(days=180)
self.place.submitted_when = timezone.now() - six_month_ago
votings = [
{
'date': timezone.now() - datetime.timedelta(days=0),
'vote': 4
}
]
for vote in votings:
voting = PlaceVoting.objects.create(
submitted_by=explorer,
place=self.place,
vote= vote['vote']
)
voting.submitted_when = vote['date']
voting.save()
self.assertEqual(
100,
self.place.calculate_voting_accuracy(),
msg='Expecting the accurcy to be 100% when the vote is 0 time units old'
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -46,6 +46,10 @@ class TestIsAuthenticated(ViewTestCase):
self.assertHttpRedirect(response, '?'.join([str(reverse_lazy('login')), 'next=/'])) self.assertHttpRedirect(response, '?'.join([str(reverse_lazy('login')), 'next=/']))
self.assertTrue(len(messages) > 0) self.assertTrue(len(messages) > 0)
self.assertTrue(
_('Please login to proceed') in response.content.decode(),
msg='Expecting a message to tell the user to login'
)
class TestIsPlaceSubmitterMixin(TestCase): class TestIsPlaceSubmitterMixin(TestCase):

View File

@@ -0,0 +1,272 @@
import os
import shutil
from django.utils.translation import gettext as _
from django.utils import timezone
from django.contrib.auth.models import User
from django.conf import settings
from django.urls import reverse
from lostplaces.tests.views import (
ViewTestCase,
GlobalTemplateTestCaseMixin
)
from lostplaces.views import ExplorerProfileView
from lostplaces.models import(
Place,
PlaceImage,
PhotoAlbum
)
class TestExplorerProfileView(GlobalTemplateTestCaseMixin, ViewTestCase):
view = ExplorerProfileView
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
def test_unauth_profile_access(self):
user = User.objects.get(username='testpeter')
response = self.client.get(
reverse('explorer_profile', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpCode(response, 302)
self.assertFalse(
user.username in response.content.decode(),
msg='Expecting the username to not be visible to unauthorized users'
)
def test_unauth_profile_access_follow_redirect(self):
user = User.objects.get(username='testpeter')
response = self.client.get(
reverse('explorer_profile', kwargs={
'explorer_id': user.explorer.id
}),
follow=True
)
self.assertHttpOK(response)
self.assertTrue(
_('Please login to proceed') in response.content.decode(),
msg='Expecting a message to tell the user to login'
)
def test_explorer_places(self):
user = User.objects.get(username='testpeter')
Place.objects.create(
name='Im the latest place 4369',
submitted_when=timezone.now(),
submitted_by=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
)
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(
reverse('explorer_profile', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpOK(response)
self.assertTrue(
'Im the latest place 4369' in response.content.decode(),
msg='Expecting the latest place to be visible on the submitters profile page'
)
def test_explorer_image(self):
user = User.objects.get(username='testpeter')
place = Place.objects.create(
name='Im a the latest place 4369',
submitted_when=timezone.now(),
submitted_by=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
)
current_dir = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(
settings.MEDIA_ROOT,
settings.RELATIVE_THUMBNAIL_PATH,
'im_a_image_3649.jpeg'
)
shutil.copyfile(
os.path.join(current_dir, 'im_a_image.jpeg'),
file_path
)
PlaceImage.objects.create(
description='Im a description',
filename=file_path,
place=place,
submitted_when=timezone.now(),
submitted_by=user.explorer
)
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(
reverse('explorer_profile', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpOK(response)
self.assertTrue(
os.path.join(settings.RELATIVE_THUMBNAIL_PATH,'im_a_image_3649.jpeg') in response.content.decode(),
msg='Expecting the latest place image to be visible on the submitters profile page'
)
def test_explorer_photoalbum(self):
user = User.objects.get(username='testpeter')
place = Place.objects.create(
name='Im a the latest place 4369',
submitted_when=timezone.now(),
submitted_by=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
)
PhotoAlbum.objects.create(
place=place,
submitted_by=user.explorer,
url='http://example.org/6897134',
label='Im a exmpale link label 6423'
)
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(
reverse('explorer_profile', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpOK(response)
self.assertTrue(
'href="http://example.org/6897134"' in response.content.decode(),
msg='Expecting the latest photoalbum url to be linked on the submitters profile page'
)
self.assertTrue(
'Im a exmpale link label 6423' in response.content.decode(),
msg='Expecting the latest photoalbum label to be on the submitters profile page'
)
class TestExplorerDraftsView(GlobalTemplateTestCaseMixin, ViewTestCase):
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
User.objects.create_user(
username='otheruser',
password='Develop123'
)
superuser = User.objects.create_user(
username='toor',
password='Develop123'
)
superuser.is_superuser = True
superuser.save()
def test_draft_view(self):
user = User.objects.get(username='testpeter')
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(
reverse('explorer_drafts', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpOK(response)
def test_draft_view_unauthorized_user(self):
user = User.objects.get(username='testpeter')
self.client.login(username='otheruser', password='Develop123')
response = self.client.get(
reverse('explorer_drafts', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpForbidden(response)
def test_draft_view_superuser(self):
user = User.objects.get(username='testpeter')
self.client.login(username='toor', password='Develop123')
response = self.client.get(
reverse('explorer_drafts', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertHttpOK(response)
def test_place_in_draft_view(self):
user = User.objects.get(username='testpeter')
Place.objects.create(
name='Im a draft place 3792',
submitted_when=timezone.now(),
submitted_by=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,
mode='draft',
level=3
)
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(
reverse('explorer_drafts', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertTrue(
'Im a draft place 3792' in response.content.decode(),
msg='Expecting a place draft to be visible in the submitters drafs view'
)
def test_place_not_in_draft_view(self):
user = User.objects.get(username='testpeter')
Place.objects.create(
name='Im a draft place 3819',
submitted_when=timezone.now(),
submitted_by=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
)
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(
reverse('explorer_drafts', kwargs={
'explorer_id': user.explorer.id
})
)
self.assertFalse(
'Im a draft place 3819' in response.content.decode(),
msg='Expecting a live place to not be visible in the submitters drafs view'
)

View File

@@ -13,6 +13,7 @@ from django.utils.translation import gettext as _
from lostplaces.models import Place from lostplaces.models import Place
from lostplaces.models import PLACE_MODES
from lostplaces.views import ( from lostplaces.views import (
PlaceCreateView, PlaceCreateView,
PlaceListView, PlaceListView,
@@ -155,6 +156,38 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
msg='Expecting the user to see places where their level is high enough' msg='Expecting the user to see places where their level is high enough'
) )
def test_place_mode_filter(self):
explorer = User.objects.get(username='testpeter').explorer
Place.objects.all().delete()
for mode in PLACE_MODES:
place = Place.objects.create(
name='Im a place in mode %s' % mode[0],
submitted_when=timezone.now(),
submitted_by=explorer,
location='Test town',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry %s' % mode[0],
level=3,
mode=mode[0]
)
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse('place_list'))
for mode in PLACE_MODES:
if ('Im a place in mode %s' % mode[0]) in response.content.decode():
self.assertTrue(
mode[0] == 'live',
msg='Expecting only places in mode \'live\' to be listed, saw a place in mode %s' % mode[0]
)
elif mode[0] == 'live':
self.fail(
msg='Expecting at least one place in mode \'live\' to be listed'
)
class TestPlaceCreateView(ViewTestCase): class TestPlaceCreateView(ViewTestCase):
view = PlaceCreateView view = PlaceCreateView
@@ -209,6 +242,11 @@ class TestPlaceCreateView(ViewTestCase):
'success', 'success',
msg='Expecting a visible success message' msg='Expecting a visible success message'
) )
self.assertEqual(
Place.objects.get(name='test place 486').mode,
'live',
msg='Expeting the place to be in \'live\' mode'
)
def test_positive_image(self): def test_positive_image(self):
self.client.login(username='testpeter', password='Develop123') self.client.login(username='testpeter', password='Develop123')
@@ -258,6 +296,11 @@ class TestPlaceCreateView(ViewTestCase):
'success', 'success',
msg='Expecting a visible success message' msg='Expecting a visible success message'
) )
self.assertEqual(
Place.objects.get(name='test place 894').mode,
'live',
msg='Expeting the place to be in \'live\' mode'
)
def test_negative_no_name(self): def test_negative_no_name(self):
self.client.login(username='testpeter', password='Develop123') self.client.login(username='testpeter', password='Develop123')
@@ -363,6 +406,31 @@ class TestPlaceCreateView(ViewTestCase):
msg='Expecing a visible error message' msg='Expecing a visible error message'
) )
def test_positve_save_as_draft(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.post(
reverse('place_create'),
{
'name': 'test name 6483',
'location': 'wurstwasser',
'latitude': 45.4654,
'longitude': 68.135489,
'description': """
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
""",
'draft': True
},
follow=True
)
self.assertHttpOK(response)
self.assertEqual(
Place.objects.get(name='test name 6483').mode,
'draft',
msg='Expeting the place to be in \'draft\' mode'
)
class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixin, ViewTestCase): class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixin, ViewTestCase):
view = PlaceDetailView view = PlaceDetailView
@@ -416,6 +484,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
self.client.login(username='blubberbernd', password='Develop123') self.client.login(username='blubberbernd', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 1})) response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
self.assertHttpForbidden(response)
self.assertFalse( self.assertFalse(
'Im a place' in response.content.decode(), 'Im a place' in response.content.decode(),
msg='Expecting the user to not see the places' msg='Expecting the user to not see the places'
@@ -425,6 +494,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
self.client.login(username='toor', password='Develop123') self.client.login(username='toor', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 1})) response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
self.assertHttpOK(response)
self.assertTrue( self.assertTrue(
'Im a place' in response.content.decode(), 'Im a place' in response.content.decode(),
msg='Expecting the superuser to see all places' msg='Expecting the superuser to see all places'
@@ -434,6 +504,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
self.client.login(username='blubberbernd', password='Develop123') self.client.login(username='blubberbernd', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 2})) response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
self.assertHttpOK(response)
self.assertTrue( self.assertTrue(
'Im a own place' in response.content.decode(), 'Im a own place' in response.content.decode(),
msg='Expecting the user to see it\'s own places' msg='Expecting the user to see it\'s own places'
@@ -443,6 +514,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
self.client.login(username='testpeter', password='Develop123') self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 2})) response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
self.assertHttpOK(response)
self.assertTrue( self.assertTrue(
'Im a own place' in response.content.decode(), 'Im a own place' in response.content.decode(),
msg='Expecting the user to see places where their level is high enough' msg='Expecting the user to see places where their level is high enough'

View File

@@ -138,4 +138,10 @@ class LevelCapPlaceListView(ListView):
model = Place model = Place
def get_queryset(self): def get_queryset(self):
return self.request.user.explorer.get_places_eligible_to_see() return self.request.user.explorer.get_place_list_to_display()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place_list'] = context.pop('object_list')
return context

View File

@@ -74,7 +74,8 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
}, },
'placevoting': { 'placevoting': {
'users_vote': PlaceVoting.objects.filter(place=place, submitted_by=explorer).first(), 'users_vote': PlaceVoting.objects.filter(place=place, submitted_by=explorer).first(),
'all_choices': reversed(PLACE_LEVELS) 'all_choices': reversed(PLACE_LEVELS),
'accuracy': place.calculate_voting_accuracy()
} }
} }
return render(request, 'place/place_detail.html', context) return render(request, 'place/place_detail.html', context)
@@ -112,6 +113,9 @@ class PlaceCreateView(MultiplePlaceImageUploadMixin, IsAuthenticatedMixin, View)
place = place_form.save(commit=False) place = place_form.save(commit=False)
# Save logged in user as "submitted_by" # Save logged in user as "submitted_by"
place.submitted_by = submitter place.submitted_by = submitter
if place_form.cleaned_data['draft']:
place.mode = 'draft';
place.save() place.save()
self.handle_place_images(request, place) self.handle_place_images(request, place)
@@ -204,8 +208,11 @@ class PlaceVisitDeleteView(IsAuthenticatedMixin, View):
class PlaceVoteView(IsEligibleToSeePlaceMixin, View): class PlaceVoteView(IsEligibleToSeePlaceMixin, View):
delta = timedelta(weeks=24) delta = timedelta(weeks=24)
def get_place(self):
return get_object_or_404(Place, pk=self.kwargs['place_id'])
def get(self, request, place_id, vote): def get(self, request, place_id, vote):
place = get_object_or_404(Place, id=place_id) place = self.get_place()
explorer = request.user.explorer explorer = request.user.explorer
voting = PlaceVoting.objects.filter( voting = PlaceVoting.objects.filter(
@@ -217,12 +224,11 @@ class PlaceVoteView(IsEligibleToSeePlaceMixin, View):
voting = PlaceVoting.objects.create( voting = PlaceVoting.objects.create(
submitted_by=explorer, submitted_by=explorer,
place=place, place=place,
vote=vote, vote=vote
expires_when=timezone.now()+self.delta
) )
messages.success(self.request, _('Vote submitted')) messages.success(self.request, _('Vote submitted'))
else: else:
voting.expires_when=timezone.now()+self.delta voting.submitted_when = timezone.now()
voting.vote = vote voting.vote = vote
messages.success(self.request, _('Your vote has been update')) messages.success(self.request, _('Your vote has been update'))

View File

@@ -13,7 +13,7 @@ from django.utils.translation import gettext as _
from lostplaces.forms import SignupVoucherForm, TagSubmitForm from lostplaces.forms import SignupVoucherForm, TagSubmitForm
from lostplaces.models import Place, PhotoAlbum from lostplaces.models import Place, PhotoAlbum
from lostplaces.views.base_views import IsAuthenticatedMixin from lostplaces.views.base_views import IsAuthenticatedMixin, LevelCapPlaceListView
from lostplaces.common import redirect_referer_or from lostplaces.common import redirect_referer_or
from lostplaces.views.base_views import ( from lostplaces.views.base_views import (
@@ -29,17 +29,19 @@ class SignUpView(SuccessMessageMixin, CreateView):
template_name = 'signup.html' template_name = 'signup.html'
success_message = _('User created') success_message = _('User created')
class HomeView(IsAuthenticatedMixin, View): class HomeView(IsAuthenticatedMixin, LevelCapPlaceListView, View):
def get(self, request, *args, **kwargs): template_name = 'home.html'
place_list = request.user.explorer.get_places_eligible_to_see()
context = { def get_context_data(self, **kwargs):
'place_list': place_list, context = super().get_context_data(**kwargs)
'mapping_config': { place_list = context['place_list']
context['mapping_config'] = {
'all_points': place_list, 'all_points': place_list,
'map_center': Place.average_latlon(place_list) 'map_center': Place.average_latlon(place_list)
} }
} return context
return render(request, 'home.html', context)
def handle_no_permission(self): def handle_no_permission(self):
place_list = Place.objects.filter(level=1)[:5] place_list = Place.objects.filter(level=1)[:5]