Merge branch 'develop' of mowoe.com:reverend/lostplaces-backend into develop
This commit is contained in:
		@@ -1,11 +1,12 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
settings.THUMBNAIL_ALIASES = {
 | 
			
		||||
    '': {
 | 
			
		||||
        'thumbnail': {'size': (300, 200), 'crop': True},
 | 
			
		||||
        'hero': {'size': (700, 466), 'crop': True},
 | 
			
		||||
        'large': {'size': (1920, 1920), 'crop': False},
 | 
			
		||||
        'thumbnail': {'size': (300, 200), 'sharpen': True, 'crop': True},
 | 
			
		||||
        'hero': {'size': (700, 466), 'sharpen': True, 'crop': True},
 | 
			
		||||
        'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False},
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
from django.contrib.auth.admin import UserAdmin
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from lostplaces.models import *
 | 
			
		||||
 | 
			
		||||
from lostplaces.forms import ExplorerCreationForm, ExplorerChangeForm
 | 
			
		||||
@@ -15,9 +16,24 @@ from lostplaces.forms import ExplorerCreationForm, ExplorerChangeForm
 | 
			
		||||
class VoucherAdmin(admin.ModelAdmin):
 | 
			
		||||
    fields = ['code', 'expires_when', 'created_when']
 | 
			
		||||
    readonly_fields = ['created_when']
 | 
			
		||||
    list_display = ('__str__', 'code', 'created_when', 'expires_when', 'valid')
 | 
			
		||||
 | 
			
		||||
    def valid(self, instance):
 | 
			
		||||
        return timezone.now() <= instance.expires_when
 | 
			
		||||
    
 | 
			
		||||
    valid.boolean = True
 | 
			
		||||
 | 
			
		||||
class PhotoAlbumsAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('label', 'place', 'url' )
 | 
			
		||||
 | 
			
		||||
class PlacesAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('name', 'submitted_by', 'submitted_when')
 | 
			
		||||
 | 
			
		||||
class PlaceImagesAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('__str__', 'place', 'submitted_by')
 | 
			
		||||
 | 
			
		||||
admin.site.register(Explorer)
 | 
			
		||||
admin.site.register(Voucher, VoucherAdmin)
 | 
			
		||||
admin.site.register(Place)
 | 
			
		||||
admin.site.register(PlaceImage)
 | 
			
		||||
admin.site.register(PhotoAlbum)
 | 
			
		||||
admin.site.register(Place, PlacesAdmin)
 | 
			
		||||
admin.site.register(PlaceImage, PlaceImagesAdmin)
 | 
			
		||||
admin.site.register(PhotoAlbum, PhotoAlbumsAdmin)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.apps import AppConfig
 | 
			
		||||
 | 
			
		||||
class LostplacesAppConfig(AppConfig):
 | 
			
		||||
 
 | 
			
		||||
@@ -19,13 +19,17 @@ class ExplorerCreationForm(UserCreationForm):
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        super().is_valid()
 | 
			
		||||
        sumitted_voucher = self.cleaned_data.get('voucher')
 | 
			
		||||
        submitted_voucher = self.cleaned_data.get('voucher')
 | 
			
		||||
        try:
 | 
			
		||||
            fetched_voucher = Voucher.objects.get(code=sumitted_voucher)
 | 
			
		||||
            fetched_voucher = Voucher.objects.get(code=submitted_voucher)
 | 
			
		||||
        except Voucher.DoesNotExist:
 | 
			
		||||
            self.add_error('voucher', 'Invalid voucher')
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if not submitted_voucher.valid:
 | 
			
		||||
            self.add_error('voucher', 'Expired voucher')
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        fetched_voucher.delete()
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +44,7 @@ class PlaceForm(forms.ModelForm):
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        exclude = ['submitted_by']
 | 
			
		||||
        
 | 
			
		||||
class PlaceImageCreateForm(forms.ModelForm):
 | 
			
		||||
class PlaceImageForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = PlaceImage
 | 
			
		||||
        fields = ['filename']
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,9 @@ from django.contrib.auth.models import User
 | 
			
		||||
from django.db.models.signals import post_save
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
from django.core.validators import MaxValueValidator, MinValueValidator
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from easy_thumbnails.fields import ThumbnailerImageField
 | 
			
		||||
from easy_thumbnails.files import get_thumbnailer
 | 
			
		||||
from taggit.managers import TaggableManager
 | 
			
		||||
 | 
			
		||||
# Create your models here.
 | 
			
		||||
@@ -46,7 +48,7 @@ def save_user_profile(sender, instance, **kwargs):
 | 
			
		||||
 | 
			
		||||
class Taggable(models.Model):
 | 
			
		||||
    '''
 | 
			
		||||
    This abstract model represtens an object that is taggalble
 | 
			
		||||
    This abstract model represtens an object that is taggable
 | 
			
		||||
    using django-taggit
 | 
			
		||||
    '''
 | 
			
		||||
    class Meta:
 | 
			
		||||
@@ -106,8 +108,12 @@ class Voucher(models.Model):
 | 
			
		||||
    created_when = models.DateTimeField(auto_now_add=True)
 | 
			
		||||
    expires_when = models.DateTimeField()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def valid(self):
 | 
			
		||||
        return timezone.now() <= self.expires_when
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "Voucher " + str(self.code)
 | 
			
		||||
        return "Voucher " + str(self.pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
@@ -145,20 +151,25 @@ class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
def generate_image_upload_path(instance, filename):
 | 
			
		||||
    """
 | 
			
		||||
    Callback for generating path for uploaded images.
 | 
			
		||||
    Returns filename as: place_pk-placename{-rnd_string}.jpg
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    return 'places/' + str(uuid.uuid4())+'.'+filename.split('.')[-1]
 | 
			
		||||
    return 'places/' + str(instance.place.pk) + '-' + str(instance.place.name) + '.' + filename.split('.')[-1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlaceImage (Submittable):
 | 
			
		||||
    """
 | 
			
		||||
    PlaceImage defines an image file object that points to a file in uploads/.
 | 
			
		||||
    Intermediate image sizes are generated as defined in SIZES.
 | 
			
		||||
    Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES.
 | 
			
		||||
    PlaceImage references a Place to which it belongs.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    description = models.TextField(blank=True)
 | 
			
		||||
    filename = ThumbnailerImageField(upload_to=generate_image_upload_path)
 | 
			
		||||
    filename = ThumbnailerImageField(
 | 
			
		||||
        upload_to=generate_image_upload_path, 
 | 
			
		||||
        resize_source=dict(size=(2560, 2560), 
 | 
			
		||||
        sharpen=True)
 | 
			
		||||
    )
 | 
			
		||||
    place = models.ForeignKey(
 | 
			
		||||
        Place,
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
@@ -168,23 +179,24 @@ class PlaceImage (Submittable):
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        """
 | 
			
		||||
        Returning the name of the corresponding place + id 
 | 
			
		||||
        of this image as textual represntation of this instance
 | 
			
		||||
        of this image as textual representation of this instance
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        return ' '.join([self.place.name, str(self.pk)])
 | 
			
		||||
        return 'Image ' + str(self.pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# These two auto-delete files from filesystem when they are unneeded:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(models.signals.post_delete, sender=PlaceImage)
 | 
			
		||||
def auto_delete_file_on_delete(sender, instance, **kwargs):
 | 
			
		||||
    """
 | 
			
		||||
    Deletes file from filesystem
 | 
			
		||||
    Deletes file (including thumbnails) from filesystem
 | 
			
		||||
    when corresponding `PlaceImage` object is deleted.
 | 
			
		||||
    """
 | 
			
		||||
    if instance.filename:
 | 
			
		||||
        if os.path.isfile(instance.filename.path):
 | 
			
		||||
            os.remove(instance.filename.path)
 | 
			
		||||
        # Get and delete all files and thumbnails from instance
 | 
			
		||||
        thumbmanager = get_thumbnailer(instance.filename)
 | 
			
		||||
        thumbmanager.delete(save=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(models.signals.pre_save, sender=PlaceImage)
 | 
			
		||||
@@ -202,6 +214,7 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
 | 
			
		||||
    except PlaceImage.DoesNotExist:
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    # No need to delete thumbnails, as they will be overwritten on regeneration.
 | 
			
		||||
    new_file = instance.filename
 | 
			
		||||
    if not old_file == new_file:
 | 
			
		||||
        if os.path.isfile(old_file.path):
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,5 +1,4 @@
 | 
			
		||||
Copyright (c) 2010, Sebastian Kosch (sebastian@aldusleaf.org),
 | 
			
		||||
with Reserved Font Name "Crimson" and "Crimson Text".
 | 
			
		||||
Copyright 2018 The Crimson Pro Project Authors (https://github.com/Fonthausen/CrimsonPro)
 | 
			
		||||
 | 
			
		||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
 | 
			
		||||
This license is copied below, and is also available with a FAQ at:
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										93
									
								
								django_lostplaces/lostplaces/static/fonts/Montserrat/OFL.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								django_lostplaces/lostplaces/static/fonts/Montserrat/OFL.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat)
 | 
			
		||||
 | 
			
		||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
 | 
			
		||||
This license is copied below, and is also available with a FAQ at:
 | 
			
		||||
http://scripts.sil.org/OFL
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-----------------------------------------------------------
 | 
			
		||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
 | 
			
		||||
-----------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
PREAMBLE
 | 
			
		||||
The goals of the Open Font License (OFL) are to stimulate worldwide
 | 
			
		||||
development of collaborative font projects, to support the font creation
 | 
			
		||||
efforts of academic and linguistic communities, and to provide a free and
 | 
			
		||||
open framework in which fonts may be shared and improved in partnership
 | 
			
		||||
with others.
 | 
			
		||||
 | 
			
		||||
The OFL allows the licensed fonts to be used, studied, modified and
 | 
			
		||||
redistributed freely as long as they are not sold by themselves. The
 | 
			
		||||
fonts, including any derivative works, can be bundled, embedded, 
 | 
			
		||||
redistributed and/or sold with any software provided that any reserved
 | 
			
		||||
names are not used by derivative works. The fonts and derivatives,
 | 
			
		||||
however, cannot be released under any other type of license. The
 | 
			
		||||
requirement for fonts to remain under this license does not apply
 | 
			
		||||
to any document created using the fonts or their derivatives.
 | 
			
		||||
 | 
			
		||||
DEFINITIONS
 | 
			
		||||
"Font Software" refers to the set of files released by the Copyright
 | 
			
		||||
Holder(s) under this license and clearly marked as such. This may
 | 
			
		||||
include source files, build scripts and documentation.
 | 
			
		||||
 | 
			
		||||
"Reserved Font Name" refers to any names specified as such after the
 | 
			
		||||
copyright statement(s).
 | 
			
		||||
 | 
			
		||||
"Original Version" refers to the collection of Font Software components as
 | 
			
		||||
distributed by the Copyright Holder(s).
 | 
			
		||||
 | 
			
		||||
"Modified Version" refers to any derivative made by adding to, deleting,
 | 
			
		||||
or substituting -- in part or in whole -- any of the components of the
 | 
			
		||||
Original Version, by changing formats or by porting the Font Software to a
 | 
			
		||||
new environment.
 | 
			
		||||
 | 
			
		||||
"Author" refers to any designer, engineer, programmer, technical
 | 
			
		||||
writer or other person who contributed to the Font Software.
 | 
			
		||||
 | 
			
		||||
PERMISSION & CONDITIONS
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining
 | 
			
		||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
 | 
			
		||||
redistribute, and sell modified and unmodified copies of the Font
 | 
			
		||||
Software, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
1) Neither the Font Software nor any of its individual components,
 | 
			
		||||
in Original or Modified Versions, may be sold by itself.
 | 
			
		||||
 | 
			
		||||
2) Original or Modified Versions of the Font Software may be bundled,
 | 
			
		||||
redistributed and/or sold with any software, provided that each copy
 | 
			
		||||
contains the above copyright notice and this license. These can be
 | 
			
		||||
included either as stand-alone text files, human-readable headers or
 | 
			
		||||
in the appropriate machine-readable metadata fields within text or
 | 
			
		||||
binary files as long as those fields can be easily viewed by the user.
 | 
			
		||||
 | 
			
		||||
3) No Modified Version of the Font Software may use the Reserved Font
 | 
			
		||||
Name(s) unless explicit written permission is granted by the corresponding
 | 
			
		||||
Copyright Holder. This restriction only applies to the primary font name as
 | 
			
		||||
presented to the users.
 | 
			
		||||
 | 
			
		||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
 | 
			
		||||
Software shall not be used to promote, endorse or advertise any
 | 
			
		||||
Modified Version, except to acknowledge the contribution(s) of the
 | 
			
		||||
Copyright Holder(s) and the Author(s) or with their explicit written
 | 
			
		||||
permission.
 | 
			
		||||
 | 
			
		||||
5) The Font Software, modified or unmodified, in part or in whole,
 | 
			
		||||
must be distributed entirely under this license, and must not be
 | 
			
		||||
distributed under any other license. The requirement for fonts to
 | 
			
		||||
remain under this license does not apply to any document created
 | 
			
		||||
using the Font Software.
 | 
			
		||||
 | 
			
		||||
TERMINATION
 | 
			
		||||
This license becomes null and void if any of the above conditions are
 | 
			
		||||
not met.
 | 
			
		||||
 | 
			
		||||
DISCLAIMER
 | 
			
		||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 | 
			
		||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 | 
			
		||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
 | 
			
		||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 | 
			
		||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
 | 
			
		||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | 
			
		||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
 | 
			
		||||
OTHER DEALINGS IN THE FONT SOFTWARE.
 | 
			
		||||
@@ -424,46 +424,46 @@
 | 
			
		||||
  object-position: center; }
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Crimson Text';
 | 
			
		||||
  font-family: 'Crimson Pro';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  font-display: swap;
 | 
			
		||||
  src: local("Crimson Text Regular"), local("CrimsonText-Regular"), url(fonts/Crimson/CrimsonText-Regular.ttf) format("truetype"); }
 | 
			
		||||
  src: local("Crimson Pro Regular"), local("CrimsonPro-Regular"), url(fonts/Crimson/CrimsonPro-Regular.ttf) format("truetype"); }
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Crimson Text';
 | 
			
		||||
  font-family: 'Crimson Pro';
 | 
			
		||||
  font-style: italic;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  font-display: swap;
 | 
			
		||||
  src: local("Crimson Text Italic"), local("CrimsonText-Italic"), url(fonts/Crimson/CrimsonText-Italic.ttf) format("truetype"); }
 | 
			
		||||
  src: local("Crimson Pro Italic"), local("CrimsonPro-Italic"), url(fonts/Crimson/CrimsonPro-Italic.ttf) format("truetype"); }
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Crimson Text';
 | 
			
		||||
  font-family: 'Crimson Pro';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  font-display: swap;
 | 
			
		||||
  src: local("Crimson Text Bold"), local("CrimsonText-Bold"), url(fonts/Crimson/CrimsonText-Bold.ttf) format("truetype"); }
 | 
			
		||||
  src: local("Crimson Pro Bold"), local("CrimsonPro-Bold"), url(fonts/Crimson/CrimsonPro-Bold.ttf) format("truetype"); }
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Montserrat';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  font-display: swap;
 | 
			
		||||
  src: local("Montserrat Regular"), local("Montserrat-Regular"), url(fonts/Montserrat/Montserrat-Regular.ttf) format("truetype"); }
 | 
			
		||||
  src: local("Montserrat Regular"), local("Montserrat-Regular"), url(fonts/Montserrat/Montserrat-Regular.woff2) format("woff2"); }
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Montserrat';
 | 
			
		||||
  font-style: italic;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  font-display: swap;
 | 
			
		||||
  src: local("Montserrat Italic"), local("Montserrat-Italic"), url(fonts/Montserrat/Montserrat-Italic.ttf) format("truetype"); }
 | 
			
		||||
  src: local("Montserrat Italic"), local("Montserrat-Italic"), url(fonts/Montserrat/Montserrat-Italic.woff2) format("woff2"); }
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: 'Montserrat';
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  font-display: swap;
 | 
			
		||||
  src: local("Montserrat Bold"), local("Montserrat-Bold"), url(fonts/Montserrat/Montserrat-Bold.ttf) format("truetype"); }
 | 
			
		||||
  src: local("Montserrat Bold"), local("Montserrat-Bold"), url(fonts/Montserrat/Montserrat-Bold.woff2) format("woff2"); }
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
@@ -583,7 +583,7 @@ body {
 | 
			
		||||
 | 
			
		||||
.LP-Paragraph {
 | 
			
		||||
  color: black;
 | 
			
		||||
  font-family: "Crimson Text", Times, serif;
 | 
			
		||||
  font-family: "Crimson Pro", Times, serif;
 | 
			
		||||
  font-size: 1.4rem;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@@ -856,7 +856,7 @@ body {
 | 
			
		||||
    padding: 15px; } }
 | 
			
		||||
 | 
			
		||||
.LP-TextSection__Text {
 | 
			
		||||
  font-family: "Crimson Text", Times, serif;
 | 
			
		||||
  font-family: "Crimson Pro", Times, serif;
 | 
			
		||||
  font-size: 1.4rem; }
 | 
			
		||||
  .LP-TextSection__Text .LP-Link {
 | 
			
		||||
    margin: 0 3px; }
 | 
			
		||||
@@ -1642,7 +1642,7 @@ body {
 | 
			
		||||
    margin-bottom: 25px; } }
 | 
			
		||||
 | 
			
		||||
.LP-TextSection .LP-UnorderedList {
 | 
			
		||||
  font-family: "Crimson Text", Times, serif;
 | 
			
		||||
  font-family: "Crimson Pro", Times, serif;
 | 
			
		||||
  font-size: 1.4rem; }
 | 
			
		||||
  .LP-TextSection .LP-UnorderedList li {
 | 
			
		||||
    margin-bottom: 0.75em;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
.ol-box{box-sizing:border-box;border-radius:2px;border:2px solid #00f}.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width;transition:all .25s}.ol-scale-bar{position:absolute;bottom:8px;left:8px}.ol-scale-step-marker{width:1px;height:15px;background-color:#000;float:right;z-Index:10}.ol-scale-step-text{position:absolute;bottom:-5px;font-size:12px;z-Index:11;color:#000;text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff}.ol-scale-text{position:absolute;font-size:14px;text-align:center;bottom:25px;color:#000;text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff}.ol-scale-singlebar{position:relative;height:10px;z-Index:9;border:1px solid #000}.ol-unsupported{display:none}.ol-unselectable,.ol-viewport{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-overlaycontainer,.ol-overlaycontainer-stopevent{pointer-events:none}.ol-overlaycontainer-stopevent>*,.ol-overlaycontainer>*{pointer-events:auto}.ol-selectable{-webkit-touch-callout:default;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ol-grabbing{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.ol-grab{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.ol-control{position:absolute;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-control button span{pointer-events:none}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;font-size:1.2em;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:rgba(0,60,136,.7)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;color:#000;text-shadow:0 0 2px #fff}.ol-attribution li{display:inline;list-style:none}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit;vertical-align:middle}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;height:200px}.ol-zoomslider button{position:relative;height:10px}.ol-touch .ol-zoomslider{top:5.5em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:inline-block}.ol-overviewmap .ol-overviewmap-map{border:1px solid #7b98bc;height:150px;margin:2px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:1px;left:2px;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-overviewmap-box{border:2px dotted rgba(0,60,136,.7)}.ol-overviewmap .ol-overviewmap-box:hover{cursor:move}
 | 
			
		||||
.ol-box{box-sizing:border-box;border-radius:2px;border:2px solid #00f}.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width;transition:all .25s}.ol-scale-bar{position:absolute;bottom:8px;left:8px}.ol-scale-step-marker{width:1px;height:15px;background-color:#000;float:right;z-Index:10}.ol-scale-step-text{position:absolute;bottom:-5px;font-size:12px;z-Index:11;color:#000;text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff}.ol-scale-text{position:absolute;font-size:14px;text-align:center;bottom:25px;color:#000;text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff}.ol-scale-singlebar{position:relative;height:10px;z-Index:9;box-sizing:border-box;border:1px solid #000}.ol-unsupported{display:none}.ol-unselectable,.ol-viewport{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-selectable{-webkit-touch-callout:default;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ol-grabbing{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.ol-grab{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.ol-control{position:absolute;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;font-size:1.2em;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:rgba(0,60,136,.7)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;color:#000;text-shadow:0 0 2px #fff}.ol-attribution li{display:inline;list-style:none}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit;vertical-align:middle}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;height:200px}.ol-zoomslider button{position:relative;height:10px}.ol-touch .ol-zoomslider{top:5.5em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:inline-block}.ol-overviewmap .ol-overviewmap-map{border:1px solid #7b98bc;height:150px;margin:2px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:1px;left:2px;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-overviewmap-box{border:2px dotted rgba(0,60,136,.7)}.ol-overviewmap .ol-overviewmap-box:hover{cursor:move}
 | 
			
		||||
/*# sourceMappingURL=ol.css.map */
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
{"version":3,"sources":["src/ol/ol.css"],"names":[],"mappings":"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"}
 | 
			
		||||
{"version":3,"sources":["src/ol/ol.css"],"names":[],"mappings":"AAAA,QACE,WAAY,WACZ,cAAe,IACf,OAAQ,IAAI,MAAM,KAGpB,mBACE,IAAK,IACL,MAAO,IACP,SAAU,SAGZ,eACE,WAAY,kBACZ,cAAe,IACf,OAAQ,IACR,KAAM,IACN,QAAS,IACT,SAAU,SAEZ,qBACE,OAAQ,IAAI,MAAM,KAClB,WAAY,KACZ,MAAO,KACP,UAAW,KACX,WAAY,OACZ,OAAQ,IACR,YAAa,QAAQ,CAAE,MACvB,WAAY,IAAI,KAElB,cACE,SAAU,SACV,OAAQ,IACR,KAAM,IAER,sBACE,MAAO,IACP,OAAQ,KACR,iBAAkB,KAClB,MAAO,MACP,QAAS,GAEX,oBACE,SAAU,SACV,OAAQ,KACR,UAAW,KACX,QAAS,GACT,MAAO,KACP,YAAa,KAAK,EAAE,IAAO,CAAE,EAAE,IAAI,IAAO,CAAE,IAAI,EAAE,IAAO,CAAE,EAAE,KAAK,KAEpE,eACE,SAAU,SACV,UAAW,KACX,WAAY,OACZ,OAAQ,KACR,MAAO,KACP,YAAa,KAAK,EAAE,IAAO,CAAE,EAAE,IAAI,IAAO,CAAE,IAAI,EAAE,IAAO,CAAE,EAAE,KAAK,KAEpE,oBACE,SAAU,SACV,OAAQ,KACR,QAAS,EACT,WAAY,WACZ,OAAQ,IAAI,MAAM,KAGpB,gBACE,QAAS,KAEG,iBAAd,aACE,sBAAuB,KACvB,oBAAqB,KACrB,iBAAkB,KAClB,gBAAiB,KACjB,YAAa,KACb,4BAA6B,YAE/B,eACE,sBAAuB,QACvB,oBAAqB,KACrB,iBAAkB,KAClB,gBAAiB,KACjB,YAAa,KAEf,aACE,OAAQ,iBACR,OAAQ,cACR,OAAQ,SAEV,SACE,OAAQ,KACR,OAAQ,aACR,OAAQ,UACR,OAAQ,KAEV,YACE,SAAU,SACV,iBAAkB,qBAClB,cAAe,IACf,QAAS,IAEX,kBACE,iBAAkB,qBAEpB,SACE,IAAK,KACL,KAAM,KAER,WACE,IAAK,KACL,MAAO,KACP,WAAY,QAAQ,KAAK,MAAM,CAAE,WAAW,GAAG,OAEjD,qBACE,QAAS,EACT,WAAY,OACZ,WAAY,QAAQ,KAAK,MAAM,CAAE,WAAW,GAAG,OAAO,KAExD,gBACE,IAAK,QACL,KAAM,KAER,gBACE,MAAO,KACP,IAAK,KAGP,mBACE,QAAS,MACT,OAAQ,IACR,QAAS,EACT,MAAO,KACP,UAAW,OACX,YAAa,IACb,gBAAiB,KACjB,WAAY,OACZ,OAAQ,QACR,MAAO,QACP,YAAa,KACb,iBAAkB,kBAClB,OAAQ,KACR,cAAe,IAEjB,qCACE,OAAQ,KACR,QAAS,EAEX,uBACE,YAAa,MAEf,YACE,QAAS,MACT,YAAa,IACb,UAAW,MACX,YAAa,UAEf,6BACE,UAAW,MAEb,0BACE,IAAK,MAGP,yBADA,yBAEE,gBAAiB,KACjB,iBAAkB,kBAEpB,qBACE,cAAe,IAAI,IAAI,EAAE,EAE3B,sBACE,cAAe,EAAE,EAAE,IAAI,IAIzB,gBACE,WAAY,MACZ,OAAQ,KACR,MAAO,KACP,UAAW,mBAGb,mBACE,OAAQ,EACR,QAAS,EAAE,KACX,MAAO,KACP,YAAa,EAAE,EAAE,IAAI,KAEvB,mBACE,QAAS,OACT,WAAY,KAEd,0CACE,QAAS,IAEX,oBACE,WAAY,IACZ,UAAW,QACX,eAAgB,OAEE,uBAApB,mBACE,QAAS,aAEX,gCACE,QAAS,KAEX,mCACE,WAAY,qBAEd,iCACE,OAAQ,EACR,MAAO,EACP,cAAe,IAAI,EAAE,EAEvB,qCACE,WAAY,MACZ,WAAY,MAEd,wCACE,QAAS,KAGX,eACE,IAAK,MACL,KAAM,KACN,OAAQ,MAEV,sBACE,SAAU,SACV,OAAQ,KAGV,yBACE,IAAK,MAGP,gBACE,KAAM,KACN,OAAQ,KAEV,iCACE,OAAQ,EACR,KAAM,EACN,cAAe,EAAE,IAAI,EAAE,EAEzB,oCACA,uBACE,QAAS,aAEX,oCACE,OAAQ,IAAI,MAAM,QAClB,OAAQ,MACR,OAAQ,IACR,MAAO,MAET,0CACE,OAAQ,IACR,KAAM,IACN,SAAU,SAEZ,iDACA,wCACE,QAAS,KAEX,mCACE,WAAY,qBAEd,oBACE,OAAQ,IAAI,OAAO,kBAGrB,0CACE,OAAQ"}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,7 +0,0 @@
 | 
			
		||||
# Keeping these files up-to-date is something for CI / release management.
 | 
			
		||||
# But for now I noted the source urls down here for later reference.
 | 
			
		||||
 | 
			
		||||
https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/css/ol.css
 | 
			
		||||
https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/css/ol.css.map
 | 
			
		||||
https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/build/ol.js
 | 
			
		||||
https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/build/ol.js.map
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
 | 
			
		||||
<div id="map" class="map" style="height: 300px"></div>
 | 
			
		||||
<div tabindex="1" id="map" class="map" style="height: 300px"></div>
 | 
			
		||||
    <div id="info" class="map-popup"></div>
 | 
			
		||||
 | 
			
		||||
        <script type="text/javascript">
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
 | 
			
		||||
    const tagify = new Tagify(input, {
 | 
			
		||||
		'whitelist': [
 | 
			
		||||
			{% for tag in config.tagged_item.tags.all %}
 | 
			
		||||
			{% for tag in config.all_tags %}
 | 
			
		||||
            '{{tag}}',
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        ]
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
    <section class="LP-Section">
 | 
			
		||||
        <h1 class="LP-Headline">Map-Links</h1>
 | 
			
		||||
        <h1 class="LP-Headline">Map links</h1>
 | 
			
		||||
        {% include 'partials/osm_map.html' with config=mapping_config%}
 | 
			
		||||
        <div class="LP-LinkList">
 | 
			
		||||
            <ul class="LP-LinkList__Container">
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
    <section class=" LP-Section">
 | 
			
		||||
        <h1 class="LP-Headline">Photoalben</h1>
 | 
			
		||||
        <h1 class="LP-Headline">Photo albums</h1>
 | 
			
		||||
        <div class="LP-LinkList">
 | 
			
		||||
            <ul class="LP-LinkList__Container">
 | 
			
		||||
                {% for photo_album in place.photo_albums.all %}
 | 
			
		||||
@@ -89,7 +89,7 @@
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
    <section class="LP-Section">
 | 
			
		||||
        <h1 class="LP-Headline">Bilder</h1>
 | 
			
		||||
        <h1 class="LP-Headline">Images</h1>
 | 
			
		||||
        <div class="LP-ImageGrid">
 | 
			
		||||
            <ul class="LP-ImageGrid__Container">
 | 
			
		||||
                {% for place_image in place.placeimages.all %}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django import template
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import json, os
 | 
			
		||||
from importlib import import_module
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.core.exceptions import FieldDoesNotExist
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import datetime
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.db import models
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										93
									
								
								django_lostplaces/lostplaces/tests/models/test_link_model.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								django_lostplaces/lostplaces/tests/models/test_link_model.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
from unittest import mock
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.core.files import File
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import ExternalLink, PhotoAlbum, Place
 | 
			
		||||
from lostplaces.tests.models import ModelTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExternalLinkTestCase(ModelTestCase):
 | 
			
		||||
    model = ExternalLink
 | 
			
		||||
 | 
			
		||||
    def setup(self):
 | 
			
		||||
        self.albumlink = ExternalLink.objects.get(id=1)
 | 
			
		||||
 | 
			
		||||
    def test_label(self):
 | 
			
		||||
        self.assertField('label', models.CharField)
 | 
			
		||||
 | 
			
		||||
    def test_url(self):
 | 
			
		||||
        self.assertField('url', models.URLField)
 | 
			
		||||
    
 | 
			
		||||
class PhotoAlbumTestCase(ModelTestCase):
 | 
			
		||||
    model = PhotoAlbum
 | 
			
		||||
    
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        user = User.objects.create_user(
 | 
			
		||||
            username='testpeter',
 | 
			
		||||
            password='Develop123'
 | 
			
		||||
        )
 | 
			
		||||
    
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=User.objects.get(username='testpeter').explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
            longitude=7.0,
 | 
			
		||||
            description='This is just a test, do not worry'
 | 
			
		||||
        )
 | 
			
		||||
        place.tags.add('I am a tag', 'testlocation')
 | 
			
		||||
        place.save()
 | 
			
		||||
 | 
			
		||||
        PhotoAlbum.objects.create(
 | 
			
		||||
            url='https://lostplaces.example.com/album/',
 | 
			
		||||
            label='TestLink',
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            place=place,
 | 
			
		||||
            submitted_when=timezone.now()
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.albumlink = PhotoAlbum.objects.get(id=1)
 | 
			
		||||
        self.place = Place.objects.get(id=1)
 | 
			
		||||
 | 
			
		||||
    def test_place(self):
 | 
			
		||||
        field = self.assertField('place', models.ForeignKey)
 | 
			
		||||
        self.assertEqual(field.remote_field.on_delete, models.CASCADE,
 | 
			
		||||
            msg='Expecting the deletion of %s to be cascading' % (
 | 
			
		||||
                str(field)
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        expected_related_name = 'photo_albums'
 | 
			
		||||
        self.assertEqual(field.remote_field.related_name, expected_related_name,
 | 
			
		||||
            msg='Expecting the related name of %s to be %s' % (
 | 
			
		||||
                str(field),
 | 
			
		||||
                expected_related_name
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_label(self):
 | 
			
		||||
        albumlink = self.albumlink
 | 
			
		||||
    
 | 
			
		||||
        self.assertTrue('TestLink' in albumlink.label,
 | 
			
		||||
            msg='Expecting albumlink.label to contain \'TestLink\' string'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_url(self):
 | 
			
		||||
        albumlink = self.albumlink
 | 
			
		||||
    
 | 
			
		||||
        self.assertTrue('lostplaces.example.com' in albumlink.url,
 | 
			
		||||
            msg='Expecting albumlink.url to contain \'lostplaces.example.com\' string'
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
import datetime
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
from unittest import mock
 | 
			
		||||
@@ -8,6 +10,7 @@ from django.db import models
 | 
			
		||||
from django.core.files import File
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import PlaceImage, Place
 | 
			
		||||
from lostplaces.tests.models import ModelTestCase
 | 
			
		||||
@@ -26,7 +29,7 @@ class PlaceImageTestCase(ModelTestCase):
 | 
			
		||||
        
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=User.objects.get(username='testpeter').explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
@@ -55,7 +58,7 @@ class PlaceImageTestCase(ModelTestCase):
 | 
			
		||||
            description='Im a description',
 | 
			
		||||
            filename=os.path.join(settings.MEDIA_ROOT, 'im_a_image_copy.jpeg'),
 | 
			
		||||
            place=place,
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -83,13 +86,6 @@ class PlaceImageTestCase(ModelTestCase):
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_str(self):
 | 
			
		||||
        self.assertTrue(self.place_image.place.name.lower() in str(self.place_image).lower(),
 | 
			
		||||
            msg='Expecting %s.__str__ to contain  the name of the place' % (
 | 
			
		||||
                self.model.__name__
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
    def test_change_filename(self):
 | 
			
		||||
        path = self.place_image.filename.path
 | 
			
		||||
        self.place_image.filename = os.path.join(settings.MEDIA_ROOT, 'im_a_image_changed.jpeg')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
import datetime
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
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.tests.models import ModelTestCase
 | 
			
		||||
@@ -22,7 +24,7 @@ class PlaceTestCase(ModelTestCase):
 | 
			
		||||
        
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
@@ -43,7 +45,7 @@ class PlaceTestCase(ModelTestCase):
 | 
			
		||||
            max_length=100
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_decsription(self):
 | 
			
		||||
    def test_description(self):
 | 
			
		||||
        self.assertField('description', models.TextField)
 | 
			
		||||
 | 
			
		||||
    def test_average_latlon(self):
 | 
			
		||||
@@ -110,7 +112,7 @@ class PlaceTestCase(ModelTestCase):
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(avg_latlon['longitude'], 0,
 | 
			
		||||
            msg='%s: a(no places) verage longitude missmatch' % (
 | 
			
		||||
            msg='%s: (no places) average longitude missmatch' % (
 | 
			
		||||
                self.model.__name__
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import datetime
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
@@ -8,7 +10,7 @@ from lostplaces.models import Voucher
 | 
			
		||||
from lostplaces.tests.models import ModelTestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VoucheTestCase(ModelTestCase):
 | 
			
		||||
class VoucherTestCase(ModelTestCase):
 | 
			
		||||
    model = Voucher
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
@@ -42,11 +44,3 @@ class VoucheTestCase(ModelTestCase):
 | 
			
		||||
            field_class=models.DateTimeField,
 | 
			
		||||
            must_not_have={'auto_now_add': True}
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_str(self):
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
            self.voucher.code.lower() in str(self.voucher).lower(),
 | 
			
		||||
            msg='Expecting %s.__str__ to contain the voucher code' % (
 | 
			
		||||
                self.model.__name__
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Taggable, Mapable
 | 
			
		||||
@@ -6,7 +9,7 @@ from taggit.models import Tag
 | 
			
		||||
 | 
			
		||||
class ViewTestCase(TestCase):
 | 
			
		||||
    '''
 | 
			
		||||
    This is a mixni for testing views. It provides functionality to
 | 
			
		||||
    This is a Mixin for testing views. It provides functionality to
 | 
			
		||||
    test the context, forms and HTTP Response of responses. 
 | 
			
		||||
    All methods take responses, so this base class can be used
 | 
			
		||||
    with django's RequestFactory and Test-Client
 | 
			
		||||
@@ -68,7 +71,7 @@ class ViewTestCase(TestCase):
 | 
			
		||||
    def assertHttpRedirect(self, response, redirect_to=None):
 | 
			
		||||
        '''
 | 
			
		||||
        Checks weather the response redirected, and if passed, 
 | 
			
		||||
        if it redirected to the expected loaction
 | 
			
		||||
        if it redirected to the expected location
 | 
			
		||||
        '''
 | 
			
		||||
        
 | 
			
		||||
        self.assertTrue(
 | 
			
		||||
@@ -84,7 +87,7 @@ class ViewTestCase(TestCase):
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                response['location'],
 | 
			
		||||
                redirect_to,
 | 
			
		||||
                msg='Expecing the response to redirect to %s, where redirected to %s instea' % (
 | 
			
		||||
                msg='Expecting the response to redirect to %s, where redirected to %s instea' % (
 | 
			
		||||
                    str(redirect_to),
 | 
			
		||||
                    str(response['location'])
 | 
			
		||||
                )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
import datetime
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase, RequestFactory, Client
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.contrib.auth.models import User, AnonymousUser
 | 
			
		||||
from django.contrib.messages.storage.fallback import FallbackStorage
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place
 | 
			
		||||
from lostplaces.views import IsAuthenticatedMixin
 | 
			
		||||
@@ -54,7 +56,7 @@ class TestIsPlaceSubmitterMixin(TestCase):
 | 
			
		||||
        
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
import datetime
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.test import TestCase, Client
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place
 | 
			
		||||
from lostplaces.views import (
 | 
			
		||||
@@ -10,7 +13,7 @@ from lostplaces.views import (
 | 
			
		||||
    PlaceListView,
 | 
			
		||||
    PlaceDetailView
 | 
			
		||||
)
 | 
			
		||||
from lostplaces.forms import PlaceImageCreateForm, PlaceForm
 | 
			
		||||
from lostplaces.forms import PlaceImageForm, PlaceForm
 | 
			
		||||
from lostplaces.tests.views import (
 | 
			
		||||
    ViewTestCase,
 | 
			
		||||
    TaggableViewTestCaseMixin,
 | 
			
		||||
@@ -30,7 +33,7 @@ class TestPlaceCreateView(ViewTestCase):
 | 
			
		||||
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
@@ -47,7 +50,7 @@ class TestPlaceCreateView(ViewTestCase):
 | 
			
		||||
        self.client.login(username='testpeter', password='Develop123')
 | 
			
		||||
        response = self.client.get(reverse('place_create'))
 | 
			
		||||
 | 
			
		||||
        self.assertHasForm(response, 'place_image_form', PlaceImageCreateForm)
 | 
			
		||||
        self.assertHasForm(response, 'place_image_form', PlaceImageForm)
 | 
			
		||||
        self.assertHasForm(response, 'place_form', PlaceForm)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -63,7 +66,7 @@ class TestPlaceListView(ViewTestCase):
 | 
			
		||||
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
@@ -95,7 +98,7 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
 | 
			
		||||
 | 
			
		||||
        place = Place.objects.create(
 | 
			
		||||
            name='Im a place',
 | 
			
		||||
            submitted_when=datetime.datetime.now(),
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.urls import path
 | 
			
		||||
from lostplaces.views import (
 | 
			
		||||
    HomeView, 
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from lostplaces.views.base_views import *
 | 
			
		||||
from lostplaces.views.views import * 
 | 
			
		||||
from lostplaces.views.place_views import *
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.generic.edit import CreateView
 | 
			
		||||
from django.views.generic.detail import SingleObjectMixin
 | 
			
		||||
@@ -6,14 +9,14 @@ 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.shortcuts import redirect, get_object_or_404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place
 | 
			
		||||
 | 
			
		||||
class IsAuthenticatedMixin(LoginRequiredMixin, View):
 | 
			
		||||
    '''
 | 
			
		||||
    A view mixin that checks wether a user is loged in or not.
 | 
			
		||||
    A view mixin that checks wether a user is logged in or not.
 | 
			
		||||
    If the user is not logged in, he gets redirected to 
 | 
			
		||||
    the login page.
 | 
			
		||||
    '''
 | 
			
		||||
@@ -26,9 +29,9 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
 | 
			
		||||
 | 
			
		||||
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
 | 
			
		||||
    '''
 | 
			
		||||
    A view mixin that checks wethe a user is the submitter
 | 
			
		||||
    of a place Throws 403 if the user is not. The subclass 
 | 
			
		||||
    has to provide a get_place method, wich returns the
 | 
			
		||||
    A view mixin that checks wether a user is the submitter
 | 
			
		||||
    of a place, throws 403 if the user is not. The subclass 
 | 
			
		||||
    has to provide a get_place method, which returns the
 | 
			
		||||
    place to check.
 | 
			
		||||
    '''
 | 
			
		||||
    place_submitter_error_message = None
 | 
			
		||||
@@ -62,11 +65,11 @@ class PlaceAssetCreateView(IsAuthenticatedMixin, SuccessMessageMixin, CreateView
 | 
			
		||||
    success_message = ''
 | 
			
		||||
 | 
			
		||||
    def get(self, request, place_id, *args, **kwargs):
 | 
			
		||||
        self.place = Place.objects.get(pk=place_id)
 | 
			
		||||
        self.place = get_object_or_404(Place, 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)
 | 
			
		||||
        self.place = get_object_or_404(Place, pk=place_id)
 | 
			
		||||
        response = super().post(request, *args, **kwargs)
 | 
			
		||||
        self.object.place = self.place
 | 
			
		||||
        self.object.submitted_by = request.user.explorer
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
 | 
			
		||||
from django.views.generic.detail import SingleObjectMixin
 | 
			
		||||
@@ -6,12 +9,12 @@ 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.shortcuts import render, redirect, get_object_or_404
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place, PlaceImage
 | 
			
		||||
from lostplaces.views import IsAuthenticatedMixin, IsPlaceSubmitterMixin
 | 
			
		||||
from lostplaces.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm
 | 
			
		||||
from lostplaces.forms import PlaceForm, PlaceImageForm, TagSubmitForm
 | 
			
		||||
 | 
			
		||||
from taggit.models import Tag
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +34,7 @@ class PlaceListView(IsAuthenticatedMixin, ListView):
 | 
			
		||||
 | 
			
		||||
class PlaceDetailView(IsAuthenticatedMixin, View):
 | 
			
		||||
    def get(self, request, pk):
 | 
			
		||||
        place = Place.objects.get(pk=pk)
 | 
			
		||||
        place = get_object_or_404(Place, pk=pk)
 | 
			
		||||
        context = {
 | 
			
		||||
            'place': place,
 | 
			
		||||
            'mapping_config': {
 | 
			
		||||
@@ -64,7 +67,7 @@ class PlaceUpdateView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SuccessMessag
 | 
			
		||||
class PlaceCreateView(IsAuthenticatedMixin, View):
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        place_image_form = PlaceImageCreateForm()
 | 
			
		||||
        place_image_form = PlaceImageForm()
 | 
			
		||||
        place_form = PlaceForm()
 | 
			
		||||
 | 
			
		||||
        context = {
 | 
			
		||||
@@ -102,7 +105,7 @@ class PlaceCreateView(IsAuthenticatedMixin, View):
 | 
			
		||||
                self.request,
 | 
			
		||||
                'Please fill in all required fields.'
 | 
			
		||||
            )
 | 
			
		||||
            return render(request, 'place/place_create.html', context={'form': form_place})
 | 
			
		||||
            return render(request, 'place/place_create.html', context={'form': place_form})
 | 
			
		||||
 | 
			
		||||
    def _apply_multipart_image_upload(self, files, place, submitter):
 | 
			
		||||
        for image in files:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.generic.edit import CreateView
 | 
			
		||||
 | 
			
		||||
@@ -28,9 +31,9 @@ class HomeView(IsAuthenticatedMixin, View):
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        place_list = Place.objects.all().order_by('-submitted_when')[:10]
 | 
			
		||||
        context = {
 | 
			
		||||
            'all_points': place_list,
 | 
			
		||||
            'place_list': place_list,
 | 
			
		||||
            'mapping_config': {
 | 
			
		||||
                'point_list': place_list,
 | 
			
		||||
                'all_points': place_list,
 | 
			
		||||
                'map_center': Place.average_latlon(place_list)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -57,7 +60,7 @@ class PhotoAlbumDeleteView(PlaceAssetDeleteView):
 | 
			
		||||
 | 
			
		||||
class PlaceTagSubmitView(IsAuthenticatedMixin, View):
 | 
			
		||||
	def post(self, request, tagged_id, *args, **kwargs):
 | 
			
		||||
		place = Place.objects.get(pk=tagged_id)
 | 
			
		||||
		place = get_object_or_404(Place, pk=tagged_id)
 | 
			
		||||
		form = TagSubmitForm(request.POST)
 | 
			
		||||
		if form.is_valid():
 | 
			
		||||
			tag_list_raw = form.cleaned_data['tag_list']
 | 
			
		||||
@@ -72,8 +75,8 @@ class PlaceTagSubmitView(IsAuthenticatedMixin, View):
 | 
			
		||||
 | 
			
		||||
class PlaceTagDeleteView(IsAuthenticatedMixin, View):
 | 
			
		||||
    def get(self, request, tagged_id, tag_id, *args, **kwargs):
 | 
			
		||||
        place = Place.objects.get(pk=tagged_id)
 | 
			
		||||
        tag = Tag.objects.get(pk=tag_id)
 | 
			
		||||
        place = get_object_or_404(Place, pk=tagged_id)
 | 
			
		||||
        tag = get_object_or_404(Tag, pk=tag_id)
 | 
			
		||||
        place.tags.remove(tag)
 | 
			
		||||
        return redirect(reverse_lazy('place_detail', kwargs={'pk': tagged_id}))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user