From 9980a2d190ac8b67000acabee3d0d7fa279b9091 Mon Sep 17 00:00:00 2001 From: reverend Date: Thu, 30 Dec 2021 23:20:05 +0100 Subject: [PATCH] #42 Place Voting / Level --- django_lostplaces/lostplaces/admin.py | 1 + django_lostplaces/lostplaces/forms.py | 4 +- django_lostplaces/lostplaces/models/place.py | 42 +++++++++-- .../lostplaces/templates/partials/voting.html | 39 +++++++++++ .../templates/place/place_detail.html | 69 +++++++++++++++---- django_lostplaces/lostplaces/urls.py | 5 +- .../lostplaces/views/place_views.py | 45 +++++++++++- 7 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 django_lostplaces/lostplaces/templates/partials/voting.html diff --git a/django_lostplaces/lostplaces/admin.py b/django_lostplaces/lostplaces/admin.py index ec58dac..357df41 100644 --- a/django_lostplaces/lostplaces/admin.py +++ b/django_lostplaces/lostplaces/admin.py @@ -35,3 +35,4 @@ admin.site.register(Voucher, VoucherAdmin) admin.site.register(Place, PlacesAdmin) admin.site.register(PlaceImage, PlaceImagesAdmin) admin.site.register(PhotoAlbum, PhotoAlbumsAdmin) +admin.site.register(PlaceVoting) diff --git a/django_lostplaces/lostplaces/forms.py b/django_lostplaces/lostplaces/forms.py index 865751b..e68bef2 100644 --- a/django_lostplaces/lostplaces/forms.py +++ b/django_lostplaces/lostplaces/forms.py @@ -22,7 +22,7 @@ class SignupVoucherForm(UserCreationForm): ) def is_valid(self): - super().is_valid() + super_result = super().is_valid() submitted_voucher = self.cleaned_data.get('voucher') try: fetched_voucher = Voucher.objects.get(code=submitted_voucher) @@ -35,7 +35,7 @@ class SignupVoucherForm(UserCreationForm): return False fetched_voucher.delete() - return True + return super_result class ExplorerUserChangeForm(UserChangeForm): class Meta: diff --git a/django_lostplaces/lostplaces/models/place.py b/django_lostplaces/lostplaces/models/place.py index acfcda7..2df13b4 100644 --- a/django_lostplaces/lostplaces/models/place.py +++ b/django_lostplaces/lostplaces/models/place.py @@ -1,4 +1,5 @@ import os +from math import floor from django.db import models from django.urls import reverse @@ -6,7 +7,7 @@ from django.dispatch import receiver from django.db.models.signals import post_delete, pre_save from django.utils.translation import ugettext_lazy as _ -from lostplaces.models.abstract_models import Submittable, Taggable, Mapable +from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.files import get_thumbnailer @@ -15,7 +16,7 @@ PLACE_LEVELS = ( (1, 'Ruin'), (2, 'Vandalized'), (3, 'Natures Treasure'), - (4, 'Long Time no See'), + (4, 'Lost in History'), (5, 'Time Capsule') ) @@ -58,10 +59,15 @@ class Place(Submittable, Taggable, Mapable): return reverse('place_detail', kwargs={'pk': self.pk}) def get_hero_index_in_queryset(self): + ''' + Calculates the index of the hero image within + the list / queryset of images. Necessary for + the lightbox. + ''' for i in range(0, len(self.placeimages.all())): image = self.placeimages.all()[i] if image == self.hero: - return i + return i return None @@ -84,6 +90,24 @@ class Place(Submittable, Taggable, Mapable): return {'latitude': latitude, 'longitude': longitude} + def calculate_place_level(self): + self.remove_expired_votes() + + if self.placevotings.count() == 0: + return 5 + + level = 0 + + for vote in self.placevotings.all(): + level += vote.vote + + self.level = floor(level / self.placevotings.count()) + + def remove_expired_votes(self): + for vote in self.placevotings.all(): + if vote.is_expired: + vote.delete() + def __str__(self): return self.name @@ -104,7 +128,7 @@ class PlaceAsset(Submittable): """ class Meta: - abstract = True + abstract = True place = models.ForeignKey( Place, @@ -180,3 +204,13 @@ def auto_delete_file_on_change(sender, instance, **kwargs): new_file = instance.filename if not old_file == new_file: old_file.delete(save=False) + + +class PlaceVoting(PlaceAsset, Expireable): + vote = models.IntegerField(choices=PLACE_LEVELS) + + def get_human_readable_level(self): + return PLACE_LEVELS[self.vote - 1][1] + + def get_all_choices(self): + return reversed(PLACE_LEVELS) \ No newline at end of file diff --git a/django_lostplaces/lostplaces/templates/partials/voting.html b/django_lostplaces/lostplaces/templates/partials/voting.html new file mode 100644 index 0000000..8c12cd2 --- /dev/null +++ b/django_lostplaces/lostplaces/templates/partials/voting.html @@ -0,0 +1,39 @@ +{% load i18n %} + +
+
+

+ Place level +

+
+ {% for choice in voting.get_all_choices %} + + + + {{choice.1}} + + + {% endfor %} +
+
+ {{place.get_level_display}} +
+
+
+
+
+ You voted this place as + {{voting.get_human_readable_level}} +
+ +
+ Your vote expires on + + + +
+
+
+
\ No newline at end of file diff --git a/django_lostplaces/lostplaces/templates/place/place_detail.html b/django_lostplaces/lostplaces/templates/place/place_detail.html index eeb77b2..f01c9f5 100644 --- a/django_lostplaces/lostplaces/templates/place/place_detail.html +++ b/django_lostplaces/lostplaces/templates/place/place_detail.html @@ -40,25 +40,64 @@

{{ place.description }}

-
- - {% url 'place_tag_submit' place_id=place.id as tag_submit_url%} - {% partial tagging %} - {% set config=tagging_config %} - {% endpartial %} - -
+
+
+ {% url 'place_tag_submit' place_id=place.id as tag_submit_url %} + {% partial tagging %} + {% set config=tagging_config %} + {% endpartial %} +
+ + {{votingplace.vote}} +
+ {% partial voting %} + {% set place=place %} + {% set voting=placevoting %} + {% endpartial %} +
+

{% translate 'Map links' %}

{% partial osm_map config=mapping_config %} - +
diff --git a/django_lostplaces/lostplaces/urls.py b/django_lostplaces/lostplaces/urls.py index 35f6ce4..ca4cf5c 100644 --- a/django_lostplaces/lostplaces/urls.py +++ b/django_lostplaces/lostplaces/urls.py @@ -22,7 +22,8 @@ from lostplaces.views import ( PhotoAlbumCreateView, PhotoAlbumDeleteView, ExplorerProfileView, - ExplorerProfileUpdateView + ExplorerProfileUpdateView, + PlaceVoteView ) urlpatterns = [ @@ -47,6 +48,8 @@ urlpatterns = [ path('place_image/create//', PlaceImageCreateView.as_view(), name='place_image_create'), path('place_image/delete//', PlaceImageDeleteView.as_view(), name='place_image_delete'), + + path('place/vote//', PlaceVoteView.as_view(), name='place_vote'), path('photo_album/create//', PhotoAlbumCreateView.as_view(), name='photo_album_create'), path('photo_album/delete//', PhotoAlbumDeleteView.as_view(), name='photo_album_delete') diff --git a/django_lostplaces/lostplaces/views/place_views.py b/django_lostplaces/lostplaces/views/place_views.py index bb63435..71e08cb 100644 --- a/django_lostplaces/lostplaces/views/place_views.py +++ b/django_lostplaces/lostplaces/views/place_views.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from datetime import timedelta from django.db.models.functions import Lower @@ -10,11 +11,17 @@ from django.views.generic.detail import SingleObjectMixin from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse_lazy, reverse -from lostplaces.models import Place, PlaceImage +from lostplaces.models import ( + Place, + PlaceImage, + PlaceVoting +) + from lostplaces.views.base_views import ( IsAuthenticatedMixin, IsPlaceSubmitterMixin, @@ -46,8 +53,10 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View): def get_place(self): return get_object_or_404(Place, pk=self.kwargs['pk']) - def get(self, request, pk): + def get(self, request, pk): place = self.get_place() + place.calculate_place_level() + explorer = request.user.explorer context = { 'place': place, @@ -61,7 +70,8 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View): 'tagged_item': place, 'submit_url': reverse('place_tag_submit', kwargs={'tagged_id': place.id}), 'delete_url_name': 'place_tag_delete' - } + }, + 'placevoting': PlaceVoting.objects.filter(place=place, submitted_by=explorer).first() } return render(request, 'place/place_detail.html', context) @@ -185,3 +195,32 @@ class PlaceVisitDeleteView(IsAuthenticatedMixin, View): request.user.explorer.save() return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk})) + + +class PlaceVoteView(IsEligibleToSeePlaceMixin, View): + delta = timedelta(weeks=24) + + def get(self, request, place_id, vote): + place = get_object_or_404(Place, id=place_id) + explorer = request.user.explorer + + voting = PlaceVoting.objects.filter( + submitted_by=explorer, + place=place + ).first() + + if voting is None: + voting = PlaceVoting.objects.create( + submitted_by=explorer, + place=place, + vote=vote, + expires_when=timezone.now()+self.delta + ) + messages.success(self.request, _('Vote submitted')) + else: + voting.expires_when=timezone.now()+self.delta + voting.vote = vote + messages.success(self.request, _('Your vote has been update')) + + voting.save() + return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk})) \ No newline at end of file