Compare commits

...

10 Commits

15 changed files with 418 additions and 352 deletions

View File

@ -12,6 +12,7 @@ easy-thumbnails = "*"
image = "*" image = "*"
django-widget-tweaks = "*" django-widget-tweaks = "*"
django-svg-icons = "*" django-svg-icons = "*"
django-taggit = "*"
# Commented out to not explicitly specify Python 3 subversion. # Commented out to not explicitly specify Python 3 subversion.
# [requires] # [requires]

View File

@ -43,7 +43,8 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'easy_thumbnails', 'easy_thumbnails',
'widget_tweaks' 'widget_tweaks',
'taggit'
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -48,3 +48,7 @@ class PlaceImageCreateForm(forms.ModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['filename'].required = False self.fields['filename'].required = False
class TagSubmitForm(forms.Form):
tag_list = forms.CharField(max_length=500)

View File

@ -10,6 +10,7 @@ from django.db import models
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from taggit.managers import TaggableManager
# Create your models here. # Create your models here.
@ -29,7 +30,7 @@ class Voucher(models.Model):
Creation date is being set automatically during voucher creation. Creation date is being set automatically during voucher creation.
""" """
code = models.CharField(unique=True, max_length=10) code = models.CharField(unique=True, max_length=30)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
expires = models.DateField() expires = models.DateField()
@ -55,6 +56,7 @@ class Place (models.Model):
longitude = models.FloatField() longitude = models.FloatField()
description = models.TextField() description = models.TextField()
tags = TaggableManager(blank=True)
# Get center position of LP-geocoordinates. # Get center position of LP-geocoordinates.
def average_latlon(place_list): def average_latlon(place_list):
@ -63,10 +65,13 @@ class Place (models.Model):
longitude = 0 longitude = 0
latitude = 0 latitude = 0
for place in place_list: if amount > 0:
longitude += place.longitude for place in place_list:
latitude += place.latitude longitude += place.longitude
return (latitude / amount, longitude / amount) latitude += place.latitude
return (latitude / amount, longitude / amount)
return (latitude, longitude)
def __str__(self): def __str__(self):
return self.name return self.name
@ -142,21 +147,21 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
class ExternalLink(models.Model): class ExternalLink(models.Model):
url = models.URLField(max_length=200) url = models.URLField(max_length=200)
label = models.CharField(max_length=100) label = models.CharField(max_length=100)
submitted_by = models.ForeignKey( submitted_by = models.ForeignKey(
Explorer, Explorer,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
blank=True, blank=True,
related_name='external_links' related_name='external_links'
) )
submitted_when = models.DateTimeField(auto_now_add=True, null=True) submitted_when = models.DateTimeField(auto_now_add=True, null=True)
class PhotoAlbum(ExternalLink): class PhotoAlbum(ExternalLink):
place = models.ForeignKey( place = models.ForeignKey(
Place, Place,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='photo_albums', related_name='photo_albums',
null=True null=True
) )

View File

@ -17,109 +17,127 @@
{% block title %}{{place.name}}{% endblock %} {% block title %}{{place.name}}{% endblock %}
{% block additional_menu_items %} {% block additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Edit place</span></a></li>
class="LP-Link__Text">Edit place</span></a></li> <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Delete place</span></a></li>
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span
class="LP-Link__Text">Delete place</span></a></li>
{% endblock additional_menu_items %} {% endblock additional_menu_items %}
{% 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 }}</h1> <h1 class="LP-Headline">{{ place.name }}</h1>
{% if place.images.first.filename.hero.url %} {% if place.images.first.filename.hero.url %}
<figure class="LP-PlaceDetail__Image"> <figure class="LP-PlaceDetail__Image">
<img src="{{ place.images.first.filename.hero.url }}" class="LP-Image" /> <img src="{{ place.images.first.filename.hero.url }}" class="LP-Image" />
</figure> </figure>
{% endif %} {% endif %}
</header> </header>
<div class="LP-PlaceDetail__Description"> <div class="LP-PlaceDetail__Description">
<p class="LP-Paragraph">{{ place.description }}</p> <p class="LP-Paragraph">{{ place.description }}</p>
</div> </div>
<section class="LP-Section"> <section class="LP-Section">
<h1 class="LP-Headline">Map-Links</h1> <!--
{% include 'partials/osm_map.html' %} <input name='basic' value='tag1, tag2'>
<div class="LP-LinkList"> <script>
<ul class="LP-LinkList__Container"> // The DOM element you wish to replace with Tagify
<li class="LP-LinkList__Item"><a target="_blank" var input = document.querySelector('input[name=basic]');
href="https://www.google.com/maps?q={{place.latitude}},{{place.longitude}}"
class="LP-Link"><span class="LP-Text">Google Maps</span></a></li>
<li class="LP-LinkList__Item"><a target="_blank"
href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude}},{{place.longitude}}&icon=true&bg=dop"
class="LP-Link"><span class="LP-Text">TIM Online</span></a></li>
<li class="LP-LinkList__Item"><a target="_blank"
href="http://www.openstreetmap.org/?mlat={{place.latitude}}&mlon={{place.longitude}}&zoom=16"
class="LP-Link"><span class="LP-Text">OSM</span></a></li>
</ul>
</div>
</section>
<section class="LP-Section"> // initialize Tagify on the above input node reference
<input name='basic' value='tag1, tag2'> new Tagify(input, {
<script> 'whitelist': ['wurstwasser']
// The DOM element you wish to replace with Tagify })
var input = document.querySelector('input[name=basic]'); </script>-->
// initialize Tagify on the above input node reference <div class="LP-TagList">
new Tagify(input, { <ul class="LP-TagList__List">
'whitelist': ['wurstwasser'] {% for tag in place.tags.all %}
}) <li class="LP-TagList__Item">
</script> <div class="LP-Tag">
</section> <p class="LP-Paragraph">{{tag}}</p>
</div>
</li>
{% endfor %}
</ul>
</div>
<section class="LP-Section"> <form class="LP-Form LP-Form--inline" method="POST" action="{% url 'place_tag_submit' place_id=place.id %}">
<h1 class="LP-Headline">Photoalben</h1> <fieldset class="LP-Form__Fieldset">
<div class="LP-LinkList"> <legend class="LP-Form__Legend">Tags hinzufügen</legend>
<ul class="LP-LinkList__Container"> {% csrf_token %}
{% for photo_album in place.photo_albums.all %} <div class="LP-Form__Composition LP-Form__Composition--breakable">
<li class="LP-LinkList__Item"> <div class="LP-Form__Field">
<a target="_blank" href="{{photo_album.url}}" class="LP-Link"> {% include 'partials/form/inputField.html' with field=tagging_form.tag_list %}
<span class="LP-Text">{{photo_album.label}}</span> </div>
</a> <div class="LP-Form__Field LP-Form__Button LP-Input">
{% if user == photo_album.submitted_by or user == place.submitted_by %} <button class="LP-Button">hinzufügen</button>
<a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album"> </div>
<div class="RV-Iconized__Container RV-Iconized__Container--small"> </div>
{% icon 'trash' className="RV-Iconized__Icon" %} </fieldset>
</div>
</a> </form>
{% endif %} </section>
</li>
{% endfor %} <section class="LP-Section">
<li class="LP-LinkList__Item"> <h1 class="LP-Headline">Map-Links</h1>
<a href="{% url 'photo_album_create' place_id=place.id %}" class="LP-Link"> {% include 'partials/osm_map.html' %}
<div class="RV-Iconized__Container RV-Iconized__Container--small"> <div class="LP-LinkList">
<svg class="RV-Iconized__Icon" version="1.1" id="Capa_1" <ul class="LP-LinkList__Container">
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" <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>
x="0px" y="0px" viewBox="0 0 512 512" xml:space="preserve"> <li class="LP-LinkList__Item"><a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude}},{{place.longitude}}&icon=true&bg=dop" class="LP-Link"><span class="LP-Text">TIM Online</span></a></li>
<g> <li class="LP-LinkList__Item"><a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude}}&mlon={{place.longitude}}&zoom=16" class="LP-Link"><span class="LP-Text">OSM</span></a></li>
<path d="M492,236H276V20c0-11.046-8.954-20-20-20c-11.046,0-20,8.954-20,20v216H20c-11.046,0-20,8.954-20,20s8.954,20,20,20h216 </ul>
</div>
</section>
<section class=" LP-Section">
<h1 class="LP-Headline">Photoalben</h1>
<div class="LP-LinkList">
<ul class="LP-LinkList__Container">
{% for photo_album in place.photo_albums.all %}
<li class="LP-LinkList__Item">
<a target="_blank" href="{{photo_album.url}}" class="LP-Link">
<span class="LP-Text">{{photo_album.label}}</span>
</a>
{% if user == photo_album.submitted_by or user == place.submitted_by %}
<a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album">
<div class="RV-Iconized__Container RV-Iconized__Container--small">
{% icon 'trash' className="RV-Iconized__Icon" %}
</div>
</a>
{% endif %}
</li>
{% endfor %}
<li class="LP-LinkList__Item">
<a href="{% url 'photo_album_create' place_id=place.id %}" class="LP-Link">
<div class="RV-Iconized__Container RV-Iconized__Container--small">
<svg class="RV-Iconized__Icon" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" xml:space="preserve">
<g>
<path d="M492,236H276V20c0-11.046-8.954-20-20-20c-11.046,0-20,8.954-20,20v216H20c-11.046,0-20,8.954-20,20s8.954,20,20,20h216
v216c0,11.046,8.954,20,20,20s20-8.954,20-20V276h216c11.046,0,20-8.954,20-20C512,244.954,503.046,236,492,236z" /> v216c0,11.046,8.954,20,20,20s20-8.954,20-20V276h216c11.046,0,20-8.954,20-20C512,244.954,503.046,236,492,236z" />
</g> </g>
</svg> </svg>
<span class="RV-Iconized__Text">Fotoalbum hinzufügen</span> <span class="RV-Iconized__Text">Fotoalbum hinzufügen</span>
</div> </div>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</section> </section>
<section class="LP-Section"> <section class="LP-Section">
<h1 class="LP-Headline">Bilder</h1> <h1 class="LP-Headline">Bilder</h1>
<div class="LP-ImageGrid"> <div class="LP-ImageGrid">
<ul class="LP-ImageGrid__Container"> <ul class="LP-ImageGrid__Container">
{% for place_image in place.images.all %} {% for place_image in place.images.all %}
<li class="LP-ImageGrid__Item"> <li class="LP-ImageGrid__Item">
<a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image" <a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image" src="{{ place_image.filename.thumbnail.url }}"></a>
src="{{ place_image.filename.thumbnail.url }}"></a> </li>
</li> {% endfor %}
{% endfor %} </ul>
</ul> </div>
</div> </section>
</section>
</article> </article>
{% endblock maincontent %} {% endblock maincontent %}

View File

@ -1,11 +1,12 @@
import json import json, os
from importlib import import_module from importlib import import_module
from django.core.cache import cache from django.core.cache import cache
from django.conf import settings from django.conf import settings
from django.template import Library, TemplateSyntaxError from django.template import Library, TemplateSyntaxError
icons_json_path = getattr(settings, 'SVG_ICONS_SOURCE_FILE') #icons_json_path = getattr(settings, 'SVG_ICONS_SOURCE_FILE')
icons_json_path = os.path.join(settings.BASE_DIR, 'lostplaces_app', 'static', 'icons', 'icons.icomoon.json')
icons_json = json.load(open(icons_json_path)) icons_json = json.load(open(icons_json_path))
register = Library() register = Library()

View File

@ -8,7 +8,8 @@ from .views import (
PlaceUpdateView, PlaceUpdateView,
PlaceDeleteView, PlaceDeleteView,
PhotoAlbumCreateView, PhotoAlbumCreateView,
PhotoAlbumDeleteView PhotoAlbumDeleteView,
PlaceTagSubmitView
) )
urlpatterns = [ urlpatterns = [
@ -20,5 +21,6 @@ urlpatterns = [
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('place/update/<int:pk>/', PlaceUpdateView.as_view(), name='place_edit'), path('place/update/<int:pk>/', PlaceUpdateView.as_view(), name='place_edit'),
path('place/delete/<int:pk>/', PlaceDeleteView.as_view(), name='place_delete'), path('place/delete/<int:pk>/', PlaceDeleteView.as_view(), name='place_delete'),
path('place/', PlaceListView.as_view(), name='place_list') path('place/', PlaceListView.as_view(), name='place_list'),
path('place/tag/<int:place_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
] ]

View File

@ -1,245 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' Django views. '''
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.views import View
from django.http import Http404
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from .forms import (
ExplorerCreationForm,
PlaceForm,
PlaceImageCreateForm
)
from .models import Place, PlaceImage, Voucher, PhotoAlbum
# Create your views here.
# BaseView that checks if user is logged in.
class IsAuthenticated(LoginRequiredMixin, View):
redirect_field_name = 'redirect_to'
login_required_message = 'Please login to proceed'
def handle_no_permission(self):
messages.error(self.request, self.login_required_message)
return super().handle_no_permission()
# BaseView that checks if logged in user is submitter of place.
class IsPlaceSubmitter(UserPassesTestMixin, View):
place_submitter_error_message = None
def get_place(self):
pass
def test_func(self):
""" Check if user is eligible to modify place. """
if not hasattr(self.request, 'user'):
return False
if self.request.user.is_superuser:
return True
# Check if currently logged in user was the submitter
place_obj = self.get_place()
if place_obj and hasattr(place_obj, 'submitted_by') and self.request.user == place_obj.submitted_by:
return True
if self.place_submitter_error_message:
messages.error(self.request, self.place_submitter_error_message)
return False
class SignUpView(SuccessMessageMixin, CreateView):
form_class = ExplorerCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
success_message = 'User created.'
class PlaceListView(IsAuthenticated, ListView):
paginate_by = 5
model = Place
template_name = 'place/place_list.html'
ordering = ['name']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place_map_center'] = Place.average_latlon(context['place_list'])
return context
class PlaceDetailView(IsAuthenticated, 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 ]
}
return render(request, 'place/place_detail.html', context)
class HomeView(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
}
return render(request, 'home.html', context)
class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView):
template_name = 'place/place_update.html'
model = Place
form_class = PlaceForm
success_message = 'Successfully updated place.'
place_submitter_error_message = 'You do no have permissions to alter this place'
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk})
def get_place(self):
return self.get_object()
class PlaceCreateView(IsAuthenticated, View):
def get(self, request, *args, **kwargs):
place_image_form = PlaceImageCreateForm()
place_form = PlaceForm()
context = {
'place_form': place_form,
'place_image_form': place_image_form
}
return render(request, 'place/place_create.html', context)
def post(self, request, *args, **kwargs):
place_form = PlaceForm(request.POST)
if place_form.is_valid():
submitter = request.user
place = place_form.save(commit=False)
# Save logged in user as "submitted_by"
place.submitted_by = submitter
place.save()
if request.FILES:
self._apply_multipart_image_upload(
files=request.FILES.getlist('filename'),
place=place,
submitter=submitter
)
kwargs_to_pass = {
'pk': place.pk
}
messages.success(
self.request, 'Successfully created place.')
return redirect(reverse_lazy('place_detail', kwargs=kwargs_to_pass))
else:
context = {
'form': form_place
}
# Usually the browser should have checked the form before sending.
messages.error(
self.request, 'Please fill in all required fields.')
return render(request, 'place/place_create.html', context)
def _apply_multipart_image_upload(self, files, place, submitter):
for image in files:
place_image = PlaceImage.objects.create(
filename=image,
place=place,
submitted_by=submitter
)
place_image.save()
class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView):
template_name = 'place/place_delete.html'
model = Place
success_message = 'Successfully deleted place.'
success_url = reverse_lazy('place_list')
success_message = 'Place deleted'
place_submitter_error_message = 'You do no have permission to delete this place'
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super().delete(request, *args, **kwargs)
def get_place(self):
return self.get_object()
class AlbumCreateView(IsAuthenticated, View):
def get(self, request, *args, **kwargs):
url = request.GET['url']
place_id = request.GET['place_id']
place = Place.objects.get(pk=place_id)
photo_album = PhotoAlbum()
photo_album.url = url
photo_album.place = place
photo_album.submitted_by = request.user
photo_album.save()
print(photo_album)
return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id}))
class PhotoAlbumCreateView(IsAuthenticated, SuccessMessageMixin, CreateView):
model = PhotoAlbum
fields = ['url', 'label']
template_name = 'photo_album/photo_album_create.html'
success_message = 'Photo Album submitted'
def get(self, request, place_id, *args, **kwargs):
self.place = Place.objects.get(pk=place_id)
return super().get(request, *args, **kwargs)
def post(self, request, place_id, *args, **kwargs):
self.place = Place.objects.get(pk=place_id)
response = super().post(request, *args, **kwargs)
self.object.place = self.place
self.object.submitted_by = request.user
self.object.save()
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place'] = self.place
return context
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk': self.place.id})
class PhotoAlbumDeleteView(IsAuthenticated, IsPlaceSubmitter, SingleObjectMixin, View):
model = PhotoAlbum
pk_url_kwarg = 'pk'
success_message = 'Photo Album deleted'
def get_place(self):
place_id = self.get_object().place.id
return Place.objects.get(pk=place_id)
def test_func(self):
can_edit_place = super().test_func()
if can_edit_place:
return True
if self.get_object().submitted_by == self.request.user:
return True
messages.error(self.request, 'You do not have permissions to alter this photo album')
return False
def get(self, request, *args, **kwargs):
place_id = self.get_object().place.id
self.get_object().delete()
messages.success(self.request, self.success_message)
return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id}))

View File

@ -0,0 +1,3 @@
from lostplaces_app.views.base_views import *
from lostplaces_app.views.views import *
from lostplaces_app.views.place_views import *

View File

@ -0,0 +1,98 @@
from django.views import View
from django.views.generic.edit import CreateView
from django.views.generic.detail import SingleObjectMixin
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect
from django.urls import reverse_lazy
from lostplaces_app.models import Place
class IsAuthenticated(LoginRequiredMixin, View):
redirect_field_name = 'redirect_to'
permission_denied_message = 'Please login to proceed'
def handle_no_permission(self):
messages.error(self.request, self.permission_denied_message)
return super().handle_no_permission()
class IsPlaceSubmitter(UserPassesTestMixin, View):
place_submitter_error_message = None
def get_place(self):
pass
def test_func(self):
""" Check if user is eligible to modify place. """
if not hasattr(self.request, 'user'):
return False
if self.request.user.is_superuser:
return True
# Check if currently logged in user was the submitter
place_obj = self.get_place()
if place_obj and hasattr(place_obj, 'submitted_by') and self.request.user == place_obj.submitted_by:
return True
if self.place_submitter_error_message:
messages.error(self.request, self.place_submitter_error_message)
return False
class PlaceAssetCreateView(IsAuthenticated, SuccessMessageMixin, CreateView):
model = None
fields = []
template_name = ''
success_message = ''
def get(self, request, place_id, *args, **kwargs):
self.place = Place.objects.get(pk=place_id)
return super().get(request, *args, **kwargs)
def post(self, request, place_id, *args, **kwargs):
self.place = Place.objects.get(pk=place_id)
response = super().post(request, *args, **kwargs)
self.object.place = self.place
self.object.submitted_by = request.user
self.object.save()
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place'] = self.place
return context
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk': self.place.id})
class PlaceAssetDeleteView(IsAuthenticated, IsPlaceSubmitter, SingleObjectMixin, View):
model = None
pk_url_kwarg = 'pk'
success_message = ''
permission_denied_message = ''
def get_place(self):
place_id = self.get_object().place.id
return Place.objects.get(pk=place_id)
def test_func(self):
can_edit_place = super().test_func()
if can_edit_place:
return True
if self.get_object().submitted_by == self.request.user:
return True
messages.error(self.request, self.permission_denied_message)
return False
def get(self, request, *args, **kwargs):
place_id = self.get_object().place.id
self.get_object().delete()
messages.success(self.request, self.success_message)
return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id}))

View File

@ -0,0 +1,120 @@
from django.views import View
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
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.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm
class PlaceListView(IsAuthenticated, ListView):
paginate_by = 5
model = Place
template_name = 'place/place_list.html'
ordering = ['name']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place_map_center'] = Place.average_latlon(context['place_list'])
return context
class PlaceDetailView(IsAuthenticated, 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 ],
'tagging_form': TagSubmitForm()
}
return render(request, 'place/place_detail.html', context)
class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView):
template_name = 'place/place_update.html'
model = Place
form_class = PlaceForm
success_message = 'Successfully updated place.'
place_submitter_error_message = 'You do no have permissions to alter this place'
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk})
def get_place(self):
return self.get_object()
class PlaceCreateView(IsAuthenticated, View):
def get(self, request, *args, **kwargs):
place_image_form = PlaceImageCreateForm()
place_form = PlaceForm()
context = {
'place_form': place_form,
'place_image_form': place_image_form
}
return render(request, 'place/place_create.html', context)
def post(self, request, *args, **kwargs):
place_form = PlaceForm(request.POST)
if place_form.is_valid():
submitter = request.user
place = place_form.save(commit=False)
# Save logged in user as "submitted_by"
place.submitted_by = submitter
place.save()
if request.FILES:
self._apply_multipart_image_upload(
files=request.FILES.getlist('filename'),
place=place,
submitter=submitter
)
kwargs_to_pass = {
'pk': place.pk
}
messages.success(
self.request, 'Successfully created place.')
return redirect(reverse_lazy('place_detail', kwargs=kwargs_to_pass))
else:
context = {
'form': form_place
}
# Usually the browser should have checked the form before sending.
messages.error(
self.request, 'Please fill in all required fields.')
return render(request, 'place/place_create.html', context)
def _apply_multipart_image_upload(self, files, place, submitter):
for image in files:
place_image = PlaceImage.objects.create(
filename=image,
place=place,
submitted_by=submitter
)
place_image.save()
class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView):
template_name = 'place/place_delete.html'
model = Place
success_message = 'Successfully deleted place.'
success_url = reverse_lazy('place_list')
success_message = 'Place deleted'
place_submitter_error_message = 'You do no have permission to delete this place'
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super().delete(request, *args, **kwargs)
def get_place(self):
return self.get_object()

View File

@ -0,0 +1,58 @@
from django.views import View
from django.views.generic.edit import CreateView
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib import messages
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
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 (
PlaceAssetCreateView,
PlaceAssetDeleteView
)
class SignUpView(SuccessMessageMixin, CreateView):
form_class = ExplorerCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
success_message = 'User created.'
class HomeView(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
}
return render(request, 'home.html', context)
class PhotoAlbumCreateView(PlaceAssetCreateView):
model = PhotoAlbum
fields = ['url', 'label']
template_name = 'photo_album/photo_album_create.html'
success_message = 'Photo Album submitted'
class PhotoAlbumDeleteView(PlaceAssetDeleteView):
model = PhotoAlbum
pk_url_kwarg = 'pk'
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)
form = TagSubmitForm(request.POST)
if form.is_valid():
tag_list_raw = form.cleaned_data['tag_list']
tag_list_raw = tag_list_raw.strip().split(',')
tag_list = []
for tag in tag_list_raw:
tag_list.append(tag.strip())
place.tags.add(*tag_list)
place.save()
return redirect(reverse_lazy('place_detail', kwargs={'pk': place.id}))