Compare commits
15 Commits
update/dja
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
3982db1375 | ||
|
e60a6ea9be | ||
|
bc0ace7bf3 | ||
|
df67bcf639 | ||
|
724c26c926 | ||
|
06d68380c9 | ||
|
f06b6bdae9 | ||
|
7a7c06882a | ||
|
c9d83dfc2c | ||
|
49301afe51 | ||
|
624878624f | ||
|
a2ee323fa4 | ||
|
86c9de3213 | ||
|
8597e53599 | ||
|
d213b51a59 |
1
Pipfile
1
Pipfile
@@ -22,6 +22,7 @@ easy-thumbnails = "*"
|
|||||||
image = "*"
|
image = "*"
|
||||||
django-widget-tweaks = "*"
|
django-widget-tweaks = "*"
|
||||||
django-taggit = "*"
|
django-taggit = "*"
|
||||||
|
pykml = "*"
|
||||||
|
|
||||||
[scripts]
|
[scripts]
|
||||||
test = "django_lostplaces/manage.py test lostplaces"
|
test = "django_lostplaces/manage.py test lostplaces"
|
||||||
|
@@ -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.
|
||||||
|
|
||||||
|
@@ -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
|
||||||
@@ -122,3 +127,9 @@ class TagSubmitForm(forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
widget=forms.TextInput(attrs={'autocomplete':'off'})
|
widget=forms.TextInput(attrs={'autocomplete':'off'})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class UploadMapFileForm(forms.Form):
|
||||||
|
map_file = forms.FileField()
|
||||||
|
description = forms.CharField(
|
||||||
|
widget=forms.Textarea
|
||||||
|
)
|
||||||
|
@@ -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').order_by('submitted_when')
|
||||||
|
|
||||||
|
return (Place.objects.filter(
|
||||||
|
level__lte=self.level,
|
||||||
|
mode='live'
|
||||||
|
) | Place.objects.filter(
|
||||||
|
submitted_by=self,
|
||||||
|
mode='live'
|
||||||
|
)).order_by('submitted_when')
|
||||||
|
|
||||||
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):
|
||||||
@@ -94,6 +110,12 @@ class Explorer(models.Model):
|
|||||||
place in self.get_places_eligible_to_see()
|
place in self.get_places_eligible_to_see()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_drafts(self):
|
||||||
|
return Place.objects.filter(
|
||||||
|
submitted_by=self,
|
||||||
|
mode='draft'
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
|
|
||||||
|
@@ -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,43 @@ PLACE_LEVELS = (
|
|||||||
(5, 'Time Capsule')
|
(5, 'Time Capsule')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PLACE_MODES = (
|
||||||
|
('live', 'live'),
|
||||||
|
('draft', 'draft'),
|
||||||
|
('review', 'review'),
|
||||||
|
('archive', 'archive'),
|
||||||
|
('imported', 'imported')
|
||||||
|
)
|
||||||
|
|
||||||
|
PLACE_IMPORT_TYPES = (
|
||||||
|
('kml', 'KML-File import'),
|
||||||
|
)
|
||||||
|
|
||||||
|
class PlaceImport(models.Model):
|
||||||
|
imported_when = models.DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
verbose_name=_('When the imported has taken place')
|
||||||
|
)
|
||||||
|
|
||||||
|
description = models.TextField(
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('Description of the import')
|
||||||
|
)
|
||||||
|
|
||||||
|
explorer = models.ForeignKey(
|
||||||
|
'Explorer',
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='place_imports'
|
||||||
|
)
|
||||||
|
|
||||||
|
import_type = models.TextField(
|
||||||
|
default='kml',
|
||||||
|
choices=PLACE_IMPORT_TYPES,
|
||||||
|
verbose_name=_('What kind of import this is')
|
||||||
|
)
|
||||||
|
|
||||||
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.).
|
||||||
@@ -38,7 +77,7 @@ class Place(Submittable, Taggable, Mapable):
|
|||||||
hero = models.ForeignKey(
|
hero = models.ForeignKey(
|
||||||
'PlaceImage',
|
'PlaceImage',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name='place_heros'
|
related_name='place_heros'
|
||||||
)
|
)
|
||||||
@@ -48,6 +87,22 @@ 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')
|
||||||
|
)
|
||||||
|
|
||||||
|
place_import = models.ForeignKey(
|
||||||
|
PlaceImport,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='place_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_imported(self):
|
||||||
|
return self.place_import != None
|
||||||
|
|
||||||
def get_hero_image(self):
|
def get_hero_image(self):
|
||||||
if self.hero:
|
if self.hero:
|
||||||
return self.hero
|
return self.hero
|
||||||
@@ -76,24 +131,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 +157,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,11 +272,11 @@ 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):
|
||||||
return PLACE_LEVELS[self.vote - 1][1]
|
return PLACE_LEVELS[self.vote - 1][1]
|
||||||
|
|
||||||
def get_all_choices(self):
|
def get_all_choices(self):
|
||||||
return reversed(PLACE_LEVELS)
|
return reversed(PLACE_LEVELS)
|
||||||
|
28
django_lostplaces/lostplaces/templates/explorer/drafts.html
Normal file
28
django_lostplaces/lostplaces/templates/explorer/drafts.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{% extends 'global.html'%}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% load svg_icon %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<section class="LP-Section">
|
||||||
|
<div class="LP-PlaceList">
|
||||||
|
<h1 class="LP-Headline">
|
||||||
|
{% if user.username == explorer.user.username %}
|
||||||
|
{% translate 'Your drafts' %}
|
||||||
|
{% else %}
|
||||||
|
{{explorer.user.username}}{% translate '\'s drafts' %}
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
<ul class="LP-PlaceList__List">
|
||||||
|
{% for place in place_list %}
|
||||||
|
<li class="LP-PlaceList__Item">
|
||||||
|
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
|
||||||
|
{% include 'partials/place_teaser.html' with place=place extended=True %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock maincontent %}
|
@@ -32,7 +32,8 @@
|
|||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
Hi {{ user.username }}!
|
Hi {{ user.username }}!
|
||||||
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% translate 'Logout' %}</span></a> |
|
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% translate 'Logout' %}</span></a> |
|
||||||
<a class="LP-Link" href="{% url 'explorer_profile' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Profile' %}</span></a>
|
<a class="LP-Link" href="{% url 'explorer_profile' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Profile' %}</span></a> |
|
||||||
|
<a class="LP-Link" href="{% url 'explorer_drafts' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Drafts' %}</span></a>
|
||||||
{% if user.is_superuser %}
|
{% if user.is_superuser %}
|
||||||
| <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% translate 'Admin' %}</span></a>
|
| <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% translate 'Admin' %}</span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -58,6 +59,10 @@
|
|||||||
|
|
||||||
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_create'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Create place' %}</span></a></li>
|
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_create'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Create place' %}</span></a></li>
|
||||||
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_list'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'All places' %}</span></a></li>
|
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_list'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'All places' %}</span></a></li>
|
||||||
|
|
||||||
|
{% if user.is_superuser %}
|
||||||
|
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'import_upload'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Import KML File' %}</span></a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends 'global.html'%}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% load svg_icon %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<h1 class="LP-Headline">
|
||||||
|
{{ import_type}} from {{import.imported_when|date:"d F Y"}} by {{import.explorer.user.username}}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="SP-Paragraph">
|
||||||
|
{{import.place_list.all|length}} places where import in this import
|
||||||
|
</p>
|
||||||
|
<p class="SP-Paragraph">
|
||||||
|
{{import.description}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="LP-PlaceList">
|
||||||
|
<h1 class="LP-Headline">{% translate 'Places imported' %}</h1>
|
||||||
|
<ul class="LP-PlaceList__List">
|
||||||
|
{% for place in paginated_places %}
|
||||||
|
<li class="LP-PlaceList__Item">
|
||||||
|
{% include 'partials/place_teaser.html' with place=place extended=True %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% include 'partials/nav/pagination.html' with page_obj=paginated_places is_paginated=True %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock maincontent %}
|
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends 'global.html'%}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% load svg_icon %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<form class="LP-Form" method="POST" enctype="multipart/form-data">
|
||||||
|
<fieldset class="LP-Form__Fieldset">
|
||||||
|
<legend class="LP-Form__Legend">{% translate 'Import Places from KML File' %}</legend>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||||
|
<div class="LP-Form__Field">
|
||||||
|
{% include 'partials/form/inputField.html' with field=upload_form.description %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||||
|
<div class="LP-Form__Field">
|
||||||
|
{% include 'partials/form/inputField.html' with field=upload_form.map_file %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% translate 'Create' as action %}
|
||||||
|
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||||
|
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{% endblock maincontent %}
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 %}
|
||||||
@@ -41,7 +49,6 @@
|
|||||||
{% include '../partials/tagging.html' with config=tagging_config %}
|
{% include '../partials/tagging.html' with config=tagging_config %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{votingplace.vote}}
|
|
||||||
<section class="LP-Section">
|
<section class="LP-Section">
|
||||||
{% include '../partials/voting.html' with voting=placevoting %}
|
{% include '../partials/voting.html' with voting=placevoting %}
|
||||||
</section>
|
</section>
|
||||||
|
@@ -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>
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
BIN
django_lostplaces/lostplaces/tests/views/im_a_image.jpeg
Normal file
BIN
django_lostplaces/lostplaces/tests/views/im_a_image.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
@@ -1,4 +1,4 @@
|
|||||||
23238#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.test import TestCase, RequestFactory, Client
|
from django.test import TestCase, RequestFactory, Client
|
||||||
@@ -7,6 +7,7 @@ from django.db import models
|
|||||||
from django.contrib.auth.models import User, AnonymousUser
|
from django.contrib.auth.models import User, AnonymousUser
|
||||||
from django.contrib.messages.storage.fallback import FallbackStorage
|
from django.contrib.messages.storage.fallback import FallbackStorage
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
from lostplaces.models import Place
|
from lostplaces.models import Place
|
||||||
|
300
django_lostplaces/lostplaces/tests/views/test_explorer_views.py
Normal file
300
django_lostplaces/lostplaces/tests/views/test_explorer_views.py
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
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_draft_in_explorer_places(self):
|
||||||
|
user = User.objects.get(username='testpeter')
|
||||||
|
Place.objects.create(
|
||||||
|
name='Im the latest place in draft 3671',
|
||||||
|
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,
|
||||||
|
mode='draft'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.login(username='testpeter', password='Develop123')
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('explorer_profile', kwargs={
|
||||||
|
'explorer_id': user.explorer.id
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertHttpOK(response)
|
||||||
|
self.assertFalse(
|
||||||
|
'Im the latest place in draft 3671' in response.content.decode(),
|
||||||
|
msg='Expecting a place in draft mode to not show up 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'
|
||||||
|
)
|
@@ -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,
|
||||||
@@ -154,6 +155,38 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
|
|||||||
'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'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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'
|
||||||
@@ -605,6 +677,63 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
|
|||||||
user.explorer not in place.explorer_visits.all(),
|
user.explorer not in place.explorer_visits.all(),
|
||||||
msg='Expecting the explorer to not be in the reverse list of visits after deleting visit'
|
msg='Expecting the explorer to not be in the reverse list of visits after deleting visit'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_accessing_place_in_draft(self):
|
||||||
|
user = User.objects.get(username='testpeter')
|
||||||
|
place = Place.objects.create(
|
||||||
|
name='Im a own place in draft 387948',
|
||||||
|
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,
|
||||||
|
mode='draft'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.login(username='testpeter', password='Develop123')
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('place_detail', kwargs={
|
||||||
|
'pk': place.pk
|
||||||
|
})
|
||||||
|
)
|
||||||
|
self.assertHttpOK(response)
|
||||||
|
self.assertTrue(
|
||||||
|
'Im a own place in draft 387948' in response.content.decode(),
|
||||||
|
msg='Expecting the user to see his own place in draft mode'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
self.client.login(username='blubberbernd', password='Develop123')
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('place_detail', kwargs={
|
||||||
|
'pk': place.pk
|
||||||
|
})
|
||||||
|
)
|
||||||
|
self.assertHttpForbidden(response)
|
||||||
|
|
||||||
|
self.client.login(username='toor', password='Develop123')
|
||||||
|
response = self.client.get(
|
||||||
|
reverse('place_detail', kwargs={
|
||||||
|
'pk': place.pk
|
||||||
|
})
|
||||||
|
)
|
||||||
|
self.assertHttpOK(response)
|
||||||
|
self.assertTrue(
|
||||||
|
'Im a own place in draft 387948' in response.content.decode(),
|
||||||
|
msg='Expecting a superuser to see all places in draft mode'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -23,7 +23,10 @@ from lostplaces.views import (
|
|||||||
PhotoAlbumDeleteView,
|
PhotoAlbumDeleteView,
|
||||||
ExplorerProfileView,
|
ExplorerProfileView,
|
||||||
ExplorerProfileUpdateView,
|
ExplorerProfileUpdateView,
|
||||||
PlaceVoteView
|
ExplorerDraftsView,
|
||||||
|
PlaceVoteView,
|
||||||
|
UploadMapFileView,
|
||||||
|
ImportDetailView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -33,6 +36,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
path('explorer/<int:explorer_id>/', ExplorerProfileView.as_view(), name='explorer_profile'),
|
path('explorer/<int:explorer_id>/', ExplorerProfileView.as_view(), name='explorer_profile'),
|
||||||
path('explorer/update/', ExplorerProfileUpdateView.as_view(), name='explorer_profile_update'),
|
path('explorer/update/', ExplorerProfileUpdateView.as_view(), name='explorer_profile_update'),
|
||||||
|
path('explorer/<int:explorer_id>/drafts', ExplorerDraftsView.as_view(), name='explorer_drafts'),
|
||||||
|
|
||||||
path('place/', PlaceListView.as_view(), name='place_list'),
|
path('place/', PlaceListView.as_view(), name='place_list'),
|
||||||
path('place/<int:pk>/', PlaceDetailView.as_view(), name='place_detail'),
|
path('place/<int:pk>/', PlaceDetailView.as_view(), name='place_detail'),
|
||||||
@@ -52,5 +56,8 @@ urlpatterns = [
|
|||||||
path('place/vote/<int:place_id>/<int:vote>', PlaceVoteView.as_view(), name='place_vote'),
|
path('place/vote/<int:place_id>/<int:vote>', PlaceVoteView.as_view(), name='place_vote'),
|
||||||
|
|
||||||
path('photo_album/create/<int:place_id>/', PhotoAlbumCreateView.as_view(), name='photo_album_create'),
|
path('photo_album/create/<int:place_id>/', PhotoAlbumCreateView.as_view(), name='photo_album_create'),
|
||||||
path('photo_album/delete/<int:pk>/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete')
|
path('photo_album/delete/<int:pk>/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete'),
|
||||||
|
|
||||||
|
path('import/upload', UploadMapFileView.as_view(), name='import_upload'),
|
||||||
|
path('import/<int:pk>', ImportDetailView.as_view(), name='import_detail')
|
||||||
]
|
]
|
||||||
|
@@ -5,4 +5,5 @@ from lostplaces.views.base_views import *
|
|||||||
from lostplaces.views.views import *
|
from lostplaces.views.views import *
|
||||||
from lostplaces.views.place_views import *
|
from lostplaces.views.place_views import *
|
||||||
from lostplaces.views.place_image_views import *
|
from lostplaces.views.place_image_views import *
|
||||||
from lostplaces.views.explorer_views import *
|
from lostplaces.views.explorer_views import *
|
||||||
|
from lostplaces.views.imports import *
|
@@ -31,6 +31,21 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
|
|||||||
messages.error(self.request, self.permission_denied_message)
|
messages.error(self.request, self.permission_denied_message)
|
||||||
return super().handle_no_permission()
|
return super().handle_no_permission()
|
||||||
|
|
||||||
|
class IsSuperUserMixin(UserPassesTestMixin, View):
|
||||||
|
'''
|
||||||
|
A view mixin that checks if the user is a superuser.
|
||||||
|
Users who are not logged in or users who are no superuser get
|
||||||
|
a permission deined message.
|
||||||
|
'''
|
||||||
|
permission_denied_message = _('You are not allowed to see this page')
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.is_superuser
|
||||||
|
|
||||||
|
def handle_no_permission(self):
|
||||||
|
messages.error(self.request, self.permission_denied_message)
|
||||||
|
return super().handle_no_permission()
|
||||||
|
|
||||||
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
|
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
|
||||||
'''
|
'''
|
||||||
A view mixin that checks wether a user is the submitter
|
A view mixin that checks wether a user is the submitter
|
||||||
@@ -138,4 +153,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
|
||||||
|
|
@@ -8,6 +8,7 @@ from django.utils.translation import gettext as _
|
|||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
|
|
||||||
from lostplaces.common import get_all_subclasses
|
from lostplaces.common import get_all_subclasses
|
||||||
from lostplaces.views.base_views import IsAuthenticatedMixin
|
from lostplaces.views.base_views import IsAuthenticatedMixin
|
||||||
@@ -18,7 +19,7 @@ from lostplaces.forms import ExplorerChangeForm, ExplorerUserChangeForm
|
|||||||
class ExplorerProfileView(IsAuthenticatedMixin, View):
|
class ExplorerProfileView(IsAuthenticatedMixin, View):
|
||||||
def get(self, request, explorer_id):
|
def get(self, request, explorer_id):
|
||||||
explorer = get_object_or_404(Explorer, pk=explorer_id)
|
explorer = get_object_or_404(Explorer, pk=explorer_id)
|
||||||
place_list = explorer.places.all()
|
place_list = explorer.get_place_list_to_display()
|
||||||
place_count = place_list.count()
|
place_count = place_list.count()
|
||||||
|
|
||||||
context={
|
context={
|
||||||
@@ -81,4 +82,21 @@ class ExplorerProfileUpdateView(IsAuthenticatedMixin, View):
|
|||||||
_('Please fill in all required fields.')
|
_('Please fill in all required fields.')
|
||||||
)
|
)
|
||||||
return redirect(reverse_lazy('explorer_profile_update'))
|
return redirect(reverse_lazy('explorer_profile_update'))
|
||||||
|
|
||||||
|
class ExplorerDraftsView(IsAuthenticatedMixin, UserPassesTestMixin, View):
|
||||||
|
|
||||||
|
def get_explorer(self):
|
||||||
|
return get_object_or_404(Explorer, pk=self.kwargs['explorer_id'])
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
if not hasattr(self.request, 'user'):
|
||||||
|
return False
|
||||||
|
return self.request.user == self.get_explorer().user or self.request.user.is_superuser
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
context = {
|
||||||
|
'explorer': self.get_explorer(),
|
||||||
|
'place_list': self.get_explorer().get_drafts()
|
||||||
|
}
|
||||||
|
return render(request, 'explorer/drafts.html', context)
|
||||||
|
|
91
django_lostplaces/lostplaces/views/imports.py
Normal file
91
django_lostplaces/lostplaces/views/imports.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
from django.views import View
|
||||||
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
|
from pykml import parser as kml_parser
|
||||||
|
|
||||||
|
from lostplaces.forms import UploadMapFileForm
|
||||||
|
from lostplaces.models import Place, PlaceImport
|
||||||
|
from lostplaces.views.base_views import (
|
||||||
|
IsSuperUserMixin
|
||||||
|
)
|
||||||
|
|
||||||
|
class UploadMapFileView(IsSuperUserMixin, View):
|
||||||
|
permission_denied_message = _('You are not allowed to import any places')
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
upload_form = UploadMapFileForm()
|
||||||
|
return render(request, 'import/upload_map_file.html', {'upload_form': upload_form})
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
upload_form = UploadMapFileForm(request.POST, request.FILES)
|
||||||
|
explorer = request.user.explorer
|
||||||
|
|
||||||
|
if upload_form.is_valid():
|
||||||
|
map_file = upload_form.cleaned_data['map_file']
|
||||||
|
parsed_kml = kml_parser.fromstring(map_file.read())
|
||||||
|
|
||||||
|
place_import = PlaceImport.objects.create(
|
||||||
|
explorer=request.user.explorer,
|
||||||
|
description=upload_form.cleaned_data['description']
|
||||||
|
)
|
||||||
|
|
||||||
|
for folder in parsed_kml.Document.Folder:
|
||||||
|
for place_kml in folder.Placemark:
|
||||||
|
lat_long = self.get_lat_long(place_kml)
|
||||||
|
|
||||||
|
name = str(place_kml.name) if hasattr(place_kml, 'name') else ''
|
||||||
|
description = str(place_kml.description) if hasattr(place_kml, 'description') else ''
|
||||||
|
|
||||||
|
place_model = Place.objects.create(
|
||||||
|
name=name.strip(),
|
||||||
|
latitude=lat_long[0],
|
||||||
|
longitude=lat_long[1],
|
||||||
|
description=description.strip(),
|
||||||
|
place_import=place_import,
|
||||||
|
mode='imported'
|
||||||
|
)
|
||||||
|
|
||||||
|
place_import.save()
|
||||||
|
|
||||||
|
return redirect(reverse('lostplaces_home'))
|
||||||
|
|
||||||
|
def get_lat_long(self, place_kml):
|
||||||
|
if hasattr(place_kml, 'Point') and len(place_kml.Point.coordinates) >= 1:
|
||||||
|
coordinates = str(place_kml.Point.coordinates[0]).strip()
|
||||||
|
splited = coordinates.split(',')
|
||||||
|
latitude = 0
|
||||||
|
longitude = 0
|
||||||
|
|
||||||
|
if len(splited) >= 1:
|
||||||
|
longitude = splited[0]
|
||||||
|
if len(splited) >= 2:
|
||||||
|
latitude = splited[1]
|
||||||
|
|
||||||
|
return (latitude, longitude)
|
||||||
|
else:
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
class ImportDetailView(IsSuperUserMixin, View):
|
||||||
|
permission_denied_message = _('You are not allowed to see this import\'s details')
|
||||||
|
|
||||||
|
def get_import(self):
|
||||||
|
return get_object_or_404(PlaceImport, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
place_import = self.get_import()
|
||||||
|
|
||||||
|
place_paginator = Paginator(place_import.place_list.all(), 18)
|
||||||
|
paginated_places = place_paginator.get_page(
|
||||||
|
request.GET.get('page')
|
||||||
|
)
|
||||||
|
context = {
|
||||||
|
'import': place_import,
|
||||||
|
'paginated_places': paginated_places,
|
||||||
|
'import_type': place_import.get_import_type_display()
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'import/import_detail_view.html', context)
|
||||||
|
|
@@ -36,7 +36,7 @@ from lostplaces.common import redirect_referer_or
|
|||||||
from taggit.models import Tag
|
from taggit.models import Tag
|
||||||
|
|
||||||
class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
|
class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
|
||||||
paginate_by = 5
|
paginate_by = 18
|
||||||
template_name = 'place/place_list.html'
|
template_name = 'place/place_list.html'
|
||||||
ordering = [Lower('name')]
|
ordering = [Lower('name')]
|
||||||
|
|
||||||
@@ -59,6 +59,17 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
|
|||||||
place.calculate_place_level()
|
place.calculate_place_level()
|
||||||
explorer = request.user.explorer
|
explorer = request.user.explorer
|
||||||
|
|
||||||
|
if place.mode == 'draft':
|
||||||
|
messages.info(
|
||||||
|
self.request,
|
||||||
|
_('This place is still in draft mode and only visible to the submitter and superusers')
|
||||||
|
)
|
||||||
|
elif place.mode == 'imported':
|
||||||
|
messages.info(
|
||||||
|
self.request,
|
||||||
|
_('This place was imported and not reviewed & correted yet. This place is visbible for superusers only and does not appear in the list views.')
|
||||||
|
)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'place': place,
|
'place': place,
|
||||||
'mapping_config': {
|
'mapping_config': {
|
||||||
@@ -74,7 +85,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 +124,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 +219,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 +235,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'))
|
||||||
|
|
||||||
|
@@ -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']
|
||||||
'all_points': place_list,
|
|
||||||
'map_center': Place.average_latlon(place_list)
|
context['mapping_config'] = {
|
||||||
}
|
'all_points': place_list,
|
||||||
|
'map_center': Place.average_latlon(place_list)
|
||||||
}
|
}
|
||||||
return render(request, 'home.html', context)
|
return 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]
|
||||||
|
Reference in New Issue
Block a user