diff --git a/lostplaces/lostplaces_app/models.py b/lostplaces/lostplaces_app/models.py index a6d16fa..d513bb0 100644 --- a/lostplaces/lostplaces_app/models.py +++ b/lostplaces/lostplaces_app/models.py @@ -29,7 +29,7 @@ class Voucher(models.Model): 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) expires = models.DateField() @@ -63,10 +63,13 @@ class Place (models.Model): longitude = 0 latitude = 0 - for place in place_list: - longitude += place.longitude - latitude += place.latitude - return (latitude / amount, longitude / amount) + if amount > 0: + for place in place_list: + longitude += place.longitude + latitude += place.latitude + return (latitude / amount, longitude / amount) + + return (latitude, longitude) def __str__(self): return self.name diff --git a/lostplaces/templates/403.html b/lostplaces/lostplaces_app/templates/403.html similarity index 100% rename from lostplaces/templates/403.html rename to lostplaces/lostplaces_app/templates/403.html diff --git a/lostplaces/templates/registration/login.html b/lostplaces/lostplaces_app/templates/registration/login.html similarity index 100% rename from lostplaces/templates/registration/login.html rename to lostplaces/lostplaces_app/templates/registration/login.html diff --git a/lostplaces/templates/signup.html b/lostplaces/lostplaces_app/templates/signup.html similarity index 100% rename from lostplaces/templates/signup.html rename to lostplaces/lostplaces_app/templates/signup.html diff --git a/lostplaces/lostplaces_app/templatetags/svg_icon.py b/lostplaces/lostplaces_app/templatetags/svg_icon.py index 7090b6c..beb0038 100644 --- a/lostplaces/lostplaces_app/templatetags/svg_icon.py +++ b/lostplaces/lostplaces_app/templatetags/svg_icon.py @@ -1,11 +1,12 @@ -import json +import json, os from importlib import import_module from django.core.cache import cache from django.conf import settings 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)) register = Library() diff --git a/lostplaces/lostplaces_app/views.py b/lostplaces/lostplaces_app/views.py deleted file mode 100644 index bd07381..0000000 --- a/lostplaces/lostplaces_app/views.py +++ /dev/null @@ -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})) diff --git a/lostplaces/lostplaces_app/views/__init__.py b/lostplaces/lostplaces_app/views/__init__.py new file mode 100644 index 0000000..72591e2 --- /dev/null +++ b/lostplaces/lostplaces_app/views/__init__.py @@ -0,0 +1,3 @@ +from lostplaces_app.views.base_views import * +from lostplaces_app.views.views import * +from lostplaces_app.views.place_views import * \ No newline at end of file diff --git a/lostplaces/lostplaces_app/views/base_views.py b/lostplaces/lostplaces_app/views/base_views.py new file mode 100644 index 0000000..4eeded9 --- /dev/null +++ b/lostplaces/lostplaces_app/views/base_views.py @@ -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})) \ No newline at end of file diff --git a/lostplaces/lostplaces_app/views/place_views.py b/lostplaces/lostplaces_app/views/place_views.py new file mode 100644 index 0000000..f407d71 --- /dev/null +++ b/lostplaces/lostplaces_app/views/place_views.py @@ -0,0 +1,119 @@ +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 + +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 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() \ No newline at end of file diff --git a/lostplaces/lostplaces_app/views/views.py b/lostplaces/lostplaces_app/views/views.py new file mode 100644 index 0000000..28f7f25 --- /dev/null +++ b/lostplaces/lostplaces_app/views/views.py @@ -0,0 +1,42 @@ +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 +from lostplaces_app.models import Place, PhotoAlbum + +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' \ No newline at end of file