65 Commits

Author SHA1 Message Date
c3eede548c Added comment ot POST-only url. 2020-09-01 22:14:19 +02:00
0eea31a0af Removed obsolete jquery files. 2020-09-01 22:14:06 +02:00
2509c669f9 Merge branch 'feature/tags' of mowoe.com:reverend/lostplaces-backend into feature/tags 2020-09-01 21:33:07 +02:00
e00c9318fa Tagify dropdown styling 2020-09-01 18:31:46 +02:00
6ed6c2c990 Better Tagggin integration 2020-09-01 18:20:02 +02:00
328e6899a6 Merge branch 'master' into feature/tags 2020-08-31 18:28:21 +02:00
843832d978 Viewport fix 2020-08-31 18:28:12 +02:00
e4cd8bb301 Tagging using JS using Partials 2020-08-31 18:18:24 +02:00
66581a9d2d Adding tags is now possible 2020-08-30 18:39:45 +02:00
490a0e9f3e Merge branch 'master' into feature/tags 2020-08-30 17:53:34 +02:00
7a3b8529f8 base views for place assets 2020-08-30 17:26:43 +02:00
538b43c2a1 Splitted views in multiple files 2020-08-30 17:11:24 +02:00
ca2eac533f Removed unused View 2020-08-30 16:54:11 +02:00
2007b512c7 Bugfix 2020-08-30 16:53:58 +02:00
751d4c81fe Merge branch 'master' of mowoe.com:reverend/lostplaces-backend into master 2020-08-28 14:34:11 +02:00
7a757bcf35 Moved Templates 2020-08-28 14:34:09 +02:00
fc39b46c52 Merge branch 'master' into feature/tags 2020-08-27 17:17:39 +02:00
55b8d16751 No realy 2020-08-27 17:17:28 +02:00
36744c65fb Merge branch 'master' into feature/tags 2020-08-27 17:16:20 +02:00
f34d70edd5 Division by zero fix 2020-08-27 17:16:03 +02:00
8198c43451 Adding taggit to the dependencies 2020-08-27 17:00:41 +02:00
78f0e80136 First tries on using tags 2020-08-27 15:46:45 +02:00
c84be34a37 Place delete, more abstraction for ISPlaceSubmitter and more messages 2020-08-27 10:31:14 +02:00
3959096c95 Place delete 2020-08-27 10:30:50 +02:00
76daa71217 Sign up link in login template 2020-08-27 10:30:36 +02:00
0707d2d51e Icons and icon template tag 2020-08-27 09:06:24 +02:00
d45724b774 Will show x for deleting when allowed 2020-08-26 21:59:21 +02:00
c8e46d42ee Relabeling 2020-08-26 21:36:37 +02:00
591469799f Photoalbums can now be linked by users 2020-08-26 21:36:10 +02:00
3cd44959b1 Config mistake 2020-08-26 21:24:08 +02:00
ffef52269a SVG Icons as a dependency 2020-08-26 19:31:06 +02:00
9a30ced90c Calculates correct map_center for paginated place_list. 2020-08-22 09:58:57 +02:00
d44bf0c0f2 Customized PlaceListView to provide data for map rendering. 2020-08-22 09:24:50 +02:00
cad53c070c Added map to place_list, but place_center_map is still a bit off. 2020-08-22 09:23:12 +02:00
a0627761a7 Fixed indentation again. Because I'm stupid ;-) 2020-08-22 09:22:20 +02:00
04940a0901 Added ol.css.map to static files and noted it down in linklist. 2020-08-22 09:19:50 +02:00
3ef31eda0f Added OSM map to place_detail view and template. 2020-08-22 09:19:01 +02:00
7cb8bc6bac Added place_list sorting to sort by name. 2020-08-22 02:59:04 +02:00
636bb880c1 Removed debug output. 2020-08-21 20:38:52 +02:00
c9d9ca9de0 Indentation fixed to 4 spaces. 2020-08-21 20:38:05 +02:00
5f3be5fc83 Pointed template to new location of static map files. 2020-08-21 10:33:56 +02:00
f916ac9742 Removed obsolete include of static files function. 2020-08-21 10:33:33 +02:00
03af8a0c02 Made the center of the map consider only shown places (dynamic). 2020-08-21 10:31:30 +02:00
65f3642272 Added note to where these files were pulled from. 2020-08-21 10:30:17 +02:00
e00a0f687b Moved static osm_map files to subfolder. 2020-08-21 10:29:59 +02:00
9af55d3f24 Made place urls dynamic, changed zoom level. Works so far - but div is still 0px high :P. 2020-08-20 23:49:16 +02:00
719e75a449 Reverted to paginate by 5 in place_list. 2020-08-20 23:33:33 +02:00
6688536a78 Revert "Reverted to paginate by 5 in place_list."
This reverts commit 7835ddcf16.
2020-08-20 23:32:52 +02:00
7835ddcf16 Reverted to paginate by 5 in place_list. 2020-08-20 23:32:27 +02:00
3caada8f08 Moved additional headers to parent template. 2020-08-20 23:07:52 +02:00
4113811c5f Added and included ol.css. 2020-08-20 22:46:56 +02:00
a660763ea4 Corrected map_center_coords variables. 2020-08-20 22:08:35 +02:00
55fe79b82c Added missing ol.js.map. 2020-08-20 22:02:47 +02:00
b34e88a3ad Formatting. 2020-08-20 21:50:00 +02:00
f52dd733d8 Inluded ol.js. 2020-08-20 21:47:44 +02:00
ff428beaef Added ol.js for map rendering. 2020-08-20 21:44:05 +02:00
e8c4faff8e Minor corrections. 2020-08-20 21:38:35 +02:00
0b0a401486 Added export of place coords to home conext. 2020-08-20 21:38:01 +02:00
2b56eed759 Added OSM map partial, Place model function and added it to home. 2020-08-20 21:37:27 +02:00
e387091da6 Removed obsoleted JS. 2020-08-20 21:07:39 +02:00
5c5ad9502c Updated dependencies 2020-08-18 14:35:13 +02:00
baf3f54e1a Again 2020-08-18 14:24:29 +02:00
8cb7e61274 Fixed formatting 2020-08-18 14:24:08 +02:00
fa1274c595 Merge branch 'master' of mowoe.com:reverend/lostplaces-backend into master 2020-08-18 14:22:35 +02:00
a5839c2c36 Added installation instructions 2020-08-18 14:22:33 +02:00
35 changed files with 2188 additions and 308 deletions

View File

@@ -11,6 +11,7 @@ django = "*"
easy-thumbnails = "*"
image = "*"
django-widget-tweaks = "*"
django-taggit = "*"
# Commented out to not explicitly specify Python 3 subversion.
# [requires]

View File

@@ -9,7 +9,11 @@ Right now it depends on the following non-core Python 3 libraries. These can be
* [django](https://www.djangoproject.com/) django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.
* [easy-thumbnails](https://github.com/SmileyChris/easy-thumbnails) A powerful, yet easy to implement thumbnailing application for Django 1.11+
* [image](https://github.com/francescortiz/image) Image cropping for django
* [django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks) Tweak the form field rendering in templates, not in python-level form definitions.
## Development
### Setting up a (pipenv) virtual environment for development
After having obtained the repository contents (either via .zip download or git clone), you can easily setup a pipenv virtual environment. The repo provides a Pipfile for easy dependency management that does not mess with your system.
@@ -18,6 +22,7 @@ After having obtained the repository contents (either via .zip download or git c
$ cd lostplaces-backend
$ pipenv install
$ pipenv shell
(lostplaces-backend) $ lostplaces/manage.py makemigrations
(lostplaces-backend) $ lostplaces/manage.py migrate
(lostplaces-backend) $ lostplaces/manage.py createsuperuser
(lostplaces-backend) $ lostplaces/manage.py runserver
@@ -33,4 +38,65 @@ $ pipenv shell
Visit: [admin](http://localhost:8000/admin) for administrative backend or
[frontend](http://localhost:8000/)
## Installing lostplaces
### Install dependencies
Python3, Django3, easy-thumbnails, image, django-widget-tweaks
```
pip install --user django easy-thumbnails image django-widget-tweaks
```
Or, if you use pipenv
```
pipenv install
```
### Add 'lostplaces_app' to your INSTALLED_APPS setting like this
```
INSTALLED_APPS = [
...
'lostplaces_app',
'easy_thumbnails',
'widget_tweaks',
]
```
### Add this configuration to your settings.py
```
from django.urls import reverse_lazy
...
AUTH_USER_MODEL = 'lostplaces_app.Explorer'
LOGIN_URL = reverse_lazy('login')
THUMBNAIL_ALIASES = {
'': {
'thumbnail': {'size': (300, 300), 'crop': False},
'hero': {'size': (700, 700), 'crop': False},
'large': {'size': (1920, 1920), 'crop': False},
},
}
```
### Include the lostplaces URLconf in your project urls.py like this
```
from django.urls import path, include
...
urlpatterns = [
...
path('lostplaces/', include('lostplaces_app.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
Run ``python manage.py migrate`` to create the lost places models.
Start the development server and visit http://127.0.0.1:8000/admin/
Visit http://127.0.0.1:8000/lostplaces/ to CRUD lost places.
Happy developing ;-)

View File

@@ -29,7 +29,7 @@ SECRET_KEY = 'n$(bx8(^)*wz1ygn@-ekt7rl^1km*!_c+fwwjiua8m@-x_rpl0'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['localhost', '192.168.178.49']
# Application definition
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'easy_thumbnails',
'widget_tweaks',
'taggit'
]
MIDDLEWARE = [
@@ -148,3 +149,5 @@ THUMBNAIL_ALIASES = {
'large': {'size': (1920, 1920), 'crop': False},
},
}
SVG_ICONS_SOURCE_FILE = os.path.join(BASE_DIR, 'lostplaces_app', 'static', 'icons', 'icons.icomoon.json')

View File

@@ -26,3 +26,4 @@ admin.site.register(Explorer, ExplorerAdmin)
admin.site.register(Voucher, VoucherAdmin)
admin.site.register(Place)
admin.site.register(PlaceImage)
admin.site.register(PhotoAlbum)

View File

@@ -48,3 +48,7 @@ class PlaceImageCreateForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields['filename'].required = False
class TagSubmitForm(forms.Form):
tag_list = forms.CharField(max_length=500, required=False)

View File

@@ -10,6 +10,7 @@ from django.db import models
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser
from easy_thumbnails.fields import ThumbnailerImageField
from taggit.managers import TaggableManager
# Create your models here.
@@ -29,7 +30,7 @@ class Voucher(models.Model):
Creation date is being set automatically during voucher creation.
"""
code = models.CharField(unique=True, max_length=10)
code = models.CharField(unique=True, max_length=30)
created = models.DateTimeField(auto_now_add=True)
expires = models.DateField()
@@ -55,6 +56,23 @@ class Place (models.Model):
longitude = models.FloatField()
description = models.TextField()
tags = TaggableManager(blank=True)
# Get center position of LP-geocoordinates.
def average_latlon(place_list):
amount = len(place_list)
# Init fill values to prevent None
longitude = 0
latitude = 0
if amount > 0:
for place in place_list:
longitude += place.longitude
latitude += place.latitude
return (latitude / amount, longitude / amount)
return (latitude, longitude)
def __str__(self):
return self.name
@@ -126,3 +144,24 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
if not old_file == new_file:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
class ExternalLink(models.Model):
url = models.URLField(max_length=200)
label = models.CharField(max_length=100)
submitted_by = models.ForeignKey(
Explorer,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='external_links'
)
submitted_when = models.DateTimeField(auto_now_add=True, null=True)
class PhotoAlbum(ExternalLink):
place = models.ForeignKey(
Place,
on_delete=models.CASCADE,
related_name='photo_albums',
null=True
)

View File

@@ -0,0 +1 @@
<svg height="427pt" viewBox="-40 0 427 427.00131" width="427pt" xmlns="http://www.w3.org/2000/svg"><path d="m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/><path d="m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/><path d="m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0"/><path d="m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1 +1,4 @@
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"><path d="M 3 7 A 1.0001 1.0001 0 1 0 3 9 L 27 9 A 1.0001 1.0001 0 1 0 27 7 L 3 7 z M 3 14 A 1.0001 1.0001 0 1 0 3 16 L 27 16 A 1.0001 1.0001 0 1 0 27 14 L 3 14 z M 3 21 A 1.0001 1.0001 0 1 0 3 23 L 27 23 A 1.0001 1.0001 0 1 0 27 21 L 3 21 z"/></svg>
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px">
<path
d="M 3 7 A 1.0001 1.0001 0 1 0 3 9 L 27 9 A 1.0001 1.0001 0 1 0 27 7 L 3 7 z M 3 14 A 1.0001 1.0001 0 1 0 3 16 L 27 16 A 1.0001 1.0001 0 1 0 27 14 L 3 14 z M 3 21 A 1.0001 1.0001 0 1 0 3 23 L 27 23 A 1.0001 1.0001 0 1 0 27 21 L 3 21 z" />
</svg>

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 358 B

View File

@@ -0,0 +1,13 @@
{
"trash": {
"paths": [
"m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0",
"m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0",
"m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0",
"m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"
],
"height": "427pt",
"width": "427pt",
"viewBox": "-40 0 427 427.00131"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +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}
/*# sourceMappingURL=ol.css.map */

View File

@@ -0,0 +1 @@
{"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,OAAQ,IAAI,MAAM,KAGpB,gBACE,QAAS,KAEG,iBAAd,aACE,sBAAuB,KACvB,oBAAqB,KACrB,iBAAkB,KAClB,gBAAiB,KACjB,YAAa,KACb,4BAA6B,YAE/B,qBAAsB,+BACpB,eAAgB,KAEQ,iCAA1B,uBACE,eAAgB,KAElB,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,wBACE,eAAgB,KAElB,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

View File

@@ -0,0 +1,7 @@
# 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

View File

@@ -0,0 +1,548 @@
:root {
--tagify-dd-color-primary: rgb(53, 149, 246);
--tagify-dd-bg-color: white
}
.tagify {
--tags-border-color: #DDD;
--tags-hover-border-color: #CCC;
--tags-focus-border-color: #3595f6;
--tag-bg: #E5E5E5;
--tag-hover: #D3E2E2;
--tag-text-color: black;
--tag-text-color--edit: black;
--tag-pad: 0.3em 0.5em;
--tag-inset-shadow-size: 1.1em;
--tag-invalid-color: #D39494;
--tag-invalid-bg: rgba(211, 148, 148, 0.5);
--tag-remove-bg: rgba(211, 148, 148, 0.3);
--tag-remove-btn-color: black;
--tag-remove-btn-bg: none;
--tag-remove-btn-bg--hover: #c77777;
--input-color: inherit;
--tag--min-width: 1ch;
--tag--max-width: auto;
--tag-hide-transition: .3s;
--placeholder-color: rgba(0, 0, 0, 0.4);
--placeholder-color-focus: rgba(0, 0, 0, 0.25);
--loader-size: .8em;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
border: 1px solid #ddd;
border: 1px solid var(--tags-border-color);
padding: 0;
line-height: 1.1;
cursor: text;
outline: 0;
position: relative;
transition: .1s
}
@keyframes tags--bump {
30% {
transform: scale(1.2)
}
}
@keyframes rotateLoader {
to {
transform: rotate(1turn)
}
}
.tagify:hover {
border-color: #ccc;
border-color: var(--tags-hover-border-color)
}
.tagify.tagify--focus {
transition: 0s;
border-color: #3595f6;
border-color: var(--tags-focus-border-color)
}
.tagify[readonly]:not(.tagify--mix) {
cursor: default
}
.tagify[readonly]:not(.tagify--mix)>.tagify__input {
visibility: hidden;
width: 0;
margin: 5px 0
}
.tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn {
display: none
}
.tagify[readonly]:not(.tagify--mix) .tagify__tag>div {
padding: .3em .5em;
padding: var(--tag-pad)
}
.tagify[readonly]:not(.tagify--mix) .tagify__tag>div::before {
background: linear-gradient(45deg, var(--tag-bg) 25%, transparent 25%, transparent 50%, var(--tag-bg) 50%, var(--tag-bg) 75%, transparent 75%, transparent) 0/5px 5px;
box-shadow: none;
filter: brightness(.95)
}
.tagify--loading .tagify__input::before {
content: none
}
.tagify--loading .tagify__input::after {
content: '';
vertical-align: middle;
opacity: 1;
width: .7em;
height: .7em;
width: var(--loader-size);
height: var(--loader-size);
border: 3px solid;
border-color: #eee #bbb #888 transparent;
border-radius: 50%;
animation: rotateLoader .4s infinite linear;
margin: -2px 0 -2px .5em
}
.tagify--loading .tagify__input:empty::after {
margin-left: 0
}
.tagify+input,
.tagify+textarea {
display: none !important
}
.tagify__tag {
display: inline-flex;
align-items: center;
margin: 5px 0 5px 5px;
position: relative;
z-index: 1;
outline: 0;
cursor: default;
transition: .13s ease-out
}
.tagify__tag>div {
vertical-align: top;
box-sizing: border-box;
max-width: 100%;
padding: .3em .5em;
padding: var(--tag-pad);
color: #000;
color: var(--tag-text-color);
line-height: inherit;
border-radius: 3px;
-webkit-user-select: none;
user-select: none;
white-space: nowrap;
transition: .13s ease-out
}
.tagify__tag>div>* {
white-space: pre-wrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: top;
min-width: var(--tag--min-width);
max-width: var(--tag--max-width);
transition: .8s ease, .1s color
}
.tagify__tag>div>[contenteditable] {
outline: 0;
-webkit-user-select: text;
user-select: text;
cursor: text;
margin: -2px;
padding: 2px;
max-width: 350px
}
.tagify__tag>div::before {
content: '';
position: absolute;
border-radius: inherit;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: -1;
pointer-events: none;
transition: 120ms ease;
animation: tags--bump .3s ease-out 1;
box-shadow: 0 0 0 1.1em #e5e5e5 inset;
box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-bg) inset
}
.tagify__tag:hover:not([readonly]) div::before {
top: -2px;
right: -2px;
bottom: -2px;
left: -2px;
box-shadow: 0 0 0 1.1em #d3e2e2 inset;
box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-hover) inset
}
.tagify__tag--loading {
pointer-events: none
}
.tagify__tag--loading .tagify__tag__removeBtn {
display: none
}
.tagify__tag--loading::after {
--loader-size: .4em;
content: '';
vertical-align: middle;
opacity: 1;
width: .7em;
height: .7em;
width: var(--loader-size);
height: var(--loader-size);
border: 3px solid;
border-color: #eee #bbb #888 transparent;
border-radius: 50%;
animation: rotateLoader .4s infinite linear;
margin: 0 .5em 0 -.1em
}
.tagify__tag--flash div::before {
animation: none
}
.tagify__tag--hide {
width: 0 !important;
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
opacity: 0;
transform: scale(0);
transition: .3s;
transition: var(--tag-hide-transition);
pointer-events: none
}
.tagify__tag.tagify--noAnim>div::before {
animation: none
}
.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span {
opacity: .5
}
.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before {
box-shadow: 0 0 0 1.1em rgba(211, 148, 148, .5) inset !important;
box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-invalid-bg) inset !important;
transition: .2s
}
.tagify__tag[readonly] .tagify__tag__removeBtn {
display: none
}
.tagify__tag[readonly]>div::before {
background: linear-gradient(45deg, var(--tag-bg) 25%, transparent 25%, transparent 50%, var(--tag-bg) 50%, var(--tag-bg) 75%, transparent 75%, transparent) 0/5px 5px;
box-shadow: none;
filter: brightness(.95)
}
.tagify__tag--editable>div {
color: #000;
color: var(--tag-text-color--edit)
}
.tagify__tag--editable>div::before {
box-shadow: 0 0 0 2px #d3e2e2 inset !important;
box-shadow: 0 0 0 2px var(--tag-hover) inset !important
}
.tagify__tag--editable.tagify--invalid>div::before {
box-shadow: 0 0 0 2px #d39494 inset !important;
box-shadow: 0 0 0 2px var(--tag-invalid-color) inset !important
}
.tagify__tag__removeBtn {
order: 5;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50px;
cursor: pointer;
font: 14px/1 Arial;
background: 0 0;
background: var(--tag-remove-btn-bg);
color: #000;
color: var(--tag-remove-btn-color);
width: 14px;
height: 14px;
margin-right: 4.66667px;
margin-left: -4.66667px;
transition: .2s ease-out
}
.tagify__tag__removeBtn::after {
content: "\00D7"
}
.tagify__tag__removeBtn:hover {
color: #fff;
background: #c77777;
background: var(--tag-remove-btn-bg--hover)
}
.tagify__tag__removeBtn:hover+div>span {
opacity: .5
}
.tagify__tag__removeBtn:hover+div::before {
box-shadow: 0 0 0 1.1em rgba(211, 148, 148, .3) inset !important;
box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-remove-bg) inset !important;
transition: .2s
}
.tagify:not(.tagify--mix) .tagify__input br {
display: none
}
.tagify:not(.tagify--mix) .tagify__input * {
display: inline;
white-space: nowrap
}
.tagify__input {
flex-grow: 1;
display: inline-block;
min-width: 110px;
margin: 5px;
padding: .3em .5em;
padding: var(--tag-pad, .3em .5em);
line-height: inherit;
position: relative;
white-space: pre-wrap;
color: inherit;
color: var(--input-color)
}
.tagify__input:empty::before {
transition: .2s ease-out;
opacity: 1;
transform: none;
display: inline-block;
width: auto
}
.tagify--mix .tagify__input:empty::before {
display: inline-block
}
.tagify__input:focus {
outline: 0
}
.tagify__input:focus::before {
transition: .2s ease-out;
opacity: 0;
transform: translatex(6px)
}
@media all and (-ms-high-contrast:none),
(-ms-high-contrast:active) {
.tagify__input:focus::before {
display: none
}
}
@supports (-ms-ime-align:auto) {
.tagify__input:focus::before {
display: none
}
}
.tagify__input:focus:empty::before {
transition: .2s ease-out;
opacity: 1;
transform: none;
color: rgba(0, 0, 0, .25);
color: var(--placeholder-color-focus)
}
@-moz-document url-prefix() {
.tagify__input:focus:empty::after {
display: none
}
}
.tagify__input::before {
content: attr(data-placeholder);
height: 1em;
line-height: 1em;
margin: auto 0;
z-index: 1;
color: rgba(0, 0, 0, .4);
color: var(--placeholder-color);
white-space: nowrap;
pointer-events: none;
opacity: 0;
position: absolute
}
.tagify--mix .tagify__input::before {
display: none;
position: static;
line-height: inherit
}
.tagify__input::after {
content: attr(data-suggest);
display: inline-block;
white-space: pre;
color: #000;
opacity: .3;
pointer-events: none;
max-width: 100px
}
.tagify__input .tagify__tag {
margin: 0
}
.tagify__input .tagify__tag>div {
padding-top: 0;
padding-bottom: 0
}
.tagify--mix {
display: block
}
.tagify--mix .tagify__input {
padding: 5px;
margin: 0;
width: 100%;
height: 100%;
line-height: 1.5
}
.tagify--mix .tagify__input::before {
height: auto
}
.tagify--mix .tagify__input::after {
content: none
}
.tagify--select::after {
content: '>';
opacity: .5;
position: absolute;
top: 50%;
right: 0;
bottom: 0;
font: 16px monospace;
line-height: 8px;
height: 8px;
pointer-events: none;
transform: translate(-150%, -50%) scaleX(1.2) rotate(90deg);
transition: .2s ease-in-out
}
.tagify--select[aria-expanded=true]::after {
transform: translate(-150%, -50%) rotate(270deg) scaleY(1.2)
}
.tagify--select .tagify__tag {
position: absolute;
top: 0;
right: 1.8em;
bottom: 0
}
.tagify--select .tagify__tag div {
display: none
}
.tagify--select .tagify__input {
width: 100%
}
.tagify--invalid {
--tags-border-color: #D39494
}
.tagify__dropdown {
position: absolute;
z-index: 9999;
transform: translateY(1px);
overflow: hidden
}
.tagify__dropdown[placement=top] {
margin-top: 0;
transform: translateY(-100%)
}
.tagify__dropdown[placement=top] .tagify__dropdown__wrapper {
border-top-width: 1px;
border-bottom-width: 0
}
.tagify__dropdown[position=text] {
box-shadow: 0 0 0 3px rgba(var(--tagify-dd-color-primary), .1);
font-size: .9em
}
.tagify__dropdown[position=text] .tagify__dropdown__wrapper {
border-width: 1px
}
.tagify__dropdown__wrapper {
max-height: 300px;
overflow: hidden;
background: #fff;
background: var(--tagify-dd-bg-color);
border: 1px solid #3595f6;
border-color: var(--tagify-dd-color-primary);
border-top-width: 0;
box-shadow: 0 2px 4px -2px rgba(0, 0, 0, .2);
transition: .25s cubic-bezier(0, 1, .5, 1)
}
.tagify__dropdown__wrapper:hover {
overflow: auto
}
.tagify__dropdown--initial .tagify__dropdown__wrapper {
max-height: 20px;
transform: translateY(-1em)
}
.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper {
transform: translateY(2em)
}
.tagify__dropdown__item {
box-sizing: inherit;
padding: .3em .5em;
margin: 1px;
cursor: pointer;
border-radius: 2px;
position: relative;
outline: 0
}
.tagify__dropdown__item--active {
background: #3595f6;
background: var(--tagify-dd-color-primary);
color: #fff
}
.tagify__dropdown__item:active {
filter: brightness(105%)
}

File diff suppressed because one or more lines are too long

View File

@@ -2,95 +2,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{% static 'main.css' %}">
<link rel="icon" type="image/png" href="{% static 'favicon.ico' %}">
<title>
{% block title %}Urban Exploration{% endblock %}
</title>
{% block additional_head %}
{% endblock additional_head %}
<script>
document.addEventListener("DOMContentLoaded", function(){
Array.from(document.getElementsByClassName('LP-Main__Sidebar')).forEach(function(element){
element.classList.add('LP-Main__Sidebar--hidden')
})
setTimeout(function(){
Array.from(document.getElementsByClassName('LP-Main__Sidebar')).forEach(function(element){
element.classList.remove('LP-Main__Sidebar--hidden')
})
}, 500)
})
</script>
</head>
<head>
{% block additional_head %}
{% endblock additional_head %}
<body>
<div class="LP-Wrapper__Site">
<header class="LP-Header">
<div class="LP-Header__Logo">
<a class="LP-Link" href="/">
<img src="{% static 'logo.png' %}" class="LP-Image" />
</a>
</div>
<div class="LP-Header__UserInformation">
<span class="LP-Paragraph">
{% if user.is_authenticated %}
Hi {{ user.username }}!
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">logout</span></a>
{% if user.is_superuser %}
| <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">admin</span></a>
{% endif %}
{% else %}
You are not logged in.
<a class="LP-Link" href="{% url 'login' %}"><span class="LP-Link__Text">login</span></a> |
<a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">signup</span></a>
{% endif %}
</span>
</div>
</header>
<input id="toggle_sidebar" class="LP-Menu__Trigger" type="checkbox"/>
<label id="toggle_sidebar_label" for="toggle_sidebar" class="LP-Menu__TriggerLabel"></label>
<aside class="LP-Main__Sidebar">
<nav class="LP-Menu LP-Menu--sidebar">
<ul class="LP-Menu__List">
<li class="LP-Menu__Item"><a href="/" class="LP-Link"><span class="LP-Link__Text">Home</span></a></li>
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">About</span></a></li>
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">Contact</span></a></li>
{% block additional_menu_items %}
{% endblock additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_create'%}" class="LP-Link"><span class="LP-Link__Text">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">See all places</span></a></li>
</ul>
</nav>
</aside>
<main class="LP-Main__Content">
{% if messages %}
<div class="LP-MessageList">
<ul class="LP-MessageList__List">
{% for message in messages %}
<li class="LP-MessageList__Item">
<div class="LP-Message {% if message.tags %}LP-Message--{{ message.tags }}{% endif %}">
<div>
<div class="LP-Message__Icon">
</div>
</div>
<div class="LP-Message__Text">
{{ message }}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'main.css' %}">
<link rel="icon" type="image/png" href="{% static 'favicon.ico' %}">
<title>
{% block title %}Urban Exploration{% endblock %}
</title>
</head>
<body>
<div class="LP-Wrapper__Site">
<header class="LP-Header">
<div class="LP-Header__Logo">
<a class="LP-Link" href="/">
<img src="{% static 'logo.png' %}" class="LP-Image" />
</a>
</div>
<div class="LP-Header__UserInformation">
<span class="LP-Paragraph">
{% if user.is_authenticated %}
Hi {{ user.username }}!
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">logout</span></a>
{% if user.is_superuser %}
| <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">admin</span></a>
{% endif %}
{% else %}
You are not logged in.
<a class="LP-Link" href="{% url 'login' %}"><span class="LP-Link__Text">login</span></a> |
<a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">signup</span></a>
{% endif %}
</span>
</div>
</header>
<input id="toggle_sidebar" class="LP-Menu__Trigger" type="checkbox" />
<label id="toggle_sidebar_label" for="toggle_sidebar" class="LP-Menu__TriggerLabel"></label>
<aside class="LP-Main__Sidebar">
<nav class="LP-Menu LP-Menu--sidebar">
<ul class="LP-Menu__List">
<li class="LP-Menu__Item"><a href="/" class="LP-Link"><span class="LP-Link__Text">Home</span></a></li>
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">About</span></a></li>
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">Contact</span></a></li>
{% block additional_menu_items %}
{% endblock additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_create'%}" class="LP-Link"><span class="LP-Link__Text">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">See all places</span></a></li>
</ul>
</nav>
</aside>
<main class="LP-Main__Content">
{% if messages %}
<div class="LP-MessageList">
<ul class="LP-MessageList__List">
{% for message in messages %}
<li class="LP-MessageList__Item">
<div class="LP-Message {% if message.tags %}LP-Message--{{ message.tags }}{% endif %}">
<div>
<div class="LP-Message__Icon">
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% block maincontent %}
{% endblock maincontent %}
</main>
</div>
</body>
<div class="LP-Message__Text">
{{ message }}
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% block maincontent %}
{% endblock maincontent %}
</main>
</div>
</body>
</html>

View File

@@ -1,10 +1,15 @@
{% extends 'global.html'%}
{% load static %}
{% block additional_head %}
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
<script src="{% static 'maps/ol.js' %}"></script>
{% endblock additional_head %}
# {% block title %}Start{% endblock %}
{% block maincontent %}
{% include 'partials/osm_map.html' %}
<div class="LP-PlaceGrid">
<h1 class="LP-Headline LP-Headline">Explore the latest locations</h1>
<ul class="LP-PlaceGrid__Grid">

View File

@@ -1,16 +1,18 @@
{% load widget_tweaks %}
<div class="LP-Input {% if field.errors %} LP-Input--error {% endif %}">
<div class="LP-Input {% if classes%}{{classes}}{% endif %} {% if field.errors %} LP-Input--error {% endif %}">
<label for="{{field.id_for_label}}" class="LP-Input__Label">{{field.label}}</label>
{% render_field field class="LP-Input__Field"%}
{% with class="LP-Input__Field "%}
{% render_field field class=class%}
{% endwith %}
<span class="LP-Input__Message">
{% if field.errors %}
{% for error in field.errors%}
{{error}}
{% endfor %}
{% if field.errors %}
{% for error in field.errors%}
{{error}}
{% endfor %}
{% elif field.help_text%}
{{ field.help_text }}
{{ field.help_text }}
{% endif %}
</span>
</div>

View File

@@ -0,0 +1,69 @@
<div id="map" class="map" style="height: 300px"></div>
<div id="info" class="map-popup"></div>
<script type="text/javascript">
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
],
view: new ol.View({
center: ol.proj.fromLonLat([{{place_map_center|last}}, {{place_map_center|first}}]),
zoom: 9
})
});
var vectorSource = new ol.source.Vector({
features: [
{% for place in place_list %}
new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.fromLonLat([{{place.longitude}},{{place.latitude}}])
),
url: '{% url 'place_detail' pk=place.pk %}',
name: '{{place.name}}'
}),
{% endfor %}
]
});
var markerVectorLayer = new ol.layer.Vector({
source: vectorSource,
style: new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
scale: 0.3,
src: 'http://icons.iconarchive.com/icons/paomedia/small-n-flat/128/map-marker-icon.png'
})
})
});
map.addLayer(markerVectorLayer);
var overlay = new ol.Overlay({
element: document.getElementById('info'),
positioning: 'bottom-left'
});
overlay.setMap(map);
map.on(['singleclick'], function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
window.open(feature.get('url'), '_blank');
});
});
map.on(['pointermove'], function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
overlay.setPosition(evt.coordinate.map(element => element + 1));
overlay.getElement().innerHTML = feature.get('name');
return feature;
});
overlay.getElement().style.display = feature ? '' : 'none';
document.body.style.cursor = feature ? 'pointer' : '';
});
</script>

View File

@@ -0,0 +1,53 @@
<div class="LP-TagList">
<ul class="LP-TagList__List">
{% for tag in tag_list %}
<li class="LP-TagList__Item">
<div class="LP-Tag">
<p class="LP-Paragraph">{{tag}}</p>
</div>
</li>
{% endfor %}
</ul>
</div>
<form id="id_tag_submit_form" class="LP-Form LP-Form--inline LP-Form--tagging" method="POST" action="{{url}}">
<fieldset class="LP-Form__Fieldset">
<legend class="LP-Form__Legend">Tags hinzufügen</legend>
{% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field LP-Form__Button LP-Input LP-Input--tagging">
<button id="id_tag_submit_button" class="LP-Button"> Tags hinzufügen</button>
</div>
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=input_field classes="LP-Input--tagging" %}
</div>
</div>
</fieldset>
</form>
<script>
const input = document.getElementById('{{input_field.auto_id}}')
const submit_form = document.getElementById('id_tag_submit_form')
const submit_button = document.getElementById('id_tag_submit_button')
submit_form.onsubmit = () => false
const tagify = new Tagify(input, {
'whitelist': [
{% for tag in all_tags %}
'{{tag}}',
{% endfor %}
]
})
const on_form_submit = function() {
concat_value = ''
console.log(tagify)
concat_value = tagify.value.map(value => value.value).join(',')
console.log(concat_value)
input.value = concat_value
submit_form.submit()
}
submit_button.onclick = on_form_submit
</script>

View File

@@ -0,0 +1,41 @@
{% extends 'global.html'%}
{% block title %}Submit a photo album{% endblock %}
{% block additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span
class="LP-Link__Text">Edit place</span></a></li>
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span
class="LP-Link__Text">Delete place</span></a></li>
{% endblock additional_menu_items %}
{% block maincontent %}
<form class="LP-Form" method="POST">
<fieldset class="LP-Form__Fieldset">
<legend class="LP-Form__Legend">Submit a photo album for {{place.name}}</legend>
{% csrf_token %}
<div class="LP-Form__Composition">
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=form.url %}
</div>
</div>
<div class="LP-Form__Composition">
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=form.label %}
</div>
</div>
<div class="LP-Form__Composition LP-Form__Composition--buttons">
<div class="LP-Form__Field LP-Form__Button LP-Input">
<button class="LP-Button">Submit</button>
</div>
<div class="LP-Form__Field LP-Form__Button LP-Input">
<a class="LP-Link" href="{% url 'place_detail' pk=place.id%}">
<button type="button" class="LP-Button LP-Button--cancel">Cancel</button>
</a>
</div>
</div>
</fieldset>
</form>
{% endblock maincontent %}

View File

@@ -1,23 +1,35 @@
{% extends 'global.html'%}
{% load static %}
{% load thumbnail %}
{% load svg_icon %}
{% block additional_head %}
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
<script src="{% static 'maps/ol.js' %}"></script>
<script src="{% static 'tagify.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'minimal.css' %}" type="text/css">
{% endblock additional_head %}
{% block title %}{{place.name}}{% endblock %}
{% block additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Edit place</span></a></li>
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Delete place</span></a></li>
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Edit place</span></a></li>
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Delete place</span></a></li>
{% endblock additional_menu_items %}
{% block maincontent %}
<article class="LP-PlaceDetail">
<header class="LP-PlaceDetail__Header">
<h1 class="LP-Headline">{{ place.name }}</h1>
{% if place.images.first.filename.hero.url %}
<figure class="LP-PlaceDetail__Image">
<img src="{{ place.images.first.filename.hero.url }}" class="LP-Image" />
</figure>
<figure class="LP-PlaceDetail__Image">
<img src="{{ place.images.first.filename.hero.url }}" class="LP-Image" />
</figure>
{% endif %}
</header>
@@ -25,21 +37,64 @@
<p class="LP-Paragraph">{{ place.description }}</p>
</div>
<section class="LP-Section">
{% url 'place_tag_submit' place_id=place.id as tag_submit_url%}
{% include 'partials/tagging.html' with tag_list=place.tags.all url=tag_submit_url input_field=tagging_form.tag_list%}
</section>
<section class="LP-Section">
<h1 class="LP-Headline">Map-Links</h1>
{% include 'partials/osm_map.html' %}
<div class="LP-LinkList">
<ul class="LP-LinkList__List">
<ul class="LP-LinkList__Container">
<li class="LP-LinkList__Item"><a target="_blank" href="https://www.google.com/maps?q={{place.latitude}},{{place.longitude}}" class="LP-Link"><span class="LP-Text">Google Maps</span></a></li>
<li class="LP-LinkList__Item"><a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude}},{{place.longitude}}&icon=true&bg=dop" class="LP-Link"><span class="LP-Text">TIM Online</span></a></li>
<li class="LP-LinkList__Item"><a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude}}&mlon={{place.longitude}}&zoom=16" class="LP-Link"><span class="LP-Text">OSM</span></a></li>
</ul>
</div>
</section>
<section class=" LP-Section">
<h1 class="LP-Headline">Photoalben</h1>
<div class="LP-LinkList">
<ul class="LP-LinkList__Container">
{% for photo_album in place.photo_albums.all %}
<li class="LP-LinkList__Item">
<a target="_blank" href="{{photo_album.url}}" class="LP-Link">
<span class="LP-Text">{{photo_album.label}}</span>
</a>
{% if user == photo_album.submitted_by or user == place.submitted_by %}
<a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album">
<div class="RV-Iconized__Container RV-Iconized__Container--small">
{% icon 'trash' className="RV-Iconized__Icon" %}
</div>
</a>
{% endif %}
</li>
{% endfor %}
<li class="LP-LinkList__Item">
<a href="{% url 'photo_album_create' place_id=place.id %}" class="LP-Link">
<div class="RV-Iconized__Container RV-Iconized__Container--small">
<svg class="RV-Iconized__Icon" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" xml:space="preserve">
<g>
<path d="M492,236H276V20c0-11.046-8.954-20-20-20c-11.046,0-20,8.954-20,20v216H20c-11.046,0-20,8.954-20,20s8.954,20,20,20h216
v216c0,11.046,8.954,20,20,20s20-8.954,20-20V276h216c11.046,0,20-8.954,20-20C512,244.954,503.046,236,492,236z" />
</g>
</svg>
<span class="RV-Iconized__Text">Fotoalbum hinzufügen</span>
</div>
</a>
</li>
</ul>
</div>
</section>
<section class="LP-Section">
<h1 class="LP-Headline">Bilder</h1>
<div class="LP-ImageGrid">
<ul class="LP-ImageGrid__List">
<ul class="LP-ImageGrid__Container">
{% for place_image in place.images.all %}
<li class="LP-ImageGrid__Item">
<a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image" src="{{ place_image.filename.thumbnail.url }}"></a>
@@ -48,6 +103,6 @@
</ul>
</div>
</section>
</article>
{% endblock maincontent %}

View File

@@ -1,9 +1,16 @@
{% extends 'global.html'%}
{% load static %}
{% block additional_head %}
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
<script src="{% static 'maps/ol.js' %}"></script>
{% endblock additional_head %}
{% block title %}Lost Places{% endblock %}
{% block maincontent %}
{% include 'partials/osm_map.html' %}
<div class="LP-PlaceList">
<h1 class="LP-Headline">Listing our places</h1>
<ul class="LP-PlaceList__List">

View File

@@ -16,16 +16,16 @@
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=form.password %}
</div>
</div>
</div>
<div class="LP-Form__Composition LP-Form__Composition--buttons">
<div class="LP-Form__Field LP-Form__Button LP-Input">
<button class="LP-Button">Login</button>
</div>
</div>
</div>
<div class="LP-Form__Composition LP-Form__Composition--buttons">
<div class="LP-Form__Field LP-Form__Button LP-Input">
<button class="LP-Button">Login</button>
</div>
</div>
</fieldset>
</form>
<p class="LP-Headline">No account? <a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">Sign up here</span></a></p>
{% endblock maincontent %}

View File

@@ -0,0 +1,5 @@
<svg width="{{ width }}" height="{{ height }}" viewBox="{{viewBox}}" class="svg{% if className %} {{ className }}{% endif %}">
{% for path in paths %}
<path d="{{ path }}"></path>
{% endfor %}
</svg>

After

Width:  |  Height:  |  Size: 209 B

View File

@@ -0,0 +1,31 @@
import json, os
from importlib import import_module
from django.core.cache import cache
from django.conf import settings
from django.template import Library, TemplateSyntaxError
#icons_json_path = getattr(settings, 'SVG_ICONS_SOURCE_FILE')
icons_json_path = os.path.join(settings.BASE_DIR, 'lostplaces_app', 'static', 'icons', 'icons.icomoon.json')
icons_json = json.load(open(icons_json_path))
register = Library()
@register.inclusion_tag('svg_icon/icon.html')
def icon(name, **kwargs):
icon_config = icons_json.get(name, None)
if icon_config:
width = kwargs.get('width', icon_config.get('width', 16))
height = kwargs.get('height', icon_config.get('heigh', 16))
viewBox = kwargs.get('viewBox', icon_config.get('viewBox', '0 0 1024 1024'))
return {
'width': kwargs.get('size', width),
'height': kwargs.get('size', height),
'className': kwargs.get('className'),
'viewBox': viewBox,
'paths': icon_config.get('paths', [''])
}

View File

@@ -6,7 +6,10 @@ from .views import (
SignUpView,
PlaceCreateView,
PlaceUpdateView,
PlaceDeleteView
PlaceDeleteView,
PhotoAlbumCreateView,
PhotoAlbumDeleteView,
PlaceTagSubmitView
)
urlpatterns = [
@@ -14,7 +17,11 @@ urlpatterns = [
path('signup/', SignUpView.as_view(), name='signup'),
path('place/<int:pk>/', PlaceDetailView.as_view(), name='place_detail'),
path('place/create/', PlaceCreateView.as_view(), name='place_create'),
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('place/update/<int:pk>/', PlaceUpdateView.as_view(), name='place_edit'),
path('place/delete/<int:pk>/', PlaceDeleteView.as_view(), name='place_delete'),
path('place/', PlaceListView.as_view(), name='place_list')
path('place/', PlaceListView.as_view(), name='place_list'),
# POST-only URL for tag submission
path('place/tag/<int:place_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
]

View File

@@ -0,0 +1,3 @@
from lostplaces_app.views.base_views import *
from lostplaces_app.views.views import *
from lostplaces_app.views.place_views import *

View File

@@ -0,0 +1,98 @@
from django.views import View
from django.views.generic.edit import CreateView
from django.views.generic.detail import SingleObjectMixin
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect
from django.urls import reverse_lazy
from lostplaces_app.models import Place
class IsAuthenticated(LoginRequiredMixin, View):
redirect_field_name = 'redirect_to'
permission_denied_message = 'Please login to proceed'
def handle_no_permission(self):
messages.error(self.request, self.permission_denied_message)
return super().handle_no_permission()
class IsPlaceSubmitter(UserPassesTestMixin, View):
place_submitter_error_message = None
def get_place(self):
pass
def test_func(self):
""" Check if user is eligible to modify place. """
if not hasattr(self.request, 'user'):
return False
if self.request.user.is_superuser:
return True
# Check if currently logged in user was the submitter
place_obj = self.get_place()
if place_obj and hasattr(place_obj, 'submitted_by') and self.request.user == place_obj.submitted_by:
return True
if self.place_submitter_error_message:
messages.error(self.request, self.place_submitter_error_message)
return False
class PlaceAssetCreateView(IsAuthenticated, SuccessMessageMixin, CreateView):
model = None
fields = []
template_name = ''
success_message = ''
def get(self, request, place_id, *args, **kwargs):
self.place = Place.objects.get(pk=place_id)
return super().get(request, *args, **kwargs)
def post(self, request, place_id, *args, **kwargs):
self.place = Place.objects.get(pk=place_id)
response = super().post(request, *args, **kwargs)
self.object.place = self.place
self.object.submitted_by = request.user
self.object.save()
return response
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place'] = self.place
return context
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk': self.place.id})
class PlaceAssetDeleteView(IsAuthenticated, IsPlaceSubmitter, SingleObjectMixin, View):
model = None
pk_url_kwarg = 'pk'
success_message = ''
permission_denied_message = ''
def get_place(self):
place_id = self.get_object().place.id
return Place.objects.get(pk=place_id)
def test_func(self):
can_edit_place = super().test_func()
if can_edit_place:
return True
if self.get_object().submitted_by == self.request.user:
return True
messages.error(self.request, self.permission_denied_message)
return False
def get(self, request, *args, **kwargs):
place_id = self.get_object().place.id
self.get_object().delete()
messages.success(self.request, self.success_message)
return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id}))

View File

@@ -1,83 +1,56 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
''' Django views. '''
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView
from django.views import View
from django.http import Http404
from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from .forms import (
ExplorerCreationForm,
PlaceForm,
PlaceImageCreateForm
)
from .models import Place, PlaceImage, Voucher
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
# Create your views here.
from lostplaces_app.models import Place, PlaceImage
from lostplaces_app.views import IsAuthenticated, IsPlaceSubmitter
from lostplaces_app.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm
# BaseView that checks if user is logged in.
class IsAuthenticated(LoginRequiredMixin, View):
redirect_field_name = 'redirect_to'
# BaseView that checks if logged in user is submitter of place.
class IsSubmitter(UserPassesTestMixin, View):
def test_func(self):
""" Check if user is eligible to modify place. """
if self.request.user.is_superuser:
return True
# Check if currently logged in user was the submitter
place_obj = self.get_object()
if self.request.user == place_obj.submitted_by:
return True
messages.error(
self.request, 'You do not have permission to do this.')
return False
class SignUpView(SuccessMessageMixin, CreateView):
form_class = ExplorerCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
success_message = 'User created.'
from taggit.models import Tag
class PlaceListView(IsAuthenticated, ListView):
paginate_by = 2
paginate_by = 5
model = Place
template_name = 'place/place_list.html'
ordering = ['name']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['place_map_center'] = Place.average_latlon(context['place_list'])
return context
class PlaceDetailView(IsAuthenticated, View):
def get(self, request, pk):
place = Place.objects.get(pk=pk)
context = {
'place': Place.objects.get(pk=pk)
'place': place,
'place_list': [ place ],
'place_map_center': [ place.latitude, place.longitude ],
'tagging_form': TagSubmitForm(),
'all_tags': Tag.objects.all()
}
return render(request, 'place/place_detail.html', context)
class HomeView(View):
def get(self, request, *args, **kwargs):
place_list = Place.objects.all().order_by('-submitted_when')[:10]
context = {
'place_list': place_list
}
return render(request, 'home.html', context)
class PlaceUpdateView(IsAuthenticated, IsSubmitter, SuccessMessageMixin, UpdateView):
class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView):
template_name = 'place/place_update.html'
model = Place
form_class = PlaceForm
success_message = 'Successfully updated place.'
place_submitter_error_message = 'You do no have permissions to alter this place'
def get_success_url(self):
return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk})
def get_place(self):
return self.get_object()
class PlaceCreateView(IsAuthenticated, View):
def get(self, request, *args, **kwargs):
@@ -134,13 +107,17 @@ class PlaceCreateView(IsAuthenticated, View):
)
place_image.save()
class PlaceDeleteView(IsAuthenticated, IsSubmitter, DeleteView):
class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView):
template_name = 'place/place_delete.html'
model = Place
success_message = 'Successfully deleted place.'
success_url = reverse_lazy('place_list')
success_message = 'Place deleted'
place_submitter_error_message = 'You do no have permission to delete this place'
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super().delete(request, *args, **kwargs)
def get_place(self):
return self.get_object()

View File

@@ -0,0 +1,58 @@
from django.views import View
from django.views.generic.edit import CreateView
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib import messages
from django.urls import reverse_lazy
from django.shortcuts import render, redirect
from lostplaces_app.forms import ExplorerCreationForm, TagSubmitForm
from lostplaces_app.models import Place, PhotoAlbum
from lostplaces_app.views.base_views import IsAuthenticated
from lostplaces_app.views.base_views import (
PlaceAssetCreateView,
PlaceAssetDeleteView
)
class SignUpView(SuccessMessageMixin, CreateView):
form_class = ExplorerCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
success_message = 'User created.'
class HomeView(View):
def get(self, request, *args, **kwargs):
place_list = Place.objects.all().order_by('-submitted_when')[:10]
place_map_center = Place.average_latlon(place_list)
context = {
'place_list': place_list,
'place_map_center': place_map_center
}
return render(request, 'home.html', context)
class PhotoAlbumCreateView(PlaceAssetCreateView):
model = PhotoAlbum
fields = ['url', 'label']
template_name = 'photo_album/photo_album_create.html'
success_message = 'Photo Album submitted'
class PhotoAlbumDeleteView(PlaceAssetDeleteView):
model = PhotoAlbum
pk_url_kwarg = 'pk'
success_message = 'Photo Album deleted'
permission_denied_messsage = 'You do not have permissions to alter this photo album'
class PlaceTagSubmitView(IsAuthenticated, View):
def post(self, request, place_id, *args, **kwargs):
place = Place.objects.get(pk=place_id)
form = TagSubmitForm(request.POST)
if form.is_valid():
tag_list_raw = form.cleaned_data['tag_list']
tag_list_raw = tag_list_raw.strip().split(',')
tag_list = []
for tag in tag_list_raw:
tag_list.append(tag.strip())
place.tags.add(*tag_list)
place.save()
return redirect(reverse_lazy('place_detail', kwargs={'pk': place.id}))