Merge branch 'feature/tags' of mowoe.com:reverend/lostplaces-backend into feature/tags
This commit is contained in:
commit
2509c669f9
@ -48,3 +48,7 @@ class PlaceImageCreateForm(forms.ModelForm):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['filename'].required = False
|
self.fields['filename'].required = False
|
||||||
|
|
||||||
|
|
||||||
|
class TagSubmitForm(forms.Form):
|
||||||
|
tag_list = forms.CharField(max_length=500, required=False)
|
@ -10,6 +10,7 @@ from django.db import models
|
|||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from easy_thumbnails.fields import ThumbnailerImageField
|
from easy_thumbnails.fields import ThumbnailerImageField
|
||||||
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ class Place (models.Model):
|
|||||||
longitude = models.FloatField()
|
longitude = models.FloatField()
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
|
|
||||||
|
tags = TaggableManager(blank=True)
|
||||||
# Get center position of LP-geocoordinates.
|
# Get center position of LP-geocoordinates.
|
||||||
|
|
||||||
def average_latlon(place_list):
|
def average_latlon(place_list):
|
||||||
@ -145,21 +147,21 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
class ExternalLink(models.Model):
|
class ExternalLink(models.Model):
|
||||||
url = models.URLField(max_length=200)
|
url = models.URLField(max_length=200)
|
||||||
label = models.CharField(max_length=100)
|
label = models.CharField(max_length=100)
|
||||||
submitted_by = models.ForeignKey(
|
submitted_by = models.ForeignKey(
|
||||||
Explorer,
|
Explorer,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name='external_links'
|
related_name='external_links'
|
||||||
)
|
)
|
||||||
submitted_when = models.DateTimeField(auto_now_add=True, null=True)
|
submitted_when = models.DateTimeField(auto_now_add=True, null=True)
|
||||||
|
|
||||||
class PhotoAlbum(ExternalLink):
|
class PhotoAlbum(ExternalLink):
|
||||||
place = models.ForeignKey(
|
place = models.ForeignKey(
|
||||||
Place,
|
Place,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='photo_albums',
|
related_name='photo_albums',
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
@ -574,7 +574,8 @@ body {
|
|||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer; }
|
cursor: pointer;
|
||||||
|
white-space: nowrap; }
|
||||||
.LP-Button:active {
|
.LP-Button:active {
|
||||||
background-color: #76323F;
|
background-color: #76323F;
|
||||||
color: #f9f9f9; }
|
color: #f9f9f9; }
|
||||||
@ -600,19 +601,28 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: -30px;
|
margin-bottom: -30px;
|
||||||
padding: 10px 0; }
|
padding: 10px 0; }
|
||||||
.LP-Input .LP-Input__Field {
|
.LP-Input--tagging .LP-Button {
|
||||||
|
height: 53px; }
|
||||||
|
.LP-Input--tagging .LP-Input__Field, .LP-Input--tagging .tagify {
|
||||||
|
min-height: 36px;
|
||||||
|
height: max-content;
|
||||||
|
font-family: Montserrat, Helvetica, sans-serif;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 8px; }
|
||||||
|
.LP-Input .LP-Input__Field, .LP-Input .tagify {
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid #565656;
|
border-bottom: 1px solid #565656;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
.LP-Input .LP-Input__Field:focus, .LP-Input .LP-Input__Field:active, .LP-Input .LP-Input__Field:invalid {
|
.LP-Input .LP-Input__Field:focus, .LP-Input .tagify:focus, .LP-Input .LP-Input__Field:active, .LP-Input .tagify:active, .LP-Input .LP-Input__Field:invalid, .LP-Input .tagify:invalid, .LP-Input .LP-Input__Field--active, .LP-Input .tagify--focus {
|
||||||
margin-bottom: 29px;
|
margin-bottom: 29px;
|
||||||
border-bottom: 2px solid #76323F;
|
border-bottom: 2px solid #76323F;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
box-shadow: none; }
|
box-shadow: none; }
|
||||||
.LP-Input .LP-Input__Field[type=submit] {
|
.LP-Input .LP-Input__Field[type=submit], .LP-Input .tagify[type=submit] {
|
||||||
background-color: #C09F80;
|
background-color: #C09F80;
|
||||||
color: #565656;
|
color: #565656;
|
||||||
border: none;
|
border: none;
|
||||||
@ -620,7 +630,7 @@ body {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer; }
|
cursor: pointer; }
|
||||||
.LP-Input .LP-Input__Field[type=submit]:active {
|
.LP-Input .LP-Input__Field[type=submit]:active, .LP-Input .tagify[type=submit]:active {
|
||||||
background-color: #76323F;
|
background-color: #76323F;
|
||||||
color: #f9f9f9; }
|
color: #f9f9f9; }
|
||||||
.LP-Input .LP-Input__Label {
|
.LP-Input .LP-Input__Label {
|
||||||
@ -636,22 +646,30 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: -30px;
|
top: -30px;
|
||||||
overflow: hidden; }
|
overflow: hidden; }
|
||||||
.LP-Input--error .LP-Input__Field {
|
.LP-Input--error .LP-Input__Field, .LP-Input--error .tagify {
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
border-bottom: 2px solid #76323F;
|
border-bottom: 2px solid #76323F;
|
||||||
margin-bottom: 29px; }
|
margin-bottom: 29px; }
|
||||||
.LP-Input--error .LP-Input__Message {
|
.LP-Input--error .LP-Input__Message {
|
||||||
color: #76323F; }
|
color: #76323F; }
|
||||||
.LP-Input--disabled .LP-Input__Field, .LP-Input--disabled .LP-Input__Field:disabled {
|
.LP-Input--disabled .LP-Input__Field, .LP-Input--disabled .tagify,
|
||||||
|
.LP-Input--disabled .LP-Input__Field:disabled,
|
||||||
|
.LP-Input--disabled .tagify:disabled {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-bottom: 1px dashed #565656;
|
border-bottom: 1px dashed #565656;
|
||||||
cursor: not-allowed; }
|
cursor: not-allowed; }
|
||||||
label + .LP-Input--disabled .LP-Input__Field, label + .LP-Input--disabled .LP-Input__Field:disabled {
|
label + .LP-Input--disabled .LP-Input__Field, label + .LP-Input--disabled .tagify, label + .LP-Input--disabled .LP-Input__Field:disabled, label + .LP-Input--disabled .tagify:disabled {
|
||||||
color: red; }
|
color: red; }
|
||||||
.LP-Input--disabled .LP-Input__Field:focus, .LP-Input--disabled .LP-Input__Field:active, .LP-Input--disabled .LP-Input__Field:disabled:focus, .LP-Input--disabled .LP-Input__Field:disabled:active {
|
.LP-Input--disabled .LP-Input__Field:focus, .LP-Input--disabled .tagify:focus, .LP-Input--disabled .LP-Input__Field:active, .LP-Input--disabled .tagify:active,
|
||||||
|
.LP-Input--disabled .LP-Input__Field:disabled:focus,
|
||||||
|
.LP-Input--disabled .tagify:disabled:focus,
|
||||||
|
.LP-Input--disabled .LP-Input__Field:disabled:active,
|
||||||
|
.LP-Input--disabled .tagify:disabled:active {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
border-radius: 0; }
|
border-radius: 0; }
|
||||||
.LP-Input--disabled .LP-Input__Field ~ .LP-Input__Message, .LP-Input--disabled .LP-Input__Field:disabled ~ .LP-Input__Message {
|
.LP-Input--disabled .LP-Input__Field ~ .LP-Input__Message, .LP-Input--disabled .tagify ~ .LP-Input__Message,
|
||||||
|
.LP-Input--disabled .LP-Input__Field:disabled ~ .LP-Input__Message,
|
||||||
|
.LP-Input--disabled .tagify:disabled ~ .LP-Input__Message {
|
||||||
visibility: hidden; }
|
visibility: hidden; }
|
||||||
.LP-Input--disabled .LP-Input__Label {
|
.LP-Input--disabled .LP-Input__Label {
|
||||||
color: #565656; }
|
color: #565656; }
|
||||||
@ -670,12 +688,14 @@ body {
|
|||||||
width: auto;
|
width: auto;
|
||||||
object-fit: contain; }
|
object-fit: contain; }
|
||||||
|
|
||||||
.LP-Tag {
|
.LP-Tag, .tagify__tag {
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
background-color: #D7CEC7;
|
background-color: #D7CEC7;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
width: max-content; }
|
width: max-content; }
|
||||||
.LP-Tag .LP-Paragraph {
|
.LP-Tag:hover, .tagify__tag:hover {
|
||||||
|
background-color: #bdbdbd; }
|
||||||
|
.LP-Tag .LP-Paragraph, .tagify__tag .LP-Paragraph {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: Montserrat, Helvetica, sans-serif;
|
font-family: Montserrat, Helvetica, sans-serif;
|
||||||
@ -861,8 +881,8 @@ body {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0; }
|
margin: 0; }
|
||||||
.LP-TagList .LP-TagList__List .LP-TagList__Item {
|
.LP-TagList .LP-TagList__List .LP-TagList__Item, .LP-TagList .LP-TagList__List .tagify__tag {
|
||||||
margin: 6px; }
|
margin: 3px; }
|
||||||
|
|
||||||
.LP-Menu {
|
.LP-Menu {
|
||||||
border-left: 1px solid #C09F80; }
|
border-left: 1px solid #C09F80; }
|
||||||
@ -1189,7 +1209,13 @@ body {
|
|||||||
.LP-Footer .LP-LinkList__List .LP-LinkList__Item .LP-Link:hover {
|
.LP-Footer .LP-LinkList__List .LP-LinkList__Item .LP-Link:hover {
|
||||||
background-color: inherit; }
|
background-color: inherit; }
|
||||||
|
|
||||||
.LP-Form--inline .LP-Form__Legend, .LP-Form--inline .LP-Input__Label {
|
.LP-Form--tagging {
|
||||||
|
margin-top: 25px; }
|
||||||
|
.LP-Form--tagging div.LP-Form__Composition {
|
||||||
|
gap: 25px; }
|
||||||
|
|
||||||
|
.LP-Form--inline .LP-Form__Legend,
|
||||||
|
.LP-Form--inline .LP-Input__Label {
|
||||||
display: none; }
|
display: none; }
|
||||||
|
|
||||||
.LP-Form--inline .LP-Form__Button {
|
.LP-Form--inline .LP-Form__Button {
|
||||||
@ -1198,6 +1224,12 @@ body {
|
|||||||
width: min-content;
|
width: min-content;
|
||||||
flex-basis: max-content; }
|
flex-basis: max-content; }
|
||||||
|
|
||||||
|
.LP-Form--inline fieldset.LP-Form__Fieldset {
|
||||||
|
max-width: unset; }
|
||||||
|
|
||||||
|
.LP-Form--inline div.LP-Form__Composition {
|
||||||
|
padding: 0; }
|
||||||
|
|
||||||
@media (max-width: 450px) {
|
@media (max-width: 450px) {
|
||||||
.LP-Form:not(.LP-Form--inline) .LP-Form__Composition {
|
.LP-Form:not(.LP-Form--inline) .LP-Form__Composition {
|
||||||
flex-wrap: wrap; } }
|
flex-wrap: wrap; } }
|
||||||
@ -1359,25 +1391,23 @@ body {
|
|||||||
border: none; }
|
border: none; }
|
||||||
.LP-ImageGrid__Container {
|
.LP-ImageGrid__Container {
|
||||||
gap: 10px; }
|
gap: 10px; }
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item {
|
.LP-ImageGrid .LP-ImageGrid__Item, .LP-ImageGrid .LP-ImageGrid__Item * {
|
||||||
box-shadow: 0 0 10px #565656; }
|
overflow: hidden;
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item, .LP-ImageGrid .LP-ImageGrid__Item * {
|
word-break: break-all; }
|
||||||
overflow: hidden;
|
.LP-ImageGrid .LP-ImageGrid__Item img {
|
||||||
word-break: break-all; }
|
width: 100%;
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item img {
|
height: 100%;
|
||||||
width: 100%;
|
object-fit: cover; }
|
||||||
height: 100%;
|
.LP-ImageGrid .LP-ImageGrid__Item--left img {
|
||||||
object-fit: cover; }
|
object-position: left; }
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item--left img {
|
.LP-ImageGrid .LP-ImageGrid__Item--center img {
|
||||||
object-position: left; }
|
object-position: center; }
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item--center img {
|
.LP-ImageGrid .LP-ImageGrid__Item--top img {
|
||||||
object-position: center; }
|
object-position: top; }
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item--top img {
|
.LP-ImageGrid .LP-ImageGrid__Item--bottom img {
|
||||||
object-position: top; }
|
object-position: botom; }
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item--bottom img {
|
.LP-ImageGrid .LP-ImageGrid__Item--center img {
|
||||||
object-position: botom; }
|
object-position: center; }
|
||||||
.LP-ImageGrid .LP-ImageGrid__Item--center img {
|
|
||||||
object-position: center; }
|
|
||||||
|
|
||||||
.LP-MainContainer {
|
.LP-MainContainer {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@ -1413,3 +1443,115 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 25px; } }
|
margin-bottom: 25px; } }
|
||||||
|
|
||||||
|
.tagify {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap; }
|
||||||
|
.tagify + input,
|
||||||
|
.tagify + textarea {
|
||||||
|
display: none; }
|
||||||
|
|
||||||
|
.tagify__tag {
|
||||||
|
background-color: #bdbdbd;
|
||||||
|
display: inline-flex;
|
||||||
|
cursor: default;
|
||||||
|
transition: .13s ease-out;
|
||||||
|
height: max-content;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px; }
|
||||||
|
.tagify__tag:hover {
|
||||||
|
background-color: #e9e9e9; }
|
||||||
|
|
||||||
|
.tagify__input {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 110px;
|
||||||
|
margin: 5px;
|
||||||
|
line-height: inherit;
|
||||||
|
position: relative;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin-left: 15px; }
|
||||||
|
|
||||||
|
.tagify__tag__removeBtn {
|
||||||
|
order: 5;
|
||||||
|
cursor: pointer;
|
||||||
|
font: 1em/1 Arial;
|
||||||
|
transition: .2s ease-out;
|
||||||
|
color: #76323F; }
|
||||||
|
|
||||||
|
.tagify__tag__removeBtn::after {
|
||||||
|
content: "\00D7"; }
|
||||||
|
|
||||||
|
.tagify__tag__removeBtn:hover {
|
||||||
|
color: #565656; }
|
||||||
|
|
||||||
|
.tagify__tag__removeBtn:hover + div > span {
|
||||||
|
opacity: .5; }
|
||||||
|
|
||||||
|
.tagify__tag__removeBtn:hover + div::before {
|
||||||
|
box-shadow: 0 0 0 1.1em rgba(211, 148, 148, 0.3) inset !important;
|
||||||
|
box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-remove-bg) inset !important;
|
||||||
|
transition: .2s; }
|
||||||
|
|
||||||
|
.tagify__tag--loading .tagify__tag__removeBtn {
|
||||||
|
display: none; }
|
||||||
|
|
||||||
|
.tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn {
|
||||||
|
display: none; }
|
||||||
|
|
||||||
|
.tagify__dropdown {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9999;
|
||||||
|
transform: translateY(1px);
|
||||||
|
overflow: hidden; }
|
||||||
|
|
||||||
|
.tagify__dropdown[placement=top] {
|
||||||
|
margin-top: 0;
|
||||||
|
transform: translateY(-100%); }
|
||||||
|
|
||||||
|
.tagify__dropdown[placement=top] .tagify__dropdown__wrapper {
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-bottom-width: 0; }
|
||||||
|
|
||||||
|
.tagify__dropdown[position=text] {
|
||||||
|
box-shadow: 0 0 0 3px rgba(var(--tagify-dd-color-primary), 0.1);
|
||||||
|
font-size: .9em; }
|
||||||
|
|
||||||
|
.tagify__dropdown[position=text] .tagify__dropdown__wrapper {
|
||||||
|
border-width: 1px; }
|
||||||
|
|
||||||
|
.tagify__dropdown__wrapper {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
box-shadow: 0 2px 4px -2px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: 0.25s cubic-bezier(0, 1, 0.5, 1); }
|
||||||
|
|
||||||
|
.tagify__dropdown__wrapper:hover {
|
||||||
|
overflow: auto; }
|
||||||
|
|
||||||
|
.tagify__dropdown--initial .tagify__dropdown__wrapper {
|
||||||
|
max-height: 20px;
|
||||||
|
transform: translateY(-1em); }
|
||||||
|
|
||||||
|
.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper {
|
||||||
|
transform: translateY(2em); }
|
||||||
|
|
||||||
|
.tagify__dropdown__item {
|
||||||
|
box-sizing: inherit;
|
||||||
|
padding: .3em .5em;
|
||||||
|
margin: 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
position: relative;
|
||||||
|
outline: 0;
|
||||||
|
font-family: Montserrat, Helvetica, sans-serif; }
|
||||||
|
|
||||||
|
.tagify__dropdown__item--active {
|
||||||
|
color: #f9f9f9;
|
||||||
|
background-color: gray; }
|
||||||
|
|
||||||
|
.tagify__dropdown__item:active {
|
||||||
|
filter: brightness(105%); }
|
||||||
|
@ -2,82 +2,85 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<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 %}
|
<head>
|
||||||
{% endblock additional_head %}
|
{% block additional_head %}
|
||||||
|
{% endblock additional_head %}
|
||||||
|
|
||||||
</head>
|
<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>
|
||||||
|
|
||||||
<body>
|
</head>
|
||||||
<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 %}
|
<body>
|
||||||
You are not logged in.
|
<div class="LP-Wrapper__Site">
|
||||||
<a class="LP-Link" href="{% url 'login' %}"><span class="LP-Link__Text">login</span></a> |
|
<header class="LP-Header">
|
||||||
<a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">signup</span></a>
|
<div class="LP-Header__Logo">
|
||||||
{% endif %}
|
<a class="LP-Link" href="/">
|
||||||
</span>
|
<img src="{% static 'logo.png' %}" class="LP-Image" />
|
||||||
</div>
|
</a>
|
||||||
</header>
|
</div>
|
||||||
<input id="toggle_sidebar" class="LP-Menu__Trigger" type="checkbox"/>
|
<div class="LP-Header__UserInformation">
|
||||||
<label id="toggle_sidebar_label" for="toggle_sidebar" class="LP-Menu__TriggerLabel"></label>
|
<span class="LP-Paragraph">
|
||||||
<aside class="LP-Main__Sidebar">
|
{% if user.is_authenticated %}
|
||||||
<nav class="LP-Menu LP-Menu--sidebar">
|
Hi {{ user.username }}!
|
||||||
<ul class="LP-Menu__List">
|
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">logout</span></a>
|
||||||
<li class="LP-Menu__Item"><a href="/" class="LP-Link"><span class="LP-Link__Text">Home</span></a></li>
|
{% if user.is_superuser %}
|
||||||
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">About</span></a></li>
|
| <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">admin</span></a>
|
||||||
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">Contact</span></a></li>
|
{% endif %}
|
||||||
{% block additional_menu_items %}
|
|
||||||
{% endblock additional_menu_items %}
|
{% else %}
|
||||||
<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>
|
You are not logged in.
|
||||||
<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>
|
<a class="LP-Link" href="{% url 'login' %}"><span class="LP-Link__Text">login</span></a> |
|
||||||
</ul>
|
<a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">signup</span></a>
|
||||||
</nav>
|
{% endif %}
|
||||||
</aside>
|
</span>
|
||||||
<main class="LP-Main__Content">
|
</div>
|
||||||
{% if messages %}
|
</header>
|
||||||
<div class="LP-MessageList">
|
<input id="toggle_sidebar" class="LP-Menu__Trigger" type="checkbox" />
|
||||||
<ul class="LP-MessageList__List">
|
<label id="toggle_sidebar_label" for="toggle_sidebar" class="LP-Menu__TriggerLabel"></label>
|
||||||
{% for message in messages %}
|
<aside class="LP-Main__Sidebar">
|
||||||
<li class="LP-MessageList__Item">
|
<nav class="LP-Menu LP-Menu--sidebar">
|
||||||
<div class="LP-Message {% if message.tags %}LP-Message--{{ message.tags }}{% endif %}">
|
<ul class="LP-Menu__List">
|
||||||
<div>
|
<li class="LP-Menu__Item"><a href="/" class="LP-Link"><span class="LP-Link__Text">Home</span></a></li>
|
||||||
<div class="LP-Message__Icon">
|
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">About</span></a></li>
|
||||||
</div>
|
<li class="LP-Menu__Item"><a href="" class="LP-Link"><span class="LP-Link__Text">Contact</span></a></li>
|
||||||
</div>
|
{% block additional_menu_items %}
|
||||||
<div class="LP-Message__Text">
|
{% endblock additional_menu_items %}
|
||||||
{{ message }}
|
<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>
|
</div>
|
||||||
</li>
|
<div class="LP-Message__Text">
|
||||||
{% endfor %}
|
{{ message }}
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</li>
|
||||||
{% block maincontent %}
|
{% endfor %}
|
||||||
{% endblock maincontent %}
|
</ul>
|
||||||
</main>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</body>
|
{% block maincontent %}
|
||||||
|
{% endblock maincontent %}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,16 +1,18 @@
|
|||||||
{% load widget_tweaks %}
|
{% 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>
|
<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">
|
<span class="LP-Input__Message">
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
{% for error in field.errors%}
|
{% for error in field.errors%}
|
||||||
{{error}}
|
{{error}}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% elif field.help_text%}
|
{% elif field.help_text%}
|
||||||
{{ field.help_text }}
|
{{ field.help_text }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
53
lostplaces/lostplaces_app/templates/partials/tagging.html
Normal file
53
lostplaces/lostplaces_app/templates/partials/tagging.html
Normal 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>
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
{% block additional_head %}
|
{% block additional_head %}
|
||||||
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'tagify.css' %}">
|
|
||||||
|
|
||||||
<script src="{% static 'maps/ol.js' %}"></script>
|
<script src="{% static 'maps/ol.js' %}"></script>
|
||||||
|
|
||||||
@ -17,109 +17,92 @@
|
|||||||
{% block title %}{{place.name}}{% endblock %}
|
{% block title %}{{place.name}}{% endblock %}
|
||||||
|
|
||||||
{% block additional_menu_items %}
|
{% 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
|
<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>
|
||||||
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_delete' pk=place.pk %}" class="LP-Link"><span
|
|
||||||
class="LP-Link__Text">Delete place</span></a></li>
|
|
||||||
{% endblock additional_menu_items %}
|
{% endblock additional_menu_items %}
|
||||||
|
|
||||||
{% block maincontent %}
|
{% block maincontent %}
|
||||||
<article class="LP-PlaceDetail">
|
<article class="LP-PlaceDetail">
|
||||||
|
|
||||||
<header class="LP-PlaceDetail__Header">
|
<header class="LP-PlaceDetail__Header">
|
||||||
<h1 class="LP-Headline">{{ place.name }}</h1>
|
<h1 class="LP-Headline">{{ place.name }}</h1>
|
||||||
{% if place.images.first.filename.hero.url %}
|
{% if place.images.first.filename.hero.url %}
|
||||||
<figure class="LP-PlaceDetail__Image">
|
<figure class="LP-PlaceDetail__Image">
|
||||||
<img src="{{ place.images.first.filename.hero.url }}" class="LP-Image" />
|
<img src="{{ place.images.first.filename.hero.url }}" class="LP-Image" />
|
||||||
</figure>
|
</figure>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="LP-PlaceDetail__Description">
|
<div class="LP-PlaceDetail__Description">
|
||||||
<p class="LP-Paragraph">{{ place.description }}</p>
|
<p class="LP-Paragraph">{{ place.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="LP-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__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">
|
{% url 'place_tag_submit' place_id=place.id as tag_submit_url%}
|
||||||
<input name='basic' value='tag1, tag2'>
|
{% include 'partials/tagging.html' with tag_list=place.tags.all url=tag_submit_url input_field=tagging_form.tag_list%}
|
||||||
<script>
|
|
||||||
// The DOM element you wish to replace with Tagify
|
|
||||||
var input = document.querySelector('input[name=basic]');
|
|
||||||
|
|
||||||
// initialize Tagify on the above input node reference
|
</section>
|
||||||
new Tagify(input, {
|
|
||||||
'whitelist': ['wurstwasser']
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="LP-Section">
|
<section class="LP-Section">
|
||||||
<h1 class="LP-Headline">Photoalben</h1>
|
<h1 class="LP-Headline">Map-Links</h1>
|
||||||
<div class="LP-LinkList">
|
{% include 'partials/osm_map.html' %}
|
||||||
<ul class="LP-LinkList__Container">
|
<div class="LP-LinkList">
|
||||||
{% for photo_album in place.photo_albums.all %}
|
<ul class="LP-LinkList__Container">
|
||||||
<li class="LP-LinkList__Item">
|
<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>
|
||||||
<a target="_blank" href="{{photo_album.url}}" class="LP-Link">
|
<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>
|
||||||
<span class="LP-Text">{{photo_album.label}}</span>
|
<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>
|
||||||
</a>
|
</ul>
|
||||||
{% if user == photo_album.submitted_by or user == place.submitted_by %}
|
</div>
|
||||||
<a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album">
|
</section>
|
||||||
<div class="RV-Iconized__Container RV-Iconized__Container--small">
|
|
||||||
{% icon 'trash' className="RV-Iconized__Icon" %}
|
<section class=" LP-Section">
|
||||||
</div>
|
<h1 class="LP-Headline">Photoalben</h1>
|
||||||
</a>
|
<div class="LP-LinkList">
|
||||||
{% endif %}
|
<ul class="LP-LinkList__Container">
|
||||||
</li>
|
{% for photo_album in place.photo_albums.all %}
|
||||||
{% endfor %}
|
<li class="LP-LinkList__Item">
|
||||||
<li class="LP-LinkList__Item">
|
<a target="_blank" href="{{photo_album.url}}" class="LP-Link">
|
||||||
<a href="{% url 'photo_album_create' place_id=place.id %}" class="LP-Link">
|
<span class="LP-Text">{{photo_album.label}}</span>
|
||||||
<div class="RV-Iconized__Container RV-Iconized__Container--small">
|
</a>
|
||||||
<svg class="RV-Iconized__Icon" version="1.1" id="Capa_1"
|
{% if user == photo_album.submitted_by or user == place.submitted_by %}
|
||||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
<a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album">
|
||||||
x="0px" y="0px" viewBox="0 0 512 512" xml:space="preserve">
|
<div class="RV-Iconized__Container RV-Iconized__Container--small">
|
||||||
<g>
|
{% icon 'trash' className="RV-Iconized__Icon" %}
|
||||||
<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
|
</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" />
|
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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="RV-Iconized__Text">Fotoalbum hinzufügen</span>
|
<span class="RV-Iconized__Text">Fotoalbum hinzufügen</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="LP-Section">
|
<section class="LP-Section">
|
||||||
<h1 class="LP-Headline">Bilder</h1>
|
<h1 class="LP-Headline">Bilder</h1>
|
||||||
<div class="LP-ImageGrid">
|
<div class="LP-ImageGrid">
|
||||||
<ul class="LP-ImageGrid__Container">
|
<ul class="LP-ImageGrid__Container">
|
||||||
{% for place_image in place.images.all %}
|
{% for place_image in place.images.all %}
|
||||||
<li class="LP-ImageGrid__Item">
|
<li class="LP-ImageGrid__Item">
|
||||||
<a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image"
|
<a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image" src="{{ place_image.filename.thumbnail.url }}"></a>
|
||||||
src="{{ place_image.filename.thumbnail.url }}"></a>
|
</li>
|
||||||
</li>
|
{% endfor %}
|
||||||
{% endfor %}
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
{% endblock maincontent %}
|
{% endblock maincontent %}
|
@ -1,11 +1,12 @@
|
|||||||
import json
|
import json, os
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Library, TemplateSyntaxError
|
from django.template import Library, TemplateSyntaxError
|
||||||
|
|
||||||
icons_json_path = getattr(settings, 'SVG_ICONS_SOURCE_FILE')
|
#icons_json_path = getattr(settings, 'SVG_ICONS_SOURCE_FILE')
|
||||||
|
icons_json_path = os.path.join(settings.BASE_DIR, 'lostplaces_app', 'static', 'icons', 'icons.icomoon.json')
|
||||||
icons_json = json.load(open(icons_json_path))
|
icons_json = json.load(open(icons_json_path))
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
@ -8,7 +8,8 @@ from .views import (
|
|||||||
PlaceUpdateView,
|
PlaceUpdateView,
|
||||||
PlaceDeleteView,
|
PlaceDeleteView,
|
||||||
PhotoAlbumCreateView,
|
PhotoAlbumCreateView,
|
||||||
PhotoAlbumDeleteView
|
PhotoAlbumDeleteView,
|
||||||
|
PlaceTagSubmitView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -20,5 +21,6 @@ urlpatterns = [
|
|||||||
path('photo_album/delete/<int:pk>', PhotoAlbumDeleteView.as_view(), name='photo_album_delete'),
|
path('photo_album/delete/<int:pk>', PhotoAlbumDeleteView.as_view(), name='photo_album_delete'),
|
||||||
path('place/update/<int:pk>/', PlaceUpdateView.as_view(), name='place_edit'),
|
path('place/update/<int:pk>/', PlaceUpdateView.as_view(), name='place_edit'),
|
||||||
path('place/delete/<int:pk>/', PlaceDeleteView.as_view(), name='place_delete'),
|
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'),
|
||||||
|
path('place/tag/<int:place_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
|
||||||
]
|
]
|
||||||
|
@ -1,245 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
''' Django views. '''
|
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
|
||||||
from django.views.generic import ListView
|
|
||||||
from django.views import View
|
|
||||||
from django.http import Http404
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
|
|
||||||
|
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
|
||||||
|
|
||||||
from .forms import (
|
|
||||||
ExplorerCreationForm,
|
|
||||||
PlaceForm,
|
|
||||||
PlaceImageCreateForm
|
|
||||||
)
|
|
||||||
from .models import Place, PlaceImage, Voucher, PhotoAlbum
|
|
||||||
|
|
||||||
# Create your views here.
|
|
||||||
|
|
||||||
# BaseView that checks if user is logged in.
|
|
||||||
class IsAuthenticated(LoginRequiredMixin, View):
|
|
||||||
redirect_field_name = 'redirect_to'
|
|
||||||
login_required_message = 'Please login to proceed'
|
|
||||||
|
|
||||||
def handle_no_permission(self):
|
|
||||||
messages.error(self.request, self.login_required_message)
|
|
||||||
return super().handle_no_permission()
|
|
||||||
|
|
||||||
# BaseView that checks if logged in user is submitter of place.
|
|
||||||
class IsPlaceSubmitter(UserPassesTestMixin, View):
|
|
||||||
place_submitter_error_message = None
|
|
||||||
|
|
||||||
def get_place(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_func(self):
|
|
||||||
""" Check if user is eligible to modify place. """
|
|
||||||
|
|
||||||
if not hasattr(self.request, 'user'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self.request.user.is_superuser:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check if currently logged in user was the submitter
|
|
||||||
place_obj = self.get_place()
|
|
||||||
|
|
||||||
if place_obj and hasattr(place_obj, 'submitted_by') and self.request.user == place_obj.submitted_by:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self.place_submitter_error_message:
|
|
||||||
messages.error(self.request, self.place_submitter_error_message)
|
|
||||||
return False
|
|
||||||
|
|
||||||
class SignUpView(SuccessMessageMixin, CreateView):
|
|
||||||
form_class = ExplorerCreationForm
|
|
||||||
success_url = reverse_lazy('login')
|
|
||||||
template_name = 'signup.html'
|
|
||||||
success_message = 'User created.'
|
|
||||||
|
|
||||||
class PlaceListView(IsAuthenticated, ListView):
|
|
||||||
paginate_by = 5
|
|
||||||
model = Place
|
|
||||||
template_name = 'place/place_list.html'
|
|
||||||
ordering = ['name']
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context['place_map_center'] = Place.average_latlon(context['place_list'])
|
|
||||||
return context
|
|
||||||
|
|
||||||
class PlaceDetailView(IsAuthenticated, View):
|
|
||||||
def get(self, request, pk):
|
|
||||||
place = Place.objects.get(pk=pk)
|
|
||||||
context = {
|
|
||||||
'place': place,
|
|
||||||
'place_list': [ place ],
|
|
||||||
'place_map_center': [ place.latitude, place.longitude ]
|
|
||||||
}
|
|
||||||
return render(request, 'place/place_detail.html', context)
|
|
||||||
|
|
||||||
class HomeView(View):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
place_list = Place.objects.all().order_by('-submitted_when')[:10]
|
|
||||||
place_map_center = Place.average_latlon(place_list)
|
|
||||||
context = {
|
|
||||||
'place_list': place_list,
|
|
||||||
'place_map_center': place_map_center
|
|
||||||
}
|
|
||||||
return render(request, 'home.html', context)
|
|
||||||
|
|
||||||
class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView):
|
|
||||||
template_name = 'place/place_update.html'
|
|
||||||
model = Place
|
|
||||||
form_class = PlaceForm
|
|
||||||
success_message = 'Successfully updated place.'
|
|
||||||
place_submitter_error_message = 'You do no have permissions to alter this place'
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk})
|
|
||||||
|
|
||||||
def get_place(self):
|
|
||||||
return self.get_object()
|
|
||||||
|
|
||||||
class PlaceCreateView(IsAuthenticated, View):
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
place_image_form = PlaceImageCreateForm()
|
|
||||||
place_form = PlaceForm()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'place_form': place_form,
|
|
||||||
'place_image_form': place_image_form
|
|
||||||
}
|
|
||||||
return render(request, 'place/place_create.html', context)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
place_form = PlaceForm(request.POST)
|
|
||||||
|
|
||||||
if place_form.is_valid():
|
|
||||||
submitter = request.user
|
|
||||||
place = place_form.save(commit=False)
|
|
||||||
# Save logged in user as "submitted_by"
|
|
||||||
place.submitted_by = submitter
|
|
||||||
place.save()
|
|
||||||
|
|
||||||
if request.FILES:
|
|
||||||
self._apply_multipart_image_upload(
|
|
||||||
files=request.FILES.getlist('filename'),
|
|
||||||
place=place,
|
|
||||||
submitter=submitter
|
|
||||||
)
|
|
||||||
|
|
||||||
kwargs_to_pass = {
|
|
||||||
'pk': place.pk
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.success(
|
|
||||||
self.request, 'Successfully created place.')
|
|
||||||
return redirect(reverse_lazy('place_detail', kwargs=kwargs_to_pass))
|
|
||||||
|
|
||||||
else:
|
|
||||||
context = {
|
|
||||||
'form': form_place
|
|
||||||
}
|
|
||||||
|
|
||||||
# Usually the browser should have checked the form before sending.
|
|
||||||
messages.error(
|
|
||||||
self.request, 'Please fill in all required fields.')
|
|
||||||
return render(request, 'place/place_create.html', context)
|
|
||||||
|
|
||||||
def _apply_multipart_image_upload(self, files, place, submitter):
|
|
||||||
for image in files:
|
|
||||||
place_image = PlaceImage.objects.create(
|
|
||||||
filename=image,
|
|
||||||
place=place,
|
|
||||||
submitted_by=submitter
|
|
||||||
)
|
|
||||||
place_image.save()
|
|
||||||
|
|
||||||
class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView):
|
|
||||||
template_name = 'place/place_delete.html'
|
|
||||||
model = Place
|
|
||||||
success_message = 'Successfully deleted place.'
|
|
||||||
success_url = reverse_lazy('place_list')
|
|
||||||
success_message = 'Place deleted'
|
|
||||||
place_submitter_error_message = 'You do no have permission to delete this place'
|
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
|
||||||
messages.success(self.request, self.success_message)
|
|
||||||
return super().delete(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_place(self):
|
|
||||||
return self.get_object()
|
|
||||||
|
|
||||||
class AlbumCreateView(IsAuthenticated, View):
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
url = request.GET['url']
|
|
||||||
place_id = request.GET['place_id']
|
|
||||||
place = Place.objects.get(pk=place_id)
|
|
||||||
photo_album = PhotoAlbum()
|
|
||||||
photo_album.url = url
|
|
||||||
photo_album.place = place
|
|
||||||
photo_album.submitted_by = request.user
|
|
||||||
photo_album.save()
|
|
||||||
print(photo_album)
|
|
||||||
return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id}))
|
|
||||||
|
|
||||||
class PhotoAlbumCreateView(IsAuthenticated, SuccessMessageMixin, CreateView):
|
|
||||||
model = PhotoAlbum
|
|
||||||
fields = ['url', 'label']
|
|
||||||
template_name = 'photo_album/photo_album_create.html'
|
|
||||||
success_message = 'Photo Album submitted'
|
|
||||||
|
|
||||||
def get(self, request, place_id, *args, **kwargs):
|
|
||||||
self.place = Place.objects.get(pk=place_id)
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def post(self, request, place_id, *args, **kwargs):
|
|
||||||
self.place = Place.objects.get(pk=place_id)
|
|
||||||
response = super().post(request, *args, **kwargs)
|
|
||||||
self.object.place = self.place
|
|
||||||
self.object.submitted_by = request.user
|
|
||||||
self.object.save()
|
|
||||||
return response
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context['place'] = self.place
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
return reverse_lazy('place_detail', kwargs={'pk': self.place.id})
|
|
||||||
|
|
||||||
class PhotoAlbumDeleteView(IsAuthenticated, IsPlaceSubmitter, SingleObjectMixin, View):
|
|
||||||
model = PhotoAlbum
|
|
||||||
pk_url_kwarg = 'pk'
|
|
||||||
success_message = 'Photo Album deleted'
|
|
||||||
|
|
||||||
def get_place(self):
|
|
||||||
place_id = self.get_object().place.id
|
|
||||||
return Place.objects.get(pk=place_id)
|
|
||||||
|
|
||||||
def test_func(self):
|
|
||||||
can_edit_place = super().test_func()
|
|
||||||
if can_edit_place:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self.get_object().submitted_by == self.request.user:
|
|
||||||
return True
|
|
||||||
|
|
||||||
messages.error(self.request, 'You do not have permissions to alter this photo album')
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
place_id = self.get_object().place.id
|
|
||||||
self.get_object().delete()
|
|
||||||
messages.success(self.request, self.success_message)
|
|
||||||
return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id}))
|
|
3
lostplaces/lostplaces_app/views/__init__.py
Normal file
3
lostplaces/lostplaces_app/views/__init__.py
Normal 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 *
|
98
lostplaces/lostplaces_app/views/base_views.py
Normal file
98
lostplaces/lostplaces_app/views/base_views.py
Normal 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}))
|
123
lostplaces/lostplaces_app/views/place_views.py
Normal file
123
lostplaces/lostplaces_app/views/place_views.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
from django.views import View
|
||||||
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
from django.views.generic import ListView
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
from lostplaces_app.models import Place, PlaceImage
|
||||||
|
from lostplaces_app.views import IsAuthenticated, IsPlaceSubmitter
|
||||||
|
from lostplaces_app.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm
|
||||||
|
|
||||||
|
from taggit.models import Tag
|
||||||
|
|
||||||
|
class PlaceListView(IsAuthenticated, ListView):
|
||||||
|
paginate_by = 5
|
||||||
|
model = Place
|
||||||
|
template_name = 'place/place_list.html'
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['place_map_center'] = Place.average_latlon(context['place_list'])
|
||||||
|
return context
|
||||||
|
|
||||||
|
class PlaceDetailView(IsAuthenticated, View):
|
||||||
|
def get(self, request, pk):
|
||||||
|
place = Place.objects.get(pk=pk)
|
||||||
|
context = {
|
||||||
|
'place': place,
|
||||||
|
'place_list': [ place ],
|
||||||
|
'place_map_center': [ place.latitude, place.longitude ],
|
||||||
|
'tagging_form': TagSubmitForm(),
|
||||||
|
'all_tags': Tag.objects.all()
|
||||||
|
}
|
||||||
|
return render(request, 'place/place_detail.html', context)
|
||||||
|
|
||||||
|
class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView):
|
||||||
|
template_name = 'place/place_update.html'
|
||||||
|
model = Place
|
||||||
|
form_class = PlaceForm
|
||||||
|
success_message = 'Successfully updated place.'
|
||||||
|
place_submitter_error_message = 'You do no have permissions to alter this place'
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk})
|
||||||
|
|
||||||
|
def get_place(self):
|
||||||
|
return self.get_object()
|
||||||
|
|
||||||
|
class PlaceCreateView(IsAuthenticated, View):
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
place_image_form = PlaceImageCreateForm()
|
||||||
|
place_form = PlaceForm()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'place_form': place_form,
|
||||||
|
'place_image_form': place_image_form
|
||||||
|
}
|
||||||
|
return render(request, 'place/place_create.html', context)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
place_form = PlaceForm(request.POST)
|
||||||
|
|
||||||
|
if place_form.is_valid():
|
||||||
|
submitter = request.user
|
||||||
|
place = place_form.save(commit=False)
|
||||||
|
# Save logged in user as "submitted_by"
|
||||||
|
place.submitted_by = submitter
|
||||||
|
place.save()
|
||||||
|
|
||||||
|
if request.FILES:
|
||||||
|
self._apply_multipart_image_upload(
|
||||||
|
files=request.FILES.getlist('filename'),
|
||||||
|
place=place,
|
||||||
|
submitter=submitter
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs_to_pass = {
|
||||||
|
'pk': place.pk
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
self.request, 'Successfully created place.')
|
||||||
|
return redirect(reverse_lazy('place_detail', kwargs=kwargs_to_pass))
|
||||||
|
|
||||||
|
else:
|
||||||
|
context = {
|
||||||
|
'form': form_place
|
||||||
|
}
|
||||||
|
|
||||||
|
# Usually the browser should have checked the form before sending.
|
||||||
|
messages.error(
|
||||||
|
self.request, 'Please fill in all required fields.')
|
||||||
|
return render(request, 'place/place_create.html', context)
|
||||||
|
|
||||||
|
def _apply_multipart_image_upload(self, files, place, submitter):
|
||||||
|
for image in files:
|
||||||
|
place_image = PlaceImage.objects.create(
|
||||||
|
filename=image,
|
||||||
|
place=place,
|
||||||
|
submitted_by=submitter
|
||||||
|
)
|
||||||
|
place_image.save()
|
||||||
|
|
||||||
|
class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView):
|
||||||
|
template_name = 'place/place_delete.html'
|
||||||
|
model = Place
|
||||||
|
success_message = 'Successfully deleted place.'
|
||||||
|
success_url = reverse_lazy('place_list')
|
||||||
|
success_message = 'Place deleted'
|
||||||
|
place_submitter_error_message = 'You do no have permission to delete this place'
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
messages.success(self.request, self.success_message)
|
||||||
|
return super().delete(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_place(self):
|
||||||
|
return self.get_object()
|
58
lostplaces/lostplaces_app/views/views.py
Normal file
58
lostplaces/lostplaces_app/views/views.py
Normal 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}))
|
Loading…
Reference in New Issue
Block a user