Compare commits
	
		
			15 Commits
		
	
	
		
			update/dja
			...
			develop
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3982db1375 | ||
| 
						 | 
					e60a6ea9be | ||
| 
						 | 
					bc0ace7bf3 | ||
| 
						 | 
					df67bcf639 | ||
| 
						 | 
					724c26c926 | ||
| 
						 | 
					06d68380c9 | ||
| 
						 | 
					f06b6bdae9 | ||
| 
						 | 
					7a7c06882a | ||
| 
						 | 
					c9d83dfc2c | ||
| 
						 | 
					49301afe51 | ||
| 
						 | 
					624878624f | ||
| 
						 | 
					a2ee323fa4 | ||
| 
						 | 
					86c9de3213 | ||
| 
						 | 
					8597e53599 | ||
| 
						 | 
					d213b51a59 | 
							
								
								
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Pipfile
									
									
									
									
									
								
							@@ -22,6 +22,7 @@ easy-thumbnails = "*"
 | 
			
		||||
image = "*"
 | 
			
		||||
django-widget-tweaks = "*"
 | 
			
		||||
django-taggit = "*"
 | 
			
		||||
pykml = "*"
 | 
			
		||||
 | 
			
		||||
[scripts]
 | 
			
		||||
test = "django_lostplaces/manage.py test lostplaces"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# lostplaces-backend
 | 
			
		||||
 | 
			
		||||
lostplaces-backend is a django (3.x) based webproject. It once wants to become a software which allows a group of urban explorers to manage, document and share the locations of lost places while not exposing too much / any information to the public.
 | 
			
		||||
lostplaces-backend is a django (4.x) based webproject. It once wants to become a software which allows a group of urban explorers to manage, document and share the locations of lost places while not exposing too much / any information to the public.
 | 
			
		||||
 | 
			
		||||
The software is currently in early development status, neither scope, datamodel(s) nor features are finalized yet. Therefore we would not recommend to download or install this piece of software anywhere - except your local django dev server.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ class PlaceForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Place
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        exclude = ['submitted_by', 'level']
 | 
			
		||||
        exclude = ['submitted_by', 'level', 'mode']
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'hero': widgets.SelectContent()
 | 
			
		||||
        }
 | 
			
		||||
@@ -88,6 +88,11 @@ class PlaceForm(forms.ModelForm):
 | 
			
		||||
        widget=forms.NumberInput(attrs={'min':-180,'max': 180,'type': 'number', 'step': 'any'})
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    draft = forms.BooleanField(
 | 
			
		||||
        label=_('Save Place as draft'),
 | 
			
		||||
        required=False
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
class PlaceImageForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = PlaceImage
 | 
			
		||||
@@ -122,3 +127,9 @@ class TagSubmitForm(forms.Form):
 | 
			
		||||
        required=False, 
 | 
			
		||||
        widget=forms.TextInput(attrs={'autocomplete':'off'})
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
class UploadMapFileForm(forms.Form):
 | 
			
		||||
    map_file = forms.FileField()
 | 
			
		||||
    description = forms.CharField(
 | 
			
		||||
        widget=forms.Textarea
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -82,9 +82,25 @@ class Explorer(models.Model):
 | 
			
		||||
        choices=EXPLORER_LEVELS
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def get_place_list_to_display(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Gets the list of places to show on the homepage
 | 
			
		||||
        and the list views
 | 
			
		||||
        '''
 | 
			
		||||
        if self.user.is_superuser:
 | 
			
		||||
            return Place.objects.filter(mode='live').order_by('submitted_when')
 | 
			
		||||
 | 
			
		||||
        return (Place.objects.filter(
 | 
			
		||||
            level__lte=self.level,
 | 
			
		||||
            mode='live'
 | 
			
		||||
        ) | Place.objects.filter(
 | 
			
		||||
            submitted_by=self,
 | 
			
		||||
            mode='live'
 | 
			
		||||
        )).order_by('submitted_when')
 | 
			
		||||
 | 
			
		||||
    def get_places_eligible_to_see(self):
 | 
			
		||||
        if self.user.is_superuser:
 | 
			
		||||
            return Place.objects.all()
 | 
			
		||||
            return Place.objects.filter(mode='live')
 | 
			
		||||
        return Place.objects.all().filter(level__lte=self.level) | self.places.all()
 | 
			
		||||
 | 
			
		||||
    def is_eligible_to_see(self, place):
 | 
			
		||||
@@ -94,6 +110,12 @@ class Explorer(models.Model):
 | 
			
		||||
            place in self.get_places_eligible_to_see()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_drafts(self):
 | 
			
		||||
        return Place.objects.filter(
 | 
			
		||||
            submitted_by=self,
 | 
			
		||||
            mode='draft'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.user.username
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
import os
 | 
			
		||||
from math import floor
 | 
			
		||||
import datetime
 | 
			
		||||
from math import ceil
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
from django.db.models.signals import post_delete, pre_save
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable
 | 
			
		||||
@@ -21,6 +23,43 @@ PLACE_LEVELS = (
 | 
			
		||||
    (5, 'Time Capsule')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
PLACE_MODES = (
 | 
			
		||||
    ('live', 'live'),
 | 
			
		||||
    ('draft', 'draft'),
 | 
			
		||||
    ('review', 'review'),
 | 
			
		||||
    ('archive', 'archive'),
 | 
			
		||||
    ('imported', 'imported')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
PLACE_IMPORT_TYPES = (
 | 
			
		||||
    ('kml', 'KML-File import'),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class PlaceImport(models.Model):
 | 
			
		||||
    imported_when = models.DateTimeField(
 | 
			
		||||
        auto_now_add=True,
 | 
			
		||||
        verbose_name=_('When the imported has taken place')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    description = models.TextField(
 | 
			
		||||
        default=None,
 | 
			
		||||
        null=True,
 | 
			
		||||
        verbose_name=_('Description of the import')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    explorer = models.ForeignKey(
 | 
			
		||||
        'Explorer',
 | 
			
		||||
        null=True,
 | 
			
		||||
        on_delete=models.SET_NULL,
 | 
			
		||||
        related_name='place_imports'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    import_type = models.TextField(
 | 
			
		||||
        default='kml',
 | 
			
		||||
        choices=PLACE_IMPORT_TYPES,
 | 
			
		||||
        verbose_name=_('What kind of import this is')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
    """
 | 
			
		||||
    Place defines a lost place (location, name, description etc.).
 | 
			
		||||
@@ -48,6 +87,22 @@ class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
        choices=PLACE_LEVELS
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    mode = models.TextField(
 | 
			
		||||
        default='live',
 | 
			
		||||
        choices=PLACE_MODES,
 | 
			
		||||
        verbose_name=_('Mode of Place Editing')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    place_import = models.ForeignKey(
 | 
			
		||||
        PlaceImport,
 | 
			
		||||
        null=True,
 | 
			
		||||
        on_delete=models.SET_NULL,
 | 
			
		||||
        related_name='place_list'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def is_imported(self):
 | 
			
		||||
        return self.place_import != None
 | 
			
		||||
 | 
			
		||||
    def get_hero_image(self):
 | 
			
		||||
        if self.hero:
 | 
			
		||||
            return self.hero
 | 
			
		||||
@@ -76,24 +131,22 @@ class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
    # Get center position of LP-geocoordinates.
 | 
			
		||||
    def average_latlon(cls, place_list):
 | 
			
		||||
        amount = len(place_list)
 | 
			
		||||
        # Init fill values to prevent None
 | 
			
		||||
        # China Corner in Münster
 | 
			
		||||
        # Where I almost always eat lunch
 | 
			
		||||
        # (Does'nt help losing wheight, tho)
 | 
			
		||||
        longitude = 7.6295628132604385
 | 
			
		||||
        latitude = 51.961922091398904
 | 
			
		||||
 | 
			
		||||
        if amount > 0:
 | 
			
		||||
            latitude = 0
 | 
			
		||||
            longitude = 0
 | 
			
		||||
 | 
			
		||||
            for place in place_list:
 | 
			
		||||
                longitude += place.longitude
 | 
			
		||||
                latitude += place.latitude
 | 
			
		||||
            return {'latitude': latitude / amount, 'longitude': longitude / amount}
 | 
			
		||||
 | 
			
		||||
        return {'latitude': latitude, 'longitude': longitude}
 | 
			
		||||
        else:
 | 
			
		||||
            # Location of China Corner in Münster
 | 
			
		||||
            # Where I almost always eat lunch
 | 
			
		||||
            # (Does'nt help losing wheight, tho)
 | 
			
		||||
            return {'latitude': 51.961922091398904, 'longitude': 7.6295628132604385}
 | 
			
		||||
 | 
			
		||||
    def calculate_place_level(self):
 | 
			
		||||
        self.remove_expired_votes()
 | 
			
		||||
 | 
			
		||||
        if self.placevotings.count() == 0:
 | 
			
		||||
            self.level = 5
 | 
			
		||||
            self.save()
 | 
			
		||||
@@ -104,13 +157,22 @@ class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
        for vote in self.placevotings.all():
 | 
			
		||||
            level += vote.vote
 | 
			
		||||
 | 
			
		||||
        self.level = floor(level / self.placevotings.count())
 | 
			
		||||
        self.level = round(level / self.placevotings.count())
 | 
			
		||||
        self.save()
 | 
			
		||||
        
 | 
			
		||||
    def remove_expired_votes(self):
 | 
			
		||||
    def calculate_voting_accuracy(self):
 | 
			
		||||
        place_age = timezone.now() - self.submitted_when;
 | 
			
		||||
        accuaries = [];
 | 
			
		||||
 | 
			
		||||
        for vote in self.placevotings.all():
 | 
			
		||||
            if vote.is_expired:
 | 
			
		||||
                vote.delete()
 | 
			
		||||
            vote_age = timezone.now() - vote.submitted_when;
 | 
			
		||||
            accuracy = 100 - (100 / (place_age / vote_age))
 | 
			
		||||
            accuaries.append(accuracy)
 | 
			
		||||
        
 | 
			
		||||
        if len(accuaries) > 0:
 | 
			
		||||
            return ceil(sum(accuaries) / len(accuaries))
 | 
			
		||||
        else:
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
@@ -210,7 +272,7 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
 | 
			
		||||
        old_file.delete(save=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlaceVoting(PlaceAsset, Expireable):
 | 
			
		||||
class PlaceVoting(PlaceAsset):
 | 
			
		||||
    vote = models.IntegerField(choices=PLACE_LEVELS)
 | 
			
		||||
    
 | 
			
		||||
    def get_human_readable_level(self):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								django_lostplaces/lostplaces/templates/explorer/drafts.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								django_lostplaces/lostplaces/templates/explorer/drafts.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
{% extends 'global.html'%}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% load svg_icon %}
 | 
			
		||||
 | 
			
		||||
{% block maincontent %}
 | 
			
		||||
<section class="LP-Section">
 | 
			
		||||
	<div class="LP-PlaceList">
 | 
			
		||||
		<h1 class="LP-Headline">
 | 
			
		||||
            {% if user.username == explorer.user.username %}
 | 
			
		||||
                {% translate 'Your drafts' %}
 | 
			
		||||
            {% else %}
 | 
			
		||||
                {{explorer.user.username}}{% translate '\'s drafts' %}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </h1>
 | 
			
		||||
		<ul class="LP-PlaceList__List">
 | 
			
		||||
			{% for place in place_list %}
 | 
			
		||||
			<li class="LP-PlaceList__Item">
 | 
			
		||||
				<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
 | 
			
		||||
					{% include 'partials/place_teaser.html' with place=place extended=True %}
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
			{% endfor %}
 | 
			
		||||
		</ul>
 | 
			
		||||
    </div>
 | 
			
		||||
</section>
 | 
			
		||||
{% endblock maincontent %}
 | 
			
		||||
@@ -32,7 +32,8 @@
 | 
			
		||||
                    {% if user.is_authenticated %}
 | 
			
		||||
                    Hi {{ user.username }}!
 | 
			
		||||
                    <a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% translate 'Logout' %}</span></a> |
 | 
			
		||||
					<a class="LP-Link" href="{% url 'explorer_profile' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Profile' %}</span></a>
 | 
			
		||||
					<a class="LP-Link" href="{% url 'explorer_profile' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Profile' %}</span></a> |
 | 
			
		||||
                    <a class="LP-Link" href="{% url 'explorer_drafts' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Drafts' %}</span></a>
 | 
			
		||||
                    {% if user.is_superuser %}
 | 
			
		||||
                    | <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% translate 'Admin' %}</span></a>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
@@ -58,6 +59,10 @@
 | 
			
		||||
 | 
			
		||||
                    <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_create'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Create place' %}</span></a></li>
 | 
			
		||||
                    <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_list'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'All places' %}</span></a></li>
 | 
			
		||||
 | 
			
		||||
                    {% if user.is_superuser %}
 | 
			
		||||
                    <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'import_upload'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Import KML File' %}</span></a></li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </ul>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </aside>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
{% extends 'global.html'%}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% load svg_icon %}
 | 
			
		||||
 | 
			
		||||
{% block maincontent %}
 | 
			
		||||
<h1 class="LP-Headline">
 | 
			
		||||
    {{ import_type}} from {{import.imported_when|date:"d F Y"}} by {{import.explorer.user.username}}
 | 
			
		||||
</h1>
 | 
			
		||||
 | 
			
		||||
<p class="SP-Paragraph">
 | 
			
		||||
    {{import.place_list.all|length}} places where import in this import
 | 
			
		||||
</p>
 | 
			
		||||
<p class="SP-Paragraph">
 | 
			
		||||
    {{import.description}}
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<div class="LP-PlaceList">
 | 
			
		||||
    <h1 class="LP-Headline">{% translate 'Places imported' %}</h1>
 | 
			
		||||
    <ul class="LP-PlaceList__List">
 | 
			
		||||
        {% for place in paginated_places %}
 | 
			
		||||
        <li class="LP-PlaceList__Item">
 | 
			
		||||
            {% include 'partials/place_teaser.html' with place=place extended=True %}
 | 
			
		||||
        </li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    {% include 'partials/nav/pagination.html' with page_obj=paginated_places is_paginated=True %}
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock maincontent %}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
{% extends 'global.html'%}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% load svg_icon %}
 | 
			
		||||
 | 
			
		||||
{% block maincontent %}
 | 
			
		||||
<form class="LP-Form" method="POST" enctype="multipart/form-data">
 | 
			
		||||
    <fieldset class="LP-Form__Fieldset">
 | 
			
		||||
        <legend class="LP-Form__Legend">{% translate 'Import Places from KML File' %}</legend>
 | 
			
		||||
        {% csrf_token %}
 | 
			
		||||
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--breakable">
 | 
			
		||||
            <div class="LP-Form__Field">
 | 
			
		||||
                {% include 'partials/form/inputField.html' with field=upload_form.description %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--breakable">
 | 
			
		||||
            <div class="LP-Form__Field">
 | 
			
		||||
                {% include 'partials/form/inputField.html' with field=upload_form.map_file %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        {% translate 'Create' as action %}
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--buttons">
 | 
			
		||||
            {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
</form>
 | 
			
		||||
{% endblock maincontent %}
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
    <div class="LP-PlaceTeaser__Meta">
 | 
			
		||||
        <div class="LP-PlaceTeaser__Info">
 | 
			
		||||
            <span class="LP-PlaceTeaser__Title">
 | 
			
		||||
                <h1 class="LP-Headline LP-Headline--teaser">{{place.name|truncatechars:19}}</h1>
 | 
			
		||||
                <h1 class="LP-Headline LP-Headline--teaser">{{place.name}}</h1>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="LP-PlaceTeaser__Detail">
 | 
			
		||||
                <p class="LP-Paragraph">{{place.location|truncatechars:25}}</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -28,12 +28,7 @@
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="LP-Voting__Expiration">
 | 
			
		||||
					<span class="LP-Voting__InfoLabel">Your vote expires on</span>
 | 
			
		||||
					<span class="LP-Voting__Date">
 | 
			
		||||
						<time datetime="{{voting.expires_when|date:'Y-m-d'}}">
 | 
			
		||||
							{{voting.users_vote.expires_when|date:'d.m.Y'}}
 | 
			
		||||
						</time>
 | 
			
		||||
					</span>
 | 
			
		||||
					The accuracy of the voting is {{voting.accuracy}}%
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,10 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        {% translate 'Create' as action %}
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--buttons">
 | 
			
		||||
            {% include 'partials/form/inputField.html' with field=place_form.draft %}
 | 
			
		||||
            {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,15 @@
 | 
			
		||||
{% block maincontent %}
 | 
			
		||||
<article class="LP-PlaceDetail">
 | 
			
		||||
    <header class="LP-PlaceDetail__Header">
 | 
			
		||||
        <h1 class="LP-Headline">{{ place.name }} {% include 'partials/icons/place_favorite.html' %} {% include 'partials/icons/place_visited.html' %}</h1>
 | 
			
		||||
        <h1 class="LP-Headline">
 | 
			
		||||
			{{ place.name }}
 | 
			
		||||
			{% include 'partials/icons/place_favorite.html' %}
 | 
			
		||||
			{% include 'partials/icons/place_visited.html' %}
 | 
			
		||||
 | 
			
		||||
			{% if user.is_superuser %}
 | 
			
		||||
            	<a class="LP-Link" href="{{'/admin/lostplaces/place/'|addstr:place.id }}" target="_blank"><span class="LP-Link__Text">{% translate 'view place in admin panel' %}</span></a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
		</h1>
 | 
			
		||||
        {% if place.get_hero_image %}
 | 
			
		||||
        <div class="LP-PlaceDetail__Image">
 | 
			
		||||
			{% include '../partials/image.html' with source_url=place.get_hero_image.filename.hero.url link_url="#image"|addstr:place.get_hero_index_in_queryset %}
 | 
			
		||||
@@ -41,7 +49,6 @@
 | 
			
		||||
			{% include '../partials/tagging.html' with config=tagging_config %}
 | 
			
		||||
		</section>
 | 
			
		||||
		
 | 
			
		||||
		{{votingplace.vote}}
 | 
			
		||||
		<section class="LP-Section">
 | 
			
		||||
			{% include '../partials/voting.html' with voting=placevoting %}
 | 
			
		||||
		</section>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--buttons">
 | 
			
		||||
            {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER %}
 | 
			
		||||
            {% include 'partials/form/submit.html' with referer=request.META.HTTP_REFERER %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,21 +42,25 @@ class PlaceImageTestCase(ModelTestCase):
 | 
			
		||||
        if not os.path.isdir(settings.MEDIA_ROOT):
 | 
			
		||||
            os.mkdir(settings.MEDIA_ROOT)
 | 
			
		||||
 | 
			
		||||
        images_dir = os.path.join(
 | 
			
		||||
            settings.MEDIA_ROOT, 
 | 
			
		||||
            settings.RELATIVE_THUMBNAIL_PATH,
 | 
			
		||||
        )
 | 
			
		||||
        current_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
        if not os.path.isfile(os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg')):
 | 
			
		||||
            shutil.copyfile(
 | 
			
		||||
                os.path.join(current_dir, 'im_a_image.jpeg'),
 | 
			
		||||
                os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg')
 | 
			
		||||
                os.path.join(images_dir, 'im_a_image_copy.jpeg')
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
            shutil.copyfile(
 | 
			
		||||
                os.path.join(current_dir, 'im_a_image.jpeg'),
 | 
			
		||||
                os.path.join(settings.MEDIA_ROOT, 'im_a_image_changed.jpeg')
 | 
			
		||||
                os.path.join(images_dir, 'im_a_image_changed.jpeg')
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
        PlaceImage.objects.create(
 | 
			
		||||
            description='Im a description',
 | 
			
		||||
            filename=os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg'),
 | 
			
		||||
            filename=os.path.join(images_dir, 'im_a_image_copy.jpeg'),
 | 
			
		||||
            place=place,
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.db import models
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place
 | 
			
		||||
from lostplaces.models import Place, PlaceVoting
 | 
			
		||||
from lostplaces.tests.models import ModelTestCase
 | 
			
		||||
 | 
			
		||||
class PlaceTestCase(ModelTestCase):
 | 
			
		||||
@@ -106,12 +107,12 @@ class PlaceTestCase(ModelTestCase):
 | 
			
		||||
        an empty list
 | 
			
		||||
        '''
 | 
			
		||||
        avg_latlon = Place.average_latlon([])
 | 
			
		||||
        self.assertEqual(avg_latlon['latitude'], 0,
 | 
			
		||||
        self.assertEqual(avg_latlon['latitude'], 51.961922091398904,
 | 
			
		||||
            msg='%s: (no places) average latitude missmatch' % (
 | 
			
		||||
                self.model.__name__
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(avg_latlon['longitude'], 0,
 | 
			
		||||
        self.assertEqual(avg_latlon['longitude'], 7.6295628132604385,
 | 
			
		||||
            msg='%s: (no places) average longitude missmatch' % (
 | 
			
		||||
                self.model.__name__
 | 
			
		||||
            )
 | 
			
		||||
@@ -124,3 +125,169 @@ class PlaceTestCase(ModelTestCase):
 | 
			
		||||
                self.model.__name__
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_level_calculation(self):
 | 
			
		||||
        explorer = self.place.submitted_by
 | 
			
		||||
 | 
			
		||||
        PlaceVoting.objects.create(
 | 
			
		||||
            submitted_by=explorer,
 | 
			
		||||
            place=self.place,
 | 
			
		||||
            vote=5
 | 
			
		||||
        )
 | 
			
		||||
        PlaceVoting.objects.create(
 | 
			
		||||
            submitted_by=explorer,
 | 
			
		||||
            place=self.place,
 | 
			
		||||
            vote=2
 | 
			
		||||
        )
 | 
			
		||||
        PlaceVoting.objects.create(
 | 
			
		||||
            submitted_by=explorer,
 | 
			
		||||
            place=self.place,
 | 
			
		||||
            vote=4
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.place.calculate_place_level()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            4,
 | 
			
		||||
            self.place.level,
 | 
			
		||||
            msg='Expecting the place level to be 4'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_level_calculation_no_votes(self):
 | 
			
		||||
        self.place.calculate_place_level()
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            5,
 | 
			
		||||
            self.place.level,
 | 
			
		||||
            msg='Expecting the default place level to be 5'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_level_mid_accuracy(self):
 | 
			
		||||
        explorer = self.place.submitted_by
 | 
			
		||||
        six_month_ago = datetime.timedelta(days=180)
 | 
			
		||||
        self.place.submitted_when = timezone.now() - six_month_ago
 | 
			
		||||
 | 
			
		||||
        votings = [
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=170),
 | 
			
		||||
                'vote': 5
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=23),
 | 
			
		||||
                'vote': 2
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=1),
 | 
			
		||||
                'vote': 4
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for vote in votings:
 | 
			
		||||
            voting = PlaceVoting.objects.create(
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                place=self.place,
 | 
			
		||||
                vote= vote['vote']
 | 
			
		||||
            )
 | 
			
		||||
            voting.submitted_when = vote['date']
 | 
			
		||||
            voting.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            65,
 | 
			
		||||
            self.place.calculate_voting_accuracy()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_level_high_accuracy(self):
 | 
			
		||||
        explorer = self.place.submitted_by
 | 
			
		||||
        six_month_ago = datetime.timedelta(days=180)
 | 
			
		||||
        self.place.submitted_when = timezone.now() - six_month_ago
 | 
			
		||||
 | 
			
		||||
        votings = [
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=9),
 | 
			
		||||
                'vote': 5
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=14),
 | 
			
		||||
                'vote': 2
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=0),
 | 
			
		||||
                'vote': 4
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for vote in votings:
 | 
			
		||||
            voting = PlaceVoting.objects.create(
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                place=self.place,
 | 
			
		||||
                vote= vote['vote']
 | 
			
		||||
            )
 | 
			
		||||
            voting.submitted_when = vote['date']
 | 
			
		||||
            voting.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            96,
 | 
			
		||||
            self.place.calculate_voting_accuracy()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_level_low_accuracy(self):
 | 
			
		||||
        explorer = self.place.submitted_by
 | 
			
		||||
        six_month_ago = datetime.timedelta(days=180)
 | 
			
		||||
        self.place.submitted_when = timezone.now() - six_month_ago
 | 
			
		||||
 | 
			
		||||
        votings = [
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=177),
 | 
			
		||||
                'vote': 5
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=150),
 | 
			
		||||
                'vote': 2
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=100),
 | 
			
		||||
                'vote': 4
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for vote in votings:
 | 
			
		||||
            voting = PlaceVoting.objects.create(
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                place=self.place,
 | 
			
		||||
                vote= vote['vote']
 | 
			
		||||
            )
 | 
			
		||||
            voting.submitted_when = vote['date']
 | 
			
		||||
            voting.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            21,
 | 
			
		||||
            self.place.calculate_voting_accuracy()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_level_accuracy_zero_timedelta(self):
 | 
			
		||||
        explorer = self.place.submitted_by
 | 
			
		||||
        six_month_ago = datetime.timedelta(days=180)
 | 
			
		||||
        self.place.submitted_when = timezone.now() - six_month_ago
 | 
			
		||||
 | 
			
		||||
        votings = [
 | 
			
		||||
            {
 | 
			
		||||
                'date': timezone.now() - datetime.timedelta(days=0),
 | 
			
		||||
                'vote': 4
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for vote in votings:
 | 
			
		||||
            voting = PlaceVoting.objects.create(
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                place=self.place,
 | 
			
		||||
                vote= vote['vote']
 | 
			
		||||
            )
 | 
			
		||||
            voting.submitted_when = vote['date']
 | 
			
		||||
            voting.save()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            100,
 | 
			
		||||
            self.place.calculate_voting_accuracy(),
 | 
			
		||||
            msg='Expecting the accurcy to be 100% when the vote is 0 time units old'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								django_lostplaces/lostplaces/tests/views/im_a_image.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								django_lostplaces/lostplaces/tests/views/im_a_image.jpeg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 81 KiB  | 
@@ -1,4 +1,4 @@
 | 
			
		||||
23238#!/usr/bin/env python
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase, RequestFactory, Client
 | 
			
		||||
@@ -7,6 +7,7 @@ from django.db import models
 | 
			
		||||
from django.contrib.auth.models import User, AnonymousUser
 | 
			
		||||
from django.contrib.messages.storage.fallback import FallbackStorage
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										300
									
								
								django_lostplaces/lostplaces/tests/views/test_explorer_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								django_lostplaces/lostplaces/tests/views/test_explorer_views.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,300 @@
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from lostplaces.tests.views import (
 | 
			
		||||
    ViewTestCase,
 | 
			
		||||
    GlobalTemplateTestCaseMixin
 | 
			
		||||
)
 | 
			
		||||
from lostplaces.views import ExplorerProfileView
 | 
			
		||||
from lostplaces.models import(
 | 
			
		||||
    Place,
 | 
			
		||||
    PlaceImage,
 | 
			
		||||
    PhotoAlbum
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class TestExplorerProfileView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
    view = ExplorerProfileView
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        user = User.objects.create_user(
 | 
			
		||||
            username='testpeter',
 | 
			
		||||
            password='Develop123'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_unauth_profile_access(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_profile', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpCode(response, 302)
 | 
			
		||||
        self.assertFalse(
 | 
			
		||||
            user.username in response.content.decode(),
 | 
			
		||||
            msg='Expecting the username to not be visible to unauthorized users'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_unauth_profile_access_follow_redirect(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_profile', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            }),
 | 
			
		||||
            follow=True
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            _('Please login to proceed') in response.content.decode(),
 | 
			
		||||
            msg='Expecting a message to tell the user to login'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_explorer_places(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        Place.objects.create(
 | 
			
		||||
            name='Im the latest place 4369',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            level=3
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_profile', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im the latest place 4369' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the latest place to be visible on the submitters profile page'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_draft_in_explorer_places(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        Place.objects.create(
 | 
			
		||||
            name='Im the latest place in draft 3671',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            level=3,
 | 
			
		||||
            mode='draft'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_profile', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertFalse(
 | 
			
		||||
            'Im the latest place in draft 3671' in response.content.decode(),
 | 
			
		||||
            msg='Expecting a place in draft mode to not show up on the submitters profile page'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_explorer_image(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a the latest place 4369',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            level=3
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        current_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
        file_path = os.path.join(
 | 
			
		||||
            settings.MEDIA_ROOT, 
 | 
			
		||||
            settings.RELATIVE_THUMBNAIL_PATH,
 | 
			
		||||
            'im_a_image_3649.jpeg'
 | 
			
		||||
        )
 | 
			
		||||
        shutil.copyfile(
 | 
			
		||||
            os.path.join(current_dir, 'im_a_image.jpeg'),
 | 
			
		||||
            file_path
 | 
			
		||||
        )
 | 
			
		||||
        PlaceImage.objects.create(
 | 
			
		||||
            description='Im a description',
 | 
			
		||||
            filename=file_path,
 | 
			
		||||
            place=place,
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_profile', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            os.path.join(settings.RELATIVE_THUMBNAIL_PATH,'im_a_image_3649.jpeg') in response.content.decode(),
 | 
			
		||||
            msg='Expecting the latest place image to be visible on the submitters profile page'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_explorer_photoalbum(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a the latest place 4369',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            level=3
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        PhotoAlbum.objects.create(
 | 
			
		||||
            place=place,
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            url='http://example.org/6897134',
 | 
			
		||||
            label='Im a exmpale link label 6423'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_profile', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'href="http://example.org/6897134"' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the latest photoalbum url to be linked on the submitters profile page'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a exmpale link label 6423' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the latest photoalbum label to be on the submitters profile page'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
class TestExplorerDraftsView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        user = User.objects.create_user(
 | 
			
		||||
            username='testpeter',
 | 
			
		||||
            password='Develop123'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        User.objects.create_user(
 | 
			
		||||
            username='otheruser',
 | 
			
		||||
            password='Develop123'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        superuser = User.objects.create_user(
 | 
			
		||||
            username='toor',
 | 
			
		||||
            password='Develop123'
 | 
			
		||||
        )
 | 
			
		||||
        superuser.is_superuser = True
 | 
			
		||||
        superuser.save()
 | 
			
		||||
 | 
			
		||||
    def test_draft_view(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_drafts', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
 | 
			
		||||
    def test_draft_view_unauthorized_user(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        self.client.login(username='otheruser', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_drafts', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
        self.assertHttpForbidden(response)
 | 
			
		||||
 | 
			
		||||
    def test_draft_view_superuser(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        self.client.login(username='toor', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_drafts', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
    
 | 
			
		||||
    def test_place_in_draft_view(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        Place.objects.create(
 | 
			
		||||
            name='Im a draft place 3792',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            mode='draft',
 | 
			
		||||
            level=3
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_drafts', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a draft place 3792' in response.content.decode(),
 | 
			
		||||
            msg='Expecting a place draft to be visible in the submitters drafs view'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_place_not_in_draft_view(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        Place.objects.create(
 | 
			
		||||
            name='Im a draft place 3819',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            level=3
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('explorer_drafts', kwargs={
 | 
			
		||||
                'explorer_id': user.explorer.id
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertFalse(
 | 
			
		||||
            'Im a draft place 3819' in response.content.decode(),
 | 
			
		||||
            msg='Expecting a live place to not be visible in the submitters drafs view'
 | 
			
		||||
        )
 | 
			
		||||
@@ -13,6 +13,7 @@ from django.utils.translation import gettext as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place
 | 
			
		||||
from lostplaces.models import PLACE_MODES
 | 
			
		||||
from lostplaces.views import (
 | 
			
		||||
    PlaceCreateView,
 | 
			
		||||
    PlaceListView,
 | 
			
		||||
@@ -155,6 +156,38 @@ class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
            msg='Expecting the user to see places where their level is high enough'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_place_mode_filter(self):
 | 
			
		||||
        explorer = User.objects.get(username='testpeter').explorer
 | 
			
		||||
        Place.objects.all().delete()
 | 
			
		||||
 | 
			
		||||
        for mode in PLACE_MODES:
 | 
			
		||||
            place = Place.objects.create(
 | 
			
		||||
                name='Im a place in mode %s' % mode[0],
 | 
			
		||||
                submitted_when=timezone.now(),
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                location='Test town',
 | 
			
		||||
                latitude=50.5,
 | 
			
		||||
                longitude=7.0,
 | 
			
		||||
                description='This is just a test, do not worry %s' % mode[0],
 | 
			
		||||
                level=3,
 | 
			
		||||
                mode=mode[0]
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.get(reverse('place_list'))
 | 
			
		||||
 | 
			
		||||
        for mode in PLACE_MODES:
 | 
			
		||||
            if ('Im a place in mode %s' % mode[0]) in response.content.decode():
 | 
			
		||||
                self.assertTrue(
 | 
			
		||||
                    mode[0] == 'live',
 | 
			
		||||
                    msg='Expecting only places in mode \'live\' to be listed, saw a place in mode %s' % mode[0]
 | 
			
		||||
                )
 | 
			
		||||
            elif mode[0] == 'live':
 | 
			
		||||
                self.fail(
 | 
			
		||||
                    msg='Expecting at least one place in mode \'live\' to be listed'
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
class TestPlaceCreateView(ViewTestCase):
 | 
			
		||||
    view = PlaceCreateView
 | 
			
		||||
 | 
			
		||||
@@ -209,6 +242,11 @@ class TestPlaceCreateView(ViewTestCase):
 | 
			
		||||
            'success',
 | 
			
		||||
            msg='Expecting a visible success message'
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            Place.objects.get(name='test place 486').mode,
 | 
			
		||||
            'live',
 | 
			
		||||
            msg='Expeting the place to be in \'live\' mode'
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
    def test_positive_image(self):
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
@@ -258,6 +296,11 @@ class TestPlaceCreateView(ViewTestCase):
 | 
			
		||||
            'success',
 | 
			
		||||
            msg='Expecting a visible success message'
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            Place.objects.get(name='test place 894').mode,
 | 
			
		||||
            'live',
 | 
			
		||||
            msg='Expeting the place to be in \'live\' mode'
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
    def test_negative_no_name(self):
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
@@ -363,6 +406,31 @@ class TestPlaceCreateView(ViewTestCase):
 | 
			
		||||
            msg='Expecing a visible error message'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_positve_save_as_draft(self):
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            reverse('place_create'),
 | 
			
		||||
            {
 | 
			
		||||
                'name': 'test name 6483',
 | 
			
		||||
                'location': 'wurstwasser',
 | 
			
		||||
                'latitude': 45.4654,
 | 
			
		||||
                'longitude': 68.135489,
 | 
			
		||||
                'description': """
 | 
			
		||||
                Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
 | 
			
		||||
                """,
 | 
			
		||||
                'draft': True
 | 
			
		||||
            },
 | 
			
		||||
            follow=True
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            Place.objects.get(name='test name 6483').mode,
 | 
			
		||||
            'draft',
 | 
			
		||||
            msg='Expeting the place to be in \'draft\' mode'
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixin, ViewTestCase):
 | 
			
		||||
    view = PlaceDetailView
 | 
			
		||||
 | 
			
		||||
@@ -416,6 +484,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
        self.client.login(username='blubberbernd', password='Develop123')
 | 
			
		||||
        response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
 | 
			
		||||
 | 
			
		||||
        self.assertHttpForbidden(response)
 | 
			
		||||
        self.assertFalse(
 | 
			
		||||
            'Im a place' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the user to not see the places'
 | 
			
		||||
@@ -425,6 +494,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
        self.client.login(username='toor', password='Develop123')
 | 
			
		||||
        response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a place' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the superuser to see all places'
 | 
			
		||||
@@ -434,6 +504,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
        self.client.login(username='blubberbernd', password='Develop123')
 | 
			
		||||
        response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a own place' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the user to see it\'s own places'
 | 
			
		||||
@@ -443,6 +514,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
 | 
			
		||||
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a own place' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the user to see places where their level is high enough'
 | 
			
		||||
@@ -606,5 +678,62 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
            msg='Expecting the explorer to not be in the reverse list of visits after deleting visit'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_accessing_place_in_draft(self):
 | 
			
		||||
        user = User.objects.get(username='testpeter')
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a own place in draft 387948',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Test %d town' % 5,
 | 
			
		||||
            latitude=50.5 + 5/10,
 | 
			
		||||
            longitude=7.0 - 5/10,
 | 
			
		||||
            description='This is just a test, do not worry %d' % 5,
 | 
			
		||||
            level=3,
 | 
			
		||||
            mode='draft'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('place_detail', kwargs={
 | 
			
		||||
                'pk': place.pk
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a own place in draft 387948' in response.content.decode(),
 | 
			
		||||
            msg='Expecting the user to see his own place in draft mode'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='blubberbernd', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('place_detail', kwargs={
 | 
			
		||||
                'pk': place.pk
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
        self.assertHttpForbidden(response)
 | 
			
		||||
 | 
			
		||||
        self.client.login(username='toor', password='Develop123')
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            reverse('place_detail', kwargs={
 | 
			
		||||
                'pk': place.pk
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
        self.assertHttpOK(response)
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            'Im a own place in draft 387948' in response.content.decode(),
 | 
			
		||||
            msg='Expecting a superuser to see all places in draft mode'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
@@ -23,7 +23,10 @@ from lostplaces.views import (
 | 
			
		||||
	PhotoAlbumDeleteView,
 | 
			
		||||
    ExplorerProfileView,
 | 
			
		||||
    ExplorerProfileUpdateView,
 | 
			
		||||
    PlaceVoteView
 | 
			
		||||
    ExplorerDraftsView,
 | 
			
		||||
    PlaceVoteView,
 | 
			
		||||
    UploadMapFileView,
 | 
			
		||||
    ImportDetailView
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
@@ -33,6 +36,7 @@ urlpatterns = [
 | 
			
		||||
 | 
			
		||||
    path('explorer/<int:explorer_id>/', ExplorerProfileView.as_view(), name='explorer_profile'),
 | 
			
		||||
    path('explorer/update/', ExplorerProfileUpdateView.as_view(), name='explorer_profile_update'),
 | 
			
		||||
    path('explorer/<int:explorer_id>/drafts', ExplorerDraftsView.as_view(), name='explorer_drafts'),
 | 
			
		||||
 | 
			
		||||
    path('place/', PlaceListView.as_view(), name='place_list'),
 | 
			
		||||
    path('place/<int:pk>/', PlaceDetailView.as_view(), name='place_detail'),
 | 
			
		||||
@@ -52,5 +56,8 @@ urlpatterns = [
 | 
			
		||||
    path('place/vote/<int:place_id>/<int:vote>', PlaceVoteView.as_view(), name='place_vote'),
 | 
			
		||||
 | 
			
		||||
	path('photo_album/create/<int:place_id>/', PhotoAlbumCreateView.as_view(), name='photo_album_create'),
 | 
			
		||||
	path('photo_album/delete/<int:pk>/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete')
 | 
			
		||||
	path('photo_album/delete/<int:pk>/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete'),
 | 
			
		||||
 | 
			
		||||
    path('import/upload', UploadMapFileView.as_view(), name='import_upload'),
 | 
			
		||||
    path('import/<int:pk>', ImportDetailView.as_view(), name='import_detail')
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -6,3 +6,4 @@ from lostplaces.views.views import *
 | 
			
		||||
from lostplaces.views.place_views import *
 | 
			
		||||
from lostplaces.views.place_image_views import *
 | 
			
		||||
from lostplaces.views.explorer_views import *
 | 
			
		||||
from lostplaces.views.imports import *
 | 
			
		||||
@@ -31,6 +31,21 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
 | 
			
		||||
            messages.error(self.request, self.permission_denied_message)
 | 
			
		||||
        return super().handle_no_permission()
 | 
			
		||||
 | 
			
		||||
class IsSuperUserMixin(UserPassesTestMixin, View):
 | 
			
		||||
    '''
 | 
			
		||||
    A view mixin that checks if the user is a superuser.
 | 
			
		||||
    Users who are not logged in or users who are no superuser get
 | 
			
		||||
    a permission deined message.
 | 
			
		||||
    '''
 | 
			
		||||
    permission_denied_message = _('You are not allowed to see this page')
 | 
			
		||||
 | 
			
		||||
    def test_func(self):
 | 
			
		||||
        return self.request.user.is_superuser
 | 
			
		||||
 | 
			
		||||
    def handle_no_permission(self):
 | 
			
		||||
        messages.error(self.request, self.permission_denied_message)
 | 
			
		||||
        return super().handle_no_permission()
 | 
			
		||||
 | 
			
		||||
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
 | 
			
		||||
    '''
 | 
			
		||||
    A view mixin that checks wether a user is the submitter
 | 
			
		||||
@@ -138,4 +153,10 @@ class LevelCapPlaceListView(ListView):
 | 
			
		||||
    model = Place
 | 
			
		||||
    
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return self.request.user.explorer.get_places_eligible_to_see()
 | 
			
		||||
        return self.request.user.explorer.get_place_list_to_display()
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context['place_list'] = context.pop('object_list')
 | 
			
		||||
        return context
 | 
			
		||||
    
 | 
			
		||||
@@ -8,6 +8,7 @@ from django.utils.translation import gettext as _
 | 
			
		||||
from django.shortcuts import render, redirect, get_object_or_404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.mixins import UserPassesTestMixin
 | 
			
		||||
 | 
			
		||||
from lostplaces.common import get_all_subclasses
 | 
			
		||||
from lostplaces.views.base_views import IsAuthenticatedMixin
 | 
			
		||||
@@ -18,7 +19,7 @@ from lostplaces.forms import ExplorerChangeForm, ExplorerUserChangeForm
 | 
			
		||||
class ExplorerProfileView(IsAuthenticatedMixin, View):
 | 
			
		||||
    def get(self, request, explorer_id):
 | 
			
		||||
        explorer = get_object_or_404(Explorer, pk=explorer_id)
 | 
			
		||||
        place_list = explorer.places.all()
 | 
			
		||||
        place_list = explorer.get_place_list_to_display()
 | 
			
		||||
        place_count = place_list.count()
 | 
			
		||||
 | 
			
		||||
        context={
 | 
			
		||||
@@ -82,3 +83,20 @@ class ExplorerProfileUpdateView(IsAuthenticatedMixin, View):
 | 
			
		||||
            )
 | 
			
		||||
        return redirect(reverse_lazy('explorer_profile_update'))
 | 
			
		||||
    
 | 
			
		||||
class ExplorerDraftsView(IsAuthenticatedMixin, UserPassesTestMixin, View):
 | 
			
		||||
 | 
			
		||||
    def get_explorer(self):
 | 
			
		||||
        return get_object_or_404(Explorer, pk=self.kwargs['explorer_id'])
 | 
			
		||||
 | 
			
		||||
    def test_func(self):
 | 
			
		||||
        if not hasattr(self.request, 'user'):
 | 
			
		||||
            return False
 | 
			
		||||
        return self.request.user == self.get_explorer().user or self.request.user.is_superuser
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        context = {
 | 
			
		||||
            'explorer': self.get_explorer(),
 | 
			
		||||
            'place_list': self.get_explorer().get_drafts()
 | 
			
		||||
        }
 | 
			
		||||
        return render(request, 'explorer/drafts.html', context)
 | 
			
		||||
            
 | 
			
		||||
							
								
								
									
										91
									
								
								django_lostplaces/lostplaces/views/imports.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								django_lostplaces/lostplaces/views/imports.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.shortcuts import render, redirect, get_object_or_404
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext as _
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
 | 
			
		||||
from pykml import parser as kml_parser
 | 
			
		||||
 | 
			
		||||
from lostplaces.forms import UploadMapFileForm
 | 
			
		||||
from lostplaces.models import Place, PlaceImport
 | 
			
		||||
from lostplaces.views.base_views import (
 | 
			
		||||
    IsSuperUserMixin
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class UploadMapFileView(IsSuperUserMixin, View):
 | 
			
		||||
    permission_denied_message = _('You are not allowed to import any places')
 | 
			
		||||
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
        upload_form = UploadMapFileForm()
 | 
			
		||||
        return render(request, 'import/upload_map_file.html', {'upload_form': upload_form})
 | 
			
		||||
 | 
			
		||||
    def post(self, request):
 | 
			
		||||
        upload_form = UploadMapFileForm(request.POST, request.FILES)
 | 
			
		||||
        explorer = request.user.explorer
 | 
			
		||||
 | 
			
		||||
        if upload_form.is_valid():
 | 
			
		||||
            map_file = upload_form.cleaned_data['map_file']
 | 
			
		||||
            parsed_kml = kml_parser.fromstring(map_file.read())
 | 
			
		||||
 | 
			
		||||
            place_import = PlaceImport.objects.create(
 | 
			
		||||
                explorer=request.user.explorer,
 | 
			
		||||
                description=upload_form.cleaned_data['description']
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            for folder in parsed_kml.Document.Folder:
 | 
			
		||||
                for place_kml in folder.Placemark:
 | 
			
		||||
                    lat_long = self.get_lat_long(place_kml)
 | 
			
		||||
 | 
			
		||||
                    name = str(place_kml.name) if hasattr(place_kml, 'name')  else ''
 | 
			
		||||
                    description = str(place_kml.description) if hasattr(place_kml, 'description') else ''
 | 
			
		||||
 | 
			
		||||
                    place_model = Place.objects.create(
 | 
			
		||||
                        name=name.strip(),
 | 
			
		||||
                        latitude=lat_long[0],
 | 
			
		||||
                        longitude=lat_long[1],
 | 
			
		||||
                        description=description.strip(),
 | 
			
		||||
                        place_import=place_import,
 | 
			
		||||
                        mode='imported'
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            place_import.save()
 | 
			
		||||
 | 
			
		||||
        return redirect(reverse('lostplaces_home'))
 | 
			
		||||
 | 
			
		||||
    def get_lat_long(self, place_kml):
 | 
			
		||||
        if hasattr(place_kml, 'Point') and len(place_kml.Point.coordinates) >= 1:
 | 
			
		||||
            coordinates = str(place_kml.Point.coordinates[0]).strip()
 | 
			
		||||
            splited = coordinates.split(',')
 | 
			
		||||
            latitude = 0
 | 
			
		||||
            longitude = 0
 | 
			
		||||
 | 
			
		||||
            if len(splited) >= 1:
 | 
			
		||||
                longitude = splited[0]
 | 
			
		||||
            if len(splited) >= 2:
 | 
			
		||||
                latitude = splited[1]
 | 
			
		||||
 | 
			
		||||
            return (latitude, longitude)
 | 
			
		||||
        else:
 | 
			
		||||
            return (0, 0)
 | 
			
		||||
 | 
			
		||||
class ImportDetailView(IsSuperUserMixin, View):
 | 
			
		||||
    permission_denied_message = _('You are not allowed to see this import\'s details')
 | 
			
		||||
 | 
			
		||||
    def get_import(self):
 | 
			
		||||
        return get_object_or_404(PlaceImport, pk=self.kwargs['pk'])
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        place_import = self.get_import()
 | 
			
		||||
 | 
			
		||||
        place_paginator = Paginator(place_import.place_list.all(), 18)
 | 
			
		||||
        paginated_places = place_paginator.get_page(
 | 
			
		||||
            request.GET.get('page')
 | 
			
		||||
        )
 | 
			
		||||
        context = {
 | 
			
		||||
            'import': place_import,
 | 
			
		||||
            'paginated_places': paginated_places,
 | 
			
		||||
            'import_type': place_import.get_import_type_display()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return render(request, 'import/import_detail_view.html', context)
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +36,7 @@ from lostplaces.common import redirect_referer_or
 | 
			
		||||
from taggit.models import Tag
 | 
			
		||||
 | 
			
		||||
class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
 | 
			
		||||
    paginate_by = 5
 | 
			
		||||
    paginate_by = 18
 | 
			
		||||
    template_name = 'place/place_list.html'
 | 
			
		||||
    ordering = [Lower('name')]
 | 
			
		||||
 | 
			
		||||
@@ -59,6 +59,17 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
        place.calculate_place_level()
 | 
			
		||||
        explorer = request.user.explorer
 | 
			
		||||
 | 
			
		||||
        if place.mode == 'draft':
 | 
			
		||||
            messages.info(
 | 
			
		||||
                self.request,
 | 
			
		||||
                _('This place is still in draft mode and only visible to the submitter and superusers')
 | 
			
		||||
            )
 | 
			
		||||
        elif place.mode == 'imported':
 | 
			
		||||
            messages.info(
 | 
			
		||||
                self.request,
 | 
			
		||||
                _('This place was imported and not reviewed & correted yet. This place is visbible for superusers only and does not appear in the list views.')
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        context = {
 | 
			
		||||
            'place': place,
 | 
			
		||||
            'mapping_config': {
 | 
			
		||||
@@ -74,7 +85,8 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
            },
 | 
			
		||||
            'placevoting': {
 | 
			
		||||
                'users_vote': PlaceVoting.objects.filter(place=place, submitted_by=explorer).first(),
 | 
			
		||||
                'all_choices': reversed(PLACE_LEVELS)
 | 
			
		||||
                'all_choices': reversed(PLACE_LEVELS),
 | 
			
		||||
                'accuracy': place.calculate_voting_accuracy()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return render(request, 'place/place_detail.html', context)
 | 
			
		||||
@@ -112,6 +124,9 @@ class PlaceCreateView(MultiplePlaceImageUploadMixin, IsAuthenticatedMixin, View)
 | 
			
		||||
            place = place_form.save(commit=False)
 | 
			
		||||
            # Save logged in user as "submitted_by"
 | 
			
		||||
            place.submitted_by = submitter
 | 
			
		||||
            if place_form.cleaned_data['draft']:
 | 
			
		||||
                place.mode = 'draft';
 | 
			
		||||
 | 
			
		||||
            place.save()
 | 
			
		||||
 | 
			
		||||
            self.handle_place_images(request, place)
 | 
			
		||||
@@ -204,8 +219,11 @@ class PlaceVisitDeleteView(IsAuthenticatedMixin, View):
 | 
			
		||||
class PlaceVoteView(IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
    delta = timedelta(weeks=24)
 | 
			
		||||
    
 | 
			
		||||
    def get_place(self):
 | 
			
		||||
        return get_object_or_404(Place, pk=self.kwargs['place_id'])
 | 
			
		||||
 | 
			
		||||
    def get(self, request, place_id, vote):
 | 
			
		||||
        place = get_object_or_404(Place, id=place_id)
 | 
			
		||||
        place = self.get_place()
 | 
			
		||||
        explorer = request.user.explorer
 | 
			
		||||
        
 | 
			
		||||
        voting = PlaceVoting.objects.filter(
 | 
			
		||||
@@ -217,12 +235,11 @@ class PlaceVoteView(IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
            voting = PlaceVoting.objects.create(
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                place=place,
 | 
			
		||||
                vote=vote,
 | 
			
		||||
                expires_when=timezone.now()+self.delta
 | 
			
		||||
                vote=vote
 | 
			
		||||
            )
 | 
			
		||||
            messages.success(self.request, _('Vote submitted'))
 | 
			
		||||
        else:
 | 
			
		||||
            voting.expires_when=timezone.now()+self.delta
 | 
			
		||||
            voting.submitted_when = timezone.now()
 | 
			
		||||
            voting.vote = vote
 | 
			
		||||
            messages.success(self.request, _('Your vote has been update'))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ from django.utils.translation import gettext as _
 | 
			
		||||
 | 
			
		||||
from lostplaces.forms import SignupVoucherForm, TagSubmitForm
 | 
			
		||||
from lostplaces.models import Place, PhotoAlbum
 | 
			
		||||
from lostplaces.views.base_views import IsAuthenticatedMixin
 | 
			
		||||
from lostplaces.views.base_views import IsAuthenticatedMixin, LevelCapPlaceListView
 | 
			
		||||
from lostplaces.common import redirect_referer_or
 | 
			
		||||
 | 
			
		||||
from lostplaces.views.base_views import (
 | 
			
		||||
@@ -29,17 +29,19 @@ class SignUpView(SuccessMessageMixin, CreateView):
 | 
			
		||||
    template_name = 'signup.html'
 | 
			
		||||
    success_message = _('User created')
 | 
			
		||||
 | 
			
		||||
class HomeView(IsAuthenticatedMixin, View):
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        place_list = request.user.explorer.get_places_eligible_to_see()
 | 
			
		||||
        context = {
 | 
			
		||||
            'place_list': place_list,
 | 
			
		||||
            'mapping_config': {
 | 
			
		||||
class HomeView(IsAuthenticatedMixin, LevelCapPlaceListView, View):
 | 
			
		||||
    template_name = 'home.html'
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        place_list = context['place_list']
 | 
			
		||||
 | 
			
		||||
        context['mapping_config'] = {
 | 
			
		||||
            'all_points': place_list,
 | 
			
		||||
            'map_center': Place.average_latlon(place_list)
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
        return render(request, 'home.html', context)
 | 
			
		||||
        return context
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def handle_no_permission(self):
 | 
			
		||||
        place_list = Place.objects.filter(level=1)[:5]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user