Compare commits
193 Commits
feature/au
...
master
Author | SHA1 | Date | |
---|---|---|---|
f078afcdcd | |||
163e9beb51 | |||
6ba225bee3 | |||
a4f80820c9 | |||
0f7b799c11 | |||
b23dc8a627 | |||
3e2ab1e12d | |||
78efe9bebb | |||
1de203ad40 | |||
399fa70ab6 | |||
c6a28c7b66 | |||
9980a2d190 | |||
1bc283bd8d | |||
4c43f87560 | |||
c8399b9ce4 | |||
f7dab77229 | |||
8d96214a92 | |||
6678f8ff02 | |||
4cd0635d5f | |||
e11151858b | |||
84b01f3a9a | |||
3f4bf9475c | |||
b607335299 | |||
b1cd8fd0cc | |||
a7a3ddfe63 | |||
1c6366fb43 | |||
565a70bbac | |||
749aa61828 | |||
e2754dc56c | |||
|
fefc38fa9c | ||
|
58ab70ba42 | ||
|
c63b0ebb87 | ||
|
0852e35b57 | ||
e52247d80b | |||
2ac90bd35c | |||
3685db6892 | |||
d5789cef75 | |||
5fca4dc52a | |||
cbf45af5f5 | |||
244b4d2917 | |||
d8b0ce1ac2 | |||
8da6964b3c | |||
ccd701ea60 | |||
170971c44b | |||
7196820122 | |||
a02d4f5e86 | |||
a7043651e0 | |||
bcf3377190 | |||
ceabe3f6f8 | |||
6b33d20f90 | |||
1d787a3b1d | |||
1960bf3792 | |||
0c3898163e | |||
2c6676d2c9 | |||
52f9df8b2a | |||
ab669b9f5e | |||
0fe98d4f8f | |||
51addd8fbb | |||
29d2813fcc | |||
cc9343270a | |||
4f465b3162 | |||
b0bf40aa0b | |||
822a536ebe | |||
5b9672e21e | |||
0a5d22d4d9 | |||
9c86eab14c | |||
29d067002e | |||
f5f5980f9b | |||
10c08a2f5e | |||
50f60be10a | |||
8b32b56dd9 | |||
74d842a668 | |||
2ac39f719f | |||
9b6121e448 | |||
a139594863 | |||
f2eb048f0b | |||
120e63808a | |||
201ef73639 | |||
af7afd0311 | |||
cf6346fdf0 | |||
d641d7b44d | |||
d35cb8131a | |||
ceeae9e8c9 | |||
092b91825f | |||
52271811ad | |||
fb1237f111 | |||
bae4e13137 | |||
b752454233 | |||
36eb9566d9 | |||
b876435d8c | |||
224eb87d76 | |||
a78f123d1f | |||
a22c988e94 | |||
7f2283325c | |||
6b7c71ef30 | |||
067bf36118 | |||
9eef044af9 | |||
a4780a22f2 | |||
947f6520aa | |||
0d1d829abe | |||
59cfa82ce9 | |||
d47c85795c | |||
8e80614eee | |||
6a1e2d975b | |||
c5f6355f19 | |||
dac63e99ba | |||
9f369456d5 | |||
011a58f6b3 | |||
67ce6cb50b | |||
5df7cc5ec6 | |||
e242dc4add | |||
86cc7f23fe | |||
f73fa616d0 | |||
629a609ff5 | |||
04fe93c7bb | |||
3eaa186b66 | |||
b42be489dc | |||
cc59254ba4 | |||
67a6517716 | |||
0f5474c2d3 | |||
17e71b71d4 | |||
b1aa4473e9 | |||
871acd1dce | |||
14ca45d111 | |||
c71fe4cf2d | |||
7359bf5fab | |||
4f0182fc3e | |||
30f259fb4d | |||
53f89caef5 | |||
3855fb28d7 | |||
3a20a60f05 | |||
648264c9fc | |||
dc79b9d05c | |||
a7c8848fd6 | |||
b845cc054a | |||
07ea2f164c | |||
b0396f5223 | |||
88bffbef8e | |||
2022a924c4 | |||
d5827b2006 | |||
a9013d9673 | |||
0c38ca9a15 | |||
734d09df90 | |||
e601b9bf6b | |||
6f6bd6376d | |||
dc2a703c39 | |||
063777ece4 | |||
4675e5814f | |||
35e0f912fe | |||
07fe1bc3ca | |||
4a43a4bf37 | |||
61cf148417 | |||
d547ee9db3 | |||
c7b699f615 | |||
86f95a5dd0 | |||
916c4b80f7 | |||
b31dc9fc5f | |||
8f048369bf | |||
f974469996 | |||
dbbd7b0802 | |||
d04e986419 | |||
cbbda88850 | |||
981c440ce3 | |||
c7368f5c44 | |||
abca946883 | |||
196fb02436 | |||
9be993d9ba | |||
20dd880a11 | |||
946c3091c0 | |||
4bacd1a28d | |||
086789aa88 | |||
3c5b447d4c | |||
cfbb876d2c | |||
4e0bfef483 | |||
ccdb49c8ea | |||
90dd281f7c | |||
d9df6e67a8 | |||
cd20e730ba | |||
c613d15e89 | |||
2380398fa9 | |||
4c5b9c7648 | |||
91a3f3171d | |||
01f7dad37c | |||
2bfdf5c172 | |||
15f6a30cd6 | |||
812c921a42 | |||
dafe7eee34 | |||
4e366d94c9 | |||
773390b09f | |||
ac0121d96e | |||
a0c4f2cad3 | |||
66bd9f648b | |||
485eb7b886 |
1
.gitignore
vendored
@ -79,7 +79,6 @@ Pipfile.lock
|
||||
__pypackages__/
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
|
42
Jenkinsfile
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'kennethreitz/pipenv'
|
||||
args '--user root --privileged'
|
||||
}
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('setup') {
|
||||
steps {
|
||||
script {
|
||||
sh 'pipenv install'
|
||||
sh 'pipenv install --dev'
|
||||
sh 'pip install setuptools'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('migration') {
|
||||
steps {
|
||||
script {
|
||||
sh 'pipenv run makemigrations'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('build') {
|
||||
steps {
|
||||
script {
|
||||
sh 'pipenv run build'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: 'dist/*.whl', fingerprint: false
|
||||
}
|
||||
}
|
||||
}
|
10
Pipfile
@ -12,6 +12,9 @@ wheel = "*"
|
||||
twine = "*"
|
||||
pandoc = "*"
|
||||
pylint-django = "*"
|
||||
setuptools = "*"
|
||||
django-nose = "*"
|
||||
invoke = "*"
|
||||
|
||||
[packages]
|
||||
django = "*"
|
||||
@ -25,3 +28,10 @@ test = "django_lostplaces/manage.py test lostplaces"
|
||||
server = "django_lostplaces/manage.py runserver --ipv6"
|
||||
dbshell = "django_lostplaces/manage.py dbshell"
|
||||
showmigrations = "django_lostplaces/manage.py showmigrations"
|
||||
makemigrations = "django_lostplaces/manage.py makemigrations --no-input"
|
||||
migrate = "django_lostplaces/manage.py migrate"
|
||||
build = "django_lostplaces/setup.py bdist_wheel --universal"
|
||||
createsuperuser = "django_lostplaces/manage.py createsuperuser --noinput --username admin --email admin@example.org"
|
||||
createsuperuser_prompt = "django_lostplaces/manage.py createsuperuser"
|
||||
quickstart = "invoke quickstart"
|
||||
security = "pipenv check"
|
28
Readme.md
@ -6,6 +6,9 @@ The software is currently in early development status, neither scope, datamodel(
|
||||
|
||||
We value privacy as a whole, all resources the frontend requires will be shipped with lostplace's distribution. We also try to minimize the use of JavaScript as far as we can and try to offer JS-less alternatives where we can.
|
||||
|
||||
## Contact
|
||||
If you run into any issues, have any questions or If you are interested in this project in general, feel free to get in touch with us via [reverend@reverend2048.de](mailto:reverend@reverend2048.de), we do speak English and German.
|
||||
|
||||
## Features
|
||||
- Manage lost places with lots of useful information.
|
||||
- OSM-Maps
|
||||
@ -22,7 +25,7 @@ Right now it depends on the following non-core Python 3 libraries. These can be
|
||||
* [django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks) Tweak the form field rendering in templates, not in python-level form definitions.
|
||||
* [django-taggit](https://github.com/jazzband/django-taggit) A simpler approach to tagging with Django.
|
||||
|
||||
### Bundled Dependencies
|
||||
### Bundled Dependencies
|
||||
We also leverage some other great OpenSource projects' code. We bundle those in the distribution to obsolete the need to pull those files from monitored CDNs.
|
||||
|
||||
* [OpenLayers](https://openlayers.org/) [6.4.3] OpenLayers makes it easy to put a dynamic map in any web page. It can display map tiles, vector data and markers loaded from any source.
|
||||
@ -32,7 +35,7 @@ Right now it depends on the following non-core Python 3 libraries. These can be
|
||||
|
||||
# Installing a development instance
|
||||
## Clone the repository
|
||||
`git clone https://git.mowoe.com/reverend/lostplaces-backend.git`
|
||||
`git clone https://git.commander1024.de/Commander1024/lostplaces-backend`
|
||||
## 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](https://docs.pipenv.org/) virtual environment. The repo provides a Pipfile for easy dependency management that does not mess with your system.
|
||||
@ -62,6 +65,21 @@ Visit: [admin](http://localhost:8000/admin) for administrative backend or
|
||||
|
||||
Happy developing ;-)
|
||||
|
||||
# Pipenv Scripts
|
||||
This project comes with a bunch of convinient scripts, like:
|
||||
|Script|Description|
|
||||
|---|---|
|
||||
|test|Runs the tests|
|
||||
|server|Starts a **development** server|
|
||||
|dbshell|Opens a shell session in the database|
|
||||
|showmigrations|Lists all Migrations|
|
||||
|makemigrations|Creates a migration|
|
||||
|migrate|Applies unapplied migrations|
|
||||
|build|Builds this project into a wheel file|
|
||||
|createsuperuser|Creates a superuser with the username **admin** and the password **develop**. This is for development and demo instances only!
|
||||
|quickstart|Runs *migrate*, *createsuperuser* and *server*|
|
||||
|
||||
|
||||
# Installing a productive instance
|
||||
|
||||
Currently there are two ways to deploy the lostplaces project:
|
||||
@ -88,7 +106,9 @@ Run `django_lostplaces/managy.py collectstatic` and you should be ready to go.
|
||||
|
||||
If you haven't already setup a django instance, see [django's documentation](https://docs.djangoproject.com/en/3.1/topics/install/).
|
||||
|
||||
After that, download the desired release (probably the latest one) [from the releases page](https://git.mowoe.com/reverend/lostplaces-backend/releases) and install it using `pip install --user name-of-the-file.tar.gz`
|
||||
Afterwards, either
|
||||
- download the desired release [from the releases page](https://git.mowoe.com/reverend/lostplaces-backend/releases) and install it using `pip install --user name-of-the-file.tar.gz`
|
||||
- or install the latest release using `pip install django-lostplaces`
|
||||
|
||||
*Note: You can run pip install without the --user flag, which will require root privileges and introduces potential security issues.*
|
||||
|
||||
@ -143,4 +163,4 @@ Before making the django instance public, you should tweak the config `settings.
|
||||
4. Set a new (random) SECRET_KEY in settings.py, e. g.: `base64 /dev/urandom | head -c50`
|
||||
|
||||
|
||||
Run `django_lostplaces/manage.py collectstatic` you should be ready to go.
|
||||
Run `django_lostplaces/manage.py collectstatic` you should be ready to go.
|
@ -1,3 +1,4 @@
|
||||
exclude
|
||||
include LICENSE
|
||||
include Readme.rst
|
||||
include Pipfile
|
@ -49,7 +49,15 @@ INSTALLED_APPS = [
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles'
|
||||
'django.contrib.staticfiles',
|
||||
'django_nose'
|
||||
]
|
||||
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
NOSE_ARGS = [
|
||||
'--with-coverage',
|
||||
'--cover-package=lostplaces',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -28,6 +28,6 @@ from lostplaces.views import SignUpView
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('signup/', SignUpView.as_view(), name='signup'),
|
||||
path('explorers/', include('django.contrib.auth.urls')),
|
||||
path('explorer/', include('django.contrib.auth.urls')),
|
||||
path('', include('lostplaces.urls')),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
@ -5,7 +5,7 @@ from django.conf import settings
|
||||
|
||||
settings.THUMBNAIL_ALIASES = {
|
||||
'': {
|
||||
'thumbnail': {'size': (300, 200), 'sharpen': True, 'crop': True},
|
||||
'thumbnail': {'size': (300, 200), 'sharpen': True, 'crop': True, 'upscale': True},
|
||||
'hero': {'size': (700, 466), 'sharpen': True, 'crop': True},
|
||||
'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False},
|
||||
},
|
||||
|
@ -9,8 +9,6 @@ from django.contrib.auth.admin import UserAdmin
|
||||
from django.utils import timezone
|
||||
from lostplaces.models import *
|
||||
|
||||
from lostplaces.forms import ExplorerCreationForm, ExplorerChangeForm
|
||||
|
||||
# Register your models here.
|
||||
|
||||
class VoucherAdmin(admin.ModelAdmin):
|
||||
@ -37,3 +35,4 @@ admin.site.register(Voucher, VoucherAdmin)
|
||||
admin.site.register(Place, PlacesAdmin)
|
||||
admin.site.register(PlaceImage, PlaceImagesAdmin)
|
||||
admin.site.register(PhotoAlbum, PhotoAlbumsAdmin)
|
||||
admin.site.register(PlaceVoting)
|
||||
|
@ -4,4 +4,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class LostplacesAppConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.AutoField'
|
||||
name = 'lostplaces'
|
||||
|
25
django_lostplaces/lostplaces/common.py
Normal file
@ -0,0 +1,25 @@
|
||||
from django.shortcuts import redirect
|
||||
|
||||
def get_all_subclasses(cls):
|
||||
'''
|
||||
Gets all subclasses recursively, does not contain
|
||||
abstract classes
|
||||
'''
|
||||
subclass_list = []
|
||||
for subclass in cls.__subclasses__():
|
||||
if not subclass._meta.abstract:
|
||||
subclass_list.append(subclass)
|
||||
subclass_list += get_all_subclasses(subclass)
|
||||
return subclass_list
|
||||
|
||||
def redirect_referer_or(request, url='/'):
|
||||
'''
|
||||
Returns a django redirect to the requests referer,
|
||||
if there is no referer the redirect will poin to the given url
|
||||
Default url is /
|
||||
'''
|
||||
referer = request.META.get('HTTP_REFERER')
|
||||
if referer is not None:
|
||||
return redirect(referer)
|
||||
else:
|
||||
return redirect(url)
|
@ -8,19 +8,21 @@ from django.db import models
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from lostplaces.models import Place, PlaceImage, Voucher
|
||||
|
||||
class ExplorerCreationForm(UserCreationForm):
|
||||
from lostplaces import widgets
|
||||
from lostplaces.models import Place, PlaceImage, Voucher, Explorer
|
||||
|
||||
class SignupVoucherForm(UserCreationForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email')
|
||||
fields = ('username', 'email', 'first_name', 'last_name')
|
||||
voucher = forms.CharField(
|
||||
max_length=30,
|
||||
help_text=_('The Voucher you got from an administrator')
|
||||
)
|
||||
|
||||
def is_valid(self):
|
||||
super().is_valid()
|
||||
super_result = super().is_valid()
|
||||
submitted_voucher = self.cleaned_data.get('voucher')
|
||||
try:
|
||||
fetched_voucher = Voucher.objects.get(code=submitted_voucher)
|
||||
@ -33,19 +35,59 @@ class ExplorerCreationForm(UserCreationForm):
|
||||
return False
|
||||
|
||||
fetched_voucher.delete()
|
||||
return True
|
||||
return super_result
|
||||
|
||||
class ExplorerChangeForm(UserChangeForm):
|
||||
class ExplorerUserChangeForm(UserChangeForm):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email')
|
||||
fields = [ 'username', 'first_name', 'last_name', 'email' ]
|
||||
''' Hide password hint.'''
|
||||
password = None
|
||||
|
||||
''' Display username, but display it non-editable and not required. '''
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['username'].required = False
|
||||
self.fields['username'].help_text = None
|
||||
self.fields['username'].widget.attrs['disabled'] = 'disabled'
|
||||
|
||||
def clean_username(self):
|
||||
# As shown in the above answer.
|
||||
instance = getattr(self, 'instance', None)
|
||||
if instance:
|
||||
return instance.username
|
||||
else:
|
||||
return self.cleaned_data.get('username', None)
|
||||
|
||||
class ExplorerChangeForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Explorer
|
||||
fields = '__all__'
|
||||
exclude = ['user', 'favorite_places', 'level']
|
||||
|
||||
class PlaceForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Place
|
||||
fields = '__all__'
|
||||
exclude = ['submitted_by']
|
||||
|
||||
exclude = ['submitted_by', 'level']
|
||||
widgets = {
|
||||
'hero': widgets.SelectContent()
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'instance' in kwargs:
|
||||
self.fields['hero'].queryset = PlaceImage.objects.filter(place=kwargs['instance'])
|
||||
self.fields['hero'].widget.attrs['item_template'] = 'partials/select_place_image_item.html'
|
||||
|
||||
latitude = forms.DecimalField(
|
||||
widget=forms.NumberInput(attrs={'min':-90,'max': 90,'type': 'number', 'step': 'any'})
|
||||
)
|
||||
|
||||
longitude = forms.DecimalField(
|
||||
widget=forms.NumberInput(attrs={'min':-180,'max': 180,'type': 'number', 'step': 'any'})
|
||||
)
|
||||
|
||||
class PlaceImageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = PlaceImage
|
||||
@ -58,9 +100,24 @@ class PlaceImageForm(forms.ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['filename'].required = False
|
||||
|
||||
class PlaceSetHeroForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Place
|
||||
fields = ['hero']
|
||||
widgets = {
|
||||
'hero': widgets.SelectContent()
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['hero'].queryset = PlaceImage.objects.filter(place=kwargs['instance'])
|
||||
self.fields['hero'].widget.attrs['item_template'] = 'partials/select_place_image_item.html'
|
||||
|
||||
|
||||
|
||||
class TagSubmitForm(forms.Form):
|
||||
tag_list = forms.CharField(
|
||||
tag_list = forms.CharField(
|
||||
max_length=500,
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'autocomplete':'off'})
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-10-11 07:48+0200\n"
|
||||
"POT-Creation-Date: 2020-12-25 16:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Commander1024 <commander@commander1024.de>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -30,7 +30,7 @@ msgstr "Ungültiger Voucher"
|
||||
msgid "Expired voucher"
|
||||
msgstr "Abgelaufener Voucher"
|
||||
|
||||
#: models/abstract_models.py:29
|
||||
#: models/abstract_models.py:29 templates/explorer/profile.html:31
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@ -50,6 +50,22 @@ msgstr "Längengrad"
|
||||
msgid "Longitude in decimal format: e. g. 2.17403"
|
||||
msgstr "Breitengrad in dezimaler Form: z. B. 7.4840155"
|
||||
|
||||
#: models/abstract_models.py:59
|
||||
msgid "Submission date"
|
||||
msgstr "Einreichungsdatum"
|
||||
|
||||
#: models/abstract_models.py:67
|
||||
msgid "Submitter"
|
||||
msgstr "Einreicher"
|
||||
|
||||
#: models/abstract_models.py:79
|
||||
msgid "Creation date"
|
||||
msgstr "Erstellungsdatum"
|
||||
|
||||
#: models/abstract_models.py:82
|
||||
msgid "Expiration date"
|
||||
msgstr "Ablaufdatum"
|
||||
|
||||
#: models/external_links.py:13
|
||||
msgid "URL"
|
||||
msgstr "Adresse (URL)"
|
||||
@ -58,6 +74,22 @@ msgstr "Adresse (URL)"
|
||||
msgid "link text"
|
||||
msgstr "Linktext"
|
||||
|
||||
#: models/models.py:47
|
||||
msgid "Biography"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: models/models.py:48
|
||||
msgid "Describe yourself, your preferences, etc. in a few sentences."
|
||||
msgstr "Beschreibe Dich selbst, Deine Vorlieben, usw. in ein paar Sätzen."
|
||||
|
||||
#: models/models.py:56
|
||||
msgid "Profile image"
|
||||
msgstr "Profilbild"
|
||||
|
||||
#: models/models.py:57
|
||||
msgid "Optional profile image for display in Explorer profile"
|
||||
msgstr "Optionales Profilbild zur Anzeige im Explorerprofil"
|
||||
|
||||
#: models/place.py:21
|
||||
msgid "Location"
|
||||
msgstr "Ort"
|
||||
@ -94,39 +126,93 @@ msgstr "Du wirst in 5 Sekunden weitergeleitet"
|
||||
msgid "Go Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: templates/explorer/profile.html:41
|
||||
msgid "E-mail"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: templates/explorer/profile.html:52
|
||||
msgid "Joined"
|
||||
msgstr "Beigetreten"
|
||||
|
||||
#: templates/explorer/profile.html:60
|
||||
#, fuzzy
|
||||
#| msgid "All Places"
|
||||
msgid "Places"
|
||||
msgstr "Alle Places"
|
||||
|
||||
#: templates/explorer/profile.html:68
|
||||
msgid "Place assets"
|
||||
msgstr ""
|
||||
|
||||
#: templates/explorer/profile.html:76
|
||||
#, fuzzy
|
||||
#| msgid "Edit Explorer profile"
|
||||
msgid "Edit Profile"
|
||||
msgstr "Explorerprofil bearbeiten"
|
||||
|
||||
#: templates/explorer/profile.html:87
|
||||
msgid "Favorite places"
|
||||
msgstr "Favoriten"
|
||||
|
||||
#: templates/explorer/profile.html:103
|
||||
msgid "Places submitted by"
|
||||
msgstr "Places hinzugefügt von"
|
||||
|
||||
#: templates/explorer/profile.html:118
|
||||
msgid "Images submitted by"
|
||||
msgstr "Bilder hinzugefügt von"
|
||||
|
||||
#: templates/explorer/profile.html:140
|
||||
msgid "Photo albums submitted by"
|
||||
msgstr "Fotoalben hinzugefügt von"
|
||||
|
||||
#: templates/explorer/profile_update.html:6
|
||||
#: templates/explorer/profile_update.html:12
|
||||
msgid "Edit Explorer profile"
|
||||
msgstr "Explorerprofil bearbeiten"
|
||||
|
||||
#: templates/explorer/profile_update.html:48
|
||||
#: templates/place/place_update.html:42
|
||||
msgid "Update"
|
||||
msgstr "Aktualisieren"
|
||||
|
||||
#: templates/global.html:32
|
||||
msgid "Logout"
|
||||
msgstr "Abmelden"
|
||||
msgstr "Ausloggen"
|
||||
|
||||
#: templates/global.html:34
|
||||
#: templates/global.html:33
|
||||
msgid "Profile"
|
||||
msgstr "Profil"
|
||||
|
||||
#: templates/global.html:35
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: templates/global.html:39 templates/registration/login.html:4
|
||||
#: templates/global.html:40 templates/registration/login.html:4
|
||||
#: templates/registration/login.html:10 templates/registration/login.html:23
|
||||
msgid "Login"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: templates/global.html:40 templates/registration/login.html:29
|
||||
#: templates/signup.html:12 templates/signup.html:41
|
||||
msgid "Sign Up"
|
||||
#: templates/global.html:41 templates/registration/login.html:29
|
||||
#: templates/signup.html:6 templates/signup.html:12 templates/signup.html:49
|
||||
msgid "Sign up"
|
||||
msgstr "Registrieren"
|
||||
|
||||
#: templates/global.html:50 templates/home.html:10
|
||||
#: templates/global.html:51 templates/home.html:10
|
||||
msgid "Home"
|
||||
msgstr "Startseite"
|
||||
|
||||
#: templates/global.html:51
|
||||
#: templates/global.html:52
|
||||
msgid "UrBex Codex"
|
||||
msgstr "UrBex Codex"
|
||||
|
||||
#: templates/global.html:56 templates/place/place_create.html:5
|
||||
#: templates/global.html:57 templates/place/place_create.html:5
|
||||
#: templates/place/place_create.html:10
|
||||
msgid "Create Place"
|
||||
msgid "Create place"
|
||||
msgstr "Place erstellen"
|
||||
|
||||
#: templates/global.html:57
|
||||
msgid "All Places"
|
||||
#: templates/global.html:58
|
||||
msgid "All places"
|
||||
msgstr "Alle Places"
|
||||
|
||||
#: templates/home.html:20 templates/home_unauth.html:20
|
||||
@ -178,6 +264,14 @@ msgstr "Abschicken"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: templates/partials/icons/place_favorite.html:6
|
||||
msgid "Remove from favorites"
|
||||
msgstr "Aus den Favoriten entfernen"
|
||||
|
||||
#: templates/partials/icons/place_favorite.html:10
|
||||
msgid "Save as favorite"
|
||||
msgstr "In den Favoriten speichern"
|
||||
|
||||
#: templates/partials/nav/footer.html:64
|
||||
msgid "Made by"
|
||||
msgstr "Erstellt von"
|
||||
@ -247,7 +341,8 @@ msgid "Submit a photo album"
|
||||
msgstr "Fotoalbum hinzufügen"
|
||||
|
||||
#: templates/photo_album/photo_album_create.html:8
|
||||
#: templates/place/place_detail.html:18
|
||||
#: templates/place/place_detail.html:18 templates/place/place_update.html:5
|
||||
#: templates/place/place_update.html:10
|
||||
msgid "Edit place"
|
||||
msgstr "Place bearbeiten"
|
||||
|
||||
@ -265,12 +360,13 @@ msgid "Create"
|
||||
msgstr "Erstellen"
|
||||
|
||||
#: templates/place/place_delete.html:5
|
||||
msgid "Lost Place Deletion"
|
||||
msgid "Delete lost place"
|
||||
msgstr "Lost Place löschen"
|
||||
|
||||
#: templates/place/place_delete.html:15
|
||||
msgid "Are you sure you want to delete"
|
||||
msgstr "Willst Du den Ort wirklich löschen: "
|
||||
#, python-format
|
||||
msgid "Are you sure you want to delete %(place_name)s?"
|
||||
msgstr "Bist Du sicher, dass Du %(place_name)s löschen möchtest?"
|
||||
|
||||
#: templates/place/place_delete.html:19
|
||||
msgid "Delete"
|
||||
@ -293,85 +389,84 @@ msgid "Images"
|
||||
msgstr "Bilder"
|
||||
|
||||
#: templates/place/place_list.html:10
|
||||
msgid "Lost Places"
|
||||
msgid "All Places"
|
||||
msgstr "Alle Places"
|
||||
|
||||
#: templates/place/place_list.html:16
|
||||
msgid "Listing our places"
|
||||
msgid "Our lost places"
|
||||
msgstr "Unsere Lost Places"
|
||||
|
||||
#: templates/place/place_update.html:5 templates/place/place_update.html:10
|
||||
msgid "Update place"
|
||||
msgstr "Place bearbeiten"
|
||||
|
||||
#: templates/place/place_update.html:42
|
||||
msgid "Update"
|
||||
msgstr "Aktualisieren"
|
||||
|
||||
#: templates/place_image/place_image_create.html:7
|
||||
msgid "Submit images to a place"
|
||||
msgstr "Bilder zu einem Place hinzufügen"
|
||||
|
||||
#: templates/registration/login.html:29
|
||||
msgid "Have No Account Yet?"
|
||||
msgid "No account, yet?"
|
||||
msgstr "Noch kein Konto?"
|
||||
|
||||
#: templates/signup.html:6
|
||||
msgid "Sign up"
|
||||
msgstr "Registrieren"
|
||||
|
||||
#: views/base_views.py:25
|
||||
msgid "Please login to proceed"
|
||||
msgstr "Bitte log Dich ein um fortzufahren"
|
||||
|
||||
#: views/explorer_views.py:78
|
||||
#, fuzzy
|
||||
#| msgid "Successfully updated place"
|
||||
msgid "Successfully updated Explorer profile"
|
||||
msgstr "Place erfolgreich aktualisiert"
|
||||
|
||||
#: views/explorer_views.py:84 views/place_views.py:105
|
||||
msgid "Please fill in all required fields."
|
||||
msgstr "Bitte füll alle benötigten Felder aus."
|
||||
|
||||
#: views/place_image_views.py:26
|
||||
msgid "Image(s) submitted successfully"
|
||||
msgstr "Bild(er) erfolgreich hinzugefügt"
|
||||
|
||||
#: views/place_image_views.py:41
|
||||
msgid "Image(s) deleted successfully"
|
||||
msgstr "Bild(er) erfolgreich gelöscht"
|
||||
msgid "Image deleted successfully"
|
||||
msgstr "Bild erfolgreich gelöscht"
|
||||
|
||||
#: views/place_image_views.py:42
|
||||
msgid "You are not allowed to delete this image"
|
||||
msgstr "Du darfst dieses Bild nicht löschen"
|
||||
|
||||
#: views/place_views.py:62
|
||||
#, fuzzy
|
||||
#| msgid "Successfully deleted place"
|
||||
msgid "Successfully updated place"
|
||||
msgstr "Place erfolgreich gelöscht"
|
||||
msgstr "Place erfolgreich aktualisiert"
|
||||
|
||||
#: views/place_views.py:63
|
||||
msgid "You do no have permissions to alter this place"
|
||||
msgstr "Du hast nicht die Berechtigung, diesen Place zu bearbeiten"
|
||||
msgid "You are not allowed to edit this place"
|
||||
msgstr "Du darfst diesen Place nicht bearbeiten"
|
||||
|
||||
#: views/place_views.py:97
|
||||
#, fuzzy
|
||||
#| msgid "Successfully deleted place"
|
||||
msgid "Successfully created place"
|
||||
msgstr "Place erfolgreich gelöscht"
|
||||
msgstr "Place erfolgreich erstellt"
|
||||
|
||||
#: views/place_views.py:112
|
||||
msgid "Successfully deleted place"
|
||||
msgstr "Place erfolgreich gelöscht"
|
||||
|
||||
#: views/place_views.py:114
|
||||
msgid "You do no have permission to delete this place"
|
||||
msgstr "Du hast nicht die Berechtigung, diesen Place zu löschen"
|
||||
msgid "You are not allowed to delete this place"
|
||||
msgstr "Du darfst diesen Place nicht löschen"
|
||||
|
||||
#: views/views.py:29
|
||||
msgid "User created"
|
||||
msgstr "User erstellt"
|
||||
msgstr "Benutzer erstellt"
|
||||
|
||||
#: views/views.py:54
|
||||
msgid "Photo Album submitted"
|
||||
msgstr "Fotoalbum hinzugefügt"
|
||||
msgid "Photo album link submitted"
|
||||
msgstr "Fotoalbum-Link hinzugefügt"
|
||||
|
||||
#: views/views.py:59
|
||||
msgid "Photo Album deleted"
|
||||
msgstr "Fotoalbum gelöscht"
|
||||
msgid "Photo album link deleted"
|
||||
msgstr "Fotoalbum-Link gelöscht"
|
||||
|
||||
#: views/views.py:60
|
||||
msgid "You do not have permissions to alter this photo album"
|
||||
msgstr "Du hast nicht die Berechtigung, diesen Place zu bearbeiten"
|
||||
msgid "You are not allowed to edit this photo album link"
|
||||
msgstr "Du darfst diesen Fotoalbum-Link nicht bearbeiten"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Filename(s)"
|
||||
#~ msgid "Filename"
|
||||
#~ msgstr "Dateiname(n)"
|
||||
|
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('submitted_when', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('filename', easy_thumbnails.fields.ThumbnailerImageField(upload_to=lostplaces.models.place.generate_image_upload_path)),
|
||||
('filename', easy_thumbnails.fields.ThumbnailerImageField(upload_to=lostplaces.models.place.generate_place_image_filename)),
|
||||
('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='placeimages', to='lostplaces.place')),
|
||||
('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='placeimages', to='lostplaces.explorer')),
|
||||
],
|
||||
|
@ -19,4 +19,4 @@ class Migration(migrations.Migration):
|
||||
migrations.DeleteModel(
|
||||
name='Expireable'
|
||||
)
|
||||
]
|
||||
]
|
@ -22,4 +22,4 @@ class Migration(migrations.Migration):
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
]
|
102
django_lostplaces/lostplaces/migrations/0004_release_0_1_3.py
Normal file
@ -0,0 +1,102 @@
|
||||
# Generated by Django 3.1.1 on 2020-12-19 13:53
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import easy_thumbnails.fields
|
||||
import lostplaces.models.place
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lostplaces', '0003_voucher'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='photoalbum',
|
||||
name='label',
|
||||
field=models.CharField(max_length=100, verbose_name='link text'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='photoalbum',
|
||||
name='submitted_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='photoalbums', to='lostplaces.explorer', verbose_name='Submitter'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='photoalbum',
|
||||
name='submitted_when',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='photoalbum',
|
||||
name='url',
|
||||
field=models.URLField(verbose_name='URL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='description',
|
||||
field=models.TextField(help_text="Description of the place: e.g. how to get there, where to be careful, the place's history...", verbose_name='Description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='latitude',
|
||||
field=models.FloatField(help_text='Latitude in decimal format: e. g. 41.40338', validators=[django.core.validators.MinValueValidator(-90), django.core.validators.MaxValueValidator(90)], verbose_name='Latitude'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='location',
|
||||
field=models.CharField(max_length=50, verbose_name='Location'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='longitude',
|
||||
field=models.FloatField(help_text='Longitude in decimal format: e. g. 2.17403', validators=[django.core.validators.MinValueValidator(-180), django.core.validators.MaxValueValidator(180)], verbose_name='Longitude'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='name',
|
||||
field=models.CharField(max_length=50, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='submitted_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='places', to='lostplaces.explorer', verbose_name='Submitter'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='place',
|
||||
name='submitted_when',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='placeimage',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, verbose_name='Description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='placeimage',
|
||||
name='filename',
|
||||
field=easy_thumbnails.fields.ThumbnailerImageField(help_text='Optional: One or more images to upload', upload_to=lostplaces.models.place.generate_image_upload_path, verbose_name='Filename(s)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='placeimage',
|
||||
name='submitted_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='placeimages', to='lostplaces.explorer', verbose_name='Submitter'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='placeimage',
|
||||
name='submitted_when',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='voucher',
|
||||
name='created_when',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Creation date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='voucher',
|
||||
name='expires_when',
|
||||
field=models.DateTimeField(verbose_name='Expiration date'),
|
||||
),
|
||||
]
|
@ -0,0 +1,85 @@
|
||||
# Generated by Django 3.2.10 on 2021-12-31 17:20
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import easy_thumbnails.fields
|
||||
import lostplaces.models.models
|
||||
import lostplaces.models.place
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lostplaces', '0004_release_0_1_3'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='explorer',
|
||||
name='bio',
|
||||
field=models.TextField(blank=True, help_text='Describe yourself, your preferences, etc. in a few sentences.', null=True, verbose_name='Biography / Description'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='explorer',
|
||||
name='favorite_places',
|
||||
field=models.ManyToManyField(blank=True, related_name='explorer_favorites', to='lostplaces.Place', verbose_name='Explorers favorite places'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='explorer',
|
||||
name='level',
|
||||
field=models.IntegerField(choices=[(1, 'Newbie'), (2, 'Scout'), (3, 'Explorer'), (4, 'Journalist'), (5, 'Housekeeper')], default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='explorer',
|
||||
name='profile_image',
|
||||
field=easy_thumbnails.fields.ThumbnailerImageField(blank=True, help_text='Optional profile image for display in Explorer profile', null=True, upload_to=lostplaces.models.generate_profile_image_filename, verbose_name='Profile image'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='explorer',
|
||||
name='visited_places',
|
||||
field=models.ManyToManyField(blank=True, related_name='explorer_visits', to='lostplaces.Place', verbose_name='Explorers visited places'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='place',
|
||||
name='hero',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='place_heros', to='lostplaces.placeimage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='place',
|
||||
name='level',
|
||||
field=models.IntegerField(choices=[(1, 'Ruin'), (2, 'Vandalized'), (3, 'Natures Treasure'), (4, 'Lost in History'), (5, 'Time Capsule')], default=5),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='placeimage',
|
||||
name='filename',
|
||||
field=easy_thumbnails.fields.ThumbnailerImageField(help_text='Optional: One or more images to upload', upload_to=lostplaces.models.place.generate_place_image_filename, verbose_name='Images'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PlaceVoting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('submitted_when', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date')),
|
||||
('created_when', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')),
|
||||
('expires_when', models.DateTimeField(verbose_name='Expiration date')),
|
||||
('vote', models.IntegerField(choices=[(1, 'Ruin'), (2, 'Vandalized'), (3, 'Natures Treasure'), (4, 'Lost in History'), (5, 'Time Capsule')])),
|
||||
('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='placevotings', to='lostplaces.place')),
|
||||
('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='placevotings', to='lostplaces.explorer', verbose_name='Submitter')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DummyAsset',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('submitted_when', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date')),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dummyassets', to='lostplaces.place')),
|
||||
('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dummyassets', to='lostplaces.explorer', verbose_name='Submitter')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -53,13 +53,18 @@ class Submittable(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
submitted_when = models.DateTimeField(auto_now_add=True, null=True)
|
||||
submitted_when = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
null=True,
|
||||
verbose_name=_('Submission date')
|
||||
)
|
||||
submitted_by = models.ForeignKey(
|
||||
'Explorer',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='%(class)ss'
|
||||
related_name='%(class)ss',
|
||||
verbose_name=_('Submitter')
|
||||
)
|
||||
|
||||
class Expireable(models.Model):
|
||||
@ -69,8 +74,13 @@ class Expireable(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
created_when = models.DateTimeField(auto_now_add=True)
|
||||
expires_when = models.DateTimeField()
|
||||
created_when = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name=_('Creation date')
|
||||
)
|
||||
expires_when = models.DateTimeField(
|
||||
verbose_name=_('Expiration date')
|
||||
)
|
||||
|
||||
@property
|
||||
def is_expired(self):
|
||||
|
@ -6,19 +6,42 @@
|
||||
database.
|
||||
'''
|
||||
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from lostplaces.models.abstract_models import Expireable
|
||||
from lostplaces.models.place import Place
|
||||
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
from easy_thumbnails.files import get_thumbnailer
|
||||
|
||||
def generate_profile_image_filename(instance, filename):
|
||||
"""
|
||||
Callback for generating filename for uploaded explorer profile images.
|
||||
Returns filename as: explorer_pk-username.jpg
|
||||
"""
|
||||
|
||||
return 'explorers/' + str(instance.user.pk) + '-' + str(instance.user.username) + '.' + filename.split('.')[-1]
|
||||
|
||||
EXPLORER_LEVELS = (
|
||||
(1, 'Newbie'),
|
||||
(2, 'Scout'),
|
||||
(3, 'Explorer'),
|
||||
(4, 'Journalist'),
|
||||
(5, 'Housekeeper')
|
||||
)
|
||||
|
||||
class Explorer(models.Model):
|
||||
"""
|
||||
Profile that is linked to the a User.
|
||||
Profile that is linked to the Django user.
|
||||
Every user has a profile.
|
||||
Provides additional attributes for user profile.
|
||||
"""
|
||||
|
||||
user = models.OneToOneField(
|
||||
@ -26,10 +49,54 @@ class Explorer(models.Model):
|
||||
on_delete=models.CASCADE,
|
||||
related_name='explorer'
|
||||
)
|
||||
bio = models.TextField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Biography / Description'),
|
||||
help_text=_('Describe yourself, your preferences, etc. in a few sentences.')
|
||||
)
|
||||
profile_image = ThumbnailerImageField(
|
||||
blank=True,
|
||||
null=True,
|
||||
upload_to=generate_profile_image_filename,
|
||||
resize_source=dict(size=(400, 400),
|
||||
sharpen=True),
|
||||
verbose_name=_('Profile image'),
|
||||
help_text=_('Optional profile image for display in Explorer profile')
|
||||
)
|
||||
favorite_places = models.ManyToManyField(
|
||||
Place,
|
||||
related_name='explorer_favorites',
|
||||
verbose_name='Explorers favorite places',
|
||||
blank=True
|
||||
)
|
||||
visited_places = models.ManyToManyField(
|
||||
Place,
|
||||
related_name='explorer_visits',
|
||||
verbose_name='Explorers visited places',
|
||||
blank=True
|
||||
)
|
||||
|
||||
level = models.IntegerField(
|
||||
default=1,
|
||||
choices=EXPLORER_LEVELS
|
||||
)
|
||||
|
||||
def get_places_eligible_to_see(self):
|
||||
if self.user.is_superuser:
|
||||
return Place.objects.all()
|
||||
return Place.objects.all().filter(level__lte=self.level) | self.places.all()
|
||||
|
||||
def is_eligible_to_see(self, place):
|
||||
return (
|
||||
self.user.is_superuser or
|
||||
place.submitted_by == self or
|
||||
place in self.get_places_eligible_to_see()
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
@ -39,13 +106,33 @@ def create_user_profile(sender, instance, created, **kwargs):
|
||||
def save_user_profile(sender, instance, **kwargs):
|
||||
instance.explorer.save()
|
||||
|
||||
@receiver(pre_save, sender=Explorer)
|
||||
def auto_delete_file_on_change(sender, instance, **kwargs):
|
||||
"""
|
||||
Deletes old file from filesystem
|
||||
when corresponding `Explorer` object is updated
|
||||
with new file.
|
||||
"""
|
||||
if not instance.pk:
|
||||
return False
|
||||
|
||||
try:
|
||||
old_file = Explorer.objects.get(pk=instance.pk).profile_image
|
||||
except Explorer.DoesNotExist:
|
||||
return False
|
||||
new_file = instance.profile_image
|
||||
if not old_file == new_file:
|
||||
old_file.delete(save=False)
|
||||
|
||||
class Voucher(Expireable):
|
||||
"""
|
||||
Vouchers are authorization to created_when = models.DateTimeField(auto_now_add=True)
|
||||
expires_when = models.DateTimeField()kens to allow the registration of new users.
|
||||
Vouchers are authorization tokens to allow the registration of new users.
|
||||
A voucher has a code, a creation and a deletion date, which are all
|
||||
positional. Creation date is being set automatically during voucher
|
||||
creation.
|
||||
|
||||
created_when = models.DateTimeField(auto_now_add=True)
|
||||
expires_when = models.DateTimeField()
|
||||
"""
|
||||
|
||||
code = models.CharField(unique=True, max_length=30)
|
||||
@ -56,4 +143,3 @@ class Voucher(Expireable):
|
||||
|
||||
def __str__(self):
|
||||
return "Voucher " + str(self.code)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os
|
||||
from math import floor
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
@ -6,11 +7,19 @@ from django.dispatch import receiver
|
||||
from django.db.models.signals import post_delete, pre_save
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from lostplaces.models.abstract_models import Submittable, Taggable, Mapable
|
||||
from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable
|
||||
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
from easy_thumbnails.files import get_thumbnailer
|
||||
|
||||
PLACE_LEVELS = (
|
||||
(1, 'Ruin'),
|
||||
(2, 'Vandalized'),
|
||||
(3, 'Natures Treasure'),
|
||||
(4, 'Lost in History'),
|
||||
(5, 'Time Capsule')
|
||||
)
|
||||
|
||||
class Place(Submittable, Taggable, Mapable):
|
||||
"""
|
||||
Place defines a lost place (location, name, description etc.).
|
||||
@ -25,8 +34,41 @@ class Place(Submittable, Taggable, Mapable):
|
||||
verbose_name=_('Description'),
|
||||
)
|
||||
|
||||
hero = models.ForeignKey(
|
||||
'PlaceImage',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='place_heros'
|
||||
)
|
||||
|
||||
level = models.IntegerField(
|
||||
default=5,
|
||||
choices=PLACE_LEVELS
|
||||
)
|
||||
|
||||
def get_hero_image(self):
|
||||
if self.hero:
|
||||
return self.hero
|
||||
elif len(self.placeimages.all()) > 0:
|
||||
return self.placeimages.first()
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('place_detail', kwargs={'pk': self.pk})
|
||||
|
||||
def get_hero_index_in_queryset(self):
|
||||
'''
|
||||
Calculates the index of the hero image within
|
||||
the list / queryset of images. Necessary for
|
||||
the lightbox.
|
||||
'''
|
||||
for i in range(0, len(self.placeimages.all())):
|
||||
image = self.placeimages.all()[i]
|
||||
if image == self.hero:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
@classmethod
|
||||
@ -34,8 +76,11 @@ class Place(Submittable, Taggable, Mapable):
|
||||
def average_latlon(cls, place_list):
|
||||
amount = len(place_list)
|
||||
# Init fill values to prevent None
|
||||
longitude = 0
|
||||
latitude = 0
|
||||
# China Corner in Münster
|
||||
# Where I almost always eat lunch
|
||||
# (Does'nt help losing wheight, tho)
|
||||
longitude = 7.6295628132604385
|
||||
latitude = 51.961922091398904
|
||||
|
||||
if amount > 0:
|
||||
for place in place_list:
|
||||
@ -45,25 +90,48 @@ class Place(Submittable, Taggable, Mapable):
|
||||
|
||||
return {'latitude': latitude, 'longitude': longitude}
|
||||
|
||||
def calculate_place_level(self):
|
||||
self.remove_expired_votes()
|
||||
|
||||
if self.placevotings.count() == 0:
|
||||
self.level = 5
|
||||
self.save()
|
||||
return
|
||||
|
||||
level = 0
|
||||
|
||||
for vote in self.placevotings.all():
|
||||
level += vote.vote
|
||||
|
||||
self.level = floor(level / self.placevotings.count())
|
||||
self.save()
|
||||
|
||||
def remove_expired_votes(self):
|
||||
for vote in self.placevotings.all():
|
||||
if vote.is_expired:
|
||||
vote.delete()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
def generate_image_upload_path(instance, filename):
|
||||
def generate_place_image_filename(instance, filename):
|
||||
"""
|
||||
Callback for generating path for uploaded images.
|
||||
Returns filename as: place_pk-placename{-rnd_string}.jpg
|
||||
Callback for generating filename for uploaded place images.
|
||||
Returns filename as: place_pk-placename{-number}.jpg
|
||||
"""
|
||||
|
||||
return 'places/' + str(instance.place.pk) + '-' + str(instance.place.name) + '.' + filename.split('.')[-1]
|
||||
|
||||
def generate_image_upload_path(instance, filename):
|
||||
return generate_place_image_filename(instance, filename)
|
||||
|
||||
class PlaceAsset(Submittable):
|
||||
"""
|
||||
Assets to a place, i.e. images
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
abstract = True
|
||||
|
||||
place = models.ForeignKey(
|
||||
Place,
|
||||
@ -72,7 +140,10 @@ class PlaceAsset(Submittable):
|
||||
null=True
|
||||
)
|
||||
|
||||
class PlaceImage (Submittable):
|
||||
class DummyAsset(PlaceAsset):
|
||||
name = models.CharField(max_length=50)
|
||||
|
||||
class PlaceImage(PlaceAsset):
|
||||
"""
|
||||
PlaceImage defines an image file object that points to a file in uploads/.
|
||||
Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES.
|
||||
@ -84,10 +155,10 @@ class PlaceImage (Submittable):
|
||||
verbose_name=_('Description'),
|
||||
)
|
||||
filename = ThumbnailerImageField(
|
||||
upload_to=generate_image_upload_path,
|
||||
upload_to=generate_place_image_filename,
|
||||
resize_source=dict(size=(2560, 2560),
|
||||
sharpen=True),
|
||||
verbose_name=_('Filename(s)'),
|
||||
verbose_name=_('Images'),
|
||||
help_text=_('Optional: One or more images to upload')
|
||||
)
|
||||
place = models.ForeignKey(
|
||||
@ -102,8 +173,7 @@ class PlaceImage (Submittable):
|
||||
of this image as textual representation of this instance
|
||||
"""
|
||||
|
||||
return 'Image ' + str(self.pk)
|
||||
|
||||
return 'Image ' + str(self.place.name)
|
||||
|
||||
# These two auto-delete files from filesystem when they are unneeded:
|
||||
|
||||
@ -118,7 +188,6 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
|
||||
thumbmanager = get_thumbnailer(instance.filename)
|
||||
thumbmanager.delete(save=False)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=PlaceImage)
|
||||
def auto_delete_file_on_change(sender, instance, **kwargs):
|
||||
"""
|
||||
@ -137,5 +206,14 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
|
||||
# No need to delete thumbnails, as they will be overwritten on regeneration.
|
||||
new_file = instance.filename
|
||||
if not old_file == new_file:
|
||||
if os.path.isfile(old_file.path):
|
||||
os.remove(old_file.path)
|
||||
old_file.delete(save=False)
|
||||
|
||||
|
||||
class PlaceVoting(PlaceAsset, Expireable):
|
||||
vote = models.IntegerField(choices=PLACE_LEVELS)
|
||||
|
||||
def get_human_readable_level(self):
|
||||
return PLACE_LEVELS[self.vote - 1][1]
|
||||
|
||||
def get_all_choices(self):
|
||||
return reversed(PLACE_LEVELS)
|
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
163
django_lostplaces/lostplaces/static/icons/favorite_filled.svg
Normal file
After Width: | Height: | Size: 108 KiB |
50
django_lostplaces/lostplaces/static/icons/pin.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg 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 368.16 368.16" style="enable-background:new 0 0 368.16 368.16;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M184.08,0c-74.992,0-136,61.008-136,136c0,24.688,11.072,51.24,11.536,52.36c3.576,8.488,10.632,21.672,15.72,29.4
|
||||
l93.248,141.288c3.816,5.792,9.464,9.112,15.496,9.112s11.68-3.32,15.496-9.104l93.256-141.296
|
||||
c5.096-7.728,12.144-20.912,15.72-29.4c0.464-1.112,11.528-27.664,11.528-52.36C320.08,61.008,259.072,0,184.08,0z
|
||||
M293.8,182.152c-3.192,7.608-9.76,19.872-14.328,26.8l-93.256,141.296c-1.84,2.792-2.424,2.792-4.264,0L88.696,208.952
|
||||
c-4.568-6.928-11.136-19.2-14.328-26.808C74.232,181.816,64.08,157.376,64.08,136c0-66.168,53.832-120,120-120
|
||||
c66.168,0,120,53.832,120,120C304.08,157.408,293.904,181.912,293.8,182.152z"/>
|
||||
<path d="M184.08,64.008c-39.704,0-72,32.304-72,72c0,39.696,32.296,72,72,72c39.704,0,72-32.304,72-72
|
||||
C256.08,96.312,223.784,64.008,184.08,64.008z M184.08,192.008c-30.872,0-56-25.12-56-56s25.128-56,56-56s56,25.12,56,56
|
||||
S214.952,192.008,184.08,192.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
105
django_lostplaces/lostplaces/static/icons/pin_filled.svg
Normal file
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 368.16 368.16"
|
||||
style="enable-background:new 0 0 368.16 368.16;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="pin_filled.svg"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"><metadata
|
||||
id="metadata47"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs45" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="712"
|
||||
id="namedview43"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4803346"
|
||||
inkscape:cx="184.08"
|
||||
inkscape:cy="181.37791"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="28"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
<g
|
||||
id="g10">
|
||||
<g
|
||||
id="g8">
|
||||
<g
|
||||
id="g6">
|
||||
<path
|
||||
d="M184.08,0c-74.992,0-136,61.008-136,136c0,24.688,11.072,51.24,11.536,52.36c3.576,8.488,10.632,21.672,15.72,29.4 l93.248,141.288c3.816,5.792,9.464,9.112,15.496,9.112s11.68-3.32,15.496-9.104l93.256-141.296 c5.096-7.728,12.144-20.912,15.72-29.4c0.464-1.112,11.528-27.664,11.528-52.36C320.08,61.008,259.072,0,184.08,0z M293.8,182.152c-3.192,7.608-9.76,19.872-14.328,26.8l-93.256,141.296c-1.84,2.792-2.424,2.792-4.264,0L88.696,208.952 c-4.568-6.928-11.136-19.2-14.328-26.808C74.232,181.816,64.08,157.376,64.08,136c0-66.168,53.832-120,120-120 c66.168,0,120,53.832,120,120C304.08,157.408,293.904,181.912,293.8,182.152z"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M184.08,64.008c-39.704,0-72,32.304-72,72c0,39.696,32.296,72,72,72c39.704,0,72-32.304,72-72 C256.08,96.312,223.784,64.008,184.08,64.008z M184.08,192.008c-30.872,0-56-25.12-56-56s25.128-56,56-56s56,25.12,56,56 S214.952,192.008,184.08,192.008z"
|
||||
id="path4" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g12">
|
||||
</g>
|
||||
<g
|
||||
id="g14">
|
||||
</g>
|
||||
<g
|
||||
id="g16">
|
||||
</g>
|
||||
<g
|
||||
id="g18">
|
||||
</g>
|
||||
<g
|
||||
id="g20">
|
||||
</g>
|
||||
<g
|
||||
id="g22">
|
||||
</g>
|
||||
<g
|
||||
id="g24">
|
||||
</g>
|
||||
<g
|
||||
id="g26">
|
||||
</g>
|
||||
<g
|
||||
id="g28">
|
||||
</g>
|
||||
<g
|
||||
id="g30">
|
||||
</g>
|
||||
<g
|
||||
id="g32">
|
||||
</g>
|
||||
<g
|
||||
id="g34">
|
||||
</g>
|
||||
<g
|
||||
id="g36">
|
||||
</g>
|
||||
<g
|
||||
id="g38">
|
||||
</g>
|
||||
<g
|
||||
id="g40">
|
||||
</g>
|
||||
<path
|
||||
style="fill:#d40000;stroke-width:0.675523"
|
||||
d="M 183.2356,15.407728 C 148.115,15.680007 113.56046,32.165752 91.281357,59.335191 67.968201,87.080284 57.903075,125.97636 67.012946,161.30636 c 5.002813,21.56622 16.217838,40.96402 28.671518,59.02158 29.047926,44.21623 57.936976,88.55418 87.673836,132.30559 2.75528,0.71398 3.44582,-3.87416 5.23173,-5.36255 32.63958,-48.91094 65.16056,-97.91511 96.89098,-147.42174 14.94799,-26.09698 23.38967,-57.3347 16.79888,-87.25041 C 297.41258,88.732147 285.50707,66.159988 267.48079,49.622901 254.86863,36.299556 238.35251,27.47624 221.24564,21.334387 209.00763,17.320382 196.11719,15.285641 183.2356,15.407728 Z m 2.03448,49.555941 c 27.46439,-0.566602 54.10132,17.912787 64.54543,43.490751 4.10895,9.11958 5.7106,19.04018 5.10583,29.01713 0.83931,19.32103 -8.79389,38.15287 -22.74612,51.10133 -14.02478,12.97799 -33.27348,19.77398 -52.19585,18.33769 -29.18994,-0.56334 -56.21632,-22.71956 -64.31454,-50.57715 -3.46488,-10.42859 -2.56691,-21.58508 -1.94784,-32.37905 3.96475,-21.34187 17.606,-41.232363 37.2523,-51.078299 10.71386,-5.480174 22.38724,-8.587404 34.30079,-7.912402 z"
|
||||
id="path91" /></svg>
|
After Width: | Height: | Size: 3.8 KiB |
BIN
django_lostplaces/lostplaces/static/images/missing_image.png
Normal file
After Width: | Height: | Size: 24 KiB |
19116
django_lostplaces/lostplaces/static/materialdesignicons.css
Normal file
@ -1,7 +1,7 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Forbidden' %}{% endblock %}
|
||||
{% block title %}{% translate 'Forbidden' %}{% endblock %}
|
||||
|
||||
{% block additional_head %}
|
||||
{% if request.META.HTTP_REFERER %}
|
||||
@ -11,6 +11,6 @@
|
||||
|
||||
{% block maincontent %}
|
||||
{% if request.META.HTTP_REFERER %}
|
||||
<p class="LP-Headline">{% trans 'You will be redirected in 5 seconds' %}</p><p class="LP-Headline"><a href="{{ request.META.HTTP_REFERER }}" class="LP-Link">{% trans 'Go Back' %}</a></p>
|
||||
<p class="LP-Headline">{% translate 'You will be redirected in 5 seconds' %}</p><p class="LP-Headline"><a href="{{ request.META.HTTP_REFERER }}" class="LP-Link">{% translate 'Go Back' %}</a></p>
|
||||
{% endif %}
|
||||
{% endblock maincontent %}
|
128
django_lostplaces/lostplaces/templates/explorer/profile.html
Normal file
@ -0,0 +1,128 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% load svg_icon %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
<div class="LP-UserProfile">
|
||||
<div class="LP-UserProfile__Bio">
|
||||
<h2 class="LP-Headline">Bio</h2>
|
||||
<p class="LP-Paragraph">{{explorer.bio}}</p>
|
||||
</div>
|
||||
<div class="LP-UserProfile__Info">
|
||||
<div class="LP-UserInfo">
|
||||
<div class="LP-UserInfo__UserName">
|
||||
<h1 class="LP-Headline">{{explorer.user.username}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="LP-UserInfo__Meta">
|
||||
<table>
|
||||
<tr>
|
||||
{% if explorer.profile_image %}
|
||||
<figure class="LP-UserInfo__Image">
|
||||
<img src="{{ explorer.profile_image.url }}" class="LP-Image" />
|
||||
</figure>
|
||||
{% endif %}
|
||||
<tr>
|
||||
{% if explorer.user.first_name %}
|
||||
<td class="LP-UserInfo__Key">
|
||||
<span class="LP-Paragraph">{% trans 'Name' %}</span>
|
||||
</td>
|
||||
<td class="LP-UserInfo__Value">
|
||||
<span class="LP-Paragraph">{{explorer.user.first_name}} {{explorer.user.last_name}}</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
{% if explorer.user.email %}
|
||||
<td class="LP-UserInfo__Key">
|
||||
<span class="LP-Paragraph">{% trans 'E-mail' %}</span>
|
||||
</td>
|
||||
<td class="LP-UserInfo__Value">
|
||||
<span class="LP-Paragraph">
|
||||
<a href="mailto:{{explorer.user.email}}" class="LP-Link">{{explorer.user.email}}</a>
|
||||
</span>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="LP-UserInfo__Key">
|
||||
<span class="LP-Paragraph">{% translate 'Joined' %}</span>
|
||||
</td>
|
||||
<td class="LP-UserInfo__Value">
|
||||
<span class="LP-Paragraph">{{explorer.user.date_joined|date:'d.m.Y'}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="LP-UserInfo__Key">
|
||||
<span class="LP-Paragraph">{% translate 'Places'%}</span>
|
||||
</td>
|
||||
<td class="LP-UserInfo__Value">
|
||||
<span class="LP-Paragraph">{{place_count}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="LP-UserInfo__Key">
|
||||
<span class="LP-Paragraph">{% translate 'Place Assets'%}</span>
|
||||
</td>
|
||||
<td class="LP-UserInfo__Value">
|
||||
<span class="LP-Paragraph">{{asset_count}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="LP-UserInfo__Edit">
|
||||
<a href="{% url 'explorer_profile_update' %}" class="LP-Button LP-Button--fullWidth LP-Link">{% trans 'Edit Profile' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="LP-Section">
|
||||
<div class="LP-PlaceList">
|
||||
<h1 class="LP-Headline">{% translate 'Places submitted by' %} {{explorer.user.username}}</h1>
|
||||
<ul class="LP-PlaceList__List">
|
||||
{% for place in place_list %}
|
||||
<li class="LP-PlaceList__Item">
|
||||
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
|
||||
{% include 'partials/place_teaser.html' with place=place extended=True %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% include 'partials/nav/pagination.html' %}
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="LP-Section">
|
||||
{% translate 'Images submitted by' as submitted_by %}
|
||||
{% include 'partials/placeImageGrid.html' with headline=submitted_by|add:' '|add:explorer.user.username image_list=assets.placeimages.all%}
|
||||
</section>
|
||||
|
||||
<section class=" LP-Section">
|
||||
<h1 class="LP-Headline">{% translate 'Photo albums submitted by' %} {{explorer.user.username}}</h1>
|
||||
<div class="LP-LinkList">
|
||||
<ul class="LP-LinkList__Container">
|
||||
{% for photo_album in assets.photoalbums.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.explorer == photo_album.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 %}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% endblock maincontent %}
|
@ -0,0 +1,55 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load i18n %}
|
||||
{% load thumbnail %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
# {% block title %}{% trans 'Edit Explorer profile' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
<form class="LP-Form" method="POST" enctype="multipart/form-data">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Edit Explorer profile' %}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.username %}
|
||||
</div>
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.email %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.first_name %}
|
||||
</div>
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.last_name %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="LP-Form__Composition">
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=explorer_change_form.bio %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="LP-Form__Composition">
|
||||
{% if explorer_image %}
|
||||
<div class="LP-Form__Field">
|
||||
<img src="{% thumbnail explorer_image 200x200 %}"/>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=explorer_change_form.profile_image %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% trans 'Update' as action %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
{% endblock maincontent %}
|
@ -11,7 +11,26 @@
|
||||
<article class="LP-TextSection LP-TextSection__Text">
|
||||
|
||||
<p>Das sollte doch eigentlich eindeutig sein, oder? Aber wie wir alle wissen, hat jedes Schild eine Geschichte. Wir haben zu viel sinnlose Zerstörung gesehen, sodass wir uns dazu entschieden haben, einen grundlegenden Urban Exploration Codex niederzuschreiben.</p>
|
||||
<ul class="LP-UnorderedList">
|
||||
<h2 class="LP-Headline">Zu deiner eigenen Sicherheit</h2>
|
||||
<ol class="LP-UnorderedList">
|
||||
<li>
|
||||
<b>Wir gehen selten alleine</b>, ein bisschen rumlaufen und Fotos machen klingt erstmal ungefährlich. Du weißt aber nie in welchem Zustand die Anlage ist oder wer sich dort aufhält. Den Fuß umknickt, an Scherben geschnitten oder vielleicht auf einen unerwarteten Bewohner oder Sicherheitspersonal getroffen. In solchen Situationen ist immer besser jemanden an deiner Seite zu haben, der dich nicht im Stich lässt.
|
||||
</li>
|
||||
<li>
|
||||
<b>Wir sagen vor einer Tour mindestens einer Person unseres Vertrauens, wo wir sind</b> (Koordinaten!), wie lange wir uns dort aufhalten werden und was wir dort tun. Wir verabreden, daß wir uns bei einem längeren Aufenthalt dort regelmäßig melden, oder aber ein "okay" geben, wenn wir die Location wieder verlassen haben. Wenn wir uns verspäten, dann denken wir daran, eine entsprechende Nachricht abzusetzen. Unsere Kontaktperson soll nach einer gewissen Zeit, wenn sie nichts von mir hört, für Hilfe sorgen.
|
||||
</li>
|
||||
<li>
|
||||
<b>In einer Location verhalten wir uns vorsichtig.</b> Wir werfen nichts in Löcher oder aus Fenstern und berühren auch keine elektrischen Anlagen. Nicht immer ist der Strom wirklich abgeschaltet. Deswegen öffnen wor auch keine Flaschen und andere Behälter. Wir gehen niemals ohne Licht in einen dunklen Raum, und nehmen immer eine Reservelampe mit.
|
||||
</li>
|
||||
<li>
|
||||
<b>Wir bereiten unsere Tour sorgfältig vor.</b> Es ist ärgerlich, wenn die Kamera-Akkus nicht geladen sind - aber gefährlich ist es keineswegs. Es ist aber lebensgefährlich, wenn ein verschmutzter Nagel durch eine Sohle getreten wird. Sicherheitsschuhe zu tragen ist eine gute Idee. Wenn wir unter Tage gehen, nehmen wir angemessenes Equipment mit um uns zu schützen — z. B. einen Gaswarner oder vllt. sogar einen Geigerzähler.
|
||||
</li>
|
||||
<li>
|
||||
<b>Wir rennen nicht vor bewaffnetem Personal weg</b>, insbesondere nicht der Polizei! Eine Anzeige ist ätzend und das reden mit den Beamten ist mit Sicherheit unangenehm, flieht ihr die Szene, macht euch das aber noch verdächtiger. Mal ganz davon abgesehen dass die Polizei in der Überzahl ist und euch wahrscheinlich sowie fasst.
|
||||
</li>
|
||||
</ol>
|
||||
<h2 class="LP-Headline">Damit ein Lostplace auch lost bleibt</h2>
|
||||
<ol class="LP-UnorderedList">
|
||||
<li>
|
||||
<b>Wir respektieren fremdes Eigentum</b>, so wie wir das von anderen auch verlangen. Deswegen öffnen wir keinen Zugang gewaltsam, oder beschädigen eine Verschlusseinrichtung. Wir betreten ein Grundstück oder ein Gebäude / eine Anlage / eine Ruine nur dann, wenn wir davon ausgehen können, nicht gegen den Willen des Eigentümers dort zu sein. Eine gut erhaltene Umzäunung, oder ein ständiger Wachschutz sprechen dafür, dass der Eigentümer sich noch mit seiner Anlage befasst. Längst umgesunkene Zäune, Ruinen im letzten Stadium eines Verfalls sprecher dafür, dass dem Eigentümer das eher Wurst ist.
|
||||
</li>
|
||||
@ -25,15 +44,6 @@
|
||||
<li>
|
||||
<b>Sprayen ist ein absolutes "no-go"!</b>
|
||||
</li>
|
||||
<li>
|
||||
<b>In einer Location verhalten wir uns vorsichtig.</b> Wir werfen nichts in Löcher oder aus Fenstern und berühren auch keine elektrischen Anlagen. Nicht immer ist der Strom wirklich abgeschaltet. Deswegen öffnen wor auch keine Flaschen und andere Behälter. Wir gehen niemals ohne Licht in einen dunklen Raum, und nehmen immer eine Reservelampe mit.
|
||||
</li>
|
||||
<li>
|
||||
<b>Wir sagen vor einer Tour mindestens einer Person unseres Vertrauens, wo wir sind</b> (Koordinaten!), wie lange wir uns dort aufhalten werden und was wir dort tun. Wir verabreden, daß wir uns bei einem längeren Aufenthalt dort regelmäßig melden, oder aber ein "okay" geben, wenn wir die Location wieder verlassen haben. Wenn wir uns verspäten, dann denken wir daran, eine entsprechende Nachricht abzusetzen. Unsere Kontaktperson soll nach einer gewissen Zeit, wenn sie nichts von mir hört, für Hilfe sorgen.
|
||||
</li>
|
||||
<li>
|
||||
<b>Wir bereiten unsere Tour sorgfältig vor.</b> Es ist ärgerlich, wenn die Kamera-Akkus nicht geladen sind - aber gefährlich ist es keineswegs. Es ist aber lebensgefährlich, wenn ein verschmutzter Nagel durch eine Sohle getreten wird. Sicherheitsschuhe zu tragen ist eine gute Idee. Wenn wir unter Tage gehen, nehmen wir angemessenes Equipment mit um uns zu schützen — z. B. einen Gaswarner oder vllt. sogar einen Geigerzähler.
|
||||
</li>
|
||||
<li>
|
||||
<b>Wir parken nicht so, daß Dritte auf unser Vorhaben aufmerksam werden.</b> Unser Hobby braucht keine öffentliche Aufmerksamkeit, die sich auf illegales Betreten von Grundstücken richtet (und illegal dürfte das Betreten ja meist sein ...).
|
||||
</li>
|
||||
@ -46,7 +56,19 @@
|
||||
<li>
|
||||
<b>Wir sorgen durch Argumente und Überzeugung dafür, daß unsere Begleiter sich ebenso verhalten!</b> In erster Linie versuchen wir durch unser eigenes Verhalten zu überzeugen.
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
<h2 class="LP-Headline">Rechtliches (für Deutschland)</h2>
|
||||
<ol class="LP-UnorderedList">
|
||||
<li>
|
||||
<b>Das Betreten von fremden Grundstücken ist nicht zwangsläufig Illegal.</b> In Deutschland gibt es keinen Tatbestand des Einbruchs, lediglich die dabei begangen Vergehen. Machst du nichts kaputt und stiehlst du auch nichts kann dir nur Hausfriedensbruch zur Lasst gelegt werden. Hausfriedensbruch ist in Deutschland anzeigepflichtig, werdet ihr erwischt notieren sich die Beamten eure Personalien und melden euren Besuch dem Eigentümer des Grundstücks. Dieser Entscheidet ob er gegen euch Anzeige erstattet oder nicht. Erfahrungsgemäß sind den meisten Eigentümern ein paar Leute die nur Fotos machen ziemlich egal, darauf soltet ihr euch aber nicht verlassen.
|
||||
</li>
|
||||
<li>
|
||||
<b>Nehmt einen Ausweis mit</b> solltet ihr erwischt werden müssen die Beamten eure Identität erfassen. Gelingt das nicht vor Ort, werdet ihr einen Ausflug auf die nächste Polizeiwache machen. Darauf haben weder ihr nocht die Beamten lust.
|
||||
</li>
|
||||
<li>
|
||||
<b>Graffiti ist Sachbeschädigung</b> also lasst es lieber sein.
|
||||
</li>
|
||||
</ol>
|
||||
</article>
|
||||
|
||||
{% endblock maincontent %}
|
4
django_lostplaces/lostplaces/templates/flat_form.html
Normal file
@ -0,0 +1,4 @@
|
||||
<form method={{form.method}}>
|
||||
{{form}}
|
||||
<input type="submit"/>
|
||||
</form>
|
@ -1,5 +1,6 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load lostplaces%}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@ -11,6 +12,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="{% static 'main.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'materialdesignicons.css' %}">
|
||||
<link rel="icon" type="image/png" href="{% static 'favicon.ico' %}">
|
||||
<title>
|
||||
{% block title %}Urban Exploration{% endblock %}
|
||||
@ -29,15 +31,16 @@
|
||||
<span class="LP-Paragraph">
|
||||
{% if user.is_authenticated %}
|
||||
Hi {{ user.username }}!
|
||||
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% trans 'Logout' %}</span></a>
|
||||
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% translate 'Logout' %}</span></a> |
|
||||
<a class="LP-Link" href="{% url 'explorer_profile' explorer_id=user.pk%}"><span class="LP-Link__Text">{% translate 'Profile' %}</span></a>
|
||||
{% if user.is_superuser %}
|
||||
<a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% trans 'Admin' %}</span></a>
|
||||
| <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% translate 'Admin' %}</span></a>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
You are not logged in.
|
||||
<a class="LP-Link" href="{% url 'login' %}"><span class="LP-Link__Text">{% trans 'Login' %}</span></a> |
|
||||
<a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">{% trans 'Sign Up' %}</span></a>
|
||||
<a class="LP-Link" href="{% url 'login' %}"><span class="LP-Link__Text">{% translate 'Login' %}</span></a> |
|
||||
<a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">{% translate 'Sign up' %}</span></a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
@ -47,14 +50,14 @@
|
||||
<aside class="LP-Main__Sidebar">
|
||||
<nav class="LP-Menu LP-Menu--sidebar">
|
||||
<ul class="LP-Menu__List">
|
||||
<li class="LP-Menu__Item"><a href="{% url 'lostplaces_home' %}" class="LP-Link"><span class="LP-Link__Text">{% trans 'Home' %}</span></a></li>
|
||||
<li class="LP-Menu__Item"><a href="{% url 'flatpage' slug='codex' %}" class="LP-Link"><span class="LP-Link__Text">{% trans 'UrBex Codex' %}</span></a></li>
|
||||
<li class="LP-Menu__Item"><a href="{% url 'lostplaces_home' %}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Home' %}</span></a></li>
|
||||
<li class="LP-Menu__Item"><a href="{% url 'flatpage' slug='codex' %}" class="LP-Link"><span class="LP-Link__Text">{% translate 'UrBex Codex' %}</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">{% trans '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">{% trans 'All Places' %}</span></a></li>
|
||||
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_create'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Create place' %}</span></a></li>
|
||||
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_list'%}" class="LP-Link"><span class="LP-Link__Text">{% translate 'All places' %}</span></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
@ -86,7 +89,7 @@
|
||||
</div>
|
||||
|
||||
{% block footer %}
|
||||
{% include 'partials/nav/footer.html' %}
|
||||
{% partial 'nav/footer' %}
|
||||
{% endblock footer %}
|
||||
|
||||
</body>
|
||||
|
@ -2,52 +2,31 @@
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load lostplaces %}
|
||||
{% 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 %}{% trans 'Home' %}{% endblock %}
|
||||
# {% block title %}{% translate 'Home' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
{% include 'partials/welcome.html' %}
|
||||
{% partial 'welcome' %}
|
||||
<article class="LP-TextSection">
|
||||
</article>
|
||||
|
||||
{% include 'partials/osm_map.html' with config=mapping_config %}
|
||||
{% partial 'osm_map' %}
|
||||
{% set config mapping_config %}
|
||||
{% set modifier 'wide' %}
|
||||
{% endpartial %}
|
||||
<div class="LP-PlaceGrid">
|
||||
<h1 class="LP-Headline LP-Headline">{% trans 'Explore the latest places' %}</h1>
|
||||
<h1 class="LP-Headline LP-Headline">{% translate 'Explore the latest places' %}</h1>
|
||||
<ul class="LP-PlaceGrid__Grid">
|
||||
{% for place in place_list %}
|
||||
<li class="LP-PlaceGrid__Item">
|
||||
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
|
||||
<article class="LP-PlaceTeaser">
|
||||
<div class="LP-PlaceTeaser__Image">
|
||||
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url}}" />
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Meta">
|
||||
<div class="LP-PlaceTeaser__Info">
|
||||
<span class="LP-PlaceTeaser__Title">
|
||||
<h1 class="LP-Headline LP-Headline--teaser">{{place.name|truncatechars:19}}</h1>
|
||||
</span>
|
||||
<span class="LP-PlaceTeaser__Detail">
|
||||
<p class="LP-Paragraph">{{place.location|truncatechars:25}}</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Icons">
|
||||
<ul class="LP-Icon__List">
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/favourite.svg' %}" /></li>
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/location.svg' %}" /></li>
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
{% include 'partials/place_teaser.html' with place=place%}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% endblock maincontent %}
|
@ -1,30 +1,33 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load i18n %}
|
||||
{% load lostplaces%}
|
||||
|
||||
# {% block title %}Start{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
{% include 'partials/welcome.html' %}
|
||||
{% partial 'welcome' %}
|
||||
<article class="LP-TextSection">
|
||||
<p class="LP-Paragraph">
|
||||
{% blocktrans %}You can create, view and share your lost places with other members of this site. You can upload photos, place links to your web galleries and contribute your knowledge by tagging other places or commenting on them. You will find detailed information on where these locations are, how to get there and what to expect from them. This might even include detailed information on the surroundings or the history of a lost place.{% endblocktrans %}
|
||||
{% blocktranslate %}You can create, view and share your lost places with other members of this site. You can upload photos, place links to your web galleries and contribute your knowledge by tagging other places or commenting on them. You will find detailed information on where these locations are, how to get there and what to expect from them. This might even include detailed information on the surroundings or the history of a lost place.{% endblocktranslate %}
|
||||
</p>
|
||||
{% url 'signup' as signup_url %}
|
||||
<p class="LP-Paragraph">
|
||||
{% blocktrans %}Since vandalism is a growing problem these days, no sensitive information is available to the public. You have to <a class="LP-Link" href="{{signup_url}}"><span class="LP-Link__Text">sign up</span></a> with a voucher code as an invitation to join this community. Only admins can create these codes. Usually you are given a code when we know you in real life in person and when we trust you. A request from an unknown person will most probably be denied.{% endblocktrans %}
|
||||
{% blocktranslate %}Since vandalism is a growing problem these days, no sensitive information is available to the public. You have to <a class="LP-Link" href="{{signup_url}}"><span class="LP-Link__Text">sign up</span></a> with a voucher code as an invitation to join this community. Only admins can create these codes. Usually you are given a code when we know you in real life in person and when we trust you. A request from an unknown person will most probably be denied.{% endblocktranslate %}
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<div class="LP-PlaceGrid">
|
||||
<h1 class="LP-Headline LP-Headline">{% trans 'Explore the latest places' %}</h1>
|
||||
<h1 class="LP-Headline LP-Headline">{% translate 'Explore the latest places' %}</h1>
|
||||
<ul class="LP-PlaceGrid__Grid">
|
||||
{% for place in place_list %}
|
||||
<li class="LP-PlaceGrid__Item">
|
||||
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
|
||||
<article class="LP-PlaceTeaser">
|
||||
<div class="LP-PlaceTeaser__Image">
|
||||
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url}}" />
|
||||
{% partial 'image' %}
|
||||
{% set source_url place.placeimages.first.filename.thumbnail.url %}
|
||||
{% endpartial %}
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Meta">
|
||||
<div class="LP-PlaceTeaser__Info">
|
||||
|
16
django_lostplaces/lostplaces/templates/osm_map_full.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends 'global.html'%}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% 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 %}{% trans 'Map' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
{% include 'partials/osm_map.html' with config=mapping_config modifier='full' %}
|
||||
|
||||
{% endblock maincontent %}
|
@ -2,9 +2,7 @@
|
||||
|
||||
<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>
|
||||
{% with class="LP-Input__Field "%}
|
||||
{% render_field field class=class%}
|
||||
{% endwith %}
|
||||
{% render_field field class+='LP-Input__Field' %}
|
||||
|
||||
<span class="LP-Input__Message">
|
||||
{% if field.errors %}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{% load i18n %}
|
||||
<div class="LP-Form__Field LP-Form__Button LP-Input">
|
||||
<button class="LP-Button">{% if action %}{{ action }}{% else %}{% trans 'Submit' %}{% endif %}</button>
|
||||
<button class="LP-Button">{% if action %}{{ action }}{% else %}{% translate 'Submit' %}{% endif %}</button>
|
||||
</div>
|
||||
<div class="LP-Form__Field LP-Form__Button LP-Input">
|
||||
<a class="LP-Link" href="{% if referer %}{{ referer }}{% else %}{% url 'lostplaces_home' %}{% endif %}">
|
||||
<button type="button" class="LP-Button LP-Button--cancel">{% trans 'Cancel' %}</button>
|
||||
<button type="button" class="LP-Button LP-Button--cancel">{% translate 'Cancel' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -0,0 +1,14 @@
|
||||
{%load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if request.user %}
|
||||
{% if place in request.user.explorer.favorite_places.all %}
|
||||
<a href="{% url 'place_unfavorite' place_id=place.id %}" class="LP-Link" title="{% trans 'Remove from favorites' %}">
|
||||
<img class="LP-Icon" src="{% static '/icons/favorite_filled.svg' %}" />
|
||||
</a>
|
||||
{%else%}
|
||||
<a href="{% url 'place_favorite' place_id=place.id %}" class="LP-Link" title="{% trans 'Save as favorite' %}">
|
||||
<img class="LP-Icon" src="{% static '/icons/favorite.svg' %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
@ -0,0 +1,14 @@
|
||||
{%load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% if request.user %}
|
||||
{% if place in request.user.explorer.visited_places.all %}
|
||||
<a href="{% url 'place_visit_delete' place_id=place.id %}" class="LP-Link" title="{% trans 'Remove from visits' %}">
|
||||
<img class="LP-Icon" src="{% static '/icons/pin_filled.svg' %}" />
|
||||
</a>
|
||||
{%else%}
|
||||
<a href="{% url 'place_visit_create' place_id=place.id %}" class="LP-Link" title="{% trans 'Save as visited' %}">
|
||||
<img class="LP-Icon" src="{% static '/icons/pin.svg' %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
18
django_lostplaces/lostplaces/templates/partials/image.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% if link_url %}
|
||||
<a
|
||||
class="LP-Link"
|
||||
href="{{link_url}}"
|
||||
>
|
||||
{% endif %}
|
||||
<figure class="LP-Figure">
|
||||
<img
|
||||
src="{{ source_url }}"
|
||||
{% if alt_text %}
|
||||
alt="{{alt_text}}"
|
||||
{% else %}
|
||||
alt="{{block}}"
|
||||
{% endif %}
|
||||
class="LP-Image"
|
||||
/>
|
||||
</figure>
|
||||
{% if link_url %}</a>{% endif %}
|
@ -61,13 +61,13 @@
|
||||
<!-- Bottom section with last line of text and links. -->
|
||||
<section class="LP-Footer__BottomSection LP-Footer__Flex">
|
||||
<div class="LP-Footer__BottomWrapper">
|
||||
{% trans 'Made by' %} <a href="https://git.mowoe.com/reverend" class="LP-Footer__GenericAnchor">Reverend</a>
|
||||
{% trans 'and' %} <a href="https://www.commander1024.de" class="LP-Footer__GenericAnchor">Commander1024.</a></span>
|
||||
{% translate 'Made by' %} <a href="https://git.mowoe.com/reverend" class="LP-Footer__GenericAnchor">Reverend</a>
|
||||
{% translate 'and' %} <a href="https://www.commander1024.de" class="LP-Footer__GenericAnchor">Commander1024.</a></span>
|
||||
</div>
|
||||
<div class="LP-Footer__BottomWrapper">
|
||||
<a href="{% url 'flatpage' slug='privacy-policy' %}" class="LP-Footer__GenericAnchor">{% trans 'Privacy Policy' %}</a>
|
||||
<a href="{% url 'flatpage' slug='privacy-policy' %}" class="LP-Footer__GenericAnchor">{% translate 'Privacy Policy' %}</a>
|
||||
|
|
||||
<a href="{% url 'flatpage' slug='imprint' %}" class="LP-Footer__GenericAnchor">{% trans 'Imprint' %}</a>
|
||||
<a href="{% url 'flatpage' slug='imprint' %}" class="LP-Footer__GenericAnchor">{% translate 'Imprint' %}</a>
|
||||
</div>
|
||||
</section>
|
||||
</footer>
|
||||
|
@ -8,26 +8,26 @@
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--first">
|
||||
<a href="?page=1" class="LP-Link">
|
||||
<span class="LP-Icon">⟪</span>
|
||||
<span class="LP-Text">{% trans 'First' %}</span>
|
||||
<span class="LP-Text">{% translate 'First' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--previous">
|
||||
<a href="?page={{ page_obj.previous_page_number }}" class="LP-Link">
|
||||
<span class="LP-Icon">⟨</span>
|
||||
<span class="LP-Text">{% trans 'Previous' %}</span>
|
||||
<span class="LP-Text">{% translate 'Previous' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--first LP-Pagination__Item--disabled">
|
||||
<a href="#" class="LP-Link">
|
||||
<span class="LP-Icon">⟪</span>
|
||||
<span class="LP-Text">{% trans 'First' %}</span>
|
||||
<span class="LP-Text">{% translate 'First' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--previous LP-Pagination__Item--disabled">
|
||||
<a href="#" class="LP-Link">
|
||||
<span class="LP-Icon">⟨</span>
|
||||
<span class="LP-Text">{% trans 'Previous' %}</span>
|
||||
<span class="LP-Text">{% translate 'Previous' %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -47,25 +47,25 @@
|
||||
{% if page_obj.has_next %}
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--next">
|
||||
<a href="?page={{ page_obj.next_page_number }}" class="LP-Link">
|
||||
<span class="LP-Text">{% trans 'Next' %}</span>
|
||||
<span class="LP-Text">{% translate 'Next' %}</span>
|
||||
<span class="LP-Icon">⟩</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--last">
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}" class="LP-Link">
|
||||
<span class="LP-Text">{% trans 'Last' %}</span>
|
||||
<span class="LP-Text">{% translate 'Last' %}</span>
|
||||
<span class="LP-Icon">⟫</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--next LP-Pagination__Item--disabled">
|
||||
<a href="#" class="LP-Link">
|
||||
<span class="LP-Text">{% trans 'Next' %}</span>
|
||||
<span class="LP-Text">{% translate 'Next' %}</span>
|
||||
<span class="LP-Icon">⟩</span>
|
||||
</a>
|
||||
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--last LP-Pagination__Item--disabled">
|
||||
<a href="#" class="LP-Link">
|
||||
<span class="LP-Text">{% trans 'Last' %}</span>
|
||||
<span class="LP-Text">{% translate 'Last' %}</span>
|
||||
<span class="LP-Icon">⟫</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% load static %}
|
||||
<div tabindex="1" id="map" class="LP-Map map" style="height: 300px"></div>
|
||||
<div tabindex="1" id="map" class="LP-Map {% if modifier %}LP-Map--{{modifier}}{% endif %} map"></div>
|
||||
<div id="info" class="map-popup LP-Map__Popup"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
@ -0,0 +1,43 @@
|
||||
{% load static %}
|
||||
{% load lostplaces %}
|
||||
|
||||
{% if headline %}
|
||||
<h1 class="LP-Headline">{{headline}}</h1>
|
||||
{% endif %}
|
||||
<div class="LP-ImageGrid">
|
||||
<ul class="LP-ImageGrid__Container">
|
||||
{% for image in image_list %}
|
||||
<li id="thumbnail{{forloop.counter0}}" class="LP-ImageGrid__Item">
|
||||
{% include 'partials/image.html' with source_url=image.filename.thumbnail.url link_url="#image"|addstr:forloop.counter0 %}
|
||||
{% if user.explorer == image.submitted_by%}
|
||||
<span class="LP-ImageGrid__DeleteItem" title="Bild löschen">
|
||||
<a href="{% url 'place_image_delete' pk=image.id %}" class="LP-Link">
|
||||
<img class="LP-Icon" src="{% static 'icons/cancel.svg' %}" />
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<div id="image{{forloop.counter0}}" class="LP-ImageGrid__LightBox">
|
||||
<img class="LP-ImageGrid__FullSizeImage" src="{{image.filename.large.url}}" loading="lazy"/>
|
||||
{% if forloop.counter0 < image_list|length|add:-1 %}
|
||||
<a href="#image{{forloop.counter0|add:1}}" class="LP-ImageGrid__Next">Next</a>
|
||||
{% endif %}
|
||||
{% if forloop.counter0 > 0 %}
|
||||
<a href="#image{{forloop.counter0|add:-1}}" class="LP-ImageGrid__Previous">Previous</a>
|
||||
{% endif %}
|
||||
<span class="LP-ImageGrid__Close LP-ImageGrid__DeleteItem" title="Schließen">
|
||||
<a href="#thumbnail{{forloop.counter0}}" class="LP-Link">
|
||||
<img class="LP-Icon" src="{% static 'icons/cancel.svg' %}"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if place %}
|
||||
<li class="LP-ImageGrid__Item LP-ImageGrid__Item--add" title="Bild hinzufügen">
|
||||
<a class="LP-Link" href="{% url 'place_image_create' place_id=place.id %}">
|
||||
<img class="LP-Icon" src="{% static 'icons/plus.svg' %}" />
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
@ -0,0 +1,40 @@
|
||||
{%load static %}
|
||||
|
||||
<article class="LP-PlaceTeaser {% if extended %} LP-PlaceTeaser--extended{% endif %}">
|
||||
<div class="LP-PlaceTeaser__Image">
|
||||
{% if place.get_hero_image %}
|
||||
{% include 'partials/image.html' with source_url=place.get_hero_image.filename.thumbnail.url link_url=place.get_absolute_url%}
|
||||
{% else %}
|
||||
<a href="{{place.get_absolute_url}}">
|
||||
<img class="LP-Image" src="{% static 'images/missing_image.png' %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
<div class="LP-PlaceTeaser__Meta">
|
||||
<div class="LP-PlaceTeaser__Info">
|
||||
<span class="LP-PlaceTeaser__Title">
|
||||
<h1 class="LP-Headline LP-Headline--teaser">{{place.name|truncatechars:19}}</h1>
|
||||
</span>
|
||||
<span class="LP-PlaceTeaser__Detail">
|
||||
<p class="LP-Paragraph">{{place.location|truncatechars:25}}</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Description">
|
||||
<p class="LP-Paragraph">
|
||||
{% if place.description|length > 210 %}
|
||||
{{place.description|truncatechars:210|truncatewords:-1}}
|
||||
{% else %}
|
||||
{{place.description}}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Icons">
|
||||
<ul class="LP-Icon__List">
|
||||
<li class="LP-Icon__Item">{% include 'partials/icons/place_favorite.html' with place=place%}</li>
|
||||
<li class="LP-Icon__Item">{% include 'partials/icons/place_visited.html' with place=place%}</li>
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
@ -0,0 +1,3 @@
|
||||
{%load lostplaces %}
|
||||
|
||||
{% include 'partials/image.html' with source_url=object.filename.thumbnail.url %}
|
@ -25,13 +25,13 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<form id="id_tag_submit_form" class="LP-Form LP-Form--inline LP-Form--tagging" method="POST" action="{% url config.submit_url_name tagged_id=config.tagged_item.id%}">
|
||||
<form id="id_tag_submit_form" class="LP-Form LP-Form--inline LP-Form--tagging" method="POST" action="{{config.submit_url}}">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Add tags' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Add tags' %}</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">{% trans 'Add tags' %}</button>
|
||||
<button id="id_tag_submit_button" class="LP-Button">{% translate 'Add tags' %}</button>
|
||||
</div>
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=config.submit_form.tag_list classes="LP-Input--tagging" %}
|
||||
|
41
django_lostplaces/lostplaces/templates/partials/voting.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="LP-Voting">
|
||||
<div class="LP-Voting__Left">
|
||||
<h2 class="LP-Headline">
|
||||
Place level
|
||||
</h2>
|
||||
<div class="LP-Voting__Choices">
|
||||
{% for choice in voting.all_choices %}
|
||||
<a href="{% url 'place_vote' place_id=place.id vote=choice.0 %}" class="LP-Voting__Vote {% if choice.0 <= place.level %} LP-Voting__Vote--overall{% endif %}" title="Vote place as "{{choice.1}}"">
|
||||
<i class="mdi mdi-shield-home"></i>
|
||||
<span class="LP-Voting__Label">
|
||||
{{choice.1}}
|
||||
</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="LP-Voting__CurrentVote">
|
||||
{{place.get_level_display}}
|
||||
</div>
|
||||
</div>
|
||||
{% if voting.users_vote %}
|
||||
<div class="LP-Voting__Right">
|
||||
<div class="LP-Voting__Info">
|
||||
<div class="LP-Voting__UserVote">
|
||||
<span class="LP-Voting__InfoLabel">You voted this place as</span>
|
||||
<span class="LP-Voting__Vote">{{voting.users_vote.get_human_readable_level}} <i>Level {{voting.users_vote.vote}}</i></span>
|
||||
</div>
|
||||
|
||||
<div class="LP-Voting__Expiration">
|
||||
<span class="LP-Voting__InfoLabel">Your vote expires on</span>
|
||||
<span class="LP-Voting__Date">
|
||||
<time datetime="{{voting.expires_when|date:'Y-m-d'}}">
|
||||
{{voting.users_vote.expires_when|date:'d.m.Y'}}
|
||||
</time>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
@ -1,21 +1,21 @@
|
||||
{% load i18n %}
|
||||
|
||||
<header class="LP-TextSection__Headline">
|
||||
<h1 class="LP-Headline">{% trans 'Start' %}</h1>
|
||||
<h1 class="LP-Headline">{% translate 'Start' %}</h1>
|
||||
</header>
|
||||
|
||||
<div class="LP-TextSection__Text">
|
||||
<p class="LP-Paragraph">{% trans 'Welcome to our Urban Exploration community catalogue,' %}
|
||||
<p class="LP-Paragraph">{% translate 'Welcome to our Urban Exploration community catalogue,' %}
|
||||
{% if user.is_authenticated %}
|
||||
{{ user.username }}
|
||||
{% else %}
|
||||
{% trans 'explorer' %}
|
||||
{% translate 'explorer' %}
|
||||
{% endif %}
|
||||
!
|
||||
</p>
|
||||
<p class="LP-Paragraph">
|
||||
{% trans 'We strictly follow our' %}
|
||||
<a class="LP-Link" href="{% url 'flatpage' slug='codex' %}"><span class="LP-Link__Text">{% trans 'UrBex codex' %}</span></a>
|
||||
{% trans 'and expect you to do so, too.' %}'
|
||||
{% translate 'We strictly follow our' %}
|
||||
<a class="LP-Link" href="{% url 'flatpage' slug='codex' %}"><span class="LP-Link__Text">{% translate 'UrBex codex' %}</span></a>
|
||||
{% translate 'and expect you to do so, too.' %}'
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,19 +1,19 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Submit a photo album' %}{% endblock %}
|
||||
{% block title %}{% translate '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">{% trans 'Edit place' %}</span></a></li>
|
||||
class="LP-Link__Text">{% translate '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">{% trans 'Delete place' %}</span></a></li>
|
||||
class="LP-Link__Text">{% translate '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">{% trans 'Submit a photo album for' %} {{place.name}}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Submit a photo album for' %} {{place.name}}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition">
|
||||
<div class="LP-Form__Field">
|
||||
|
@ -2,12 +2,12 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
# {% block title %}{% trans 'Create Place' %}{% endblock %}
|
||||
# {% block title %}{% translate 'Create place' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<form class="LP-Form" method="POST" enctype="multipart/form-data">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Create Place' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Create place' %}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% trans 'Create' as action %}
|
||||
{% translate 'Create' as action %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
|
||||
</div>
|
||||
|
@ -2,21 +2,21 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Lost Place Deletion' %}{% endblock %}
|
||||
{% block title %}{% translate 'Delete lost place' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
<form class="LP-Form" method="POST">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Delete place' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Delete place' %}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition">
|
||||
<div class="LP-Form__Field LP-Form__InfoText">
|
||||
<p class="LP-Paragraph">{% trans 'Are you sure you want to delete' %} "{{place.name}}"?</p>
|
||||
<p class="LP-Paragraph">{% blocktranslate with place_name=place.name%}Are you sure you want to delete {{place_name}}?{% endblocktranslate %}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% trans 'Delete' as action %}
|
||||
{% translate 'Delete' as action %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||
{% include 'partials/form/submit.html' with referer=request.META.HTTP_REFERER action=action %}
|
||||
</div>
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
{% load thumbnail %}
|
||||
{% load svg_icon %}
|
||||
{% load lostplaces %}
|
||||
|
||||
{% block additional_head %}
|
||||
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
|
||||
@ -15,19 +16,23 @@
|
||||
{% 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">{% trans '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">{% trans '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">{% translate '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">{% translate '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.placeimages.first.filename.hero.url %}
|
||||
<figure class="LP-PlaceDetail__Image">
|
||||
<img src="{{ place.placeimages.first.filename.hero.url }}" class="LP-Image" />
|
||||
</figure>
|
||||
<h1 class="LP-Headline">{{ place.name }} {% include 'partials/icons/place_favorite.html' %} {% include 'partials/icons/place_visited.html' %}</h1>
|
||||
{% if place.get_hero_image %}
|
||||
<div class="LP-PlaceDetail__Image">
|
||||
{% partial image %}
|
||||
{% set source_url place.get_hero_image.filename.hero.url %}
|
||||
{% set link_url %}
|
||||
{{"#image"|addstr:place.get_hero_index_in_queryset}}
|
||||
{% endset %}
|
||||
{% endpartial %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
@ -35,27 +40,68 @@
|
||||
<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 config=tagging_config %}
|
||||
|
||||
</section>
|
||||
<div class="LP-Quickinfo">
|
||||
<section class="LP-Section">
|
||||
{% url 'place_tag_submit' place_id=place.id as tag_submit_url %}
|
||||
{% partial tagging %}
|
||||
{% set config=tagging_config %}
|
||||
{% endpartial %}
|
||||
</section>
|
||||
|
||||
{{votingplace.vote}}
|
||||
<section class="LP-Section">
|
||||
{% partial voting %}
|
||||
{% set place=place %}
|
||||
{% set voting=placevoting %}
|
||||
{% endpartial %}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="LP-Section">
|
||||
<h1 class="LP-Headline">{% trans 'Map links' %}</h1>
|
||||
{% include 'partials/osm_map.html' with config=mapping_config%}
|
||||
<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|safe}},{{place.longitude|safe}}" 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|safe}},{{place.longitude|safe}}&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|safe}}&mlon={{place.longitude|safe}}&zoom=16" class="LP-Link"><span class="LP-Text">OSM</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="LP-Headline">{% translate 'Map links' %}</h1>
|
||||
{% partial osm_map config=mapping_config %}
|
||||
<ul class="LP-LinkList">
|
||||
<li class="LP-LinkList__Item">
|
||||
<a target="_blank" href="https://www.google.com/maps?q={{place.latitude|safe}},{{place.longitude|safe}}" class="LP-Link">
|
||||
<span class="LP-Text RV-Iconized">
|
||||
<span class="RV-Iconized__Text">
|
||||
Google Maps
|
||||
</span>
|
||||
<span class="RV-Iconized__Icon RV-Iconized__Icon--left">
|
||||
<i class="mdi mdi-map-outline"></i>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="LP-LinkList__Item">
|
||||
<a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude|safe}},{{place.longitude|safe}}&icon=true&bg=dop" class="LP-Link">
|
||||
<span class="LP-Text RV-Iconized">
|
||||
<span class="RV-Iconized__Text">
|
||||
TIM Online
|
||||
</span>
|
||||
<span class="RV-Iconized__Icon RV-Iconized__Icon--left">
|
||||
<i class="mdi mdi-map-outline"></i>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="LP-LinkList__Item">
|
||||
<a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude|safe}}&mlon={{place.longitude|safe}}&zoom=16" class="LP-Link">
|
||||
<span class="LP-Text RV-Iconized">
|
||||
<span class="RV-Iconized__Text">
|
||||
OSM
|
||||
</span>
|
||||
<span class="RV-Iconized__Icon RV-Iconized__Icon--left">
|
||||
<i class="mdi mdi-map-outline"></i>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class=" LP-Section">
|
||||
<h1 class="LP-Headline">{% trans 'Photo albums' %}</h1>
|
||||
<h1 class="LP-Headline">{% translate 'Photo albums' %}</h1>
|
||||
<div class="LP-LinkList">
|
||||
<ul class="LP-LinkList__Container">
|
||||
{% for photo_album in place.photoalbums.all %}
|
||||
@ -81,7 +127,7 @@
|
||||
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">{% trans 'Add photo album' %}</span>
|
||||
<span class="RV-Iconized__Text">{% translate 'Add photo album' %}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
@ -90,30 +136,8 @@
|
||||
</section>
|
||||
|
||||
<section class="LP-Section">
|
||||
<h1 class="LP-Headline">{% trans 'Images' %}</h1>
|
||||
<div class="LP-ImageGrid">
|
||||
<ul class="LP-ImageGrid__Container">
|
||||
{% for place_image in place.placeimages.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>
|
||||
{% if user.explorer == place_image.submitted_by or user.explorer == place.submitted_by %}
|
||||
<span class="LP-ImageGrid__DeleteItem" title="Bild löschen">
|
||||
<a href="{% url 'place_image_delete' pk=place_image.id %}" class="LP-Link">
|
||||
<img class="LP-Icon" src="{% static 'icons/cancel.svg' %}" />
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="LP-ImageGrid__Item LP-ImageGrid__Item--add" title="Bild hinzufügen">
|
||||
<a class="LP-Link" href="{% url 'place_image_create' place_id=place.id%}">
|
||||
<img class="LP-Icon" src="{% static 'icons/plus.svg' %}" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% translate 'Images' as headline %}
|
||||
{% partial "placeImageGrid" image_list=place.placeimages.all %}
|
||||
</section>
|
||||
|
||||
</article>
|
||||
|
@ -7,49 +7,17 @@
|
||||
<script src="{% static 'maps/ol.js' %}"></script>
|
||||
{% endblock additional_head %}
|
||||
|
||||
{% block title %}{% trans 'Lost Places' %}{% endblock %}
|
||||
{% block title %}{% translate 'All Places' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
{% include 'partials/osm_map.html' with config=mapping_config %}
|
||||
{% include 'partials/osm_map.html' with config=mapping_config modifier='wide' %}
|
||||
<div class="LP-PlaceList">
|
||||
<h1 class="LP-Headline">{% trans 'Listing our places' %}</h1>
|
||||
<h1 class="LP-Headline">{% translate 'Our lost places' %}</h1>
|
||||
<ul class="LP-PlaceList__List">
|
||||
{% for place in place_list %}
|
||||
<li class="LP-PlaceList__Item">
|
||||
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
|
||||
<article class="LP-PlaceTeaser LP-PlaceTeaser--extended">
|
||||
<div class="LP-PlaceTeaser__Image">
|
||||
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url }}" />
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Meta">
|
||||
<div class="LP-PlaceTeaser__Info">
|
||||
<span class="LP-PlaceTeaser__Title">
|
||||
<h2 class="LP-Headline LP-Headline--teaser">{{place.name}}</h2>
|
||||
</span>
|
||||
<span class="LP-PlaceTeaser__Detail">
|
||||
<p class="LP-Paragraph">{{place.location}}</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Description">
|
||||
<p class="LP-Paragraph">
|
||||
{% if place.description|length > 210 %}
|
||||
{{place.description|truncatechars:210|truncatewords:-1}}
|
||||
{% else %}
|
||||
{{place.description}}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="LP-PlaceTeaser__Icons">
|
||||
<ul class="LP-Icon__List">
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/favourite.svg' %}" /></li>
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/location.svg' %}" /></li>
|
||||
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</a>
|
||||
{% include 'partials/place_teaser.html' with place=place extended=True %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -1,13 +1,14 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
# {% block title %}{% trans 'Update place' %}{% endblock %}
|
||||
# {% block title %}{% translate 'Edit place' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<form class="LP-Form" method="POST" enctype="multipart/form-data">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Update place' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Edit place' %}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
@ -38,8 +39,17 @@
|
||||
{% include 'partials/form/inputField.html' with field=form.filename %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.placeimages.all|length > 0 %}
|
||||
<legend class="LP-Form__Legend">{% translate 'Set Hero Image' %}</legend>
|
||||
<div class="LP-Form__Composition">
|
||||
<div class="LP-Form__Field">
|
||||
{% render_field form.hero container_class='LP-ImageGrid__Container' item_class='LP-ImageGrid__Item LP-Select' current_selected_value=object.hero.id%}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% trans 'Update' as action %}
|
||||
{% translate 'Update' as action %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
{% block maincontent %}
|
||||
<form class="LP-Form" method="POST" enctype="multipart/form-data">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Submit images to a place' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Submit images to a place' %}</legend>
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="LP-Form__Composition">
|
||||
|
@ -1,13 +1,13 @@
|
||||
{% extends 'global.html'%}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
# {% block title %}{% trans 'Login' %}{% endblock %}
|
||||
# {% block title %}{% translate 'Login' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
<form class="LP-Form" method="POST" enctype="multipart/form-data">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Login' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Login' %}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
@ -20,12 +20,12 @@
|
||||
|
||||
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||
<div class="LP-Form__Field LP-Form__Button LP-Input">
|
||||
<button class="LP-Button">{% trans 'Login' %}</button>
|
||||
<button class="LP-Button">{% translate 'Login' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<p class="LP-Headline">{% trans 'Have No Account Yet?' %} <a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">{% trans 'Sign Up' %}</span></a></p>
|
||||
<p class="LP-Headline">{% translate 'No account, yet?' %} <a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">{% translate 'Sign up' %}</span></a></p>
|
||||
|
||||
{% endblock maincontent %}
|
@ -3,13 +3,13 @@
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
# {% block title %}{% trans 'Sign up' %}{% endblock %}
|
||||
# {% block title %}{% translate 'Sign up' %}{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
|
||||
<form class="LP-Form" method="POST">
|
||||
<fieldset class="LP-Form__Fieldset">
|
||||
<legend class="LP-Form__Legend">{% trans 'Sign Up' %}</legend>
|
||||
<legend class="LP-Form__Legend">{% translate 'Sign up' %}</legend>
|
||||
{% csrf_token %}
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
@ -19,6 +19,14 @@
|
||||
{% include 'partials/form/inputField.html' with field=form.email %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="LP-Form__Composition LP-Form__Composition--breakable">
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=form.first_name %}
|
||||
</div>
|
||||
<div class="LP-Form__Field">
|
||||
{% include 'partials/form/inputField.html' with field=form.last_name %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="LP-Form__Composition">
|
||||
<div class="LP-Form__Field">
|
||||
@ -38,7 +46,7 @@
|
||||
|
||||
<div class="LP-Form__Composition LP-Form__Composition--buttons">
|
||||
<div class="LP-Form__Field LP-Form__Button LP-Input">
|
||||
<button class="LP-Button">{% trans 'Sign Up' %}</button>
|
||||
<button class="LP-Button">{% translate 'Sign up' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -0,0 +1,23 @@
|
||||
<ul class="{{widget.attrs.container_class}}">
|
||||
{% for group_name, group_choices, group_index in widget.optgroups %}
|
||||
{% for option in group_choices %}
|
||||
{% if option.value %}
|
||||
<li style="display: content;">
|
||||
<input
|
||||
style="display: none;"
|
||||
name="{{option.name}}"
|
||||
id="{{widget.attrs.id}}{{group_index}}"
|
||||
type="radio"
|
||||
value="{{option.value.instance.id}}"
|
||||
{% if widget.attrs.current_selected_value|stringformat:'s' == option.value.instance.id|stringformat:'s' %}checked="checked"{% endif %}
|
||||
/>
|
||||
<label class="{{widget.attrs.item_class}}" for="{{widget.attrs.id}}{{group_index}}">
|
||||
{% if widget.attrs.item_template %}
|
||||
{% include widget.attrs.item_template with object=option.value.instance %}
|
||||
{% endif %}
|
||||
</label>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
@ -6,6 +6,16 @@ from django.http import request
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def remove_formatting(string):
|
||||
if type(string) is str:
|
||||
for to_strip in ["'", '"', ' ']:
|
||||
string = string.strip(to_strip)
|
||||
|
||||
for to_remove in ['\t', '\n',]:
|
||||
string = string.replace(to_remove, '')
|
||||
|
||||
return string
|
||||
|
||||
@register.filter(name='proper_paginate')
|
||||
def proper_paginate(paginator, current_page, neighbors=2):
|
||||
if paginator.num_pages > 2*neighbors:
|
||||
@ -25,11 +35,96 @@ def proper_paginate(paginator, current_page, neighbors=2):
|
||||
return page_list[:(2*neighbors + 1)]
|
||||
return paginator.page_range
|
||||
|
||||
@register.simple_tag
|
||||
def can_modify_place_asset(place_asset):
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
if request.user == ( place_asset.place.submitted_by or place_asset.submitted_by):
|
||||
return True
|
||||
@register.filter
|
||||
def addstr(value, parameter):
|
||||
return str(value) + str(parameter)
|
||||
|
||||
class VariableNode(template.Node):
|
||||
def __init__(self, name, content=None):
|
||||
if content==None and '=' in name:
|
||||
self.content = name.split('=')[1]
|
||||
self.name = name.split('=')[0]
|
||||
else:
|
||||
self.content = content
|
||||
self.name = name
|
||||
|
||||
def render(self, context):
|
||||
if type(self.content) is not str:
|
||||
self.content = self.content.render(context)
|
||||
try:
|
||||
self.content = template.Variable(self.content).resolve(context)
|
||||
except template.VariableDoesNotExist:
|
||||
pass
|
||||
|
||||
self.content = remove_formatting(self.content)
|
||||
context[self.name] = self.content
|
||||
return ''
|
||||
|
||||
class PartialNode(template.Node):
|
||||
def __init__(self, name, block):
|
||||
self.name = remove_formatting(name)
|
||||
self.block = block
|
||||
|
||||
def render(self, context):
|
||||
if type(self.block) is template.NodeList:
|
||||
self.block = self.block.render(context)
|
||||
|
||||
self.block = remove_formatting(self.block)
|
||||
|
||||
context = context.flatten()
|
||||
context['block'] = self.block
|
||||
t = template.loader.get_template('partials/%s.html' % self.name)
|
||||
return t.render(context)
|
||||
|
||||
@register.tag(name='set')
|
||||
def set_block(parser, token):
|
||||
split = token.split_contents()
|
||||
if len(split) >= 1:
|
||||
variable_name = split[1]
|
||||
else:
|
||||
return False
|
||||
raise template.TemplateSyntaxError('%r expects a variable name' % split[0])
|
||||
|
||||
if len(split) == 2:
|
||||
content=None
|
||||
if '=' not in variable_name:
|
||||
content = parser.parse(('end%s'%split[0],))
|
||||
parser.delete_first_token()
|
||||
return VariableNode(variable_name, content)
|
||||
else:
|
||||
return VariableNode(variable_name, split[2])
|
||||
|
||||
@register.tag(name='partial')
|
||||
def partial(parser, token):
|
||||
split = token.split_contents()
|
||||
if len(split) >= 1:
|
||||
partial_name = split[1]
|
||||
else:
|
||||
raise template.TemplateSyntaxError('%r expects a partial name' % split[0])
|
||||
|
||||
if len(split) == 2:
|
||||
block_tag = False
|
||||
for token in reversed(parser.tokens):
|
||||
if 'end%s'%split[0] in token.contents:
|
||||
block_tag = True
|
||||
break
|
||||
|
||||
if split[0] in token.contents:
|
||||
break
|
||||
|
||||
if block_tag:
|
||||
nodeList = parser.parse(('end%s'%split[0],))
|
||||
parser.delete_first_token()
|
||||
else:
|
||||
nodeList = template.NodeList()
|
||||
return PartialNode(partial_name, nodeList)
|
||||
else:
|
||||
nodeList = template.NodeList()
|
||||
for parameter in split[2:]:
|
||||
if '=' in parameter:
|
||||
nodeList.append(
|
||||
VariableNode(name=parameter)
|
||||
)
|
||||
else:
|
||||
raise template.TemplateSyntaxError('%r expects kwargs, got args' % split[0])
|
||||
return PartialNode(partial_name, nodeList)
|
||||
|
@ -4,15 +4,15 @@ from django import forms
|
||||
from django.utils import timezone
|
||||
|
||||
from lostplaces.tests.forms import FormTestCase
|
||||
from lostplaces.forms import ExplorerCreationForm
|
||||
from lostplaces.forms import SignupVoucherForm
|
||||
from lostplaces.models.models import Voucher
|
||||
|
||||
class ExplorerCreationFormTestCase(FormTestCase):
|
||||
class SignupVoucherFormTestCase(FormTestCase):
|
||||
"""
|
||||
This test case only tests for the voucher since all other aspects don't realy matter
|
||||
to this project and are already tested by django
|
||||
"""
|
||||
form = ExplorerCreationForm
|
||||
form = SignupVoucherForm
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@ -37,7 +37,7 @@ class ExplorerCreationFormTestCase(FormTestCase):
|
||||
)
|
||||
|
||||
def test_validation_valid(self):
|
||||
form = ExplorerCreationForm(self.post_data)
|
||||
form = SignupVoucherForm(self.post_data)
|
||||
self.assertTrue(
|
||||
form.is_valid(),
|
||||
msg='Expecting the %s to validate' % (
|
||||
@ -49,7 +49,7 @@ class ExplorerCreationFormTestCase(FormTestCase):
|
||||
self.post_data = {
|
||||
'voucher': 'Imanotacode123'
|
||||
}
|
||||
form = ExplorerCreationForm(self.post_data)
|
||||
form = SignupVoucherForm(self.post_data)
|
||||
self.assertFalse(
|
||||
form.is_valid(),
|
||||
msg='Expecting the %s to not validate' % (
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
@ -16,6 +17,20 @@ class ViewTestCase(TestCase):
|
||||
'''
|
||||
view = None
|
||||
|
||||
def assertMessage(self, response, message_text, message_type, msg=None):
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""<div.*message.*%s.*>.*%s.*</div>""" % (
|
||||
message_type.lower(),
|
||||
message_text.lower()
|
||||
),
|
||||
response.content.decode().replace('\n', '').lower()
|
||||
),
|
||||
msg
|
||||
)
|
||||
|
||||
|
||||
def assertContext(self, response, key, value=None):
|
||||
'''
|
||||
Checks weather the response's context has the given key
|
||||
@ -87,7 +102,7 @@ class ViewTestCase(TestCase):
|
||||
self.assertEqual(
|
||||
response['location'],
|
||||
redirect_to,
|
||||
msg='Expecting the response to redirect to %s, where redirected to %s instea' % (
|
||||
msg='Expecting the response to redirect to %s, where redirected to %s instead' % (
|
||||
str(redirect_to),
|
||||
str(response['location'])
|
||||
)
|
||||
@ -113,6 +128,84 @@ class ViewTestCase(TestCase):
|
||||
|
||||
def assertHttpMethodNotAllowed(self, response):
|
||||
self.assertHttpCode(response, 405)
|
||||
|
||||
class GlobalTemplateTestCaseMixin:
|
||||
|
||||
def assertGlobal(self, response):
|
||||
self.assertLogo(response)
|
||||
self.assertMenu(response)
|
||||
self.assertProfileMenu(response)
|
||||
|
||||
def assertLogo(self, response):
|
||||
"""
|
||||
Looks for an image tag with with 'logo' or 'Logo' in the file name
|
||||
all within an <header> tag.
|
||||
"""
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""<header.*>.*<img.*src=("|').*(logo|Logo).*("|').*>.*</header>""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg='Expecting a header containing a logo with \'logo\' in the source name'
|
||||
)
|
||||
|
||||
def assertMenu(self, response):
|
||||
"""
|
||||
Looks for an '<nav>' tag containing a link to / and a link labeled 'urbex code.*'
|
||||
"""
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""<nav.*>.*<a.*href=("|')/("|').*>.*<a.*>.*urbex code.*</nav>""",
|
||||
response.content.decode().replace('\n', '').lower()
|
||||
),
|
||||
msg='Expecting an menu containing a link to the homepage and a link to the urbex codex'
|
||||
)
|
||||
|
||||
def assertMenuItem(self, response, label=None, url=None):
|
||||
"""
|
||||
Checks for an additional menu item by label or url. Regex can be passed.
|
||||
"""
|
||||
if label is None and url is None:
|
||||
raise ValueError("Either a label or url has to be provided")
|
||||
|
||||
found_label = None
|
||||
if label != None:
|
||||
label = label.lower()
|
||||
found_label = re.search(
|
||||
"""<nav.*>.*<a.*>.*%s.*</a>.*</nav>""" % label,
|
||||
response.content.decode().replace('\n', '').lower()
|
||||
)
|
||||
|
||||
found_url = None
|
||||
if url != None:
|
||||
url = url.lower()
|
||||
found_url = re.search(
|
||||
"""<nav.*>.*<a.*href=("|')%s("|')>.*</a>.*</nav>""" % url,
|
||||
response.content.decode().replace('\n', '')
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
found_label != None and found_url != None,
|
||||
msg='Expecting an menu item with either label \'%s\' or url \'%s\'' % (
|
||||
label,
|
||||
url
|
||||
)
|
||||
)
|
||||
|
||||
def assertProfileMenu(self, response):
|
||||
"""
|
||||
Looks for a logout or login+signup link within the an header tag
|
||||
"""
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""<header.*>.*(<a.*href=("|').*logout.*("|')|<a.*href=("|').*login.*("|').*<a.*href=("|').*signup.*("|')).*</header>""",
|
||||
response.content.decode().replace('\n', '').lower()
|
||||
),
|
||||
msg='Expecting a profile menu containing either a logout link or a login and then a signup link'
|
||||
)
|
||||
|
||||
class TaggableViewTestCaseMixin:
|
||||
|
||||
@ -149,13 +242,13 @@ class TaggableViewTestCaseMixin:
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
'submit_url_name' in context,
|
||||
msg='Expecting the context for taggable to contain \'submit_url_name\' attribute'
|
||||
'submit_url' in context,
|
||||
msg='Expecting the context for taggable to contain \'submit_url\' attribute'
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
type(context['submit_url_name']) == str,
|
||||
msg='Expecting submit_url_name to be of type string'
|
||||
type(context['submit_url']) == str,
|
||||
msg='Expecting submit_url to be of type string'
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
|
@ -1,15 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
23238#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
from django.urls import reverse_lazy
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.contrib.messages.storage.fallback import FallbackStorage
|
||||
from django.utils import timezone
|
||||
from django.shortcuts import render
|
||||
|
||||
from lostplaces.models import Place
|
||||
from lostplaces.views import IsAuthenticatedMixin
|
||||
from lostplaces.views import (
|
||||
IsAuthenticatedMixin,
|
||||
PlaceAssetCreateView
|
||||
)
|
||||
from lostplaces.tests.views import ViewTestCase
|
||||
from lostplaces.models import DummyAsset
|
||||
|
||||
class TestIsAuthenticated(ViewTestCase):
|
||||
view = IsAuthenticatedMixin
|
||||
@ -20,9 +26,6 @@ class TestIsAuthenticated(ViewTestCase):
|
||||
username='testpeter',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_logged_in(self):
|
||||
request = RequestFactory().get('/')
|
||||
@ -33,16 +36,15 @@ class TestIsAuthenticated(ViewTestCase):
|
||||
self.assertHttpMethodNotAllowed(response)
|
||||
|
||||
def test_not_logged_in(self):
|
||||
request = RequestFactory().get('/someurl1234')
|
||||
request = RequestFactory().get('/')
|
||||
request.user = AnonymousUser()
|
||||
request.session = 'session'
|
||||
messages = FallbackStorage(request)
|
||||
request._messages = messages
|
||||
|
||||
response = IsAuthenticatedMixin.as_view()(request)
|
||||
self.assertHttpRedirect(response, '?'.join([str(reverse_lazy('login')), 'next=/someurl1234']))
|
||||
self.assertHttpRedirect(response, '?'.join([str(reverse_lazy('login')), 'next=/']))
|
||||
|
||||
response = self.client.get(response['Location'])
|
||||
self.assertTrue(len(messages) > 0)
|
||||
|
||||
class TestIsPlaceSubmitterMixin(TestCase):
|
||||
@ -86,4 +88,52 @@ class TestIsPlaceSubmitterMixin(TestCase):
|
||||
response = self.client.get(reverse_lazy('place_edit', kwargs={'pk': 1}))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertTrue(response.context['messages'])
|
||||
self.assertTrue(len(response.context['messages']) > 0)
|
||||
self.assertTrue(len(response.context['messages']) > 0)
|
||||
|
||||
class TestPlaceAssetCreateView(ViewTestCase):
|
||||
view = PlaceAssetCreateView
|
||||
|
||||
class DummyAssetCreateView(PlaceAssetCreateView):
|
||||
model = DummyAsset
|
||||
fields = ['name']
|
||||
template_name = 'flat_form.html'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = User.objects.create_user(
|
||||
username='testpeter',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
place = Place.objects.create(
|
||||
name='Im a place',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry'
|
||||
)
|
||||
|
||||
def test_create_asset(self):
|
||||
request = RequestFactory().post(
|
||||
'/',
|
||||
{
|
||||
'name': 'im a place'
|
||||
}
|
||||
)
|
||||
request.user = User.objects.get(id=1)
|
||||
|
||||
response = TestPlaceAssetCreateView.DummyAssetCreateView.as_view()(request, place_id=1)
|
||||
|
||||
self.assertTrue(
|
||||
DummyAsset.objects.all().count() >= 1,
|
||||
msg='Expecting a dummy place asset to be created by the view'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
DummyAsset.objects.get(id=1).name,
|
||||
'im a place',
|
||||
msg='Expecting the dummy place asset to have the correct data'
|
||||
)
|
||||
|
@ -1,10 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
from os import path
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
from lostplaces.models import Place
|
||||
@ -17,62 +22,62 @@ from lostplaces.forms import PlaceImageForm, PlaceForm
|
||||
from lostplaces.tests.views import (
|
||||
ViewTestCase,
|
||||
TaggableViewTestCaseMixin,
|
||||
MapableViewTestCaseMixin
|
||||
MapableViewTestCaseMixin,
|
||||
GlobalTemplateTestCaseMixin
|
||||
)
|
||||
|
||||
|
||||
class TestPlaceCreateView(ViewTestCase):
|
||||
view = PlaceCreateView
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = User.objects.create_user(
|
||||
username='testpeter',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
place = Place.objects.create(
|
||||
name='Im a place',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry'
|
||||
)
|
||||
place.tags.add('I a tag', 'testlocation')
|
||||
place.save()
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_has_forms(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_create'))
|
||||
|
||||
self.assertHasForm(response, 'place_image_form', PlaceImageForm)
|
||||
self.assertHasForm(response, 'place_form', PlaceForm)
|
||||
|
||||
|
||||
class TestPlaceListView(ViewTestCase):
|
||||
class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
|
||||
"""
|
||||
Tests the view listing all placs
|
||||
"""
|
||||
view = PlaceListView
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = User.objects.create_user(
|
||||
username='testpeter',
|
||||
password='Develop123',
|
||||
)
|
||||
user.explorer.level = 3
|
||||
user.explorer.save()
|
||||
|
||||
# default level should be 1, not setting required
|
||||
other_user = User.objects.create_user(
|
||||
username='blubberbernd',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
place = Place.objects.create(
|
||||
name='Im a place',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry'
|
||||
superuser = User.objects.create_user(
|
||||
username='toor',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
superuser.is_superuser = True
|
||||
superuser.save()
|
||||
|
||||
Place.objects.create(
|
||||
name='Im a own place',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=other_user.explorer,
|
||||
location='Test %d town' % 5,
|
||||
latitude=50.5 + 5/10,
|
||||
longitude=7.0 - 5/10,
|
||||
description='This is just a test, do not worry %d' % 5,
|
||||
level=3
|
||||
)
|
||||
|
||||
for i in range(12):
|
||||
place = Place.objects.create(
|
||||
name='Im a place %d' % i,
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Test %d town' % i,
|
||||
latitude=50.5 + i/10,
|
||||
longitude=7.0 - i/10,
|
||||
description='This is just a test, do not worry %d' % i,
|
||||
level=3
|
||||
)
|
||||
place.tags.add('I a tag', 'testlocation')
|
||||
place.save()
|
||||
|
||||
@ -84,7 +89,279 @@ class TestPlaceListView(ViewTestCase):
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
self.assertContext(response, 'mapping_config')
|
||||
self.assertGlobal(response)
|
||||
|
||||
def test_first_places(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
for i in range(1, 4):
|
||||
place = Place.objects.get(id=i)
|
||||
self.assertTrue(
|
||||
place.name in response.content.decode(),
|
||||
msg='Expecting the %dth place name to be on the first page of list' % i
|
||||
)
|
||||
self.assertTrue(
|
||||
place.description in response.content.decode(),
|
||||
msg='Expecting the %dth place description to be on the first page of list' % i
|
||||
)
|
||||
|
||||
def test_pagination(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""<ul.*pagination.*>\s*(<li.*>.*</li>\s*){6,}</ul>""",
|
||||
response.content.decode().replace('\n', '').lower()
|
||||
),
|
||||
msg='Expecting the place list to be paginated like [first] [previous] [item] at least 2 times [next] [last]'
|
||||
)
|
||||
|
||||
def test_not_eligible_to_see_because_of_low_level(self):
|
||||
self.client.login(username='blubberbernd', password='Develop123')
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
self.assertFalse(
|
||||
'Im a place' in response.content.decode(),
|
||||
msg='Expecting the user to not see any places'
|
||||
)
|
||||
|
||||
def test_not_eligible_to_see_because_of_low_level_superuser(self):
|
||||
self.client.login(username='toor', password='Develop123')
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
self.assertTrue(
|
||||
'Im a place' in response.content.decode(),
|
||||
msg='Expecting the superuser to see all places'
|
||||
)
|
||||
|
||||
def test_not_eligible_to_see_because_of_low_level_own_place(self):
|
||||
self.client.login(username='blubberbernd', password='Develop123')
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
self.assertTrue(
|
||||
'Im a own place' in response.content.decode(),
|
||||
msg='Expecting the user to see it\'s own places'
|
||||
)
|
||||
|
||||
def test_eligible_to_see(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_list'))
|
||||
|
||||
self.assertTrue(
|
||||
'Im a own place' in response.content.decode(),
|
||||
msg='Expecting the user to see places where their level is high enough'
|
||||
)
|
||||
|
||||
class TestPlaceCreateView(ViewTestCase):
|
||||
view = PlaceCreateView
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = User.objects.create_user(
|
||||
username='testpeter',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_has_forms(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_create'))
|
||||
|
||||
self.assertHasForm(response, 'place_image_form', PlaceImageForm)
|
||||
self.assertHasForm(response, 'place_form', PlaceForm)
|
||||
|
||||
def test_positive_no_image(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.post(
|
||||
reverse('place_create'),
|
||||
data={
|
||||
'name': 'test place 486',
|
||||
'location': 'test location',
|
||||
'latitude': 45.804192,
|
||||
'longitude': 1.860222,
|
||||
'description': """
|
||||
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
|
||||
"""
|
||||
},
|
||||
follow=True
|
||||
)
|
||||
self.assertHttpOK(response)
|
||||
place = Place.objects.get(name='test place 486')
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
place,
|
||||
msg='Submitted place not found in database / model'
|
||||
),
|
||||
self.assertEqual(
|
||||
reverse(
|
||||
'place_detail', kwargs={'pk': place.id}
|
||||
),
|
||||
response.redirect_chain[-1][0]
|
||||
)
|
||||
self.assertMessage(
|
||||
response,
|
||||
_('Successfully created place'),
|
||||
'success',
|
||||
msg='Expecting a visible success message'
|
||||
)
|
||||
|
||||
def test_positive_image(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
image = open(
|
||||
path.join(
|
||||
settings.BASE_DIR,
|
||||
'testdata',
|
||||
'test_image.jpg'
|
||||
),
|
||||
'rb'
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse('place_create'),
|
||||
data={
|
||||
'name': 'test place 894',
|
||||
'location': 'test location',
|
||||
'latitude': 45.804192,
|
||||
'longitude': 1.860222,
|
||||
'description': """
|
||||
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
|
||||
""",
|
||||
'filename': [image]
|
||||
},
|
||||
follow=True
|
||||
)
|
||||
self.assertHttpOK(response)
|
||||
place = Place.objects.get(name='test place 894')
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
place,
|
||||
msg='Submitted place not found in database / model'
|
||||
),
|
||||
self.assertEqual(
|
||||
reverse(
|
||||
'place_detail', kwargs={'pk': place.id}
|
||||
),
|
||||
response.redirect_chain[-1][0]
|
||||
)
|
||||
self.assertEqual(
|
||||
len(place.placeimages.all()),
|
||||
1,
|
||||
msg='Expecting the place to have exactly 1 place image'
|
||||
)
|
||||
self.assertMessage(
|
||||
response,
|
||||
_('Successfully created place'),
|
||||
'success',
|
||||
msg='Expecting a visible success message'
|
||||
)
|
||||
|
||||
def test_negative_no_name(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.post(
|
||||
reverse('place_create'),
|
||||
{
|
||||
'location': 'test location 456',
|
||||
'latitude': 45.804192,
|
||||
'longitude': 1.860222,
|
||||
'description': """
|
||||
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
|
||||
"""
|
||||
}
|
||||
)
|
||||
self.assertHttpBadRequest(response)
|
||||
self.assertEqual(
|
||||
len(Place.objects.filter(location='test location 456')),
|
||||
0,
|
||||
msg='Expecting no place to be created'
|
||||
)
|
||||
self.assertMessage(
|
||||
response,
|
||||
_('Please fill in all required fields.'),
|
||||
'error',
|
||||
msg='Expecing a visible error message'
|
||||
)
|
||||
|
||||
def test_negative_no_location(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.post(
|
||||
reverse('place_create'),
|
||||
{
|
||||
'name': 'test name 376',
|
||||
'latitude': 45.804192,
|
||||
'longitude': 1.860222,
|
||||
'description': """
|
||||
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
|
||||
"""
|
||||
}
|
||||
)
|
||||
self.assertHttpBadRequest(response)
|
||||
self.assertEqual(
|
||||
len(Place.objects.filter(name='test name 376')),
|
||||
0,
|
||||
msg='Expecting no place to be created'
|
||||
)
|
||||
self.assertMessage(
|
||||
response,
|
||||
_('Please fill in all required fields.'),
|
||||
'error',
|
||||
msg='Expecing a visible error message'
|
||||
)
|
||||
|
||||
def test_negative_no_latitude(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.post(
|
||||
reverse('place_create'),
|
||||
{
|
||||
'name': 'test name 417',
|
||||
'location': 'location',
|
||||
'longitude': 1.860222,
|
||||
'description': """
|
||||
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
|
||||
"""
|
||||
}
|
||||
)
|
||||
self.assertHttpBadRequest(response)
|
||||
self.assertEqual(
|
||||
len(Place.objects.filter(name='test name 417')),
|
||||
0,
|
||||
msg='Expecting no place to be created'
|
||||
)
|
||||
self.assertMessage(
|
||||
response,
|
||||
_('Please fill in all required fields.'),
|
||||
'error',
|
||||
msg='Expecing a visible error message'
|
||||
)
|
||||
|
||||
def test_negative_no_longitude(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.post(
|
||||
reverse('place_create'),
|
||||
{
|
||||
'name': 'test name 417',
|
||||
'location': 'location',
|
||||
'latitude': 45.804192,
|
||||
'description': """
|
||||
Cupiditate harum reprehenderit ipsam iure consequuntur eaque eos reiciendis. Blanditiis vel minima minus repudiandae voluptate aut quia sed. Provident ex omnis illo molestiae. Ullam eos et est provident enim deserunt.
|
||||
"""
|
||||
}
|
||||
)
|
||||
self.assertHttpBadRequest(response)
|
||||
self.assertEqual(
|
||||
len(Place.objects.filter(name='test name 417')),
|
||||
0,
|
||||
msg='Expecting no place to be created'
|
||||
)
|
||||
self.assertMessage(
|
||||
response,
|
||||
_('Please fill in all required fields.'),
|
||||
'error',
|
||||
msg='Expecing a visible error message'
|
||||
)
|
||||
|
||||
class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixin, ViewTestCase):
|
||||
view = PlaceDetailView
|
||||
@ -95,6 +372,8 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
|
||||
username='testpeter',
|
||||
password='Develop123'
|
||||
)
|
||||
user.explorer.level = 3
|
||||
user.explorer.save()
|
||||
|
||||
place = Place.objects.create(
|
||||
name='Im a place',
|
||||
@ -103,11 +382,85 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry'
|
||||
description='This is just a test, do not worry',
|
||||
level=3
|
||||
)
|
||||
place.tags.add('I a tag', 'testlocation')
|
||||
place.save()
|
||||
|
||||
other_user = User.objects.create_user(
|
||||
username='blubberbernd',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
superuser = User.objects.create_user(
|
||||
username='toor',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
superuser.is_superuser = True
|
||||
superuser.save()
|
||||
|
||||
Place.objects.create(
|
||||
name='Im a own place',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=other_user.explorer,
|
||||
location='Test %d town' % 5,
|
||||
latitude=50.5 + 5/10,
|
||||
longitude=7.0 - 5/10,
|
||||
description='This is just a test, do not worry %d' % 5,
|
||||
level=3
|
||||
)
|
||||
|
||||
def test_not_eligible_to_see_because_of_low_level(self):
|
||||
self.client.login(username='blubberbernd', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
|
||||
self.assertFalse(
|
||||
'Im a place' in response.content.decode(),
|
||||
msg='Expecting the user to not see the places'
|
||||
)
|
||||
|
||||
def test_not_eligible_to_see_because_of_low_level_superuser(self):
|
||||
self.client.login(username='toor', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
|
||||
self.assertTrue(
|
||||
'Im a place' in response.content.decode(),
|
||||
msg='Expecting the superuser to see all places'
|
||||
)
|
||||
|
||||
def test_not_eligible_to_see_because_of_low_level_own_place(self):
|
||||
self.client.login(username='blubberbernd', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
|
||||
|
||||
self.assertTrue(
|
||||
'Im a own place' in response.content.decode(),
|
||||
msg='Expecting the user to see it\'s own places'
|
||||
)
|
||||
|
||||
def test_eligible_to_see(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 2}))
|
||||
|
||||
self.assertTrue(
|
||||
'Im a own place' in response.content.decode(),
|
||||
msg='Expecting the user to see places where their level is high enough'
|
||||
)
|
||||
|
||||
def test_not_authenticated(self):
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
self.assertHttpRedirect(response)
|
||||
|
||||
self.assertEqual(
|
||||
'%s?next=%s' % (
|
||||
reverse('login'),
|
||||
reverse('place_detail', kwargs={'pk': 1})
|
||||
),
|
||||
response.url,
|
||||
msg='Expecting unauthenticated user to be redirected to login page, using the \'next\' GET-Param to store the places details page'
|
||||
)
|
||||
|
||||
def test_context(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
@ -127,3 +480,131 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
|
||||
)
|
||||
)
|
||||
self.assertMapableContext(response.context['mapping_config'])
|
||||
|
||||
def test_place_data(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
place = Place.objects.get(id=1)
|
||||
|
||||
self.assertTrue(
|
||||
place.name in response.content.decode(),
|
||||
msg='Expecting the places name to be on the page'
|
||||
)
|
||||
self.assertTrue(
|
||||
place.description in response.content.decode(),
|
||||
msg='Expecting the places description to be on the page'
|
||||
)
|
||||
self.assertTrue(
|
||||
str(place.latitude) in response.content.decode(),
|
||||
msg='Expecting the places latitude to be on the page'
|
||||
)
|
||||
self.assertTrue(
|
||||
str(place.longitude) in response.content.decode(),
|
||||
msg='Expecting the places longitude to be on the page'
|
||||
)
|
||||
|
||||
def test_favorite(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
|
||||
user = User.objects.get(username='testpeter')
|
||||
place = Place.objects.get(id=1)
|
||||
|
||||
self.assertTrue(
|
||||
reverse('place_favorite', kwargs={'place_id': 1}) in response.content.decode(),
|
||||
msg='Expecting the (right) create favorite url to be on the page '
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('place_favorite', kwargs={'place_id': 1}),
|
||||
follow=True
|
||||
)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
self.assertTrue(
|
||||
reverse('place_unfavorite', kwargs={'place_id': 1}) in response.content.decode(),
|
||||
msg='Expecting the (right) delete favorite url to be on the page '
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
place in user.explorer.favorite_places.all(),
|
||||
msg='Expecting the place to be in the explorers favorites'
|
||||
)
|
||||
self.assertTrue(
|
||||
user.explorer in place.explorer_favorites.all(),
|
||||
msg='Expecting the explorere to be in the reverse list of favorites'
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('place_unfavorite', kwargs={'place_id': 1}),
|
||||
follow=True
|
||||
)
|
||||
self.assertHttpOK(response)
|
||||
self.assertTrue(
|
||||
reverse('place_favorite', kwargs={'place_id': 1}) in response.content.decode(),
|
||||
msg='Expecting the (right) create favorite url to be on the page after unfavorite'
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
place not in user.explorer.favorite_places.all(),
|
||||
msg='Expecting the place to not be in the explorers favorites after unfavorite'
|
||||
)
|
||||
self.assertTrue(
|
||||
user.explorer not in place.explorer_favorites.all(),
|
||||
msg='Expecting the explorere to not be in the reverse list of favorites after unfavorite'
|
||||
)
|
||||
|
||||
|
||||
def test_visit(self):
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
|
||||
|
||||
user = User.objects.get(username='testpeter')
|
||||
place = Place.objects.get(id=1)
|
||||
|
||||
self.assertTrue(
|
||||
reverse('place_visit_create', kwargs={'place_id': 1}) in response.content.decode(),
|
||||
msg='Expecting the (right) create visit url to be on the page '
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('place_visit_create', kwargs={'place_id': 1}),
|
||||
follow=True
|
||||
)
|
||||
self.assertHttpOK(response)
|
||||
|
||||
self.assertTrue(
|
||||
reverse('place_visit_delete', kwargs={'place_id': 1}) in response.content.decode(),
|
||||
msg='Expecting the (right) delete visist url to be on the page '
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
place in user.explorer.visited_places.all(),
|
||||
msg='Expecting the place to be in the explorers visits'
|
||||
)
|
||||
self.assertTrue(
|
||||
user.explorer in place.explorer_visits.all(),
|
||||
msg='Expecting the explorer to be in the reverse list of visits'
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse('place_visit_delete', kwargs={'place_id': 1}),
|
||||
follow=True
|
||||
)
|
||||
self.assertHttpOK(response)
|
||||
self.assertTrue(
|
||||
reverse('place_visit_create', kwargs={'place_id': 1}) in response.content.decode(),
|
||||
msg='Expecting the (right) create favorite url to be on the page after deleting visit'
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
place not in user.explorer.visited_places.all(),
|
||||
msg='Expecting the place to not be in the explorers visits after deleting visit'
|
||||
)
|
||||
self.assertTrue(
|
||||
user.explorer not in place.explorer_visits.all(),
|
||||
msg='Expecting the explorer to not be in the reverse list of visits after deleting visit'
|
||||
)
|
||||
|
||||
|
||||
|
156
django_lostplaces/lostplaces/tests/views/test_views.py
Normal file
@ -0,0 +1,156 @@
|
||||
import re
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
from lostplaces.models import Place
|
||||
|
||||
from lostplaces.tests.views import (
|
||||
ViewTestCase,
|
||||
GlobalTemplateTestCaseMixin
|
||||
)
|
||||
|
||||
class TestHomeView(GlobalTemplateTestCaseMixin, ViewTestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
user = User.objects.create_user(
|
||||
username='testpeter',
|
||||
password='Develop123'
|
||||
)
|
||||
|
||||
place = Place.objects.create(
|
||||
name='Im a place',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry'
|
||||
)
|
||||
place.tags.add('I a tag', 'testlocation')
|
||||
place.save()
|
||||
|
||||
# Creating a place with level one to test against
|
||||
# unauth's users and users with level 1
|
||||
Place.objects.create(
|
||||
name='Im a place level 1',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry',
|
||||
level=1
|
||||
)
|
||||
|
||||
# Creating a place with level two to test against
|
||||
# unauth's users and users above level 1
|
||||
Place.objects.create(
|
||||
name='Im a place level 2',
|
||||
submitted_when=timezone.now(),
|
||||
submitted_by=user.explorer,
|
||||
location='Testtown',
|
||||
latitude=50.5,
|
||||
longitude=7.0,
|
||||
description='This is just a test, do not worry',
|
||||
level=2
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def test_global(self):
|
||||
response = self.client.get(reverse('lostplaces_home'))
|
||||
self.assertGlobal(response)
|
||||
|
||||
def test_place_list_authenticated(self):
|
||||
"""
|
||||
Testing there is the place list containing the name,
|
||||
location and description of the latest place for
|
||||
authenticated users.
|
||||
"""
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('lostplaces_home'))
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""Im a place.*Testtown.*This is just a test, do not worry""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg='Expecting the test place to show up on the homepage'
|
||||
)
|
||||
|
||||
def test_place_list_unauthenticated(self):
|
||||
"""
|
||||
Testing there is a place list of the lates places
|
||||
containing the place names only for unauthenticated users.
|
||||
"""
|
||||
response = self.client.get(reverse('lostplaces_home'))
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""Im a place""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg='Expecting the test place to show up on the homepage'
|
||||
)
|
||||
self.assertEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""Testtown.*This is just a test, do not worry""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg='Expecting the test place to show up on the homepage'
|
||||
)
|
||||
|
||||
print(response.content.decode().replace('\n', ''))
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""Im a place level 1""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg="Expecting the level 1 places to show up on the homepage publicly"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""Im a place level 2""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg="Expecting the level 2 places to *not* show up on the homepage publicly"
|
||||
)
|
||||
|
||||
def test_map_authenticated(self):
|
||||
"""
|
||||
Testing there is a map showing all the lates places
|
||||
on a map for authenticated users.
|
||||
"""
|
||||
self.client.login(username='testpeter', password='Develop123')
|
||||
response = self.client.get(reverse('lostplaces_home'))
|
||||
self.assertNotEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""map.*7.0,.*50.5.*Im a place""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg='Expecting the test place to show up on the map'
|
||||
)
|
||||
|
||||
def test_map_unauthenticated(self):
|
||||
"""
|
||||
Testing there is no map for unauthenticated users.
|
||||
"""
|
||||
response = self.client.get(reverse('lostplaces_home'))
|
||||
self.assertEqual(
|
||||
None,
|
||||
re.search(
|
||||
"""7.0,.*50.5.*Im a place""",
|
||||
response.content.decode().replace('\n', '')
|
||||
),
|
||||
msg='Expecting the test place to show up on the map'
|
||||
)
|
@ -4,6 +4,8 @@
|
||||
from django.urls import path
|
||||
from lostplaces.views import (
|
||||
HomeView,
|
||||
FlatView,
|
||||
OSMMapView,
|
||||
PlaceDetailView,
|
||||
PlaceListView,
|
||||
PlaceCreateView,
|
||||
@ -11,27 +13,44 @@ from lostplaces.views import (
|
||||
PlaceDeleteView,
|
||||
PlaceTagDeleteView,
|
||||
PlaceTagSubmitView,
|
||||
PhotoAlbumCreateView,
|
||||
PhotoAlbumDeleteView,
|
||||
PlaceFavoriteView,
|
||||
PlaceUnfavoriteView,
|
||||
PlaceVisitCreateView,
|
||||
PlaceVisitDeleteView,
|
||||
PlaceImageCreateView,
|
||||
PlaceImageDeleteView,
|
||||
FlatView
|
||||
PhotoAlbumCreateView,
|
||||
PhotoAlbumDeleteView,
|
||||
ExplorerProfileView,
|
||||
ExplorerProfileUpdateView,
|
||||
PlaceVoteView
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('', HomeView.as_view(), name='lostplaces_home'),
|
||||
path('flat/<slug:slug>/', FlatView, name='flatpage'),
|
||||
path('osm/', OSMMapView.as_view(), name='osm'),
|
||||
|
||||
path('explorer/<int:explorer_id>/', ExplorerProfileView.as_view(), name='explorer_profile'),
|
||||
path('explorer/update/', ExplorerProfileUpdateView.as_view(), name='explorer_profile_update'),
|
||||
|
||||
path('place/', PlaceListView.as_view(), name='place_list'),
|
||||
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_image/create/<int:place_id>', PlaceImageCreateView.as_view(), name='place_image_create'),
|
||||
path('place_image/delete/<int:pk>', PlaceImageDeleteView.as_view(), name='place_image_delete'),
|
||||
path('flat/<slug:slug>/', FlatView, name='flatpage'),
|
||||
path('place/tag/create/<int:tagged_id>/', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
|
||||
path('place/tag/delete/<int:tagged_id>/<int:tag_id>/', PlaceTagDeleteView.as_view(), name='place_tag_delete'),
|
||||
path('place/fav/create/<int:place_id>/', PlaceFavoriteView.as_view(), name='place_favorite'),
|
||||
path('place/fav/delete/<int:place_id>/', PlaceUnfavoriteView.as_view(), name='place_unfavorite'),
|
||||
path('place/visit/create/<int:place_id>/', PlaceVisitCreateView.as_view(), name='place_visit_create'),
|
||||
path('place/visit/delete/<int:place_id>/', PlaceVisitDeleteView.as_view(), name='place_visit_delete'),
|
||||
|
||||
# POST-only URLs for tag submission
|
||||
path('place/tag/<int:tagged_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
|
||||
path('place/tag/delete/<int:tagged_id>/<int:tag_id>', PlaceTagDeleteView.as_view(), name='place_tag_delete')
|
||||
path('place_image/create/<int:place_id>/', PlaceImageCreateView.as_view(), name='place_image_create'),
|
||||
path('place_image/delete/<int:pk>/', PlaceImageDeleteView.as_view(), name='place_image_delete'),
|
||||
|
||||
path('place/vote/<int:place_id>/<int:vote>', PlaceVoteView.as_view(), name='place_vote'),
|
||||
|
||||
path('photo_album/create/<int:place_id>/', PhotoAlbumCreateView.as_view(), name='photo_album_create'),
|
||||
path('photo_album/delete/<int:pk>/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete')
|
||||
]
|
||||
|
@ -4,4 +4,5 @@
|
||||
from lostplaces.views.base_views import *
|
||||
from lostplaces.views.views import *
|
||||
from lostplaces.views.place_views import *
|
||||
from lostplaces.views.place_image_views import *
|
||||
from lostplaces.views.place_image_views import *
|
||||
from lostplaces.views.explorer_views import *
|
@ -4,16 +4,18 @@
|
||||
from django.views import View
|
||||
from django.views.generic.edit import CreateView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.views.generic import ListView
|
||||
|
||||
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, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from lostplaces.models import Place
|
||||
from lostplaces.common import redirect_referer_or
|
||||
|
||||
class IsAuthenticatedMixin(LoginRequiredMixin, View):
|
||||
'''
|
||||
@ -25,7 +27,8 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
|
||||
permission_denied_message = _('Please login to proceed')
|
||||
|
||||
def handle_no_permission(self):
|
||||
messages.error(self.request, self.permission_denied_message)
|
||||
if not self.request.user.is_authenticated:
|
||||
messages.error(self.request, self.permission_denied_message)
|
||||
return super().handle_no_permission()
|
||||
|
||||
class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
|
||||
@ -59,7 +62,27 @@ class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
|
||||
messages.error(self.request, self.place_submitter_error_message)
|
||||
return False
|
||||
|
||||
class IsEligibleToSeePlaceMixin(UserPassesTestMixin):
|
||||
not_eligible_to_see_message = None
|
||||
|
||||
def get_place(self):
|
||||
pass
|
||||
|
||||
def test_func(self):
|
||||
if not hasattr(self.request, 'user'):
|
||||
return False
|
||||
|
||||
if self.request.user.explorer.is_eligible_to_see(self.get_place()):
|
||||
return True
|
||||
|
||||
if self.not_eligible_to_see_message:
|
||||
messages.error(self.request, self.not_eligible_to_see_message)
|
||||
return False
|
||||
|
||||
class PlaceAssetCreateView(IsAuthenticatedMixin, SuccessMessageMixin, CreateView):
|
||||
"""
|
||||
Abstract View for creating a place asset (i.e. PlaceImage)
|
||||
"""
|
||||
model = None
|
||||
template_name = ''
|
||||
success_message = ''
|
||||
@ -108,4 +131,11 @@ class PlaceAssetDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SingleOb
|
||||
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}))
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place_id}))
|
||||
|
||||
|
||||
class LevelCapPlaceListView(ListView):
|
||||
model = Place
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.user.explorer.get_places_eligible_to_see()
|
84
django_lostplaces/lostplaces/views/explorer_views.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.views import View
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
|
||||
from lostplaces.common import get_all_subclasses
|
||||
from lostplaces.views.base_views import IsAuthenticatedMixin
|
||||
from lostplaces.models.models import Explorer
|
||||
from lostplaces.models.place import Place, PlaceAsset
|
||||
from lostplaces.forms import ExplorerChangeForm, ExplorerUserChangeForm
|
||||
|
||||
class ExplorerProfileView(IsAuthenticatedMixin, View):
|
||||
def get(self, request, explorer_id):
|
||||
explorer = get_object_or_404(Explorer, pk=explorer_id)
|
||||
place_list = explorer.places.all()
|
||||
place_count = place_list.count()
|
||||
|
||||
context={
|
||||
'explorer': explorer,
|
||||
'place_count': place_count,
|
||||
'place_list': place_list,
|
||||
'assets': {}
|
||||
}
|
||||
|
||||
asset_count = 0
|
||||
for subclass in get_all_subclasses(PlaceAsset): # kinda ugly, but PlaceAsset cannot be abstract according to django ORM
|
||||
objects = subclass.objects.filter(submitted_by=explorer)
|
||||
context['assets'][subclass.__name__.lower()+'s'] = objects
|
||||
asset_count += objects.count()
|
||||
|
||||
context['asset_count'] = asset_count
|
||||
|
||||
return render(
|
||||
request=request,
|
||||
template_name='explorer/profile.html',
|
||||
context=context
|
||||
)
|
||||
|
||||
class ExplorerProfileUpdateView(IsAuthenticatedMixin, View):
|
||||
success_message = ''
|
||||
permission_denied_message = ''
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
context = {
|
||||
'explorer_user_change_form': ExplorerUserChangeForm(instance=request.user),
|
||||
'explorer_change_form': ExplorerChangeForm(instance=request.user.explorer)
|
||||
}
|
||||
if request.user.explorer.profile_image:
|
||||
context['explorer_image'] = request.user.explorer.profile_image
|
||||
return render(request, 'explorer/profile_update.html', context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
explorer_user_change_form = ExplorerUserChangeForm(
|
||||
request.POST,
|
||||
instance=request.user
|
||||
)
|
||||
explorer_change_form = ExplorerChangeForm(
|
||||
request.POST,
|
||||
request.FILES,
|
||||
instance=request.user.explorer
|
||||
)
|
||||
|
||||
if explorer_change_form.is_valid() and explorer_user_change_form.is_valid():
|
||||
explorer_user_change_form.save()
|
||||
explorer_change_form.save()
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
_('Successfully updated Explorer profile')
|
||||
)
|
||||
else:
|
||||
# Usually the browser should have checked the form before sending.
|
||||
messages.error(
|
||||
self.request,
|
||||
_('Please fill in all required fields.')
|
||||
)
|
||||
return redirect(reverse_lazy('explorer_profile_update'))
|
||||
|
@ -18,7 +18,10 @@ class MultiplePlaceImageUploadMixin:
|
||||
submitted_by=submitted_by
|
||||
)
|
||||
place_image.save()
|
||||
|
||||
if place.hero is None:
|
||||
place.hero = place.placeimages.all()[0]
|
||||
place.save()
|
||||
|
||||
class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView):
|
||||
model = PlaceImage
|
||||
form_class = PlaceImageForm
|
||||
@ -38,5 +41,5 @@ class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView):
|
||||
|
||||
class PlaceImageDeleteView(PlaceAssetDeleteView):
|
||||
model = PlaceImage
|
||||
success_message = _('Image(s) deleted successfully')
|
||||
success_message = _('Image deleted successfully')
|
||||
permission_denied_message = _('You are not allowed to delete this image')
|
||||
|
@ -1,30 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db.models.functions import Lower
|
||||
|
||||
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.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.urls import reverse_lazy, reverse
|
||||
|
||||
from lostplaces.models import Place, PlaceImage
|
||||
from lostplaces.views.base_views import IsAuthenticatedMixin, IsPlaceSubmitterMixin
|
||||
from lostplaces.models import (
|
||||
Place,
|
||||
PlaceImage,
|
||||
PlaceVoting,
|
||||
PLACE_LEVELS
|
||||
)
|
||||
|
||||
from lostplaces.views.base_views import (
|
||||
IsAuthenticatedMixin,
|
||||
IsPlaceSubmitterMixin,
|
||||
LevelCapPlaceListView,
|
||||
IsEligibleToSeePlaceMixin
|
||||
)
|
||||
from lostplaces.views.place_image_views import MultiplePlaceImageUploadMixin
|
||||
from lostplaces.forms import PlaceForm, PlaceImageForm, TagSubmitForm
|
||||
from lostplaces.common import redirect_referer_or
|
||||
|
||||
from taggit.models import Tag
|
||||
|
||||
class PlaceListView(IsAuthenticatedMixin, ListView):
|
||||
class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
|
||||
paginate_by = 5
|
||||
model = Place
|
||||
template_name = 'place/place_list.html'
|
||||
ordering = [Lower('name')]
|
||||
|
||||
@ -36,9 +48,17 @@ class PlaceListView(IsAuthenticatedMixin, ListView):
|
||||
}
|
||||
return context
|
||||
|
||||
class PlaceDetailView(IsAuthenticatedMixin, View):
|
||||
def get(self, request, pk):
|
||||
place = get_object_or_404(Place, pk=pk)
|
||||
class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
|
||||
not_eligible_to_see_message = _('You\'r not allowed to see this place')
|
||||
|
||||
def get_place(self):
|
||||
return get_object_or_404(Place, pk=self.kwargs['pk'])
|
||||
|
||||
def get(self, request, pk):
|
||||
place = self.get_place()
|
||||
place.calculate_place_level()
|
||||
explorer = request.user.explorer
|
||||
|
||||
context = {
|
||||
'place': place,
|
||||
'mapping_config': {
|
||||
@ -49,8 +69,12 @@ class PlaceDetailView(IsAuthenticatedMixin, View):
|
||||
'all_tags': Tag.objects.all(),
|
||||
'submit_form': TagSubmitForm(),
|
||||
'tagged_item': place,
|
||||
'submit_url_name': 'place_tag_submit',
|
||||
'submit_url': reverse('place_tag_submit', kwargs={'tagged_id': place.id}),
|
||||
'delete_url_name': 'place_tag_delete'
|
||||
},
|
||||
'placevoting': {
|
||||
'users_vote': PlaceVoting.objects.filter(place=place, submitted_by=explorer).first(),
|
||||
'all_choices': reversed(PLACE_LEVELS)
|
||||
}
|
||||
}
|
||||
return render(request, 'place/place_detail.html', context)
|
||||
@ -60,7 +84,7 @@ class PlaceUpdateView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SuccessMessag
|
||||
model = Place
|
||||
form_class = PlaceForm
|
||||
success_message = _('Successfully updated place')
|
||||
place_submitter_error_message = _('You do no have permissions to alter this place')
|
||||
place_submitter_error_message = _('You are not allowed to edit this place')
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk})
|
||||
@ -102,16 +126,24 @@ class PlaceCreateView(MultiplePlaceImageUploadMixin, IsAuthenticatedMixin, View)
|
||||
# Usually the browser should have checked the form before sending.
|
||||
messages.error(
|
||||
self.request,
|
||||
'Please fill in all required fields.'
|
||||
_('Please fill in all required fields.')
|
||||
)
|
||||
return render(
|
||||
request=request,
|
||||
template_name='place/place_create.html',
|
||||
context={
|
||||
'place_form': place_form,
|
||||
'place_image_form': PlaceImageForm()
|
||||
},
|
||||
status=400
|
||||
)
|
||||
return render(request, 'place/place_create.html', context={'form': place_form})
|
||||
|
||||
class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
|
||||
template_name = 'place/place_delete.html'
|
||||
model = Place
|
||||
success_message = _('Successfully deleted place')
|
||||
success_url = reverse_lazy('place_list')
|
||||
place_submitter_error_message = _('You do no have permission to delete this place')
|
||||
place_submitter_error_message = _('You are not allowed to delete this place')
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
messages.success(self.request, self.success_message)
|
||||
@ -119,3 +151,80 @@ class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
|
||||
|
||||
def get_place(self):
|
||||
return self.get_object()
|
||||
|
||||
class PlaceFavoriteView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
|
||||
not_eligible_to_see_message = _('You\'r not allowed to favorite this place')
|
||||
|
||||
def get_place(self):
|
||||
return get_object_or_404(Place, pk=self.kwargs['place_id'])
|
||||
|
||||
def get(self, request, place_id):
|
||||
place = self.get_place()
|
||||
if request.user is not None:
|
||||
request.user.explorer.favorite_places.add(place)
|
||||
request.user.explorer.save()
|
||||
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
|
||||
|
||||
class PlaceUnfavoriteView(IsAuthenticatedMixin, View):
|
||||
|
||||
def get(self, request, place_id):
|
||||
place = get_object_or_404(Place, id=place_id)
|
||||
if request.user is not None:
|
||||
request.user.explorer.favorite_places.remove(place)
|
||||
request.user.explorer.save()
|
||||
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
|
||||
|
||||
class PlaceVisitCreateView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
|
||||
not_eligible_to_see_message = _('You\'r not allowed to visit this place :P (Now please stop trying out URL\'s)')
|
||||
|
||||
def get_place(self):
|
||||
return get_object_or_404(Place, pk=self.kwargs['place_id'])
|
||||
|
||||
def get(self, request, place_id):
|
||||
place = self.get_place()
|
||||
if request.user is not None:
|
||||
request.user.explorer.visited_places.add(place)
|
||||
request.user.explorer.save()
|
||||
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
|
||||
|
||||
class PlaceVisitDeleteView(IsAuthenticatedMixin, View):
|
||||
|
||||
def get(self, request, place_id):
|
||||
place = get_object_or_404(Place, id=place_id)
|
||||
if request.user is not None:
|
||||
request.user.explorer.visited_places.remove(place)
|
||||
request.user.explorer.save()
|
||||
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
|
||||
|
||||
|
||||
class PlaceVoteView(IsEligibleToSeePlaceMixin, View):
|
||||
delta = timedelta(weeks=24)
|
||||
|
||||
def get(self, request, place_id, vote):
|
||||
place = get_object_or_404(Place, id=place_id)
|
||||
explorer = request.user.explorer
|
||||
|
||||
voting = PlaceVoting.objects.filter(
|
||||
submitted_by=explorer,
|
||||
place=place
|
||||
).first()
|
||||
|
||||
if voting is None:
|
||||
voting = PlaceVoting.objects.create(
|
||||
submitted_by=explorer,
|
||||
place=place,
|
||||
vote=vote,
|
||||
expires_when=timezone.now()+self.delta
|
||||
)
|
||||
messages.success(self.request, _('Vote submitted'))
|
||||
else:
|
||||
voting.expires_when=timezone.now()+self.delta
|
||||
voting.vote = vote
|
||||
messages.success(self.request, _('Your vote has been update'))
|
||||
|
||||
voting.save()
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
|
@ -6,14 +6,15 @@ 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.urls import reverse_lazy, reverse
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from lostplaces.forms import ExplorerCreationForm, TagSubmitForm
|
||||
from lostplaces.forms import SignupVoucherForm, TagSubmitForm
|
||||
from lostplaces.models import Place, PhotoAlbum
|
||||
from lostplaces.views.base_views import IsAuthenticatedMixin
|
||||
from lostplaces.common import redirect_referer_or
|
||||
|
||||
from lostplaces.views.base_views import (
|
||||
PlaceAssetCreateView,
|
||||
@ -23,14 +24,14 @@ from lostplaces.views.base_views import (
|
||||
from taggit.models import Tag
|
||||
|
||||
class SignUpView(SuccessMessageMixin, CreateView):
|
||||
form_class = ExplorerCreationForm
|
||||
form_class = SignupVoucherForm
|
||||
success_url = reverse_lazy('login')
|
||||
template_name = 'signup.html'
|
||||
success_message = _('User created')
|
||||
|
||||
class HomeView(IsAuthenticatedMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
place_list = Place.objects.all().order_by('-submitted_when')[:10]
|
||||
place_list = request.user.explorer.get_places_eligible_to_see()
|
||||
context = {
|
||||
'place_list': place_list,
|
||||
'mapping_config': {
|
||||
@ -41,7 +42,7 @@ class HomeView(IsAuthenticatedMixin, View):
|
||||
return render(request, 'home.html', context)
|
||||
|
||||
def handle_no_permission(self):
|
||||
place_list = Place.objects.all().order_by('-submitted_when')[:5]
|
||||
place_list = Place.objects.filter(level=1)[:5]
|
||||
context = {
|
||||
'place_list': place_list
|
||||
}
|
||||
@ -51,13 +52,13 @@ class PhotoAlbumCreateView(PlaceAssetCreateView):
|
||||
model = PhotoAlbum
|
||||
fields = ['url', 'label']
|
||||
template_name = 'photo_album/photo_album_create.html'
|
||||
success_message = _('Photo Album submitted')
|
||||
success_message = _('Photo album link 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')
|
||||
success_message = _('Photo album link deleted')
|
||||
permission_denied_messsage = _('You are not allowed to edit this photo album link')
|
||||
|
||||
class PlaceTagSubmitView(IsAuthenticatedMixin, View):
|
||||
def post(self, request, tagged_id, *args, **kwargs):
|
||||
@ -79,10 +80,22 @@ class PlaceTagDeleteView(IsAuthenticatedMixin, View):
|
||||
place = get_object_or_404(Place, pk=tagged_id)
|
||||
tag = get_object_or_404(Tag, pk=tag_id)
|
||||
place.tags.remove(tag)
|
||||
return redirect(reverse_lazy('place_detail', kwargs={'pk': tagged_id}))
|
||||
|
||||
|
||||
return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': tagged_id}))
|
||||
|
||||
def FlatView(request, slug):
|
||||
if request.LANGUAGE_CODE == 'de':
|
||||
return render(request, 'flat/' + slug + '-de' + '.html')
|
||||
else:
|
||||
return render(request, 'flat/' + slug + '.html')
|
||||
|
||||
class OSMMapView(IsAuthenticatedMixin, View):
|
||||
def get(self, request):
|
||||
place_list = Place.objects.all()
|
||||
context = {
|
||||
'mapping_config': {
|
||||
'all_points': place_list,
|
||||
'map_center': Place.average_latlon(place_list)
|
||||
}
|
||||
}
|
||||
return render(request, 'osm_map_full.html', context)
|
||||
|
5
django_lostplaces/lostplaces/widgets.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django import forms
|
||||
|
||||
class SelectContent(forms.Select):
|
||||
template_name = 'widgets/select_content.html'
|
||||
|
@ -1,8 +0,0 @@
|
||||
# Config options for coverage
|
||||
# Docs: https://coverage.readthedocs.io/en/latest/config.html
|
||||
|
||||
[coverage:run]
|
||||
source = .
|
||||
|
||||
[coverage:report]
|
||||
show_missing = True
|
13
setup.py → django_lostplaces/setup.py
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
@ -6,14 +6,16 @@ from setuptools import setup, find_packages
|
||||
with open('Readme.md') as f:
|
||||
readme = f.read()
|
||||
|
||||
# Keep PEP 440 for version identification in mind
|
||||
# https://www.python.org/dev/peps/pep-0440/#post-releases
|
||||
setup(
|
||||
name='django-lostplaces',
|
||||
version='0.1.2 HotFix',
|
||||
version='0.1.4.post1',
|
||||
description='A django app to manage lost places',
|
||||
author='Reverend',
|
||||
author_email='reverend@reverend2048.de',
|
||||
url='https://git.mowoe.com/reverend/lostplaces-backend',
|
||||
packages=find_packages(exclude=['lostplaces']),
|
||||
packages=find_packages(exclude=['django_lostplaces']),
|
||||
long_description=readme,
|
||||
long_description_content_type='text/markdown',
|
||||
classifiers=[
|
||||
@ -31,5 +33,6 @@ setup(
|
||||
'django-widget-tweaks',
|
||||
'django-taggit'
|
||||
],
|
||||
include_package_data=True
|
||||
)
|
||||
include_package_data=True,
|
||||
license='MIT'
|
||||
)
|
BIN
django_lostplaces/testdata/test_image.jpg
vendored
Normal file
After Width: | Height: | Size: 89 KiB |
21
tasks.py
Normal file
@ -0,0 +1,21 @@
|
||||
from invoke import task
|
||||
|
||||
@task
|
||||
def quickstart(c):
|
||||
commands = [
|
||||
'pipenv run migrate',
|
||||
'pipenv run createsuperuser',
|
||||
'pipenv run server'
|
||||
]
|
||||
c.run(' && '.join(commands))
|
||||
|
||||
@task
|
||||
def live(c):
|
||||
commands = [
|
||||
'pipenv check',
|
||||
'pipenv run test',
|
||||
'pipenv run migrate',
|
||||
'pipenv run createsuperuser_prompt'
|
||||
'pipenv run server'
|
||||
]
|
||||
c.run(' && '.join(commands))
|