Compare commits

..

159 Commits

Author SHA1 Message Date
b23dc8a627 Generating Migrations für new Release 0.1.4 2021-12-31 18:37:54 +01:00
3e2ab1e12d Merge branch 'develop' Release 0.1.4 2021-12-31 16:55:40 +01:00
78efe9bebb #58 Voting UI is bugged when no vote was submitted 2021-12-31 15:56:31 +01:00
1de203ad40 More Detailed view of user's vote 2021-12-31 01:15:37 +01:00
399fa70ab6 #42 Tests for Level-System / Homepage 2021-12-31 00:35:19 +01:00
c6a28c7b66 Inlcuding Material Design Icons 2021-12-30 23:20:29 +01:00
9980a2d190 #42 Place Voting / Level 2021-12-30 23:20:05 +01:00
4c43f87560 Removing annoying warning since Django update 3.2 2021-10-02 01:54:43 +02:00
c8399b9ce4 #54 Scaling image up while cropping 2021-10-02 01:54:21 +02:00
f7dab77229 #42 Testing place detail for level 2021-10-02 01:31:12 +02:00
8d96214a92 Nose testing sutie 2021-10-02 01:30:53 +02:00
6678f8ff02 Nose testing suite 2021-10-02 01:30:44 +02:00
4cd0635d5f #42 Testing place level on place list 2021-10-02 00:50:32 +02:00
e11151858b #42 Fined tuned list of eligible to see places 2021-10-02 00:03:45 +02:00
84b01f3a9a #42 Place submitter and super users should be able to see a place 2021-10-01 23:44:46 +02:00
3f4bf9475c #42 Restricting Acces to Place requests 2021-10-01 23:41:34 +02:00
b607335299 #42 Checking ListView for Levels 2021-10-01 22:38:44 +02:00
b1cd8fd0cc #47 Settings Place Hero when creating a place 2021-10-01 21:44:35 +02:00
a7a3ddfe63 #42 Excluding Level from Forms 2021-10-01 21:14:53 +02:00
1c6366fb43 Migrations between development systems make life just more painful 2021-10-01 19:36:48 +02:00
565a70bbac Migrations between development systems make life just more painful 2021-10-01 19:36:29 +02:00
749aa61828 Migrations between development systems make life just more painful 2021-10-01 19:36:11 +02:00
e2754dc56c No django migrations in development branches, screws everything up 2021-10-01 19:31:06 +02:00
Leonhard Strohmidel
fefc38fa9c Migration Script 2021-10-01 09:40:53 +02:00
Leonhard Strohmidel
58ab70ba42 #42 Levels for places 2021-10-01 09:40:42 +02:00
Leonhard Strohmidel
c63b0ebb87 Mehr Urbex-Codex (German only) 2021-10-01 09:29:09 +02:00
Leonhard Strohmidel
0852e35b57 #42 Level for Users 2021-10-01 09:28:45 +02:00
e52247d80b Trying to make artifacts publicly available 2021-07-16 13:54:03 +02:00
2ac90bd35c Archiving build artefacts 2021-07-16 13:35:32 +02:00
3685db6892 Im dumb 2021-07-16 13:31:03 +02:00
d5789cef75 Screw it, building is not handled by pipenv 2021-07-16 13:28:58 +02:00
5fca4dc52a Setuptools outside of venv 2021-07-16 13:24:01 +02:00
cbf45af5f5 setuptools dev depedency 2021-07-16 13:20:03 +02:00
244b4d2917 relative paths instead of cd 2021-07-16 13:17:23 +02:00
d8b0ce1ac2 Manual merging current models 2021-07-16 13:15:19 +02:00
8da6964b3c Setting Up pipenv in building 2021-07-16 13:11:10 +02:00
ccd701ea60 Pipfile script for migrations 2021-07-16 13:08:42 +02:00
170971c44b Privilege escalation attack inc 2021-07-16 13:06:10 +02:00
7196820122 Trying out other agents for building 2021-07-16 13:02:07 +02:00
a02d4f5e86 yes yes indeed 2021-07-10 14:43:56 +02:00
a7043651e0 , 2021-07-10 13:44:09 +02:00
bcf3377190 Trying to use pipenv in jenkins 2021-07-10 13:42:26 +02:00
ceabe3f6f8 No manual sync with repo since this is done by jenkins 2021-07-10 13:34:29 +02:00
6b33d20f90 :: 2021-07-10 13:32:56 +02:00
1d787a3b1d . 2021-07-10 13:31:34 +02:00
1960bf3792 . 2021-07-10 13:30:10 +02:00
0c3898163e ... 2021-07-10 13:27:46 +02:00
2c6676d2c9 Havin' fun with jenkins 2021-07-10 13:25:58 +02:00
52f9df8b2a Jenkinsfile to build a snapshot 2021-07-10 13:22:11 +02:00
ab669b9f5e Changes in MapContext 2021-06-25 11:04:18 +02:00
0fe98d4f8f Testing place views 2021-06-25 11:04:01 +02:00
51addd8fbb Testing messgaes 2021-06-12 17:06:09 +02:00
29d2813fcc Some weird bug only occuring when testing 2021-06-12 17:05:54 +02:00
cc9343270a explorers -> explorer 2021-06-12 17:05:32 +02:00
4f465b3162 400 Response when submitting invalid place data and testing more edge cases 2021-06-12 15:52:52 +02:00
b0bf40aa0b More tests of views 2021-06-12 15:07:14 +02:00
822a536ebe Fixing idention 2021-05-16 13:03:46 +02:00
5b9672e21e Testing PlaceAssetCreateView 2021-05-16 13:03:41 +02:00
0a5d22d4d9 More comments 2021-05-15 23:27:33 +02:00
9c86eab14c Testing home view 2021-05-15 23:27:25 +02:00
29d067002e Mixin Test does not rely on actual place view 2021-05-15 23:26:27 +02:00
f5f5980f9b Tests for global templates 2021-05-15 23:26:02 +02:00
10c08a2f5e Current CSS 2021-04-10 08:24:33 +02:00
50f60be10a Shifting index 2021-04-10 08:24:23 +02:00
8b32b56dd9 Custom Widget to select content/invisible radio input 2021-04-10 08:24:08 +02:00
74d842a668 #47 Settinge of Place Hero Image 2021-04-10 08:23:36 +02:00
2ac39f719f Removing debug 2021-04-09 19:51:45 +02:00
9b6121e448 Small fix 2021-04-09 19:51:17 +02:00
a139594863 Fixin Partial and Set Tag 2021-04-06 19:15:22 +02:00
f2eb048f0b Small check is this partial is used within place 2021-04-06 19:14:58 +02:00
120e63808a Replace include with partial tag 2021-04-06 19:14:36 +02:00
201ef73639 Accidently removed place url 2021-04-04 17:03:26 +02:00
af7afd0311 Accidently removed the add image tile 2021-04-04 17:01:00 +02:00
cf6346fdf0 Fixed #49 2021-04-04 16:56:18 +02:00
d641d7b44d Backwards compatibality 2021-04-04 16:55:56 +02:00
d35cb8131a Merge branch 'master' into develop 2021-04-03 23:07:16 +02:00
ceeae9e8c9 rudimentary implementation of a lightbox 2021-04-03 23:03:44 +02:00
092b91825f Following new naming https://is.gd/cRmcip 2021-04-02 22:06:52 +02:00
52271811ad using block in partial 2021-04-02 22:00:21 +02:00
fb1237f111 set variable and partial template tag 2021-04-02 22:00:13 +02:00
bae4e13137 Partial for PlaceImageGrid 2021-04-02 17:22:28 +02:00
b752454233 #39 Partial for Images 2021-04-02 17:21:00 +02:00
36eb9566d9 Fixed 404 because of misnamed font file. 2020-12-26 14:33:57 +01:00
b876435d8c Removed 'the mother of all debugging' again. :D 2020-12-25 19:50:34 +01:00
224eb87d76 Changed migration file reference after renaming it. 2020-12-25 19:47:05 +01:00
a78f123d1f Merge commit 'a22c988e948438415d1a58cac627243ae24785f4' into develop 2020-12-25 19:39:05 +01:00
a22c988e94 Added visited_places attribute to Explorer. 2020-12-25 19:36:11 +01:00
7f2283325c gory migration fuckery (rename) 2020-12-25 19:35:35 +01:00
6b7c71ef30 Addid visited_places system. (mostly copy&paste) :P 2020-12-25 19:32:54 +01:00
067bf36118 SVG paths updated. 2020-12-25 19:20:44 +01:00
9eef044af9 Renamed favorite* svgs. 2020-12-25 19:19:01 +01:00
a4780a22f2 Changed urlpattern tree. 2020-12-25 18:17:05 +01:00
947f6520aa CSS für Profil 2020-12-25 18:07:54 +01:00
0d1d829abe Regrouped views. 2020-12-25 17:56:17 +01:00
59cfa82ce9 Resorted and -grouped urls. 2020-12-25 17:54:15 +01:00
d47c85795c FU migrations! 2020-12-25 17:33:45 +01:00
8e80614eee Merge commit 'c5f6355f193df939ebb1b158be6b4dc2ba425a60' into develop 2020-12-25 16:51:49 +01:00
6a1e2d975b New Icon 2020-12-25 16:24:28 +01:00
c5f6355f19 Updated translations (de). 2020-12-25 16:07:50 +01:00
dac63e99ba Added Edit button to profile page. 2020-12-25 16:00:29 +01:00
9f369456d5 Changed easy_thumbnails image/thumb deletion. Now working. 2020-12-25 15:42:02 +01:00
011a58f6b3 Use thumbnail in profile_update. 2020-12-25 14:29:38 +01:00
67ce6cb50b Added working explorer_profile_image upload. 2020-12-25 14:06:53 +01:00
5df7cc5ec6 #46 Large Map 2020-12-25 13:29:34 +01:00
e242dc4add Partial takes parameter to change size of map 2020-12-25 13:29:18 +01:00
86cc7f23fe description_fuckery. 2020-12-25 13:19:34 +01:00
f73fa616d0 Minor reordering 2020-12-25 13:05:59 +01:00
629a609ff5 Forgott imports for #45 2020-12-25 13:00:30 +01:00
04fe93c7bb #45 Redirect after asset deletion 2020-12-25 12:57:05 +01:00
3eaa186b66 Delete profile_image on user_deletion. 2020-12-25 12:53:52 +01:00
b42be489dc #44 Text in place teaser is cut of 2020-12-25 12:39:13 +01:00
cc59254ba4 Added clean_username function to validate form. 2020-12-25 12:24:09 +01:00
67a6517716 Comment improved. 2020-12-25 11:14:54 +01:00
0f5474c2d3 half-working :P 2020-12-25 11:14:21 +01:00
17e71b71d4 Fix typo. 2020-12-25 08:04:35 +01:00
b1aa4473e9 Added failsafe, if there is no profile_image. 2020-12-25 02:11:02 +01:00
871acd1dce Improved class comment. 2020-12-25 01:41:09 +01:00
14ca45d111 Comments for customizations of UserChangeForm. 2020-12-25 01:36:14 +01:00
c71fe4cf2d Added new translations (de) 2020-12-25 01:31:43 +01:00
7359bf5fab ExplorerChangeForms, with deactivated username field. 2020-12-25 01:31:01 +01:00
4f0182fc3e Minor textual changes for consistency. 2020-12-25 01:28:23 +01:00
30f259fb4d WIP-commit of translations to test against fuzzy detection. 2020-12-24 23:51:40 +01:00
53f89caef5 Added a ModelForm to change profile details. 2020-12-24 20:31:52 +01:00
3855fb28d7 Rename ExplorerCreationForm to SignupVoucherForm. 2020-12-24 20:25:05 +01:00
3a20a60f05 Remove obsolete ExplorerChangeForm. 2020-12-24 20:22:33 +01:00
648264c9fc Remove obsoleted imports. 2020-12-24 20:13:06 +01:00
dc79b9d05c #43 Lat/Long Check when place is submitted 2020-12-24 19:47:17 +01:00
a7c8848fd6 Allow user to provide his first and last name on registration. 2020-12-24 19:36:41 +01:00
b845cc054a Small Tweaks 2020-12-24 19:25:37 +01:00
07ea2f164c Name and E-Mail is only shown when populated in profile. 2020-12-24 19:13:55 +01:00
b0396f5223 Added bio and email link to profile template. 2020-12-24 19:04:04 +01:00
88bffbef8e Added Explorer bio / description field. 2020-12-24 18:53:30 +01:00
2022a924c4 Added output of profile image to profile_page. 2020-12-24 18:30:29 +01:00
d5827b2006 Fixed filename generation. 2020-12-24 18:14:40 +01:00
a9013d9673 Pulled favorite_places out of explorer instance. 2020-12-24 18:03:31 +01:00
0c38ca9a15 Revert changes, as it was redundant. 2020-12-24 18:03:07 +01:00
734d09df90 Add favorite_places to user profile page. 2020-12-24 17:59:05 +01:00
e601b9bf6b Merge commit '6f6bd6376d1ef2410a728cff2e9b883560c7f5c1' into feature/30-user-profile-fields 2020-12-24 17:34:09 +01:00
6f6bd6376d Merge branch 'develop' of mowoe.com:reverend/lostplaces-backend into develop 2020-12-24 17:33:02 +01:00
dc2a703c39 More fuckery 2020-12-24 17:27:48 +01:00
063777ece4 Merge branch '27-Favorite-System' into develop 2020-12-24 17:27:20 +01:00
4675e5814f Indention and stupid mistakes fix 2020-12-24 17:26:07 +01:00
35e0f912fe Add profile_pic 2020-12-24 17:24:02 +01:00
07fe1bc3ca Rename place_image_path function. 2020-12-24 17:23:20 +01:00
4a43a4bf37 typo 2020-12-24 16:43:43 +01:00
61cf148417 Displaying favorite icon on detail page 2020-12-24 16:43:33 +01:00
d547ee9db3 favorite partial 2020-12-24 16:43:14 +01:00
c7b699f615 Changed link location in place teaser 2020-12-24 16:43:01 +01:00
86f95a5dd0 New icon 2020-12-24 16:42:34 +01:00
916c4b80f7 Filed not required in django admin 2020-12-24 16:42:27 +01:00
b31dc9fc5f Url name changed 2020-12-24 16:41:30 +01:00
8f048369bf Favorite/Unfavorite Views 2020-12-24 15:56:33 +01:00
f974469996 formatting 2020-12-24 15:56:02 +01:00
dbbd7b0802 rename migrationfile 2020-12-24 15:55:37 +01:00
d04e986419 Added display of name and e-mail. 2020-12-24 15:33:46 +01:00
cbbda88850 Remove debug output. 2020-12-24 15:13:40 +01:00
981c440ce3 Fix voucher class description 2020-12-24 15:13:31 +01:00
c7368f5c44 Merge commit '20dd880a11a008ba95cd5aedce59590445430831' into develop 2020-12-24 14:57:09 +01:00
abca946883 Model for favorite system 2020-12-24 14:48:11 +01:00
77 changed files with 49183 additions and 1785 deletions

42
Jenkinsfile vendored Normal file
View 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
}
}
}

View File

@ -12,6 +12,8 @@ wheel = "*"
twine = "*" twine = "*"
pandoc = "*" pandoc = "*"
pylint-django = "*" pylint-django = "*"
setuptools = "*"
django-nose = "*"
[packages] [packages]
django = "*" django = "*"
@ -25,3 +27,6 @@ test = "django_lostplaces/manage.py test lostplaces"
server = "django_lostplaces/manage.py runserver --ipv6" server = "django_lostplaces/manage.py runserver --ipv6"
dbshell = "django_lostplaces/manage.py dbshell" dbshell = "django_lostplaces/manage.py dbshell"
showmigrations = "django_lostplaces/manage.py showmigrations" 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"

View File

@ -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. 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 ## Features
- Manage lost places with lots of useful information. - Manage lost places with lots of useful information.
- OSM-Maps - OSM-Maps
@ -145,8 +148,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` 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.
### 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.

View File

@ -49,7 +49,15 @@ INSTALLED_APPS = [
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles' 'django.contrib.staticfiles',
'django_nose'
]
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'--with-coverage',
'--cover-package=lostplaces',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -28,6 +28,6 @@ from lostplaces.views import SignUpView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('signup/', SignUpView.as_view(), name='signup'), 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')), path('', include('lostplaces.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -5,7 +5,7 @@ from django.conf import settings
settings.THUMBNAIL_ALIASES = { 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}, 'hero': {'size': (700, 466), 'sharpen': True, 'crop': True},
'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False}, 'large': {'size': (1920, 1920), 'sharpen': True, 'crop': False},
}, },

View File

@ -9,8 +9,6 @@ from django.contrib.auth.admin import UserAdmin
from django.utils import timezone from django.utils import timezone
from lostplaces.models import * from lostplaces.models import *
from lostplaces.forms import ExplorerCreationForm, ExplorerChangeForm
# Register your models here. # Register your models here.
class VoucherAdmin(admin.ModelAdmin): class VoucherAdmin(admin.ModelAdmin):
@ -37,3 +35,4 @@ admin.site.register(Voucher, VoucherAdmin)
admin.site.register(Place, PlacesAdmin) admin.site.register(Place, PlacesAdmin)
admin.site.register(PlaceImage, PlaceImagesAdmin) admin.site.register(PlaceImage, PlaceImagesAdmin)
admin.site.register(PhotoAlbum, PhotoAlbumsAdmin) admin.site.register(PhotoAlbum, PhotoAlbumsAdmin)
admin.site.register(PlaceVoting)

View File

@ -4,4 +4,5 @@
from django.apps import AppConfig from django.apps import AppConfig
class LostplacesAppConfig(AppConfig): class LostplacesAppConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField'
name = 'lostplaces' name = 'lostplaces'

View File

@ -1,3 +1,5 @@
from django.shortcuts import redirect
def get_all_subclasses(cls): def get_all_subclasses(cls):
''' '''
Gets all subclasses recursively, does not contain Gets all subclasses recursively, does not contain
@ -8,4 +10,16 @@ def get_all_subclasses(cls):
if not subclass._meta.abstract: if not subclass._meta.abstract:
subclass_list.append(subclass) subclass_list.append(subclass)
subclass_list += get_all_subclasses(subclass) subclass_list += get_all_subclasses(subclass)
return subclass_list 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)

View File

@ -8,19 +8,21 @@ from django.db import models
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ 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: class Meta:
model = User model = User
fields = ('username', 'email') fields = ('username', 'email', 'first_name', 'last_name')
voucher = forms.CharField( voucher = forms.CharField(
max_length=30, max_length=30,
help_text=_('The Voucher you got from an administrator') help_text=_('The Voucher you got from an administrator')
) )
def is_valid(self): def is_valid(self):
super().is_valid() super_result = super().is_valid()
submitted_voucher = self.cleaned_data.get('voucher') submitted_voucher = self.cleaned_data.get('voucher')
try: try:
fetched_voucher = Voucher.objects.get(code=submitted_voucher) fetched_voucher = Voucher.objects.get(code=submitted_voucher)
@ -33,19 +35,59 @@ class ExplorerCreationForm(UserCreationForm):
return False return False
fetched_voucher.delete() fetched_voucher.delete()
return True return super_result
class ExplorerChangeForm(UserChangeForm): class ExplorerUserChangeForm(UserChangeForm):
class Meta: class Meta:
model = User 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 PlaceForm(forms.ModelForm):
class Meta: class Meta:
model = Place model = Place
fields = '__all__' 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 PlaceImageForm(forms.ModelForm):
class Meta: class Meta:
model = PlaceImage model = PlaceImage
@ -58,9 +100,24 @@ class PlaceImageForm(forms.ModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['filename'].required = False 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): class TagSubmitForm(forms.Form):
tag_list = forms.CharField( tag_list = forms.CharField(
max_length=500, max_length=500,
required=False, required=False,
widget=forms.TextInput(attrs={'autocomplete':'off'}) widget=forms.TextInput(attrs={'autocomplete':'off'})

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-11 21:53+0200\n" "POT-Creation-Date: 2020-12-25 16:04+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Commander1024 <commander@commander1024.de>\n" "Last-Translator: Commander1024 <commander@commander1024.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -30,7 +30,7 @@ msgstr "Ungültiger Voucher"
msgid "Expired voucher" msgid "Expired voucher"
msgstr "Abgelaufener Voucher" msgstr "Abgelaufener Voucher"
#: models/abstract_models.py:29 #: models/abstract_models.py:29 templates/explorer/profile.html:31
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@ -74,6 +74,22 @@ msgstr "Adresse (URL)"
msgid "link text" msgid "link text"
msgstr "Linktext" 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 #: models/place.py:21
msgid "Location" msgid "Location"
msgstr "Ort" msgstr "Ort"
@ -110,38 +126,92 @@ msgstr "Du wirst in 5 Sekunden weitergeleitet"
msgid "Go Back" msgid "Go Back"
msgstr "Zurück" 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 #: templates/global.html:32
msgid "Logout" msgid "Logout"
msgstr "Ausloggen" msgstr "Ausloggen"
#: templates/global.html:34 #: templates/global.html:33
msgid "Profile"
msgstr "Profil"
#: templates/global.html:35
msgid "Admin" msgid "Admin"
msgstr "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 #: templates/registration/login.html:10 templates/registration/login.html:23
msgid "Login" msgid "Login"
msgstr "Anmelden" msgstr "Anmelden"
#: templates/global.html:40 templates/registration/login.html:29 #: templates/global.html:41 templates/registration/login.html:29
#: templates/signup.html:6 templates/signup.html:12 templates/signup.html:41 #: templates/signup.html:6 templates/signup.html:12 templates/signup.html:49
msgid "Sign up" msgid "Sign up"
msgstr "Registrieren" msgstr "Registrieren"
#: templates/global.html:50 templates/home.html:10 #: templates/global.html:51 templates/home.html:10
msgid "Home" msgid "Home"
msgstr "Startseite" msgstr "Startseite"
#: templates/global.html:51 #: templates/global.html:52
msgid "UrBex Codex" msgid "UrBex Codex"
msgstr "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 #: templates/place/place_create.html:10
msgid "Create place" msgid "Create place"
msgstr "Place erstellen" msgstr "Place erstellen"
#: templates/global.html:57 #: templates/global.html:58
msgid "All places" msgid "All places"
msgstr "Alle Places" msgstr "Alle Places"
@ -194,6 +264,14 @@ msgstr "Abschicken"
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" 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 #: templates/partials/nav/footer.html:64
msgid "Made by" msgid "Made by"
msgstr "Erstellt von" msgstr "Erstellt von"
@ -318,10 +396,6 @@ msgstr "Alle Places"
msgid "Our lost places" msgid "Our lost places"
msgstr "Unsere Lost Places" msgstr "Unsere Lost Places"
#: templates/place/place_update.html:42
msgid "Update"
msgstr "Aktualisieren"
#: templates/place_image/place_image_create.html:7 #: templates/place_image/place_image_create.html:7
msgid "Submit images to a place" msgid "Submit images to a place"
msgstr "Bilder zu einem Place hinzufügen" msgstr "Bilder zu einem Place hinzufügen"
@ -334,6 +408,16 @@ msgstr "Noch kein Konto?"
msgid "Please login to proceed" msgid "Please login to proceed"
msgstr "Bitte log Dich ein um fortzufahren" 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 #: views/place_image_views.py:26
msgid "Image(s) submitted successfully" msgid "Image(s) submitted successfully"
msgstr "Bild(er) erfolgreich hinzugefügt" msgstr "Bild(er) erfolgreich hinzugefügt"
@ -358,10 +442,6 @@ msgstr "Du darfst diesen Place nicht bearbeiten"
msgid "Successfully created place" msgid "Successfully created place"
msgstr "Place erfolgreich erstellt" msgstr "Place erfolgreich erstellt"
#: views/place_views.py:105
msgid "Please fill in all required fields."
msgstr "Bitte füll alle benötigten Felder aus."
#: views/place_views.py:112 #: views/place_views.py:112
msgid "Successfully deleted place" msgid "Successfully deleted place"
msgstr "Place erfolgreich gelöscht" msgstr "Place erfolgreich gelöscht"
@ -385,3 +465,8 @@ msgstr "Fotoalbum-Link gelöscht"
#: views/views.py:60 #: views/views.py:60
msgid "You are not allowed to edit this photo album link" msgid "You are not allowed to edit this photo album link"
msgstr "Du darfst diesen Fotoalbum-Link nicht bearbeiten" msgstr "Du darfst diesen Fotoalbum-Link nicht bearbeiten"
#, fuzzy
#~| msgid "Filename(s)"
#~ msgid "Filename"
#~ msgstr "Dateiname(n)"

View File

@ -65,7 +65,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('submitted_when', models.DateTimeField(auto_now_add=True, null=True)), ('submitted_when', models.DateTimeField(auto_now_add=True, null=True)),
('description', models.TextField(blank=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')), ('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')), ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='placeimages', to='lostplaces.explorer')),
], ],

View File

@ -6,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('lostplaces', '0002_reomve_vouchers'), ('lostplaces', '0002_remove_vouchers'),
] ]
operations = [ operations = [

View File

@ -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.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,
},
),
]

View File

@ -6,19 +6,42 @@
database. database.
''' '''
import os
import uuid import uuid
from django.db import models from django.db import models
from django.contrib.auth.models import User 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.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from lostplaces.models.abstract_models import Expireable 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): 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. Every user has a profile.
Provides additional attributes for user profile.
""" """
user = models.OneToOneField( user = models.OneToOneField(
@ -26,10 +49,54 @@ class Explorer(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='explorer' 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): def __str__(self):
return self.user.username return self.user.username
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):
if created: if created:
@ -39,13 +106,33 @@ def create_user_profile(sender, instance, created, **kwargs):
def save_user_profile(sender, instance, **kwargs): def save_user_profile(sender, instance, **kwargs):
instance.explorer.save() 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): class Voucher(Expireable):
""" """
Vouchers are authorization to created_when = models.DateTimeField(auto_now_add=True) Vouchers are authorization tokens to allow the registration of new users.
expires_when = models.DateTimeField()kens to allow the registration of new users.
A voucher has a code, a creation and a deletion date, which are all A voucher has a code, a creation and a deletion date, which are all
positional. Creation date is being set automatically during voucher positional. Creation date is being set automatically during voucher
creation. creation.
created_when = models.DateTimeField(auto_now_add=True)
expires_when = models.DateTimeField()
""" """
code = models.CharField(unique=True, max_length=30) code = models.CharField(unique=True, max_length=30)
@ -56,4 +143,3 @@ class Voucher(Expireable):
def __str__(self): def __str__(self):
return "Voucher " + str(self.code) return "Voucher " + str(self.code)

View File

@ -1,4 +1,5 @@
import os import os
from math import floor
from django.db import models from django.db import models
from django.urls import reverse 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.db.models.signals import post_delete, pre_save
from django.utils.translation import ugettext_lazy as _ 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.fields import ThumbnailerImageField
from easy_thumbnails.files import get_thumbnailer 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): class Place(Submittable, Taggable, Mapable):
""" """
Place defines a lost place (location, name, description etc.). Place defines a lost place (location, name, description etc.).
@ -25,8 +34,41 @@ class Place(Submittable, Taggable, Mapable):
verbose_name=_('Description'), 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): def get_absolute_url(self):
return reverse('place_detail', kwargs={'pk': self.pk}) 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 @classmethod
@ -34,8 +76,11 @@ class Place(Submittable, Taggable, Mapable):
def average_latlon(cls, place_list): def average_latlon(cls, place_list):
amount = len(place_list) amount = len(place_list)
# Init fill values to prevent None # Init fill values to prevent None
longitude = 0 # China Corner in Münster
latitude = 0 # Where I almost always eat lunch
# (Does'nt help losing wheight, tho)
longitude = 7.6295628132604385
latitude = 51.961922091398904
if amount > 0: if amount > 0:
for place in place_list: for place in place_list:
@ -45,25 +90,48 @@ class Place(Submittable, Taggable, Mapable):
return {'latitude': latitude, 'longitude': longitude} 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): def __str__(self):
return self.name return self.name
def generate_place_image_filename(instance, filename):
def generate_image_upload_path(instance, filename):
""" """
Callback for generating path for uploaded images. Callback for generating filename for uploaded place images.
Returns filename as: place_pk-placename{-rnd_string}.jpg Returns filename as: place_pk-placename{-number}.jpg
""" """
return 'places/' + str(instance.place.pk) + '-' + str(instance.place.name) + '.' + filename.split('.')[-1] 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): class PlaceAsset(Submittable):
""" """
Assets to a place, i.e. images Assets to a place, i.e. images
""" """
class Meta: class Meta:
abstract = True abstract = True
place = models.ForeignKey( place = models.ForeignKey(
Place, Place,
@ -72,7 +140,10 @@ class PlaceAsset(Submittable):
null=True null=True
) )
class PlaceImage (PlaceAsset): 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/. PlaceImage defines an image file object that points to a file in uploads/.
Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES. Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES.
@ -84,10 +155,10 @@ class PlaceImage (PlaceAsset):
verbose_name=_('Description'), verbose_name=_('Description'),
) )
filename = ThumbnailerImageField( filename = ThumbnailerImageField(
upload_to=generate_image_upload_path, upload_to=generate_place_image_filename,
resize_source=dict(size=(2560, 2560), resize_source=dict(size=(2560, 2560),
sharpen=True), sharpen=True),
verbose_name=_('Filename(s)'), verbose_name=_('Images'),
help_text=_('Optional: One or more images to upload') help_text=_('Optional: One or more images to upload')
) )
place = models.ForeignKey( place = models.ForeignKey(
@ -102,8 +173,7 @@ class PlaceImage (PlaceAsset):
of this image as textual representation of this instance 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: # 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 = get_thumbnailer(instance.filename)
thumbmanager.delete(save=False) thumbmanager.delete(save=False)
@receiver(pre_save, sender=PlaceImage) @receiver(pre_save, sender=PlaceImage)
def auto_delete_file_on_change(sender, instance, **kwargs): 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. # No need to delete thumbnails, as they will be overwritten on regeneration.
new_file = instance.filename new_file = instance.filename
if not old_file == new_file: if not old_file == new_file:
if os.path.isfile(old_file.path): old_file.delete(save=False)
os.remove(old_file.path)
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)

View File

@ -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.

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 108 KiB

View 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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{% extends 'global.html'%} {% extends 'global.html'%}
{% load i18n %} {% load i18n %}
{% block title %}{% trans 'Forbidden' %}{% endblock %} {% block title %}{% translate 'Forbidden' %}{% endblock %}
{% block additional_head %} {% block additional_head %}
{% if request.META.HTTP_REFERER %} {% if request.META.HTTP_REFERER %}
@ -11,6 +11,6 @@
{% block maincontent %} {% block maincontent %}
{% if request.META.HTTP_REFERER %} {% 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 %} {% endif %}
{% endblock maincontent %} {% endblock maincontent %}

View File

@ -7,16 +7,49 @@
{% block maincontent %} {% block maincontent %}
<div class="LP-UserProfile"> <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-UserProfile__Info">
<div class="LP-UserInfo"> <div class="LP-UserInfo">
<div class="LP-UserInfo__UserName"> <div class="LP-UserInfo__UserName">
<h1 class="LP-Headline">{{explorer.user.username}}</h1> <h1 class="LP-Headline">{{explorer.user.username}}</h1>
</div> </div>
<div class="LP-UserInfo__Meta"> <div class="LP-UserInfo__Meta">
<table> <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> <tr>
<td class="LP-UserInfo__Key"> <td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Joined' %}</span> <span class="LP-Paragraph">{% translate 'Joined' %}</span>
</td> </td>
<td class="LP-UserInfo__Value"> <td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{explorer.user.date_joined|date:'d.m.Y'}}</span> <span class="LP-Paragraph">{{explorer.user.date_joined|date:'d.m.Y'}}</span>
@ -24,7 +57,7 @@
</tr> </tr>
<tr> <tr>
<td class="LP-UserInfo__Key"> <td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Places'%}</span> <span class="LP-Paragraph">{% translate 'Places'%}</span>
</td> </td>
<td class="LP-UserInfo__Value"> <td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{place_count}}</span> <span class="LP-Paragraph">{{place_count}}</span>
@ -32,13 +65,16 @@
</tr> </tr>
<tr> <tr>
<td class="LP-UserInfo__Key"> <td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Place Assets'%}</span> <span class="LP-Paragraph">{% translate 'Place Assets'%}</span>
</td> </td>
<td class="LP-UserInfo__Value"> <td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{asset_count}}</span> <span class="LP-Paragraph">{{asset_count}}</span>
</td> </td>
</tr> </tr>
</table> </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>
</div> </div>
@ -46,7 +82,7 @@
<section class="LP-Section"> <section class="LP-Section">
<div class="LP-PlaceList"> <div class="LP-PlaceList">
<h1 class="LP-Headline">{% trans 'Places submitted by' %} {{explorer.user.username}}</h1> <h1 class="LP-Headline">{% translate 'Places submitted by' %} {{explorer.user.username}}</h1>
<ul class="LP-PlaceList__List"> <ul class="LP-PlaceList__List">
{% for place in place_list %} {% for place in place_list %}
<li class="LP-PlaceList__Item"> <li class="LP-PlaceList__Item">
@ -57,35 +93,18 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% include 'partials/nav/pagination.html' %} {% include 'partials/nav/pagination.html' %}
</div> </div>
</section> </section>
<section class="LP-Section"> <section class="LP-Section">
<h1 class="LP-Headline">{% trans 'Images submitted by' %} {{explorer.user.username}}</h1> {% translate 'Images submitted by' as submitted_by %}
<div class="LP-ImageGrid"> {% include 'partials/placeImageGrid.html' with headline=submitted_by|add:' '|add:explorer.user.username image_list=assets.placeimages.all%}
<ul class="LP-ImageGrid__Container">
{% for place_image in assets.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%}
<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 %}
</ul>
</div>
</section> </section>
<section class=" LP-Section"> <section class=" LP-Section">
<h1 class="LP-Headline">{% trans 'Photo albums submitted by' %} {{explorer.user.username}}</h1> <h1 class="LP-Headline">{% translate 'Photo albums submitted by' %} {{explorer.user.username}}</h1>
<div class="LP-LinkList"> <div class="LP-LinkList">
<ul class="LP-LinkList__Container"> <ul class="LP-LinkList__Container">
{% for photo_album in assets.photoalbums.all %} {% for photo_album in assets.photoalbums.all %}

View File

@ -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 %}

View File

@ -11,7 +11,26 @@
<article class="LP-TextSection LP-TextSection__Text"> <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> <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> <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. <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> </li>
@ -25,15 +44,6 @@
<li> <li>
<b>Sprayen ist ein absolutes "no-go"!</b> <b>Sprayen ist ein absolutes "no-go"!</b>
</li> </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> <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 ...). <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> </li>
@ -46,7 +56,19 @@
<li> <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. <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> </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> </article>
{% endblock maincontent %} {% endblock maincontent %}

View File

@ -0,0 +1,4 @@
<form method={{form.method}}>
{{form}}
<input type="submit"/>
</form>

View File

@ -1,5 +1,6 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load lostplaces%}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -11,6 +12,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'main.css' %}"> <link rel="stylesheet" href="{% static 'main.css' %}">
<link rel="stylesheet" href="{% static 'materialdesignicons.css' %}">
<link rel="icon" type="image/png" href="{% static 'favicon.ico' %}"> <link rel="icon" type="image/png" href="{% static 'favicon.ico' %}">
<title> <title>
{% block title %}Urban Exploration{% endblock %} {% block title %}Urban Exploration{% endblock %}
@ -29,16 +31,16 @@
<span class="LP-Paragraph"> <span class="LP-Paragraph">
{% if user.is_authenticated %} {% if user.is_authenticated %}
Hi {{ user.username }}! 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">{% trans 'Profile' %}</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 %} {% 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 %} {% endif %}
{% else %} {% else %}
You are not logged in. 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 'login' %}"><span class="LP-Link__Text">{% translate '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 'signup' %}"><span class="LP-Link__Text">{% translate 'Sign up' %}</span></a>
{% endif %} {% endif %}
</span> </span>
</div> </div>
@ -48,14 +50,14 @@
<aside class="LP-Main__Sidebar"> <aside class="LP-Main__Sidebar">
<nav class="LP-Menu LP-Menu--sidebar"> <nav class="LP-Menu LP-Menu--sidebar">
<ul class="LP-Menu__List"> <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 '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">{% trans 'UrBex Codex' %}</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 %} {% block additional_menu_items %}
{% endblock 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_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">{% trans 'All places' %}</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> </ul>
</nav> </nav>
</aside> </aside>
@ -87,7 +89,7 @@
</div> </div>
{% block footer %} {% block footer %}
{% include 'partials/nav/footer.html' %} {% partial 'nav/footer' %}
{% endblock footer %} {% endblock footer %}
</body> </body>

View File

@ -2,31 +2,31 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load lostplaces %}
{% block additional_head %} {% block additional_head %}
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
<script src="{% static 'maps/ol.js' %}"></script> <script src="{% static 'maps/ol.js' %}"></script>
{% endblock additional_head %} {% endblock additional_head %}
# {% block title %}{% trans 'Home' %}{% endblock %} # {% block title %}{% translate 'Home' %}{% endblock %}
{% block maincontent %} {% block maincontent %}
{% include 'partials/welcome.html' %} {% partial 'welcome' %}
<article class="LP-TextSection"> <article class="LP-TextSection">
</article> </article>
{% partial 'osm_map' %}
{% include 'partials/osm_map.html' with config=mapping_config %} {% set config mapping_config %}
{% set modifier 'wide' %}
{% endpartial %}
<div class="LP-PlaceGrid"> <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"> <ul class="LP-PlaceGrid__Grid">
{% for place in place_list %} {% for place in place_list %}
<li class="LP-PlaceGrid__Item"> <li class="LP-PlaceGrid__Item">
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link"> {% include 'partials/place_teaser.html' with place=place%}
{% include 'partials/place_teaser.html' with place=place%}
</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% endblock maincontent %} {% endblock maincontent %}

View File

@ -1,30 +1,33 @@
{% extends 'global.html'%} {% extends 'global.html'%}
{% load i18n %} {% load i18n %}
{% load lostplaces%}
# {% block title %}Start{% endblock %} # {% block title %}Start{% endblock %}
{% block maincontent %} {% block maincontent %}
{% include 'partials/welcome.html' %} {% partial 'welcome' %}
<article class="LP-TextSection"> <article class="LP-TextSection">
<p class="LP-Paragraph"> <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> </p>
{% url 'signup' as signup_url %} {% url 'signup' as signup_url %}
<p class="LP-Paragraph"> <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> </p>
</article> </article>
<div class="LP-PlaceGrid"> <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"> <ul class="LP-PlaceGrid__Grid">
{% for place in place_list %} {% for place in place_list %}
<li class="LP-PlaceGrid__Item"> <li class="LP-PlaceGrid__Item">
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link"> <a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
<article class="LP-PlaceTeaser"> <article class="LP-PlaceTeaser">
<div class="LP-PlaceTeaser__Image"> <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>
<div class="LP-PlaceTeaser__Meta"> <div class="LP-PlaceTeaser__Meta">
<div class="LP-PlaceTeaser__Info"> <div class="LP-PlaceTeaser__Info">

View 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 %}

View File

@ -2,9 +2,7 @@
<div class="LP-Input {% if classes%}{{classes}}{% endif %} {% if field.errors %} LP-Input--error {% endif %}"> <div class="LP-Input {% if classes%}{{classes}}{% endif %} {% if field.errors %} LP-Input--error {% endif %}">
<label for="{{field.id_for_label}}" class="LP-Input__Label">{{field.label}}</label> <label for="{{field.id_for_label}}" class="LP-Input__Label">{{field.label}}</label>
{% with class="LP-Input__Field "%} {% render_field field class+='LP-Input__Field' %}
{% render_field field class=class%}
{% endwith %}
<span class="LP-Input__Message"> <span class="LP-Input__Message">
{% if field.errors %} {% if field.errors %}

View File

@ -1,9 +1,9 @@
{% load i18n %} {% load i18n %}
<div class="LP-Form__Field LP-Form__Button LP-Input"> <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>
<div class="LP-Form__Field LP-Form__Button LP-Input"> <div class="LP-Form__Field LP-Form__Button LP-Input">
<a class="LP-Link" href="{% if referer %}{{ referer }}{% else %}{% url 'lostplaces_home' %}{% endif %}"> <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> </a>
</div> </div>

View File

@ -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 %}

View File

@ -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 %}

View 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 %}

View File

@ -61,13 +61,13 @@
<!-- Bottom section with last line of text and links. --> <!-- Bottom section with last line of text and links. -->
<section class="LP-Footer__BottomSection LP-Footer__Flex"> <section class="LP-Footer__BottomSection LP-Footer__Flex">
<div class="LP-Footer__BottomWrapper"> <div class="LP-Footer__BottomWrapper">
{% trans 'Made by' %} <a href="https://git.mowoe.com/reverend" class="LP-Footer__GenericAnchor">Reverend</a> {% translate '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 'and' %} <a href="https://www.commander1024.de" class="LP-Footer__GenericAnchor">Commander1024.</a></span>
</div> </div>
<div class="LP-Footer__BottomWrapper"> <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> </div>
</section> </section>
</footer> </footer>

View File

@ -8,26 +8,26 @@
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--first"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--first">
<a href="?page=1" class="LP-Link"> <a href="?page=1" class="LP-Link">
<span class="LP-Icon"></span> <span class="LP-Icon"></span>
<span class="LP-Text">{% trans 'First' %}</span> <span class="LP-Text">{% translate 'First' %}</span>
</a> </a>
</li> </li>
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--previous"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--previous">
<a href="?page={{ page_obj.previous_page_number }}" class="LP-Link"> <a href="?page={{ page_obj.previous_page_number }}" class="LP-Link">
<span class="LP-Icon"></span> <span class="LP-Icon"></span>
<span class="LP-Text">{% trans 'Previous' %}</span> <span class="LP-Text">{% translate 'Previous' %}</span>
</a> </a>
</li> </li>
{% else %} {% else %}
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--first LP-Pagination__Item--disabled"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--first LP-Pagination__Item--disabled">
<a href="#" class="LP-Link"> <a href="#" class="LP-Link">
<span class="LP-Icon"></span> <span class="LP-Icon"></span>
<span class="LP-Text">{% trans 'First' %}</span> <span class="LP-Text">{% translate 'First' %}</span>
</a> </a>
</li> </li>
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--previous LP-Pagination__Item--disabled"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--previous LP-Pagination__Item--disabled">
<a href="#" class="LP-Link"> <a href="#" class="LP-Link">
<span class="LP-Icon"></span> <span class="LP-Icon"></span>
<span class="LP-Text">{% trans 'Previous' %}</span> <span class="LP-Text">{% translate 'Previous' %}</span>
</a> </a>
</li> </li>
{% endif %} {% endif %}
@ -47,25 +47,25 @@
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--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"> <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> <span class="LP-Icon"></span>
</a> </a>
</li> </li>
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--last"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--last">
<a href="?page={{ page_obj.paginator.num_pages }}" class="LP-Link"> <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> <span class="LP-Icon"></span>
</a> </a>
</li> </li>
{% else %} {% else %}
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--next LP-Pagination__Item--disabled"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--next LP-Pagination__Item--disabled">
<a href="#" class="LP-Link"> <a href="#" class="LP-Link">
<span class="LP-Text">{% trans 'Next' %}</span> <span class="LP-Text">{% translate 'Next' %}</span>
<span class="LP-Icon"></span> <span class="LP-Icon"></span>
</a> </a>
<li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--last LP-Pagination__Item--disabled"> <li class="LP-Pagination__Item LP-Pagination__Item--other LP-Pagination__Item--last LP-Pagination__Item--disabled">
<a href="#" class="LP-Link"> <a href="#" class="LP-Link">
<span class="LP-Text">{% trans 'Last' %}</span> <span class="LP-Text">{% translate 'Last' %}</span>
<span class="LP-Icon"></span> <span class="LP-Icon"></span>
</a> </a>
</li> </li>

View File

@ -1,5 +1,5 @@
{% load static %} {% 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> <div id="info" class="map-popup LP-Map__Popup"></div>
<script type="text/javascript"> <script type="text/javascript">

View File

@ -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>

View File

@ -2,12 +2,15 @@
<article class="LP-PlaceTeaser {% if extended %} LP-PlaceTeaser--extended{% endif %}"> <article class="LP-PlaceTeaser {% if extended %} LP-PlaceTeaser--extended{% endif %}">
<div class="LP-PlaceTeaser__Image"> <div class="LP-PlaceTeaser__Image">
{% if place.placeimages.all|length > 0 %} {% if place.get_hero_image %}
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url}}" /> {% include 'partials/image.html' with source_url=place.get_hero_image.filename.thumbnail.url link_url=place.get_absolute_url%}
{% else %} {% else %}
<img class="LP-Image" src="{% static 'images/missing_image.png' %}" /> <a href="{{place.get_absolute_url}}">
<img class="LP-Image" src="{% static 'images/missing_image.png' %}" />
</a>
{% endif %} {% endif %}
</div> </div>
</a>
<div class="LP-PlaceTeaser__Meta"> <div class="LP-PlaceTeaser__Meta">
<div class="LP-PlaceTeaser__Info"> <div class="LP-PlaceTeaser__Info">
<span class="LP-PlaceTeaser__Title"> <span class="LP-PlaceTeaser__Title">
@ -20,16 +23,16 @@
<div class="LP-PlaceTeaser__Description"> <div class="LP-PlaceTeaser__Description">
<p class="LP-Paragraph"> <p class="LP-Paragraph">
{% if place.description|length > 210 %} {% if place.description|length > 210 %}
{{place.description|truncatechars:210|truncatewords:-1}} {{place.description|truncatechars:210|truncatewords:-1}}
{% else %} {% else %}
{{place.description}} {{place.description}}
{% endif %} {% endif %}
</p> </p>
</div> </div>
<div class="LP-PlaceTeaser__Icons"> <div class="LP-PlaceTeaser__Icons">
<ul class="LP-Icon__List"> <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">{% include 'partials/icons/place_favorite.html' with place=place%}</li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/location.svg' %}" /></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> <li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,3 @@
{%load lostplaces %}
{% include 'partials/image.html' with source_url=object.filename.thumbnail.url %}

View File

@ -25,13 +25,13 @@
</ul> </ul>
</div> </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"> <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 %} {% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable"> <div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field LP-Form__Button LP-Input LP-Input--tagging"> <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>
<div class="LP-Form__Field"> <div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=config.submit_form.tag_list classes="LP-Input--tagging" %} {% include 'partials/form/inputField.html' with field=config.submit_form.tag_list classes="LP-Input--tagging" %}

View 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 &quot;{{choice.1}}&quot;">
<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>

View File

@ -1,21 +1,21 @@
{% load i18n %} {% load i18n %}
<header class="LP-TextSection__Headline"> <header class="LP-TextSection__Headline">
<h1 class="LP-Headline">{% trans 'Start' %}</h1> <h1 class="LP-Headline">{% translate 'Start' %}</h1>
</header> </header>
<div class="LP-TextSection__Text"> <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 %} {% if user.is_authenticated %}
{{ user.username }} {{ user.username }}
{% else %} {% else %}
{% trans 'explorer' %} {% translate 'explorer' %}
{% endif %} {% endif %}
! !
</p> </p>
<p class="LP-Paragraph"> <p class="LP-Paragraph">
{% trans 'We strictly follow our' %} {% translate 'We strictly follow our' %}
<a class="LP-Link" href="{% url 'flatpage' slug='codex' %}"><span class="LP-Link__Text">{% trans 'UrBex codex' %}</span></a> <a class="LP-Link" href="{% url 'flatpage' slug='codex' %}"><span class="LP-Link__Text">{% translate 'UrBex codex' %}</span></a>
{% trans 'and expect you to do so, too.' %}' {% translate 'and expect you to do so, too.' %}'
</p> </p>
</div> </div>

View File

@ -1,19 +1,19 @@
{% extends 'global.html'%} {% extends 'global.html'%}
{% load i18n %} {% load i18n %}
{% block title %}{% trans 'Submit a photo album' %}{% endblock %} {% block title %}{% translate 'Submit a photo album' %}{% endblock %}
{% block additional_menu_items %} {% block additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span
class="LP-Link__Text">{% 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 <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 %} {% endblock additional_menu_items %}
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST"> <form class="LP-Form" method="POST">
<fieldset class="LP-Form__Fieldset"> <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 %} {% csrf_token %}
<div class="LP-Form__Composition"> <div class="LP-Form__Composition">
<div class="LP-Form__Field"> <div class="LP-Form__Field">

View File

@ -2,12 +2,12 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
# {% block title %}{% trans 'Create place' %}{% endblock %} # {% block title %}{% translate 'Create place' %}{% endblock %}
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST" enctype="multipart/form-data"> <form class="LP-Form" method="POST" enctype="multipart/form-data">
<fieldset class="LP-Form__Fieldset"> <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 %} {% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable"> <div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field"> <div class="LP-Form__Field">
@ -39,7 +39,7 @@
</div> </div>
</div> </div>
{% trans 'Create' as action %} {% translate 'Create' as action %}
<div class="LP-Form__Composition LP-Form__Composition--buttons"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %} {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
</div> </div>

View File

@ -2,21 +2,21 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans 'Delete lost place' %}{% endblock %} {% block title %}{% translate 'Delete lost place' %}{% endblock %}
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST"> <form class="LP-Form" method="POST">
<fieldset class="LP-Form__Fieldset"> <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 %} {% csrf_token %}
<div class="LP-Form__Composition"> <div class="LP-Form__Composition">
<div class="LP-Form__Field LP-Form__InfoText"> <div class="LP-Form__Field LP-Form__InfoText">
<p class="LP-Paragraph">{% blocktrans with place_name=place.name%}Are you sure you want to delete {{place_name}}?{% endblocktrans %}</p> <p class="LP-Paragraph">{% blocktranslate with place_name=place.name%}Are you sure you want to delete {{place_name}}?{% endblocktranslate %}</p>
</div> </div>
</div> </div>
{% trans 'Delete' as action %} {% translate 'Delete' as action %}
<div class="LP-Form__Composition LP-Form__Composition--buttons"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
{% include 'partials/form/submit.html' with referer=request.META.HTTP_REFERER action=action %} {% include 'partials/form/submit.html' with referer=request.META.HTTP_REFERER action=action %}
</div> </div>

View File

@ -4,6 +4,7 @@
{% load thumbnail %} {% load thumbnail %}
{% load svg_icon %} {% load svg_icon %}
{% load lostplaces %}
{% block additional_head %} {% block additional_head %}
<link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css">
@ -15,19 +16,23 @@
{% block title %}{{place.name}}{% endblock %} {% block title %}{{place.name}}{% endblock %}
{% block additional_menu_items %} {% block additional_menu_items %}
<li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">{% trans 'Edit 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">{% trans 'Delete place' %}</span></a></li> <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">{% translate 'Delete place' %}</span></a></li>
{% endblock additional_menu_items %} {% endblock additional_menu_items %}
{% block maincontent %} {% block maincontent %}
<article class="LP-PlaceDetail"> <article class="LP-PlaceDetail">
<header class="LP-PlaceDetail__Header"> <header class="LP-PlaceDetail__Header">
<h1 class="LP-Headline">{{ place.name }}</h1> <h1 class="LP-Headline">{{ place.name }} {% include 'partials/icons/place_favorite.html' %} {% include 'partials/icons/place_visited.html' %}</h1>
{% if place.placeimages.first.filename.hero.url %} {% if place.get_hero_image %}
<figure class="LP-PlaceDetail__Image"> <div class="LP-PlaceDetail__Image">
<img src="{{ place.placeimages.first.filename.hero.url }}" class="LP-Image" /> {% partial image %}
</figure> {% 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 %} {% endif %}
</header> </header>
@ -35,27 +40,68 @@
<p class="LP-Paragraph">{{ place.description }}</p> <p class="LP-Paragraph">{{ place.description }}</p>
</div> </div>
<section class="LP-Section"> <div class="LP-Quickinfo">
<section class="LP-Section">
{% url 'place_tag_submit' place_id=place.id as tag_submit_url%} {% url 'place_tag_submit' place_id=place.id as tag_submit_url %}
{% include 'partials/tagging.html' with config=tagging_config %} {% partial tagging %}
{% set config=tagging_config %}
</section> {% endpartial %}
</section>
{{votingplace.vote}}
<section class="LP-Section">
{% partial voting %}
{% set place=place %}
{% set voting=placevoting %}
{% endpartial %}
</section>
</div>
<section class="LP-Section"> <section class="LP-Section">
<h1 class="LP-Headline">{% trans 'Map links' %}</h1> <h1 class="LP-Headline">{% translate 'Map links' %}</h1>
{% include 'partials/osm_map.html' with config=mapping_config%} {% partial osm_map config=mapping_config %}
<div class="LP-LinkList"> <ul class="LP-LinkList">
<ul class="LP-LinkList__Container"> <li class="LP-LinkList__Item">
<li class="LP-LinkList__Item"><a target="_blank" href="https://www.google.com/maps?q={{place.latitude|safe}},{{place.longitude|safe}}" class="LP-Link"><span class="LP-Text">Google Maps</span></a></li> <a target="_blank" href="https://www.google.com/maps?q={{place.latitude|safe}},{{place.longitude|safe}}" class="LP-Link">
<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> <span class="LP-Text RV-Iconized">
<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> <span class="RV-Iconized__Text">
</ul> Google Maps
</div> </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>
<section class=" LP-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"> <div class="LP-LinkList">
<ul class="LP-LinkList__Container"> <ul class="LP-LinkList__Container">
{% for photo_album in place.photoalbums.all %} {% 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" /> v216c0,11.046,8.954,20,20,20s20-8.954,20-20V276h216c11.046,0,20-8.954,20-20C512,244.954,503.046,236,492,236z" />
</g> </g>
</svg> </svg>
<span class="RV-Iconized__Text">{% trans 'Add photo album' %}</span> <span class="RV-Iconized__Text">{% translate 'Add photo album' %}</span>
</div> </div>
</a> </a>
</li> </li>
@ -90,30 +136,8 @@
</section> </section>
<section class="LP-Section"> <section class="LP-Section">
<h1 class="LP-Headline">{% trans 'Images' %}</h1> {% translate 'Images' as headline %}
<div class="LP-ImageGrid"> {% partial "placeImageGrid" image_list=place.placeimages.all %}
<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>
</section> </section>
</article> </article>

View File

@ -7,19 +7,17 @@
<script src="{% static 'maps/ol.js' %}"></script> <script src="{% static 'maps/ol.js' %}"></script>
{% endblock additional_head %} {% endblock additional_head %}
{% block title %}{% trans 'All Places' %}{% endblock %} {% block title %}{% translate 'All Places' %}{% endblock %}
{% block maincontent %} {% 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"> <div class="LP-PlaceList">
<h1 class="LP-Headline">{% trans 'Our lost places' %}</h1> <h1 class="LP-Headline">{% translate 'Our lost places' %}</h1>
<ul class="LP-PlaceList__List"> <ul class="LP-PlaceList__List">
{% for place in place_list %} {% for place in place_list %}
<li class="LP-PlaceList__Item"> <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 %}
{% include 'partials/place_teaser.html' with place=place extended=True %}
</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -1,13 +1,14 @@
{% extends 'global.html'%} {% extends 'global.html'%}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load widget_tweaks %}
# {% block title %}{% trans 'Edit place' %}{% endblock %} # {% block title %}{% translate 'Edit place' %}{% endblock %}
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST" enctype="multipart/form-data"> <form class="LP-Form" method="POST" enctype="multipart/form-data">
<fieldset class="LP-Form__Fieldset"> <fieldset class="LP-Form__Fieldset">
<legend class="LP-Form__Legend">{% trans 'Edit place' %}</legend> <legend class="LP-Form__Legend">{% translate 'Edit place' %}</legend>
{% csrf_token %} {% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable"> <div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field"> <div class="LP-Form__Field">
@ -38,8 +39,17 @@
{% include 'partials/form/inputField.html' with field=form.filename %} {% include 'partials/form/inputField.html' with field=form.filename %}
</div> </div>
</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"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %} {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
</div> </div>

View File

@ -4,7 +4,7 @@
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST" enctype="multipart/form-data"> <form class="LP-Form" method="POST" enctype="multipart/form-data">
<fieldset class="LP-Form__Fieldset"> <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 %} {% csrf_token %}
<div class="LP-Form__Composition"> <div class="LP-Form__Composition">

View File

@ -1,13 +1,13 @@
{% extends 'global.html'%} {% extends 'global.html'%}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
# {% block title %}{% trans 'Login' %}{% endblock %} # {% block title %}{% translate 'Login' %}{% endblock %}
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST" enctype="multipart/form-data"> <form class="LP-Form" method="POST" enctype="multipart/form-data">
<fieldset class="LP-Form__Fieldset"> <fieldset class="LP-Form__Fieldset">
<legend class="LP-Form__Legend">{% trans 'Login' %}</legend> <legend class="LP-Form__Legend">{% translate 'Login' %}</legend>
{% csrf_token %} {% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable"> <div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field"> <div class="LP-Form__Field">
@ -20,12 +20,12 @@
<div class="LP-Form__Composition LP-Form__Composition--buttons"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
<div class="LP-Form__Field LP-Form__Button LP-Input"> <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>
</div> </div>
</fieldset> </fieldset>
</form> </form>
<p class="LP-Headline">{% trans '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 %} {% endblock maincontent %}

View File

@ -3,13 +3,13 @@
{% load i18n %} {% load i18n %}
{% load widget_tweaks %} {% load widget_tweaks %}
# {% block title %}{% trans 'Sign up' %}{% endblock %} # {% block title %}{% translate 'Sign up' %}{% endblock %}
{% block maincontent %} {% block maincontent %}
<form class="LP-Form" method="POST"> <form class="LP-Form" method="POST">
<fieldset class="LP-Form__Fieldset"> <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 %} {% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable"> <div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field"> <div class="LP-Form__Field">
@ -19,6 +19,14 @@
{% include 'partials/form/inputField.html' with field=form.email %} {% include 'partials/form/inputField.html' with field=form.email %}
</div> </div>
</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__Composition">
<div class="LP-Form__Field"> <div class="LP-Form__Field">
@ -38,7 +46,7 @@
<div class="LP-Form__Composition LP-Form__Composition--buttons"> <div class="LP-Form__Composition LP-Form__Composition--buttons">
<div class="LP-Form__Field LP-Form__Button LP-Input"> <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>
</div> </div>
</fieldset> </fieldset>

View File

@ -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>

View File

@ -6,6 +6,16 @@ from django.http import request
register = template.Library() 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') @register.filter(name='proper_paginate')
def proper_paginate(paginator, current_page, neighbors=2): def proper_paginate(paginator, current_page, neighbors=2):
if paginator.num_pages > 2*neighbors: if paginator.num_pages > 2*neighbors:
@ -24,3 +34,97 @@ def proper_paginate(paginator, current_page, neighbors=2):
page_list = [f for f in range(start_index, end_index+1)] page_list = [f for f in range(start_index, end_index+1)]
return page_list[:(2*neighbors + 1)] return page_list[:(2*neighbors + 1)]
return paginator.page_range return paginator.page_range
@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:
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)

View File

@ -4,15 +4,15 @@ from django import forms
from django.utils import timezone from django.utils import timezone
from lostplaces.tests.forms import FormTestCase from lostplaces.tests.forms import FormTestCase
from lostplaces.forms import ExplorerCreationForm from lostplaces.forms import SignupVoucherForm
from lostplaces.models.models import Voucher 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 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 to this project and are already tested by django
""" """
form = ExplorerCreationForm form = SignupVoucherForm
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@ -37,7 +37,7 @@ class ExplorerCreationFormTestCase(FormTestCase):
) )
def test_validation_valid(self): def test_validation_valid(self):
form = ExplorerCreationForm(self.post_data) form = SignupVoucherForm(self.post_data)
self.assertTrue( self.assertTrue(
form.is_valid(), form.is_valid(),
msg='Expecting the %s to validate' % ( msg='Expecting the %s to validate' % (
@ -49,7 +49,7 @@ class ExplorerCreationFormTestCase(FormTestCase):
self.post_data = { self.post_data = {
'voucher': 'Imanotacode123' 'voucher': 'Imanotacode123'
} }
form = ExplorerCreationForm(self.post_data) form = SignupVoucherForm(self.post_data)
self.assertFalse( self.assertFalse(
form.is_valid(), form.is_valid(),
msg='Expecting the %s to not validate' % ( msg='Expecting the %s to not validate' % (

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re
from django.test import TestCase from django.test import TestCase
@ -16,6 +17,20 @@ class ViewTestCase(TestCase):
''' '''
view = None 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): def assertContext(self, response, key, value=None):
''' '''
Checks weather the response's context has the given key Checks weather the response's context has the given key
@ -87,7 +102,7 @@ class ViewTestCase(TestCase):
self.assertEqual( self.assertEqual(
response['location'], response['location'],
redirect_to, 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(redirect_to),
str(response['location']) str(response['location'])
) )
@ -113,6 +128,84 @@ class ViewTestCase(TestCase):
def assertHttpMethodNotAllowed(self, response): def assertHttpMethodNotAllowed(self, response):
self.assertHttpCode(response, 405) 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: class TaggableViewTestCaseMixin:
@ -149,13 +242,13 @@ class TaggableViewTestCaseMixin:
) )
self.assertTrue( self.assertTrue(
'submit_url_name' in context, 'submit_url' in context,
msg='Expecting the context for taggable to contain \'submit_url_name\' attribute' msg='Expecting the context for taggable to contain \'submit_url\' attribute'
) )
self.assertTrue( self.assertTrue(
type(context['submit_url_name']) == str, type(context['submit_url']) == str,
msg='Expecting submit_url_name to be of type string' msg='Expecting submit_url to be of type string'
) )
self.assertTrue( self.assertTrue(

View File

@ -1,15 +1,21 @@
#!/usr/bin/env python 23238#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.test import TestCase, RequestFactory, Client from django.test import TestCase, RequestFactory, Client
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.db import models
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.contrib.messages.storage.fallback import FallbackStorage from django.contrib.messages.storage.fallback import FallbackStorage
from django.utils import timezone from django.utils import timezone
from django.shortcuts import render
from lostplaces.models import Place from lostplaces.models import Place
from lostplaces.views import IsAuthenticatedMixin from lostplaces.views import (
IsAuthenticatedMixin,
PlaceAssetCreateView
)
from lostplaces.tests.views import ViewTestCase from lostplaces.tests.views import ViewTestCase
from lostplaces.models import DummyAsset
class TestIsAuthenticated(ViewTestCase): class TestIsAuthenticated(ViewTestCase):
view = IsAuthenticatedMixin view = IsAuthenticatedMixin
@ -20,9 +26,6 @@ class TestIsAuthenticated(ViewTestCase):
username='testpeter', username='testpeter',
password='Develop123' password='Develop123'
) )
def setUp(self):
self.client = Client()
def test_logged_in(self): def test_logged_in(self):
request = RequestFactory().get('/') request = RequestFactory().get('/')
@ -33,16 +36,15 @@ class TestIsAuthenticated(ViewTestCase):
self.assertHttpMethodNotAllowed(response) self.assertHttpMethodNotAllowed(response)
def test_not_logged_in(self): def test_not_logged_in(self):
request = RequestFactory().get('/someurl1234') request = RequestFactory().get('/')
request.user = AnonymousUser() request.user = AnonymousUser()
request.session = 'session' request.session = 'session'
messages = FallbackStorage(request) messages = FallbackStorage(request)
request._messages = messages request._messages = messages
response = IsAuthenticatedMixin.as_view()(request) 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) self.assertTrue(len(messages) > 0)
class TestIsPlaceSubmitterMixin(TestCase): class TestIsPlaceSubmitterMixin(TestCase):
@ -86,4 +88,52 @@ class TestIsPlaceSubmitterMixin(TestCase):
response = self.client.get(reverse_lazy('place_edit', kwargs={'pk': 1})) response = self.client.get(reverse_lazy('place_edit', kwargs={'pk': 1}))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertTrue(response.context['messages']) 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'
)

View File

@ -1,10 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re
from os import path
from django.test import TestCase, Client from django.test import TestCase, Client
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils import timezone from django.utils import timezone
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from lostplaces.models import Place from lostplaces.models import Place
@ -17,62 +22,62 @@ from lostplaces.forms import PlaceImageForm, PlaceForm
from lostplaces.tests.views import ( from lostplaces.tests.views import (
ViewTestCase, ViewTestCase,
TaggableViewTestCaseMixin, TaggableViewTestCaseMixin,
MapableViewTestCaseMixin MapableViewTestCaseMixin,
GlobalTemplateTestCaseMixin
) )
class TestPlaceCreateView(ViewTestCase): class TestPlaceListView(GlobalTemplateTestCaseMixin, ViewTestCase):
view = PlaceCreateView """
Tests the view listing all placs
@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):
view = PlaceListView view = PlaceListView
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
user = User.objects.create_user( user = User.objects.create_user(
username='testpeter', 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' password='Develop123'
) )
place = Place.objects.create( superuser = User.objects.create_user(
name='Im a place', username='toor',
submitted_when=timezone.now(), password='Develop123'
submitted_by=user.explorer,
location='Testtown',
latitude=50.5,
longitude=7.0,
description='This is just a test, do not worry'
) )
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.tags.add('I a tag', 'testlocation')
place.save() place.save()
@ -84,7 +89,279 @@ class TestPlaceListView(ViewTestCase):
response = self.client.get(reverse('place_list')) response = self.client.get(reverse('place_list'))
self.assertContext(response, 'mapping_config') 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): class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixin, ViewTestCase):
view = PlaceDetailView view = PlaceDetailView
@ -95,6 +372,8 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
username='testpeter', username='testpeter',
password='Develop123' password='Develop123'
) )
user.explorer.level = 3
user.explorer.save()
place = Place.objects.create( place = Place.objects.create(
name='Im a place', name='Im a place',
@ -103,11 +382,85 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
location='Testtown', location='Testtown',
latitude=50.5, latitude=50.5,
longitude=7.0, 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.tags.add('I a tag', 'testlocation')
place.save() 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): def test_context(self):
self.client.login(username='testpeter', password='Develop123') self.client.login(username='testpeter', password='Develop123')
response = self.client.get(reverse('place_detail', kwargs={'pk': 1})) response = self.client.get(reverse('place_detail', kwargs={'pk': 1}))
@ -127,3 +480,131 @@ class PlaceDetailViewTestCase(TaggableViewTestCaseMixin, MapableViewTestCaseMixi
) )
) )
self.assertMapableContext(response.context['mapping_config']) 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'
)

View 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'
)

View File

@ -4,6 +4,8 @@
from django.urls import path from django.urls import path
from lostplaces.views import ( from lostplaces.views import (
HomeView, HomeView,
FlatView,
OSMMapView,
PlaceDetailView, PlaceDetailView,
PlaceListView, PlaceListView,
PlaceCreateView, PlaceCreateView,
@ -11,30 +13,44 @@ from lostplaces.views import (
PlaceDeleteView, PlaceDeleteView,
PlaceTagDeleteView, PlaceTagDeleteView,
PlaceTagSubmitView, PlaceTagSubmitView,
PhotoAlbumCreateView, PlaceFavoriteView,
PhotoAlbumDeleteView, PlaceUnfavoriteView,
PlaceVisitCreateView,
PlaceVisitDeleteView,
PlaceImageCreateView, PlaceImageCreateView,
PlaceImageDeleteView, PlaceImageDeleteView,
FlatView, PhotoAlbumCreateView,
ExplorerProfileView PhotoAlbumDeleteView,
ExplorerProfileView,
ExplorerProfileUpdateView,
PlaceVoteView
) )
urlpatterns = [ urlpatterns = [
path('', HomeView.as_view(), name='lostplaces_home'), 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/<int:pk>/', PlaceDetailView.as_view(), name='place_detail'),
path('place/create/', PlaceCreateView.as_view(), name='place_create'), 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/update/<int:pk>/', PlaceUpdateView.as_view(), name='place_edit'),
path('place/delete/<int:pk>/', PlaceDeleteView.as_view(), name='place_delete'), path('place/delete/<int:pk>/', PlaceDeleteView.as_view(), name='place_delete'),
path('place/', PlaceListView.as_view(), name='place_list'), path('place/tag/create/<int:tagged_id>/', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
path('place_image/create/<int:place_id>', PlaceImageCreateView.as_view(), name='place_image_create'), path('place/tag/delete/<int:tagged_id>/<int:tag_id>/', PlaceTagDeleteView.as_view(), name='place_tag_delete'),
path('place_image/delete/<int:pk>', PlaceImageDeleteView.as_view(), name='place_image_delete'), path('place/fav/create/<int:place_id>/', PlaceFavoriteView.as_view(), name='place_favorite'),
path('flat/<slug:slug>/', FlatView, name='flatpage'), 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_image/create/<int:place_id>/', PlaceImageCreateView.as_view(), name='place_image_create'),
path('place/tag/<int:tagged_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'), path('place_image/delete/<int:pk>/', PlaceImageDeleteView.as_view(), name='place_image_delete'),
path('place/tag/delete/<int:tagged_id>/<int:tag_id>', PlaceTagDeleteView.as_view(), name='place_tag_delete'),
path('explorer/<int:explorer_id>/', ExplorerProfileView.as_view(), name='explorer_profile') 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')
] ]

View File

@ -4,16 +4,18 @@
from django.views import View from django.views import View
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect, get_object_or_404 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 django.utils.translation import ugettext_lazy as _
from lostplaces.models import Place from lostplaces.models import Place
from lostplaces.common import redirect_referer_or
class IsAuthenticatedMixin(LoginRequiredMixin, View): class IsAuthenticatedMixin(LoginRequiredMixin, View):
''' '''
@ -25,7 +27,8 @@ class IsAuthenticatedMixin(LoginRequiredMixin, View):
permission_denied_message = _('Please login to proceed') permission_denied_message = _('Please login to proceed')
def handle_no_permission(self): 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() return super().handle_no_permission()
class IsPlaceSubmitterMixin(UserPassesTestMixin, View): class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
@ -59,7 +62,27 @@ class IsPlaceSubmitterMixin(UserPassesTestMixin, View):
messages.error(self.request, self.place_submitter_error_message) messages.error(self.request, self.place_submitter_error_message)
return False 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): class PlaceAssetCreateView(IsAuthenticatedMixin, SuccessMessageMixin, CreateView):
"""
Abstract View for creating a place asset (i.e. PlaceImage)
"""
model = None model = None
template_name = '' template_name = ''
success_message = '' success_message = ''
@ -108,4 +131,11 @@ class PlaceAssetDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, SingleOb
place_id = self.get_object().place.id place_id = self.get_object().place.id
self.get_object().delete() self.get_object().delete()
messages.success(self.request, self.success_message) 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()

View File

@ -7,16 +7,18 @@ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.contrib import messages
from lostplaces.common import get_all_subclasses from lostplaces.common import get_all_subclasses
from lostplaces.views.base_views import IsAuthenticatedMixin from lostplaces.views.base_views import IsAuthenticatedMixin
from lostplaces.models.models import Explorer from lostplaces.models.models import Explorer
from lostplaces.models.place import Place, PlaceAsset from lostplaces.models.place import Place, PlaceAsset
from lostplaces.forms import ExplorerChangeForm, ExplorerUserChangeForm
class ExplorerProfileView(IsAuthenticatedMixin, View): class ExplorerProfileView(IsAuthenticatedMixin, View):
def get(self, request, explorer_id): def get(self, request, explorer_id):
explorer = get_object_or_404(Explorer, pk=explorer_id) explorer = get_object_or_404(Explorer, pk=explorer_id)
place_list = Place.objects.filter(submitted_by=explorer) place_list = explorer.places.all()
place_count = place_list.count() place_count = place_list.count()
context={ context={
@ -33,13 +35,50 @@ class ExplorerProfileView(IsAuthenticatedMixin, View):
asset_count += objects.count() asset_count += objects.count()
context['asset_count'] = asset_count context['asset_count'] = asset_count
print(context['assets'])
return render( return render(
request=request, request=request,
template_name='explorer/profile.html', template_name='explorer/profile.html',
context=context 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'))

View File

@ -18,7 +18,10 @@ class MultiplePlaceImageUploadMixin:
submitted_by=submitted_by submitted_by=submitted_by
) )
place_image.save() place_image.save()
if place.hero is None:
place.hero = place.placeimages.all()[0]
place.save()
class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView): class PlaceImageCreateView(MultiplePlaceImageUploadMixin, PlaceAssetCreateView):
model = PlaceImage model = PlaceImage
form_class = PlaceImageForm form_class = PlaceImageForm

View File

@ -1,30 +1,42 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from datetime import timedelta
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.views import View from django.views import View
from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic import ListView
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.utils.translation import ugettext_lazy as _ 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.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.models import (
from lostplaces.views.base_views import IsAuthenticatedMixin, IsPlaceSubmitterMixin 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.views.place_image_views import MultiplePlaceImageUploadMixin
from lostplaces.forms import PlaceForm, PlaceImageForm, TagSubmitForm from lostplaces.forms import PlaceForm, PlaceImageForm, TagSubmitForm
from lostplaces.common import redirect_referer_or
from taggit.models import Tag from taggit.models import Tag
class PlaceListView(IsAuthenticatedMixin, ListView): class PlaceListView(IsAuthenticatedMixin, LevelCapPlaceListView):
paginate_by = 5 paginate_by = 5
model = Place
template_name = 'place/place_list.html' template_name = 'place/place_list.html'
ordering = [Lower('name')] ordering = [Lower('name')]
@ -36,9 +48,17 @@ class PlaceListView(IsAuthenticatedMixin, ListView):
} }
return context return context
class PlaceDetailView(IsAuthenticatedMixin, View): class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
def get(self, request, pk): not_eligible_to_see_message = _('You\'r not allowed to see this place')
place = get_object_or_404(Place, pk=pk)
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 = { context = {
'place': place, 'place': place,
'mapping_config': { 'mapping_config': {
@ -49,8 +69,12 @@ class PlaceDetailView(IsAuthenticatedMixin, View):
'all_tags': Tag.objects.all(), 'all_tags': Tag.objects.all(),
'submit_form': TagSubmitForm(), 'submit_form': TagSubmitForm(),
'tagged_item': place, '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' '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) return render(request, 'place/place_detail.html', context)
@ -104,7 +128,15 @@ class PlaceCreateView(MultiplePlaceImageUploadMixin, IsAuthenticatedMixin, View)
self.request, self.request,
_('Please fill in all required fields.') _('Please fill in all required fields.')
) )
return render(request, 'place/place_create.html', context={'form': place_form}) return render(
request=request,
template_name='place/place_create.html',
context={
'place_form': place_form,
'place_image_form': PlaceImageForm()
},
status=400
)
class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView): class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
template_name = 'place/place_delete.html' template_name = 'place/place_delete.html'
@ -119,3 +151,80 @@ class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
def get_place(self): def get_place(self):
return self.get_object() 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}))

View File

@ -6,14 +6,15 @@ from django.views.generic.edit import CreateView
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.contrib import messages 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.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from django.utils.translation import ugettext_lazy as _ 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.models import Place, PhotoAlbum
from lostplaces.views.base_views import IsAuthenticatedMixin from lostplaces.views.base_views import IsAuthenticatedMixin
from lostplaces.common import redirect_referer_or
from lostplaces.views.base_views import ( from lostplaces.views.base_views import (
PlaceAssetCreateView, PlaceAssetCreateView,
@ -23,14 +24,14 @@ from lostplaces.views.base_views import (
from taggit.models import Tag from taggit.models import Tag
class SignUpView(SuccessMessageMixin, CreateView): class SignUpView(SuccessMessageMixin, CreateView):
form_class = ExplorerCreationForm form_class = SignupVoucherForm
success_url = reverse_lazy('login') success_url = reverse_lazy('login')
template_name = 'signup.html' template_name = 'signup.html'
success_message = _('User created') success_message = _('User created')
class HomeView(IsAuthenticatedMixin, View): class HomeView(IsAuthenticatedMixin, View):
def get(self, request, *args, **kwargs): 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 = { context = {
'place_list': place_list, 'place_list': place_list,
'mapping_config': { 'mapping_config': {
@ -41,7 +42,7 @@ class HomeView(IsAuthenticatedMixin, View):
return render(request, 'home.html', context) return render(request, 'home.html', context)
def handle_no_permission(self): def handle_no_permission(self):
place_list = Place.objects.all().order_by('-submitted_when')[:5] place_list = Place.objects.filter(level=1)[:5]
context = { context = {
'place_list': place_list 'place_list': place_list
} }
@ -79,10 +80,22 @@ class PlaceTagDeleteView(IsAuthenticatedMixin, View):
place = get_object_or_404(Place, pk=tagged_id) place = get_object_or_404(Place, pk=tagged_id)
tag = get_object_or_404(Tag, pk=tag_id) tag = get_object_or_404(Tag, pk=tag_id)
place.tags.remove(tag) 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): def FlatView(request, slug):
if request.LANGUAGE_CODE == 'de': if request.LANGUAGE_CODE == 'de':
return render(request, 'flat/' + slug + '-de' + '.html') return render(request, 'flat/' + slug + '-de' + '.html')
else: else:
return render(request, 'flat/' + slug + '.html') 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)

View File

@ -0,0 +1,5 @@
from django import forms
class SelectContent(forms.Select):
template_name = 'widgets/select_content.html'

View File

@ -3,7 +3,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
with open('../Readme.md') as f: with open('Readme.md') as f:
readme = f.read() readme = f.read()
setup( setup(

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB