#65 KML Import
This commit is contained in:
parent
e60a6ea9be
commit
3982db1375
1
Pipfile
1
Pipfile
@ -22,6 +22,7 @@ easy-thumbnails = "*"
|
||||
image = "*"
|
||||
django-widget-tweaks = "*"
|
||||
django-taggit = "*"
|
||||
pykml = "*"
|
||||
|
||||
[scripts]
|
||||
test = "django_lostplaces/manage.py test lostplaces"
|
||||
|
@ -127,3 +127,9 @@ class TagSubmitForm(forms.Form):
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'autocomplete':'off'})
|
||||
)
|
||||
|
||||
class UploadMapFileForm(forms.Form):
|
||||
map_file = forms.FileField()
|
||||
description = forms.CharField(
|
||||
widget=forms.Textarea
|
||||
)
|
||||
|
@ -27,9 +27,39 @@ PLACE_MODES = (
|
||||
('live', 'live'),
|
||||
('draft', 'draft'),
|
||||
('review', 'review'),
|
||||
('archive', 'archive')
|
||||
('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):
|
||||
"""
|
||||
Place defines a lost place (location, name, description etc.).
|
||||
@ -63,6 +93,16 @@ class Place(Submittable, Taggable, Mapable):
|
||||
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):
|
||||
if self.hero:
|
||||
return self.hero
|
||||
|
@ -59,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_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>
|
||||
</nav>
|
||||
</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 %}
|
@ -49,7 +49,6 @@
|
||||
{% include '../partials/tagging.html' with config=tagging_config %}
|
||||
</section>
|
||||
|
||||
{{votingplace.vote}}
|
||||
<section class="LP-Section">
|
||||
{% include '../partials/voting.html' with voting=placevoting %}
|
||||
</section>
|
||||
|
@ -24,7 +24,9 @@ from lostplaces.views import (
|
||||
ExplorerProfileView,
|
||||
ExplorerProfileUpdateView,
|
||||
ExplorerDraftsView,
|
||||
PlaceVoteView
|
||||
PlaceVoteView,
|
||||
UploadMapFileView,
|
||||
ImportDetailView
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@ -54,5 +56,8 @@ urlpatterns = [
|
||||
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/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')
|
||||
]
|
||||
|
@ -6,3 +6,4 @@ from lostplaces.views.views import *
|
||||
from lostplaces.views.place_views import *
|
||||
from lostplaces.views.place_image_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)
|
||||
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):
|
||||
'''
|
||||
A view mixin that checks wether a user is the submitter
|
||||
|
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)
|
||||
|
@ -12,7 +12,6 @@ from django.contrib import messages
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.urls import reverse_lazy, reverse
|
||||
@ -37,7 +36,7 @@ from lostplaces.common import redirect_referer_or
|
||||
from taggit.models import Tag
|
||||
|
||||
class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
|
||||
paginate_by = 5
|
||||
paginate_by = 18
|
||||
template_name = 'place/place_list.html'
|
||||
ordering = [Lower('name')]
|
||||
|
||||
@ -65,6 +64,11 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
|
||||
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 = {
|
||||
'place': place,
|
||||
|
Loading…
Reference in New Issue
Block a user