From 7a757bcf35a946e0a7db9e4bbda1db8820ca498f Mon Sep 17 00:00:00 2001 From: reverend Date: Fri, 28 Aug 2020 14:34:09 +0200 Subject: [PATCH 01/10] Moved Templates --- lostplaces/{ => lostplaces_app}/templates/403.html | 0 lostplaces/{ => lostplaces_app}/templates/registration/login.html | 0 lostplaces/{ => lostplaces_app}/templates/signup.html | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename lostplaces/{ => lostplaces_app}/templates/403.html (100%) rename lostplaces/{ => lostplaces_app}/templates/registration/login.html (100%) rename lostplaces/{ => lostplaces_app}/templates/signup.html (100%) 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 From 2007b512c7338d154ee3336d1898325681ad9960 Mon Sep 17 00:00:00 2001 From: reverend Date: Sun, 30 Aug 2020 16:53:58 +0200 Subject: [PATCH 02/10] Bugfix --- lostplaces/lostplaces_app/templatetags/svg_icon.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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() From ca2eac533fb61369729e928064a2c3dec3a46210 Mon Sep 17 00:00:00 2001 From: reverend Date: Sun, 30 Aug 2020 16:54:11 +0200 Subject: [PATCH 03/10] Removed unused View --- lostplaces/lostplaces_app/views.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lostplaces/lostplaces_app/views.py b/lostplaces/lostplaces_app/views.py index 49d73a6..4b2414f 100644 --- a/lostplaces/lostplaces_app/views.py +++ b/lostplaces/lostplaces_app/views.py @@ -179,19 +179,6 @@ class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView): 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'] From 538b43c2a15eabee42bf598fc045dc90a6459b4f Mon Sep 17 00:00:00 2001 From: reverend Date: Sun, 30 Aug 2020 17:11:24 +0200 Subject: [PATCH 04/10] Splitted views in multiple files --- lostplaces/lostplaces_app/views.py | 232 ------------------ lostplaces/lostplaces_app/views/__init__.py | 3 + lostplaces/lostplaces_app/views/base_views.py | 37 +++ .../lostplaces_app/views/place_views.py | 119 +++++++++ lostplaces/lostplaces_app/views/views.py | 80 ++++++ 5 files changed, 239 insertions(+), 232 deletions(-) delete mode 100644 lostplaces/lostplaces_app/views.py create mode 100644 lostplaces/lostplaces_app/views/__init__.py create mode 100644 lostplaces/lostplaces_app/views/base_views.py create mode 100644 lostplaces/lostplaces_app/views/place_views.py create mode 100644 lostplaces/lostplaces_app/views/views.py diff --git a/lostplaces/lostplaces_app/views.py b/lostplaces/lostplaces_app/views.py deleted file mode 100644 index 4b2414f..0000000 --- a/lostplaces/lostplaces_app/views.py +++ /dev/null @@ -1,232 +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' - 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() - -# 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 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..0fe6d5a --- /dev/null +++ b/lostplaces/lostplaces_app/views/base_views.py @@ -0,0 +1,37 @@ +from django.views import View + +from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin +from django.contrib import messages + +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 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..81fc63e --- /dev/null +++ b/lostplaces/lostplaces_app/views/views.py @@ -0,0 +1,80 @@ +from django.views import View +from django.views.generic.edit import CreateView +from django.views.generic.detail import SingleObjectMixin + +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 IsAuthenticated, IsPlaceSubmitter +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(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})) \ No newline at end of file From 7a3b8529f84557bed1bd1be3130e4573666e5f83 Mon Sep 17 00:00:00 2001 From: reverend Date: Sun, 30 Aug 2020 17:26:43 +0200 Subject: [PATCH 05/10] base views for place assets --- lostplaces/lostplaces_app/views/base_views.py | 63 ++++++++++++++++++- lostplaces/lostplaces_app/views/views.py | 52 +++------------ 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/lostplaces/lostplaces_app/views/base_views.py b/lostplaces/lostplaces_app/views/base_views.py index 0fe6d5a..4eeded9 100644 --- a/lostplaces/lostplaces_app/views/base_views.py +++ b/lostplaces/lostplaces_app/views/base_views.py @@ -1,7 +1,15 @@ from django.views import View +from django.views.generic.edit import CreateView +from django.views.generic.detail import SingleObjectMixin -from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin 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' @@ -35,3 +43,56 @@ class IsPlaceSubmitter(UserPassesTestMixin, View): 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/views.py b/lostplaces/lostplaces_app/views/views.py index 81fc63e..28f7f25 100644 --- a/lostplaces/lostplaces_app/views/views.py +++ b/lostplaces/lostplaces_app/views/views.py @@ -1,6 +1,5 @@ from django.views import View from django.views.generic.edit import CreateView -from django.views.generic.detail import SingleObjectMixin from django.contrib.messages.views import SuccessMessageMixin from django.contrib import messages @@ -10,7 +9,10 @@ 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 IsAuthenticated, IsPlaceSubmitter +from lostplaces_app.views.base_views import ( + PlaceAssetCreateView, + PlaceAssetDeleteView +) class SignUpView(SuccessMessageMixin, CreateView): form_class = ExplorerCreationForm success_url = reverse_lazy('login') @@ -27,54 +29,14 @@ class HomeView(View): } return render(request, 'home.html', context) -class PhotoAlbumCreateView(IsAuthenticated, SuccessMessageMixin, CreateView): +class PhotoAlbumCreateView(PlaceAssetCreateView): 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): +class PhotoAlbumDeleteView(PlaceAssetDeleteView): 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})) \ No newline at end of file + permission_denied_messsage = 'You do not have permissions to alter this photo album' \ No newline at end of file From 66581a9d2d22169d29ad7790e338a11d340a8f63 Mon Sep 17 00:00:00 2001 From: reverend Date: Sun, 30 Aug 2020 18:39:45 +0200 Subject: [PATCH 06/10] Adding tags is now possible --- Pipfile | 1 + lostplaces/lostplaces/settings.py | 3 +- lostplaces/lostplaces_app/forms.py | 4 + lostplaces/lostplaces_app/models.py | 14 +- .../templates/place/place_detail.html | 200 ++++++++++-------- lostplaces/lostplaces_app/urls.py | 6 +- .../lostplaces_app/views/place_views.py | 5 +- lostplaces/lostplaces_app/views/views.py | 20 +- 8 files changed, 149 insertions(+), 104 deletions(-) diff --git a/Pipfile b/Pipfile index d275152..7ece21a 100644 --- a/Pipfile +++ b/Pipfile @@ -12,6 +12,7 @@ easy-thumbnails = "*" image = "*" django-widget-tweaks = "*" django-svg-icons = "*" +django-taggit = "*" # Commented out to not explicitly specify Python 3 subversion. # [requires] diff --git a/lostplaces/lostplaces/settings.py b/lostplaces/lostplaces/settings.py index 3e53bc4..b6d5dd9 100644 --- a/lostplaces/lostplaces/settings.py +++ b/lostplaces/lostplaces/settings.py @@ -43,7 +43,8 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'easy_thumbnails', - 'widget_tweaks' + 'widget_tweaks', + 'taggit' ] MIDDLEWARE = [ diff --git a/lostplaces/lostplaces_app/forms.py b/lostplaces/lostplaces_app/forms.py index d88d090..f08e771 100644 --- a/lostplaces/lostplaces_app/forms.py +++ b/lostplaces/lostplaces_app/forms.py @@ -48,3 +48,7 @@ class PlaceImageCreateForm(forms.ModelForm): super().__init__(*args, **kwargs) self.fields['filename'].required = False + + +class TagSubmitForm(forms.Form): + tag_list = forms.CharField(max_length=500) \ No newline at end of file diff --git a/lostplaces/lostplaces_app/models.py b/lostplaces/lostplaces_app/models.py index d513bb0..cf625a2 100644 --- a/lostplaces/lostplaces_app/models.py +++ b/lostplaces/lostplaces_app/models.py @@ -10,6 +10,7 @@ from django.db import models from django.dispatch import receiver from django.contrib.auth.models import AbstractUser from easy_thumbnails.fields import ThumbnailerImageField +from taggit.managers import TaggableManager # Create your models here. @@ -55,6 +56,7 @@ class Place (models.Model): longitude = models.FloatField() description = models.TextField() + tags = TaggableManager(blank=True) # Get center position of LP-geocoordinates. def average_latlon(place_list): @@ -145,21 +147,21 @@ def auto_delete_file_on_change(sender, instance, **kwargs): class ExternalLink(models.Model): - url = models.URLField(max_length=200) - label = models.CharField(max_length=100) - submitted_by = models.ForeignKey( + url = models.URLField(max_length=200) + label = models.CharField(max_length=100) + submitted_by = models.ForeignKey( Explorer, on_delete=models.SET_NULL, null=True, blank=True, 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): - place = models.ForeignKey( + place = models.ForeignKey( Place, on_delete=models.CASCADE, related_name='photo_albums', - null=True + null=True ) \ No newline at end of file diff --git a/lostplaces/lostplaces_app/templates/place/place_detail.html b/lostplaces/lostplaces_app/templates/place/place_detail.html index ce6d90c..3c48536 100644 --- a/lostplaces/lostplaces_app/templates/place/place_detail.html +++ b/lostplaces/lostplaces_app/templates/place/place_detail.html @@ -17,109 +17,127 @@ {% block title %}{{place.name}}{% endblock %} {% block additional_menu_items %} -
  • Edit place
  • -
  • Delete place
  • +
  • Edit place
  • +
  • Delete place
  • {% endblock additional_menu_items %} {% block maincontent %}
    -
    -

    {{ place.name }}

    - {% if place.images.first.filename.hero.url %} -
    - -
    - {% endif %} -
    +
    +

    {{ place.name }}

    + {% if place.images.first.filename.hero.url %} +
    + +
    + {% endif %} +
    -
    -

    {{ place.description }}

    -
    +
    +

    {{ place.description }}

    +
    -
    -

    Map-Links

    - {% include 'partials/osm_map.html' %} - -
    +
    + - // initialize Tagify on the above input node reference - new Tagify(input, { - 'whitelist': ['wurstwasser'] - }) - -
    +
    +
      + {% for tag in place.tags.all %} +
    • +
      +

      {{tag}}

      +
      +
    • + {% endfor %} +
    +
    -
    -

    Photoalben

    -
    + +
    +

    Map-Links

    + {% include 'partials/osm_map.html' %} +
    +
    + +
    +

    Photoalben

    + -
    + + + Fotoalbum hinzufügen + + + + + + -
    -

    Bilder

    -
    -
      - {% for place_image in place.images.all %} -
    • - -
    • - {% endfor %} -
    -
    -
    +
    +

    Bilder

    +
    +
      + {% for place_image in place.images.all %} +
    • + +
    • + {% endfor %} +
    +
    +
    {% endblock maincontent %} \ No newline at end of file diff --git a/lostplaces/lostplaces_app/urls.py b/lostplaces/lostplaces_app/urls.py index b319793..d5d7b15 100644 --- a/lostplaces/lostplaces_app/urls.py +++ b/lostplaces/lostplaces_app/urls.py @@ -8,7 +8,8 @@ from .views import ( PlaceUpdateView, PlaceDeleteView, PhotoAlbumCreateView, - PhotoAlbumDeleteView + PhotoAlbumDeleteView, + PlaceTagSubmitView ) urlpatterns = [ @@ -20,5 +21,6 @@ urlpatterns = [ path('photo_album/delete/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete'), path('place/update//', PlaceUpdateView.as_view(), name='place_edit'), path('place/delete//', 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/', PlaceTagSubmitView.as_view(), name='place_tag_submit'), ] diff --git a/lostplaces/lostplaces_app/views/place_views.py b/lostplaces/lostplaces_app/views/place_views.py index f407d71..1e5bc67 100644 --- a/lostplaces/lostplaces_app/views/place_views.py +++ b/lostplaces/lostplaces_app/views/place_views.py @@ -11,7 +11,7 @@ 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 +from lostplaces_app.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm class PlaceListView(IsAuthenticated, ListView): paginate_by = 5 @@ -30,7 +30,8 @@ class PlaceDetailView(IsAuthenticated, View): context = { 'place': place, 'place_list': [ place ], - 'place_map_center': [ place.latitude, place.longitude ] + 'place_map_center': [ place.latitude, place.longitude ], + 'tagging_form': TagSubmitForm() } return render(request, 'place/place_detail.html', context) diff --git a/lostplaces/lostplaces_app/views/views.py b/lostplaces/lostplaces_app/views/views.py index 28f7f25..5725b58 100644 --- a/lostplaces/lostplaces_app/views/views.py +++ b/lostplaces/lostplaces_app/views/views.py @@ -6,8 +6,9 @@ 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.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, @@ -39,4 +40,19 @@ 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 + 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})) From e4cd8bb30155bd7dfce274c44dc6f24ba72cab27 Mon Sep 17 00:00:00 2001 From: reverend Date: Mon, 31 Aug 2020 18:18:24 +0200 Subject: [PATCH 07/10] Tagging using JS using Partials --- .../templates/partials/tagging.html | 53 +++++++++++++++++++ .../templates/place/place_detail.html | 39 +------------- .../lostplaces_app/views/place_views.py | 5 +- 3 files changed, 59 insertions(+), 38 deletions(-) create mode 100644 lostplaces/lostplaces_app/templates/partials/tagging.html diff --git a/lostplaces/lostplaces_app/templates/partials/tagging.html b/lostplaces/lostplaces_app/templates/partials/tagging.html new file mode 100644 index 0000000..e0af9dd --- /dev/null +++ b/lostplaces/lostplaces_app/templates/partials/tagging.html @@ -0,0 +1,53 @@ +
    +
      + {% for tag in tag_list %} +
    • +
      +

      {{tag}}

      +
      +
    • + {% endfor %} +
    +
    + +
    +
    + Tags hinzufügen + {% csrf_token %} +
    +
    + {% include 'partials/form/inputField.html' with field=input_field %} +
    +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/lostplaces/lostplaces_app/templates/place/place_detail.html b/lostplaces/lostplaces_app/templates/place/place_detail.html index 3c48536..d7351c9 100644 --- a/lostplaces/lostplaces_app/templates/place/place_detail.html +++ b/lostplaces/lostplaces_app/templates/place/place_detail.html @@ -38,45 +38,10 @@
    - + {% url 'place_tag_submit' place_id=place.id as tag_submit_url%} + {% include 'partials/tagging.html' with tag_list=place.tags.all url=tag_submit_url input_field=tagging_form.tag_list%} -
    -
      - {% for tag in place.tags.all %} -
    • -
      -

      {{tag}}

      -
      -
    • - {% endfor %} -
    -
    - -
    -
    - Tags hinzufügen - {% csrf_token %} -
    -
    - {% include 'partials/form/inputField.html' with field=tagging_form.tag_list %} -
    -
    - -
    -
    -
    - -
    diff --git a/lostplaces/lostplaces_app/views/place_views.py b/lostplaces/lostplaces_app/views/place_views.py index 1e5bc67..30a7123 100644 --- a/lostplaces/lostplaces_app/views/place_views.py +++ b/lostplaces/lostplaces_app/views/place_views.py @@ -13,6 +13,8 @@ from lostplaces_app.models import Place, PlaceImage from lostplaces_app.views import IsAuthenticated, IsPlaceSubmitter from lostplaces_app.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm +from taggit.models import Tag + class PlaceListView(IsAuthenticated, ListView): paginate_by = 5 model = Place @@ -31,7 +33,8 @@ class PlaceDetailView(IsAuthenticated, View): 'place': place, 'place_list': [ place ], 'place_map_center': [ place.latitude, place.longitude ], - 'tagging_form': TagSubmitForm() + 'tagging_form': TagSubmitForm(), + 'all_tags': Tag.objects.all() } return render(request, 'place/place_detail.html', context) From 843832d978e09070c92ed780056486f67ee7d88a Mon Sep 17 00:00:00 2001 From: reverend Date: Mon, 31 Aug 2020 18:28:12 +0200 Subject: [PATCH 08/10] Viewport fix --- .../lostplaces_app/templates/global.html | 153 +++++++++--------- 1 file changed, 78 insertions(+), 75 deletions(-) diff --git a/lostplaces/lostplaces_app/templates/global.html b/lostplaces/lostplaces_app/templates/global.html index ee681f6..fa23d10 100644 --- a/lostplaces/lostplaces_app/templates/global.html +++ b/lostplaces/lostplaces_app/templates/global.html @@ -2,82 +2,85 @@ - - - - - - {% block title %}Urban Exploration{% endblock %} - - - {% block additional_head %} - {% endblock additional_head %} - - - -
    -
    - -
    - - {% if user.is_authenticated %} - Hi {{ user.username }}! - logout - {% if user.is_superuser %} - | admin - {% endif %} - - {% else %} - You are not logged in. - login | - signup - {% endif %} - -
    -
    - - - -
    - {% if messages %} -
    -
      - {% for message in messages %} -
    • -
      -
      -
      -
      -
      -
      - {{ message }} + + + + + + + {% block title %}Urban Exploration{% endblock %} + + + {% block additional_head %} + {% endblock additional_head %} + + + + +
      +
      + +
      + + {% if user.is_authenticated %} + Hi {{ user.username }}! + logout + {% if user.is_superuser %} + | admin + {% endif %} + + {% else %} + You are not logged in. + login | + signup + {% endif %} + +
      +
      + + + +
      + {% if messages %} +
      +
        + {% for message in messages %} +
      • +
        +
        +
        -
      • - {% endfor %} -
      -
      - {% endif %} - {% block maincontent %} - {% endblock maincontent %} -
      -
      - +
      + {{ message }} +
      +
      +
    • + {% endfor %} +
    +
    + {% endif %} + {% block maincontent %} + {% endblock maincontent %} +
    +
    + + \ No newline at end of file From 6ed6c2c990db36fba0e3c4a7ba3b68cef90d8930 Mon Sep 17 00:00:00 2001 From: reverend Date: Tue, 1 Sep 2020 18:20:02 +0200 Subject: [PATCH 09/10] Better Tagggin integration --- lostplaces/lostplaces_app/forms.py | 2 +- lostplaces/lostplaces_app/static/main.css | 155 ++++++++++++++---- .../lostplaces_app/templates/global.html | 6 +- .../templates/partials/form/inputField.html | 16 +- .../templates/partials/tagging.html | 10 +- .../templates/place/place_detail.html | 2 +- 6 files changed, 140 insertions(+), 51 deletions(-) diff --git a/lostplaces/lostplaces_app/forms.py b/lostplaces/lostplaces_app/forms.py index f08e771..6ac307a 100644 --- a/lostplaces/lostplaces_app/forms.py +++ b/lostplaces/lostplaces_app/forms.py @@ -51,4 +51,4 @@ class PlaceImageCreateForm(forms.ModelForm): class TagSubmitForm(forms.Form): - tag_list = forms.CharField(max_length=500) \ No newline at end of file + tag_list = forms.CharField(max_length=500, required=False) \ No newline at end of file diff --git a/lostplaces/lostplaces_app/static/main.css b/lostplaces/lostplaces_app/static/main.css index de9b1c1..5d6f195 100644 --- a/lostplaces/lostplaces_app/static/main.css +++ b/lostplaces/lostplaces_app/static/main.css @@ -574,7 +574,8 @@ body { padding: 8px 14px; border-radius: 2px; font-weight: bold; - cursor: pointer; } + cursor: pointer; + white-space: nowrap; } .LP-Button:active { background-color: #76323F; color: #f9f9f9; } @@ -600,19 +601,28 @@ body { flex-direction: column; margin-bottom: -30px; padding: 10px 0; } - .LP-Input .LP-Input__Field { + .LP-Input--tagging .LP-Button { + height: 53px; } + .LP-Input--tagging .LP-Input__Field, .LP-Input--tagging .tagify { + min-height: 35px; + height: max-content; + font-family: Montserrat, Helvetica, sans-serif; + font-size: 1em; + padding: 0; + padding-left: 8px; } + .LP-Input .LP-Input__Field, .LP-Input .tagify { border: none; border-bottom: 1px solid #565656; padding: 8px 0; margin-bottom: 30px; width: 100%; } - .LP-Input .LP-Input__Field:focus, .LP-Input .LP-Input__Field:active, .LP-Input .LP-Input__Field:invalid { + .LP-Input .LP-Input__Field:focus, .LP-Input .tagify:focus, .LP-Input .LP-Input__Field:active, .LP-Input .tagify:active, .LP-Input .LP-Input__Field:invalid, .LP-Input .tagify:invalid, .LP-Input .LP-Input__Field--active, .LP-Input .tagify--focus { margin-bottom: 29px; border-bottom: 2px solid #76323F; background-color: #f9f9f9; border-radius: 3px 3px 0 0; box-shadow: none; } - .LP-Input .LP-Input__Field[type=submit] { + .LP-Input .LP-Input__Field[type=submit], .LP-Input .tagify[type=submit] { background-color: #C09F80; color: #565656; border: none; @@ -620,7 +630,7 @@ body { border-radius: 2px; font-weight: bold; cursor: pointer; } - .LP-Input .LP-Input__Field[type=submit]:active { + .LP-Input .LP-Input__Field[type=submit]:active, .LP-Input .tagify[type=submit]:active { background-color: #76323F; color: #f9f9f9; } .LP-Input .LP-Input__Label { @@ -636,22 +646,30 @@ body { position: relative; top: -30px; overflow: hidden; } - .LP-Input--error .LP-Input__Field { + .LP-Input--error .LP-Input__Field, .LP-Input--error .tagify { margin-bottom: 25px; border-bottom: 2px solid #76323F; margin-bottom: 29px; } .LP-Input--error .LP-Input__Message { color: #76323F; } - .LP-Input--disabled .LP-Input__Field, .LP-Input--disabled .LP-Input__Field:disabled { + .LP-Input--disabled .LP-Input__Field, .LP-Input--disabled .tagify, + .LP-Input--disabled .LP-Input__Field:disabled, + .LP-Input--disabled .tagify:disabled { background-color: transparent; border-bottom: 1px dashed #565656; cursor: not-allowed; } - label + .LP-Input--disabled .LP-Input__Field, label + .LP-Input--disabled .LP-Input__Field:disabled { + label + .LP-Input--disabled .LP-Input__Field, label + .LP-Input--disabled .tagify, label + .LP-Input--disabled .LP-Input__Field:disabled, label + .LP-Input--disabled .tagify:disabled { color: red; } - .LP-Input--disabled .LP-Input__Field:focus, .LP-Input--disabled .LP-Input__Field:active, .LP-Input--disabled .LP-Input__Field:disabled:focus, .LP-Input--disabled .LP-Input__Field:disabled:active { + .LP-Input--disabled .LP-Input__Field:focus, .LP-Input--disabled .tagify:focus, .LP-Input--disabled .LP-Input__Field:active, .LP-Input--disabled .tagify:active, + .LP-Input--disabled .LP-Input__Field:disabled:focus, + .LP-Input--disabled .tagify:disabled:focus, + .LP-Input--disabled .LP-Input__Field:disabled:active, + .LP-Input--disabled .tagify:disabled:active { margin-bottom: 30px; border-radius: 0; } - .LP-Input--disabled .LP-Input__Field ~ .LP-Input__Message, .LP-Input--disabled .LP-Input__Field:disabled ~ .LP-Input__Message { + .LP-Input--disabled .LP-Input__Field ~ .LP-Input__Message, .LP-Input--disabled .tagify ~ .LP-Input__Message, + .LP-Input--disabled .LP-Input__Field:disabled ~ .LP-Input__Message, + .LP-Input--disabled .tagify:disabled ~ .LP-Input__Message { visibility: hidden; } .LP-Input--disabled .LP-Input__Label { color: #565656; } @@ -670,12 +688,14 @@ body { width: auto; object-fit: contain; } -.LP-Tag { +.LP-Tag, .tagify__tag { padding: 8px 14px; background-color: #D7CEC7; border-radius: 2px; width: max-content; } - .LP-Tag .LP-Paragraph { + .LP-Tag:hover, .tagify__tag:hover { + background-color: #bdbdbd; } + .LP-Tag .LP-Paragraph, .tagify__tag .LP-Paragraph { padding: 0; margin: 0; font-family: Montserrat, Helvetica, sans-serif; @@ -861,8 +881,8 @@ body { flex-wrap: wrap; padding: 0; margin: 0; } - .LP-TagList .LP-TagList__List .LP-TagList__Item { - margin: 6px; } + .LP-TagList .LP-TagList__List .LP-TagList__Item, .LP-TagList .LP-TagList__List .tagify__tag { + margin: 3px; } .LP-Menu { border-left: 1px solid #C09F80; } @@ -1189,7 +1209,13 @@ body { .LP-Footer .LP-LinkList__List .LP-LinkList__Item .LP-Link:hover { background-color: inherit; } -.LP-Form--inline .LP-Form__Legend, .LP-Form--inline .LP-Input__Label { +.LP-Form--tagging { + margin-top: 25px; } + .LP-Form--tagging div.LP-Form__Composition { + gap: 25px; } + +.LP-Form--inline .LP-Form__Legend, +.LP-Form--inline .LP-Input__Label { display: none; } .LP-Form--inline .LP-Form__Button { @@ -1198,6 +1224,12 @@ body { width: min-content; flex-basis: max-content; } +.LP-Form--inline fieldset.LP-Form__Fieldset { + max-width: unset; } + +.LP-Form--inline div.LP-Form__Composition { + padding: 0; } + @media (max-width: 450px) { .LP-Form:not(.LP-Form--inline) .LP-Form__Composition { flex-wrap: wrap; } } @@ -1359,25 +1391,23 @@ body { border: none; } .LP-ImageGrid__Container { gap: 10px; } - .LP-ImageGrid .LP-ImageGrid__Item { - box-shadow: 0 0 10px #565656; } - .LP-ImageGrid .LP-ImageGrid__Item, .LP-ImageGrid .LP-ImageGrid__Item * { - overflow: hidden; - word-break: break-all; } - .LP-ImageGrid .LP-ImageGrid__Item img { - width: 100%; - height: 100%; - object-fit: cover; } - .LP-ImageGrid .LP-ImageGrid__Item--left img { - object-position: left; } - .LP-ImageGrid .LP-ImageGrid__Item--center img { - object-position: center; } - .LP-ImageGrid .LP-ImageGrid__Item--top img { - object-position: top; } - .LP-ImageGrid .LP-ImageGrid__Item--bottom img { - object-position: botom; } - .LP-ImageGrid .LP-ImageGrid__Item--center img { - object-position: center; } + .LP-ImageGrid .LP-ImageGrid__Item, .LP-ImageGrid .LP-ImageGrid__Item * { + overflow: hidden; + word-break: break-all; } + .LP-ImageGrid .LP-ImageGrid__Item img { + width: 100%; + height: 100%; + object-fit: cover; } + .LP-ImageGrid .LP-ImageGrid__Item--left img { + object-position: left; } + .LP-ImageGrid .LP-ImageGrid__Item--center img { + object-position: center; } + .LP-ImageGrid .LP-ImageGrid__Item--top img { + object-position: top; } + .LP-ImageGrid .LP-ImageGrid__Item--bottom img { + object-position: botom; } + .LP-ImageGrid .LP-ImageGrid__Item--center img { + object-position: center; } .LP-MainContainer { margin: 0 auto; @@ -1413,3 +1443,60 @@ body { margin: 0; padding: 0; margin-bottom: 25px; } } + +.tagify { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; } + .tagify + input, + .tagify + textarea { + display: none; } + +.tagify__tag { + background-color: #bdbdbd; + display: inline-flex; + cursor: default; + transition: .13s ease-out; + height: max-content; + align-items: center; + gap: 3px; } + .tagify__tag:hover { + background-color: #e9e9e9; } + +.tagify__input { + flex-grow: 1; + display: inline-block; + min-width: 110px; + margin: 5px; + line-height: inherit; + position: relative; + white-space: pre-wrap; + margin-left: 15px; } + +.tagify__tag__removeBtn { + order: 5; + cursor: pointer; + font: 1em/1 Arial; + transition: .2s ease-out; + color: #76323F; } + +.tagify__tag__removeBtn::after { + content: "\00D7"; } + +.tagify__tag__removeBtn:hover { + color: #565656; } + +.tagify__tag__removeBtn:hover + div > span { + opacity: .5; } + +.tagify__tag__removeBtn:hover + div::before { + box-shadow: 0 0 0 1.1em rgba(211, 148, 148, 0.3) inset !important; + box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-remove-bg) inset !important; + transition: .2s; } + +.tagify__tag--loading .tagify__tag__removeBtn { + display: none; } + +.tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn { + display: none; } diff --git a/lostplaces/lostplaces_app/templates/global.html b/lostplaces/lostplaces_app/templates/global.html index fa23d10..e657bf5 100644 --- a/lostplaces/lostplaces_app/templates/global.html +++ b/lostplaces/lostplaces_app/templates/global.html @@ -4,6 +4,9 @@ + {% block additional_head %} + {% endblock additional_head %} + @@ -12,9 +15,6 @@ {% block title %}Urban Exploration{% endblock %} - {% block additional_head %} - {% endblock additional_head %} - diff --git a/lostplaces/lostplaces_app/templates/partials/form/inputField.html b/lostplaces/lostplaces_app/templates/partials/form/inputField.html index 1d141c2..8447c67 100644 --- a/lostplaces/lostplaces_app/templates/partials/form/inputField.html +++ b/lostplaces/lostplaces_app/templates/partials/form/inputField.html @@ -1,16 +1,18 @@ {% load widget_tweaks %} -
    +
    - {% render_field field class="LP-Input__Field"%} + {% with class="LP-Input__Field "%} + {% render_field field class=class%} + {% endwith %} - {% if field.errors %} - {% for error in field.errors%} - {{error}} - {% endfor %} + {% if field.errors %} + {% for error in field.errors%} + {{error}} + {% endfor %} {% elif field.help_text%} - {{ field.help_text }} + {{ field.help_text }} {% endif %}
    \ No newline at end of file diff --git a/lostplaces/lostplaces_app/templates/partials/tagging.html b/lostplaces/lostplaces_app/templates/partials/tagging.html index e0af9dd..d9db359 100644 --- a/lostplaces/lostplaces_app/templates/partials/tagging.html +++ b/lostplaces/lostplaces_app/templates/partials/tagging.html @@ -10,16 +10,16 @@
    -
    +
    Tags hinzufügen {% csrf_token %}
    -
    - {% include 'partials/form/inputField.html' with field=input_field %} +
    +
    -
    - +
    + {% include 'partials/form/inputField.html' with field=input_field classes="LP-Input--tagging" %}
    diff --git a/lostplaces/lostplaces_app/templates/place/place_detail.html b/lostplaces/lostplaces_app/templates/place/place_detail.html index d7351c9..b687a1e 100644 --- a/lostplaces/lostplaces_app/templates/place/place_detail.html +++ b/lostplaces/lostplaces_app/templates/place/place_detail.html @@ -6,7 +6,7 @@ {% block additional_head %} - + From e00c9318fa9f2e97ee89e7315ceb3d6eb7786665 Mon Sep 17 00:00:00 2001 From: reverend Date: Tue, 1 Sep 2020 18:31:46 +0200 Subject: [PATCH 10/10] Tagify dropdown styling --- lostplaces/lostplaces_app/static/main.css | 57 ++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/lostplaces/lostplaces_app/static/main.css b/lostplaces/lostplaces_app/static/main.css index 5d6f195..8489e0c 100644 --- a/lostplaces/lostplaces_app/static/main.css +++ b/lostplaces/lostplaces_app/static/main.css @@ -604,7 +604,7 @@ body { .LP-Input--tagging .LP-Button { height: 53px; } .LP-Input--tagging .LP-Input__Field, .LP-Input--tagging .tagify { - min-height: 35px; + min-height: 36px; height: max-content; font-family: Montserrat, Helvetica, sans-serif; font-size: 1em; @@ -1500,3 +1500,58 @@ body { .tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn { display: none; } + +.tagify__dropdown { + position: absolute; + z-index: 9999; + transform: translateY(1px); + overflow: hidden; } + +.tagify__dropdown[placement=top] { + margin-top: 0; + transform: translateY(-100%); } + +.tagify__dropdown[placement=top] .tagify__dropdown__wrapper { + border-top-width: 1px; + border-bottom-width: 0; } + +.tagify__dropdown[position=text] { + box-shadow: 0 0 0 3px rgba(var(--tagify-dd-color-primary), 0.1); + font-size: .9em; } + +.tagify__dropdown[position=text] .tagify__dropdown__wrapper { + border-width: 1px; } + +.tagify__dropdown__wrapper { + max-height: 300px; + overflow: hidden; + background-color: #f9f9f9; + box-shadow: 0 2px 4px -2px rgba(0, 0, 0, 0.2); + transition: 0.25s cubic-bezier(0, 1, 0.5, 1); } + +.tagify__dropdown__wrapper:hover { + overflow: auto; } + +.tagify__dropdown--initial .tagify__dropdown__wrapper { + max-height: 20px; + transform: translateY(-1em); } + +.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper { + transform: translateY(2em); } + +.tagify__dropdown__item { + box-sizing: inherit; + padding: .3em .5em; + margin: 1px; + cursor: pointer; + border-radius: 2px; + position: relative; + outline: 0; + font-family: Montserrat, Helvetica, sans-serif; } + +.tagify__dropdown__item--active { + color: #f9f9f9; + background-color: gray; } + +.tagify__dropdown__item:active { + filter: brightness(105%); }