38 Commits

Author SHA1 Message Date
05481fc0c8 Refactoring _has_context_key 2020-09-13 12:41:31 +02:00
6b00452830 Refactoring 2020-09-13 12:39:46 +02:00
7e4c5dcf24 Adding more methods to viewtestcase 2020-09-13 12:39:02 +02:00
c0f30e56f7 Small tweak 2020-09-13 11:02:53 +02:00
9852646fff Renaming IsPlaceSubmitter 2020-09-13 10:57:53 +02:00
c2d678847e Renaming IsAuthenticated 2020-09-13 10:56:18 +02:00
e77edf18ac Testing Abstract classes 2020-09-13 10:27:01 +02:00
3780aa6cf1 Abtract classes 2020-09-12 12:24:27 +02:00
21124ec2ad adapting list views 2020-09-12 12:02:25 +02:00
0ee5fc59d3 Adapting tests 2020-09-12 12:02:17 +02:00
b8dfef691e Fixing bug; Place did not show up on map 2020-09-12 11:58:45 +02:00
a1886b0b60 Adapting home view 2020-09-12 11:47:42 +02:00
e1002b5315 Adapting place detail view 2020-09-12 11:42:31 +02:00
fed90d4f7b Refactoring osm map 2020-09-12 11:42:18 +02:00
dcfb329c5a Adapted template 2020-09-12 11:35:30 +02:00
b8a21a8baa Changed average_latlon 2020-09-12 11:34:49 +02:00
7f73035b02 Refactoring tagging 2020-09-12 11:24:16 +02:00
317437fedc Testing PlaceListView 2020-09-12 11:02:39 +02:00
26286984c2 Base functions for testing views 2020-09-12 11:02:23 +02:00
9ae31c0146 Removing obsolote code 2020-09-12 11:02:01 +02:00
87fd8fa96f Small fix 2020-09-12 11:01:34 +02:00
c3401e732f Removed obosolete code 2020-09-12 11:01:24 +02:00
4ee7373b3f Testing Placeimage file change 2020-09-12 08:48:53 +02:00
64c0c5f8e6 Setting Up Test Data mor in a Unit way 2020-09-12 08:39:06 +02:00
18a597c726 Merge branch 'develop' into testing 2020-09-12 08:38:37 +02:00
Leonhard Strohmidel
baca596603 sync 2020-09-11 23:07:19 +02:00
d993387216 Fixed text repr of Explorer model due to user model change. 2020-09-11 22:43:03 +02:00
aed2856df3 Made the test timezone aware, DateTimeFiled(auto_now_add) already was. 2020-09-11 22:22:03 +02:00
c78858c152 Changed class attibutes to match test expectation. 2020-09-11 22:03:20 +02:00
f49581259e Typo in class name. 2020-09-11 19:32:07 +02:00
f5bf642cd6 Ups, not for the settings file. 2020-09-11 19:23:13 +02:00
7687acb366 Changed scope (?) of remaining local imports. 2020-09-11 19:15:39 +02:00
e655e1598a Trying to test deletion, wip 2020-09-11 12:09:51 +02:00
64ed38332f Refactoring and testing __str__ 2020-09-11 12:09:34 +02:00
d438303aec file for model tests 2020-09-11 12:08:43 +02:00
38b3736951 New Model tests 2020-09-11 12:08:27 +02:00
6be060ea40 Refactoring and more keywords to test 2020-09-11 12:08:15 +02:00
5c5756150f More dev dep 2020-09-11 12:07:57 +02:00
24 changed files with 693 additions and 307 deletions

View File

@@ -11,6 +11,7 @@ pipenv = "*"
wheel = "*"
twine = "*"
pandoc ="*"
pylint-django ="*"
[packages]
django = "*"
@@ -18,6 +19,7 @@ easy-thumbnails = "*"
image = "*"
django-widget-tweaks = "*"
django-taggit = "*"
# Commented out to not explicitly specify Python 3 subversion.
# [requires]
# python_version = "3.8"
[requires]
python_version = "3.7"

View File

@@ -6,15 +6,15 @@
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from .models import *
from lostplaces_app.models import *
from .forms import ExplorerCreationForm, ExplorerChangeForm
from lostplaces_app.forms import ExplorerCreationForm, ExplorerChangeForm
# Register your models here.
class VoucherAdmin(admin.ModelAdmin):
fields = ['code', 'expires', 'created']
readonly_fields = ['created']
fields = ['code', 'expires_when', 'created_when']
readonly_fields = ['created_when']
admin.site.register(Explorer)
admin.site.register(Voucher, VoucherAdmin)

View File

@@ -9,6 +9,7 @@ database.
import os
import uuid
from django.urls import reverse
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
@@ -19,6 +20,31 @@ from taggit.managers import TaggableManager
# Create your models here.
class Taggable(models.Model):
class Meta:
abstract = True
tags = TaggableManager(blank=True)
class MapablePoint(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=50)
latitude = models.FloatField(
validators=[
MinValueValidator(-90),
MaxValueValidator(90)
]
)
longitude = models.FloatField(
validators=[
MinValueValidator(-180),
MaxValueValidator(180)
]
)
class Explorer(models.Model):
"""
@@ -33,7 +59,7 @@ class Explorer(models.Model):
)
def __str__(self):
return self.user.name
return self.user.username
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
@@ -54,19 +80,18 @@ class Voucher(models.Model):
"""
code = models.CharField(unique=True, max_length=30)
created = models.DateTimeField(auto_now_add=True)
expires = models.DateField()
created_when = models.DateTimeField(auto_now_add=True)
expires_when = models.DateTimeField()
def __str__(self):
return "Voucher " + str(self.pk)
return "Voucher " + str(self.code)
class Place (models.Model):
class Place(Taggable, MapablePoint):
"""
Place defines a lost place (location, name, description etc.).
"""
name = models.CharField(max_length=50)
submitted_when = models.DateTimeField(auto_now_add=True, null=True)
submitted_by = models.ForeignKey(
Explorer,
@@ -76,24 +101,15 @@ class Place (models.Model):
related_name='places'
)
location = models.CharField(max_length=50)
latitude = models.FloatField(
validators=[
MinValueValidator(-90),
MaxValueValidator(90)
]
)
longitude = models.FloatField(
validators=[
MinValueValidator(-180),
MaxValueValidator(180)
]
)
description = models.TextField()
tags = TaggableManager(blank=True)
def get_absolute_url(self):
return reverse('place_detail', kwargs={'pk': self.pk})
@classmethod
# Get center position of LP-geocoordinates.
def average_latlon(place_list):
def average_latlon(cls, place_list):
amount = len(place_list)
# Init fill values to prevent None
longitude = 0
@@ -103,9 +119,9 @@ class Place (models.Model):
for place in place_list:
longitude += place.longitude
latitude += place.latitude
return (latitude / amount, longitude / amount)
return {'latitude':latitude / amount, 'longitude': longitude / amount}
return (latitude, longitude)
return {'latitude': latitude, 'longitude': longitude}
def __str__(self):
return self.name

View File

@@ -14,7 +14,7 @@
<article class="LP-TextSection">
</article>
{% include 'partials/osm_map.html' %}
{% include 'partials/osm_map.html' with config=map_config %}
<div class="LP-PlaceGrid">
<h1 class="LP-Headline LP-Headline">Explore the latest locations</h1>
<ul class="LP-PlaceGrid__Grid">

View File

@@ -11,20 +11,20 @@
}),
],
view: new ol.View({
center: ol.proj.fromLonLat([{{place_map_center|last}}, {{place_map_center|first}}]),
center: ol.proj.fromLonLat([{{config.map_center.longitude}}, {{config.map_center.latitude}}]),
zoom: 9
})
});
var vectorSource = new ol.source.Vector({
features: [
{% for place in place_list %}
{% for point in config.point_list %}
new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.fromLonLat([{{place.longitude}},{{place.latitude}}])
ol.proj.fromLonLat([{{point.longitude}},{{point.latitude}}])
),
url: '{% url 'place_detail' pk=place.pk %}',
name: '{{place.name}}'
url: '{{point.get_absolute_url}}',
name: ' {{point.name}}'
}),
{% endfor %}
]

View File

@@ -1,6 +1,6 @@
<div class="LP-TagList">
<ul class="LP-TagList__List">
{% for tag in tag_list %}
{% for tag in config.tagged_item.tags.all %}
<li class="LP-TagList__Item">
<div class="LP-Tag">
<a href="#" class="LP-Link">
@@ -23,7 +23,7 @@
</ul>
</div>
<form id="id_tag_submit_form" class="LP-Form LP-Form--inline LP-Form--tagging" method="POST" action="{{config.submit_url}}">
<form id="id_tag_submit_form" class="LP-Form LP-Form--inline LP-Form--tagging" method="POST" action="{% url config.submit_url_name tagged_id=config.tagged_item.id%}">
<fieldset class="LP-Form__Fieldset">
<legend class="LP-Form__Legend">Tags hinzufügen</legend>
{% csrf_token %}
@@ -46,14 +46,10 @@
submit_form.onsubmit = () => false
const tagify = new Tagify(input, {
'whitelist': [{
%
for tag in all_tags %
}
'whitelist': [
{% for tag in config.tagged_item.tags.all %}
'{{tag}}',
{
% endfor %
}
{% endfor %}
]
})

View File

@@ -37,13 +37,13 @@
<section class="LP-Section">
{% url 'place_tag_submit' place_id=place.id as tag_submit_url%}
{% include 'partials/tagging.html' with tag_list=place.tags.all config=tagging_config all_tags=all_tags %}
{% include 'partials/tagging.html' with config=tagging_config %}
</section>
<section class="LP-Section">
<h1 class="LP-Headline">Map-Links</h1>
{% include 'partials/osm_map.html' %}
{% include 'partials/osm_map.html' with config=map_config%}
<div class="LP-LinkList">
<ul class="LP-LinkList__Container">
<li class="LP-LinkList__Item"><a target="_blank" href="https://www.google.com/maps?q={{place.latitude}},{{place.longitude}}" class="LP-Link"><span class="LP-Text">Google Maps</span></a></li>

View File

@@ -10,7 +10,7 @@
{% block maincontent %}
{% include 'partials/osm_map.html' %}
{% include 'partials/osm_map.html' with config=map_config %}
<div class="LP-PlaceList">
<h1 class="LP-Headline">Listing our places</h1>
<ul class="LP-PlaceList__List">

View File

@@ -1,13 +1,2 @@
from django.db import models as django_models
from django.test import TestCase
from django.contrib.auth.models import User
def mock_user():
explorer_list = User.objects.all()
if len(explorer_list) <= 0:
return User.objects.create_user(
username='testpeter',
password='Develop123'
)
else:
return explorer_list[0]

View File

@@ -1,50 +1,99 @@
from django.db import models
from django.contrib.auth.models import User
from django.core.exceptions import FieldDoesNotExist
from django.test import TestCase
class TestModel:
# Creating a test user
class ModelTestCase:
'''
Base class for Lostplaces models
Base class for ModelTests
'''
model = None
model_name = None
def _test_field(self, field_name, field_class):
def setUp(self):
if not self.model._meta.abstract:
self.object = self.model.objects.get(id=1)
self.model_name = self.model.__name__
def _test_field(self, field_name, field_class, must_have={}, must_not_have={}):
'''
Tests if a field exists under the given name and
if the field is of the right type
if the field is of the right type.
Also checks if the field has the given must_have attributes
and does not have any of the must_not_have attributes. If you
dont care about the value of the attribute you can just set it to
something that fullfills value == False (i.e. '' or 0)
'''
field = self.object._meta.get_field(field_name)
self.assertTrue(
field,
msg="%s has no field named '%s'" % (
try:
field = self.model._meta.get_field(field_name)
except FieldDoesNotExist:
self.fail(
'Expecting %s to have a field named \'%s\'' % (
self.model_name,
field_name
)
)
self.assertEqual(
type(field), field_class,
msg='%s.%s name field is no CharField' % (
msg='Expecting type of %s.%s to be %s' % (
self.model_name,
field_name
field_name,
field_class.__name__
)
)
for key, value in must_have.items():
if value:
self.assertEqual(getattr(field, key), value,
msg='Expeting %s.%s.%s to be \'%s\'' % (
self.model_name,
field_name,
key,
value
)
)
else:
self.assertTrue(hasattr(field, key),
msg='Expeting %s.%s to have \'%s\'' % (
self.model_name,
field_name,
key
)
)
for key, value in must_not_have.items():
if value:
self.assertTrue(getattr(field, key) != value,
msg='Expeting %s.%s.%s to not be \'%s\'' % (
self.model_name,
field_name,
key,
value
)
)
else:
self.assertFalse(hasattr(field, value),
msg='Expeting %s.%s to not have \'%s\'' % (
self.model_name,
field_name,
key
)
)
return field
def _test_char_field(self, field_name, min_length, max_length):
def _test_char_field(self, field_name, min_length, max_length, must_have={}, must_hot_have={}):
'''
Tests if the given field is a char field and if its max_length
is in min_length and max_legth
'''
field = self._test_field(field_name, models.CharField)
self.assertEqual(
type(field), models.CharField,
msg='%s.%s name field is no CharField' % (
self.model_name,
field_name
)
)
field = self._test_field(
field_name, models.CharField, must_have, must_hot_have)
self.assertTrue(
field.max_length in range(min_length, max_length),
msg='%s.%s field max_length not in range of %d and %d' % (
msg='Expeting %s.%s field max_length to be in the range of %d and %d' % (
self.model_name,
field_name,
min_length,
@@ -52,7 +101,7 @@ class TestModel:
)
)
def _test_float_field(self, field_name, min_value=None, max_value=None):
def _test_float_field(self, field_name, min_value=None, max_value=None, must_have={}, must_hot_have={}):
'''
Tests if the field is a floatfield. If min_value and/or max_value are passed,
the validators of the field are also checked. The validator list of the field should
@@ -61,11 +110,12 @@ class TestModel:
[MinValueValidator] if only min_value is passed,
[MaxValueValidator] if only max_value is passed
'''
field = self._test_field(field_name, models.FloatField)
field = self._test_field(
field_name, models.FloatField, must_have, must_hot_have)
if min_value:
self.assertTrue(
len(field.validators) >= 1,
msg='%s.%s first validator should check minimum' % (
msg='Expecting the first valiator of %s.%s to check the minimum' % (
self.model_name,
field_name
)
@@ -73,9 +123,10 @@ class TestModel:
self.assertEqual(
field.validators[0].limit_value,
min_value,
msg='%s.%s min value missmatch' % (
msg='Expecting the min value of %s.%s min to be at least %d' % (
self.model_name,
field_name
field_name,
min_value
)
)
if max_value:
@@ -84,7 +135,7 @@ class TestModel:
index += 1
self.assertTrue(
len(field.validators) >= index+1,
msg='%s.%s second validator should check maximum' % (
msg='Expecting the second valiator of %s.%s to check the maximum' % (
self.model_name,
field_name
)
@@ -92,30 +143,32 @@ class TestModel:
self.assertEqual(
field.validators[1].limit_value,
max_value,
msg='%s.%s max value missmatch' % (
msg='Expecting the max value of %s.%s min to be at most %d' % (
self.model_name,
field_name
field_name,
max_value
)
)
class TestSubmittable(TestModel):
model_name='<Class>'
class SubmittableTestCase(ModelTestCase):
related_name = None
nullable = False
def test_submitted_when(self):
submitted_when = self._test_field('submitted_when', models.DateTimeField)
self.assertTrue(submitted_when.auto_now_add,
msg='%s.submitted_when should be auto_now_add' % (
self.model_name
)
submitted_when = self._test_field(
'submitted_when',
models.DateTimeField,
must_have={'auto_now_add': True}
)
def test_submitted_by(self):
submitted_by = self._test_field('submitted_by', models.ForeignKey)
if self.related_name:
self.assertEqual(submitted_by.remote_field.related_name, self.related_name)
self.assertEqual(
submitted_by.remote_field.related_name, self.related_name)
if self.nullable:
self.assertTrue(submitted_by.null,)
self.assertTrue(submitted_by.blank)
self.assertEqual(submitted_by.remote_field.on_delete, models.SET_NULL)
self.assertEqual(
submitted_by.remote_field.on_delete, models.SET_NULL)

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -0,0 +1,44 @@
import datetime
from django.test import TestCase
from django.db import models
from django.contrib.auth.models import User
from lostplaces_app.models import Place, Taggable, MapablePoint
from lostplaces_app.tests.models import ModelTestCase
from taggit.managers import TaggableManager
class TaggableTestCase(ModelTestCase, TestCase):
model = Taggable
def test_tags(self):
self._test_field('tags', TaggableManager)
class MapablePointTestCase(ModelTestCase, TestCase):
model = MapablePoint
def test_name(self):
self._test_char_field(
field_name='name',
min_length=10,
max_length=100
)
def test_latitude(self):
self._test_float_field(
field_name='latitude',
min_value=-90,
max_value=90
)
def test_longitude(self):
self._test_float_field(
field_name='longitude',
min_value=-180,
max_value=180
)

View File

@@ -0,0 +1,58 @@
from django.test import TestCase
from django.db import models
from django.contrib.auth.models import User
from lostplaces_app.models import Explorer
class ExplorerTestCase(TestCase):
@classmethod
def setUpTestData(self):
User.objects.create_user(
username='testpeter',
password='Develop123'
)
def test_epxlorer_creation(self):
'''
Tests if the explorer profile will be automticly
created when a user is created
'''
user = User.objects.get(id=1)
explorer_list = Explorer.objects.all()
self.assertTrue(len(explorer_list) > 0,
msg='Expecting at least one Exlorer object, none found'
)
self.assertTrue(hasattr(user, 'explorer'),
msg='''Expecting the User instance to have an \'explorer\' attribute.
Check the Explorer model and the related name.'''
)
explorer = Explorer.objects.get(id=1)
self.assertEqual(explorer, user.explorer,
msg='''The Explorer object of the User did not match.
Expecting User with id 1 to have Explorer with id 1'''
)
explorer = Explorer.objects.get(id=1)
self.assertEqual(explorer.user, user,
msg='''The User object of the Explorer did not match.
Expecting Explorer with id 1 to have User with id 1'''
)
def test_explorer_deletion(self):
'''
Tests if the Explorer objects get's deleted when the User instance is deleted
'''
user = User.objects.get(username='testpeter')
explorer_id = user.explorer.id
user.delete()
with self.assertRaises(models.ObjectDoesNotExist,
msg='Expecting explorer objec to be deleted when the corresponding User object is deleted'
):
Explorer.objects.get(id=explorer_id)

View File

@@ -1,32 +1,63 @@
import datetime
import os
import shutil
from unittest import mock
from django.test import TestCase
from django.db import models
from django.core.files import File
from django.conf import settings
from django.contrib.auth.models import User
from lostplaces_app.models import PlaceImage
from lostplaces_app.tests.models import TestSubmittable
from lostplaces_app.tests import mock_user
from lostplaces_app.tests.models import TestModel
from lostplaces_app.tests.models.test_place_model import mock_place
from lostplaces_app.models import PlaceImage, Place
from lostplaces_app.tests.models import SubmittableTestCase
from easy_thumbnails.fields import ThumbnailerImageField
def mock_place_image():
return PlaceImage(
description='Im a description',
filename=mock.MagicMock(spec=File, name='FileMock'),
place=mock_place(),
submitted_when=datetime.datetime.now(),
submitted_by=mock_user().explorer
class TestPlaceImage(SubmittableTestCase, TestCase):
model = PlaceImage
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
class TestPlaceImage(TestSubmittable, TestCase):
model_name = 'PlaceImage'
place = Place.objects.create(
name='Im a place',
submitted_when=datetime.datetime.now(),
submitted_by=User.objects.get(username='testpeter').explorer,
location='Testtown',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry'
)
place.tags.add('I a tag', 'testlocation')
place.save()
def setUp(self):
self.object = mock_place_image()
if not os.path.isdir(settings.MEDIA_ROOT):
os.mkdir(settings.MEDIA_ROOT)
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')):
shutil.copyfile(
os.path.join(current_dir, 'im_a_image.jpeg'),
os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg')
)
shutil.copyfile(
os.path.join(current_dir, 'im_a_image.jpeg'),
os.path.join(settings.MEDIA_ROOT, 'im_a_image_changed.jpeg')
)
PlaceImage.objects.create(
description='Im a description',
filename=os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg'),
place=place,
submitted_when=datetime.datetime.now(),
submitted_by=user.explorer
)
def test_description(self):
self._test_field('description', models.TextField)
@@ -51,5 +82,26 @@ class TestPlaceImage(TestSubmittable, TestCase):
)
def test_str(self):
place_image = mock_place_image()
self.assertEqual(str(place_image), ' '.join([place_image.place.name, str(place_image.pk)]))
place_image = self.object
self.assertTrue(place_image.place.name.lower() in str(place_image).lower(),
msg='Expecting %s.__str__ to contain the name of the place' % (
self.model_name
)
)
def test_change_filename(self):
path = self.object.filename.path
self.object.filename = os.path.join(settings.MEDIA_ROOT, 'im_a_image_changed.jpeg')
self.object.save()
self.assertFalse(
os.path.isfile(path),
msg='Expecting the old file of an place_image to be deleteed when an place_image file is changed'
)
def test_deletion(self):
path = self.object.filename.path
self.object.delete()
self.assertFalse(
os.path.isfile(path),
msg='Expecting the file of an place_image to be deleteed when an place_image is deleted'
)

View File

@@ -3,42 +3,35 @@ import datetime
from django.test import TestCase
from django.db import models
from django.contrib.auth.models import User
from lostplaces_app.models import Place
from lostplaces_app.tests.models import TestSubmittable
from lostplaces_app.tests import mock_user
from lostplaces_app.tests.models import TestModel
from lostplaces_app.tests.models import SubmittableTestCase
from taggit.managers import TaggableManager
class PlaceTestCase(SubmittableTestCase, TestCase):
model = Place
related_name = 'places'
nullable = True
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
def mock_place():
place = Place.objects.create(
name='Im a place',
submitted_when=datetime.datetime.now(),
submitted_by=mock_user().explorer,
submitted_by=user.explorer,
location='Testtown',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry'
)
place.tags.add('I a tag', 'testlocation')
place.save()
return place
class PlaceTestCase(TestSubmittable, TestCase):
model_name = 'Place'
related_name = 'places'
nullable = True
def setUp(self):
self.place = mock_place()
self.object = self.place
def test_name_field(self):
self._test_char_field(
field_name='name',
min_length=10,
max_length=100
)
def test_location(self):
self._test_char_field(
@@ -47,26 +40,9 @@ class PlaceTestCase(TestSubmittable, TestCase):
max_length=100
)
def test_latitude(self):
self._test_float_field(
field_name='latitude',
min_value=-90,
max_value=90
)
def test_longitude(self):
self._test_float_field(
field_name='longitude',
min_value=-180,
max_value=180
)
def test_decsription(self):
self._test_field('description', models.TextField)
def test_tags(self):
self._test_field('tags', TaggableManager)
def test_average_latlon(self):
'''
Tests the average latitude/longitude calculation of a list
@@ -74,18 +50,28 @@ class PlaceTestCase(TestSubmittable, TestCase):
'''
place_list = []
for i in range(10):
place = mock_place()
place = Place.objects.get(id=1)
place.id = None
place.latitude = i+1
place.longitude = i+10
place.save()
place_list.append(place)
avg_latlon = Place.average_latlon(place_list)
self.assertEqual(avg_latlon[0], 5.5,
self.assertTrue('latitude' in avg_latlon,
msg='Expecting avg_latlon dict to have an \'latitude\' key'
)
self.assertTrue('longitude' in avg_latlon,
msg='Expecting avg_latlon dict to have an \'longitude\' key'
)
self.assertEqual(avg_latlon['latitude'], 5.5,
msg='%s: average latitude missmatch' % (
self.model_name
)
)
self.assertEqual(avg_latlon[1], 14.5,
self.assertEqual(avg_latlon['longitude'], 14.5,
msg='%s: average longitude missmatch' % (
self.model_name
)
@@ -96,14 +82,14 @@ class PlaceTestCase(TestSubmittable, TestCase):
Tests the average latitude/longitude calculation of a list
of one place
'''
place = mock_place()
place = Place.objects.get(id=1)
avg_latlon = Place.average_latlon([place])
self.assertEqual(avg_latlon[0], place.latitude,
self.assertEqual(avg_latlon['latitude'], place.latitude,
msg='%s:(one place) average latitude missmatch' % (
self.model_name
)
)
self.assertEqual(avg_latlon[1], place.longitude,
self.assertEqual(avg_latlon['longitude'], place.longitude,
msg='%s: (one place) average longitude missmatch' % (
self.model_name
)
@@ -115,22 +101,21 @@ class PlaceTestCase(TestSubmittable, TestCase):
an empty list
'''
avg_latlon = Place.average_latlon([])
self.assertEqual(avg_latlon[0], 0,
self.assertEqual(avg_latlon['latitude'], 0,
msg='%s: (no places) average latitude missmatch' % (
self.model_name
)
)
self.assertEqual(avg_latlon[1], 0,
self.assertEqual(avg_latlon['longitude'], 0,
msg='%s: a(no places) verage longitude missmatch' % (
self.model_name
)
)
def test_str(self):
place = mock_place()
self.assertEqual(str(place), place.name,
msg='%s __str__ should return the name' % (
place = self.object
self.assertTrue(place.name.lower() in str(place).lower(),
msg='Expecting %s.__str__ to contain the name' % (
self.model_name
)
)

View File

@@ -0,0 +1,49 @@
import datetime
from django.test import TestCase
from django.db import models
from django.utils import timezone
from lostplaces_app.models import Voucher
from lostplaces_app.tests.models import ModelTestCase
class VoucheTestCase(ModelTestCase, TestCase):
model = Voucher
@classmethod
def setUpTestData(cls):
Voucher.objects.create(
code='ayDraJCCwfhcFiYmSR5GrcjcchDfcahv',
expires_when=timezone.now() + datetime.timedelta(days=1)
)
def test_voucher_code(self):
self._test_char_field(
'code',
10,
100,
must_have={'unique': True}
)
def test_voucher_created(self):
self._test_field(
'created_when',
models.DateTimeField,
must_have={'auto_now_add': True}
)
def test_voucher_expires(self):
self._test_field(
'expires_when',
models.DateTimeField,
must_not_have={'auto_now_add': True}
)
def test_str(self):
voucher = self.object
self.assertTrue(voucher.code.lower() in str(voucher).lower(),
msg='Expecting %s.__str__ to contain the voucher code' % (
self.model_name
)
)

View File

@@ -0,0 +1,77 @@
from django.test import Client
class ViewTestCaseMixin:
'''
This is a mixni for testing views. It provides functionality to
test the context, forms and HTTP Response of responses.
Also works with django's ReqeustFactory.
'''
view = None
def setUp(self):
self.view_name = self.view.__name__
self. client = Client()
def assertHasContextKey(self, response, context_key):
self.assertTrue( context_key in response.context,
msg='Expecting the context of %s to have an attribute \'%s\'' % (
self.view_name,
context_key
)
)
def assertHasForm(self, response, context_key, form_class):
self.assertHasContextKey(response, context_key)
self.assertEqual(
type(response.context[context_key]),
form_class,
msg='Expecting %s\'s context.%s to be of the type %s' % (
self.view_name,
context_key,
form_class.__name__
)
)
def assertHttpCode(self, response, code):
self.assertEqual(
response.status_code, code,
"Expected an HTTP %s response, but got HTTP %s" % (
code,
response.status_code
)
)
def assertHttpOK(self, response):
self.assertHttpCode(response, 200)
def assertHttpCreated(self, response):
self.assertHttpCode(response, 201)
def assertHttpRedirect(self, response, redirect_to):
'''
Assert that we had any redirect status code.
'''
self.assertTrue(
300 <= response.status_code < 400,
'Expected an HTTP 3XX (redirect) response, but got HTTP %s' %
response.status_code
)
self.assertEqual(response['Location'], redirect_to)
def assertHttpBadRequest(self, response):
self.assertHttpCode(response, 400)
def assertHttpUnauthorized(self, response):
self.assertHttpCode(response, 401)
def assertHttpForbidden(self, response):
self.assertHttpCode(response, 403)
def assertHttpNotFound(self, response):
self.assertHttpCode(response, 404)
def assertHttpMethodNotAllowed(self, response):
self.assertHttpCode(response, 405)

View File

@@ -1,45 +1,65 @@
from django.test import TestCase, Client
import datetime
from django.test import TestCase, RequestFactory, Client
from django.urls import reverse_lazy
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.messages.storage.fallback import FallbackStorage
from lostplaces_app.models import Place
from lostplaces_app.views import IsAuthenticatedMixin
from django.contrib.auth.models import User
from lostplaces_app.tests.models.test_place_model import mock_place
from lostplaces_app.tests import mock_user
class TestIsAuthenticatedMixin(RedirectTestCase):
class TestIsAuthenticated(TestCase):
def setUp(self):
self. client = Client()
mock_place()
mock_user()
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
def test_logged_in(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse_lazy('place_detail', kwargs={'pk': 1}))
request = RequestFactory().get('/')
request.user = User.objects.get(id=1)
response = IsAuthenticatedMixin.as_view()(request)
self.assertEqual(response.status_code, 200)
def test_not_logged_in(self):
url = reverse_lazy('place_detail', kwargs={'pk': 1})
response = self.client.get(url, follow=True)
self.assertRedirects(
response=response,
expected_url='?'.join([str(reverse_lazy('login')), 'next=/place/1/']),
status_code=302,
target_status_code=200,
msg_prefix='''Accesing an IsAuthenticated view while not logged should
redirect to login page with redirect params
''',
fetch_redirect_response=True
)
request = RequestFactory().get('/someurl1234')
request.user = AnonymousUser()
request.session = 'session'
messages = FallbackStorage(request)
request._messages = messages
response = IsAuthenticatedMixin.as_view()(request)
self.assertRedirectsTo(response, '?'.join([str(reverse_lazy('login')), 'next=/someurl1234']))
self.assertTrue(response.context['messages'])
self.assertTrue(len(response.context['messages']) > 0)
class TestIsPlaceSubmitter(TestCase):
class TestIsPlaceSubmitterMixin(TestCase):
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
place = Place.objects.create(
name='Im a place',
submitted_when=datetime.datetime.now(),
submitted_by=user.explorer,
location='Testtown',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry'
)
place.tags.add('I a tag', 'testlocation')
place.save()
def setUp(self):
self. client = Client()
mock_place()
mock_user()
def test_is_submitter(self):
self.client.login(username='testpeter', password='Develop123')

View File

@@ -1,35 +1,73 @@
from django.test import TestCase, Client
import datetime
from django.test import TestCase
from django.urls import reverse_lazy
from django.contrib.auth.models import User
from lostplaces_app.models import Place
from lostplaces_app.views import (
PlaceCreateView,
PlaceListView
)
from lostplaces_app.forms import PlaceImageCreateForm, PlaceForm
from lostplaces_app.tests.views import ViewTestCaseMixin
from lostplaces_app.tests.models.test_place_model import mock_place
from lostplaces_app.tests import mock_user
class TestPlaceCreateView(ViewTestCaseMixin, TestCase):
class TestPlaceCreateView(TestCase):
view = PlaceCreateView
def setUp(self):
self. client = Client()
mock_place()
mock_user()
def test_url_logged_in(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse_lazy('place_detail', kwargs={'pk': 1}))
self.assertEqual(response.status_code, 200)
def test_url_not_logged_in(self):
url = reverse_lazy('place_detail', kwargs={'pk': 1})
response = self.client.get(url)
self.assertRedirects(
response=response,
expected_url='?'.join([str(reverse_lazy('login')), 'next=/place/1/']),
status_code=302,
target_status_code=200,
msg_prefix='''Accesing PlaceDetailView while not logged should
redirect to login page with redirect params
''',
fetch_redirect_response=True
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
place = Place.objects.create(
name='Im a place',
submitted_when=datetime.datetime.now(),
submitted_by=user.explorer,
location='Testtown',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry'
)
place.tags.add('I a tag', 'testlocation')
place.save()
def test_has_forms(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse_lazy('place_create'))
self.assertHasForm(response, 'place_image_form', PlaceImageCreateForm)
self.assertHasForm(response, 'place_form', PlaceForm)
class TestPlaceListView(ViewTestCaseMixin, TestCase):
view = PlaceListView
@classmethod
def setUpTestData(cls):
user = User.objects.create_user(
username='testpeter',
password='Develop123'
)
place = Place.objects.create(
name='Im a place',
submitted_when=datetime.datetime.now(),
submitted_by=user.explorer,
location='Testtown',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry'
)
place.tags.add('I a tag', 'testlocation')
place.save()
def test_list_view(self):
self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse_lazy('place_list'))
self.assertHasContextKey(response, 'map_config')

View File

@@ -1,5 +1,5 @@
from django.urls import path
from .views import (
from lostplaces_app.views import (
HomeView,
PlaceDetailView,
PlaceListView,
@@ -25,6 +25,6 @@ urlpatterns = [
path('flat/<slug:slug>/', FlatView, name='flatpage'),
# POST-only URLs for tag submission
path('place/tag/<int:place_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
path('place/tag/<int:tagged_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
path('place/tag/delete/<int:tagged_id>/<int:tag_id>', PlaceTagDeleteView.as_view(), name='place_tag_delete')
]

View File

@@ -11,7 +11,7 @@ from django.urls import reverse_lazy
from lostplaces_app.models import Place
class IsAuthenticated(LoginRequiredMixin, View):
class IsAuthenticatedMixin(LoginRequiredMixin, View):
'''
A view mixin that checks wether a user is loged in or not.
If the user is not logged in, he gets redirected to
@@ -24,7 +24,7 @@ class IsAuthenticated(LoginRequiredMixin, View):
messages.error(self.request, self.permission_denied_message)
return super().handle_no_permission()
class IsPlaceSubmitter(UserPassesTestMixin, View):
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
'''
A view mixin that checks wethe a user is the submitter
of a place Throws 403 if the user is not. The subclass
@@ -55,7 +55,7 @@ class IsPlaceSubmitter(UserPassesTestMixin, View):
messages.error(self.request, self.place_submitter_error_message)
return False
class PlaceAssetCreateView(IsAuthenticated, SuccessMessageMixin, CreateView):
class PlaceAssetCreateView(IsAuthenticatedMixin, SuccessMessageMixin, CreateView):
model = None
fields = []
template_name = ''
@@ -81,7 +81,7 @@ class PlaceAssetCreateView(IsAuthenticated, SuccessMessageMixin, CreateView):
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk': self.place.id})
class PlaceAssetDeleteView(IsAuthenticated, IsPlaceSubmitter, SingleObjectMixin, View):
class PlaceAssetDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SingleObjectMixin, View):
model = None
success_message = ''
permission_denied_message = ''

View File

@@ -10,12 +10,12 @@ from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from lostplaces_app.models import Place, PlaceImage
from lostplaces_app.views import IsAuthenticated, IsPlaceSubmitter
from lostplaces_app.views import IsAuthenticatedMixin, IsPlaceSubmitterMixin
from lostplaces_app.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm
from taggit.models import Tag
class PlaceListView(IsAuthenticated, ListView):
class PlaceListView(IsAuthenticatedMixin, ListView):
paginate_by = 5
model = Place
template_name = 'place/place_list.html'
@@ -23,27 +23,32 @@ class PlaceListView(IsAuthenticated, ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place_map_center'] = Place.average_latlon(context['place_list'])
context['map_config'] = {
'point_list': context['place_list'],
'map_center': Place.average_latlon(context['place_list'])
}
return context
class PlaceDetailView(IsAuthenticated, View):
class PlaceDetailView(IsAuthenticatedMixin, View):
def get(self, request, pk):
place = Place.objects.get(pk=pk)
context = {
'place': place,
'place_list': [ place ],
'place_map_center': [ place.latitude, place.longitude ],
'all_tags': Tag.objects.all(),
'map_config': {
'point_list': [ place ],
'map_center': {'latitude': place.latitude, 'longitude': place.longitude},
},
'tagging_config': {
'submit_url': reverse_lazy('place_tag_submit', kwargs={'place_id': place.id}),
'all_tags': Tag.objects.all(),
'submit_form': TagSubmitForm(),
'tagged_item': place,
'submit_url_name': 'place_tag_submit',
'delete_url_name': 'place_tag_delete'
}
}
return render(request, 'place/place_detail.html', context)
class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView):
class PlaceUpdateView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SuccessMessageMixin, UpdateView):
template_name = 'place/place_update.html'
model = Place
form_class = PlaceForm
@@ -56,7 +61,7 @@ class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, Up
def get_place(self):
return self.get_object()
class PlaceCreateView(IsAuthenticated, View):
class PlaceCreateView(IsAuthenticatedMixin, View):
def get(self, request, *args, **kwargs):
place_image_form = PlaceImageCreateForm()
@@ -112,7 +117,7 @@ class PlaceCreateView(IsAuthenticated, View):
)
place_image.save()
class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView):
class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
template_name = 'place/place_delete.html'
model = Place
success_message = 'Successfully deleted place.'

View File

@@ -9,7 +9,7 @@ from django.http import HttpResponseForbidden
from lostplaces_app.forms import ExplorerCreationForm, TagSubmitForm
from lostplaces_app.models import Place, PhotoAlbum
from lostplaces_app.views.base_views import IsAuthenticated
from lostplaces_app.views.base_views import IsAuthenticatedMixin
from lostplaces_app.views.base_views import (
PlaceAssetCreateView,
@@ -24,13 +24,15 @@ class SignUpView(SuccessMessageMixin, CreateView):
template_name = 'signup.html'
success_message = 'User created.'
class HomeView(IsAuthenticated, View):
class HomeView(IsAuthenticatedMixin, View):
def get(self, request, *args, **kwargs):
place_list = Place.objects.all().order_by('-submitted_when')[:10]
place_map_center = Place.average_latlon(place_list)
context = {
'place_list': place_list,
'place_map_center': place_map_center
'map_config': {
'point_list': place_list,
'map_center': Place.average_latlon(place_list)
}
}
return render(request, 'home.html', context)
@@ -53,9 +55,9 @@ class PhotoAlbumDeleteView(PlaceAssetDeleteView):
success_message = 'Photo Album deleted'
permission_denied_messsage = 'You do not have permissions to alter this photo album'
class PlaceTagSubmitView(IsAuthenticated, View):
def post(self, request, place_id, *args, **kwargs):
place = Place.objects.get(pk=place_id)
class PlaceTagSubmitView(IsAuthenticatedMixin, View):
def post(self, request, tagged_id, *args, **kwargs):
place = Place.objects.get(pk=tagged_id)
form = TagSubmitForm(request.POST)
if form.is_valid():
tag_list_raw = form.cleaned_data['tag_list']
@@ -68,7 +70,7 @@ class PlaceTagSubmitView(IsAuthenticated, View):
return redirect(reverse_lazy('place_detail', kwargs={'pk': place.id}))
class PlaceTagDeleteView(IsAuthenticated, View):
class PlaceTagDeleteView(IsAuthenticatedMixin, View):
def get(self, request, tagged_id, tag_id, *args, **kwargs):
place = Place.objects.get(pk=tagged_id)
tag = Tag.objects.get(pk=tag_id)