Compare commits
	
		
			59 Commits
		
	
	
		
			0.1
			...
			e00c9318fa
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e00c9318fa | |||
| 6ed6c2c990 | |||
| 328e6899a6 | |||
| 843832d978 | |||
| e4cd8bb301 | |||
| 66581a9d2d | |||
| 490a0e9f3e | |||
| 7a3b8529f8 | |||
| 538b43c2a1 | |||
| ca2eac533f | |||
| 2007b512c7 | |||
| 751d4c81fe | |||
| 7a757bcf35 | |||
| 55b8d16751 | |||
| f34d70edd5 | |||
| 78f0e80136 | |||
| c84be34a37 | |||
| 3959096c95 | |||
| 76daa71217 | |||
| 0707d2d51e | |||
| d45724b774 | |||
| c8e46d42ee | |||
| 591469799f | |||
| 3cd44959b1 | |||
| ffef52269a | |||
| 9a30ced90c | |||
| d44bf0c0f2 | |||
| cad53c070c | |||
| a0627761a7 | |||
| 04940a0901 | |||
| 3ef31eda0f | |||
| 7cb8bc6bac | |||
| 636bb880c1 | |||
| c9d9ca9de0 | |||
| 5f3be5fc83 | |||
| f916ac9742 | |||
| 03af8a0c02 | |||
| 65f3642272 | |||
| e00a0f687b | |||
| 9af55d3f24 | |||
| 719e75a449 | |||
| 6688536a78 | |||
| 7835ddcf16 | |||
| 3caada8f08 | |||
| 4113811c5f | |||
| a660763ea4 | |||
| 55fe79b82c | |||
| b34e88a3ad | |||
| f52dd733d8 | |||
| ff428beaef | |||
| e8c4faff8e | |||
| 0b0a401486 | |||
| 2b56eed759 | |||
| e387091da6 | |||
| 5c5ad9502c | |||
| baf3f54e1a | |||
| 8cb7e61274 | |||
| fa1274c595 | |||
| a5839c2c36 | 
							
								
								
									
										2
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Pipfile
									
									
									
									
									
								
							| @@ -11,6 +11,8 @@ django = "*" | |||||||
| easy-thumbnails = "*" | easy-thumbnails = "*" | ||||||
| image = "*" | image = "*" | ||||||
| django-widget-tweaks = "*" | django-widget-tweaks = "*" | ||||||
|  | django-svg-icons = "*" | ||||||
|  | django-taggit = "*" | ||||||
|  |  | ||||||
| # Commented out to not explicitly specify Python 3 subversion. | # Commented out to not explicitly specify Python 3 subversion. | ||||||
| # [requires] | # [requires] | ||||||
|   | |||||||
							
								
								
									
										66
									
								
								Readme.md
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								Readme.md
									
									
									
									
									
								
							| @@ -9,7 +9,11 @@ Right now it depends on the following non-core Python 3 libraries. These can be | |||||||
|  |  | ||||||
|   * [django](https://www.djangoproject.com/) django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. |   * [django](https://www.djangoproject.com/) django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. | ||||||
|   * [easy-thumbnails](https://github.com/SmileyChris/easy-thumbnails) A powerful, yet easy to implement thumbnailing application for Django 1.11+ |   * [easy-thumbnails](https://github.com/SmileyChris/easy-thumbnails) A powerful, yet easy to implement thumbnailing application for Django 1.11+ | ||||||
|  |   * [image](https://github.com/francescortiz/image) Image cropping for django | ||||||
|  |   * [django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks) Tweak the form field rendering in templates, not in python-level form definitions. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Development | ||||||
| ### Setting up a (pipenv) virtual environment for development | ### Setting up a (pipenv) virtual environment for development | ||||||
|  |  | ||||||
| After having obtained the repository contents (either via .zip download or git clone), you can easily setup a pipenv virtual environment. The repo provides a Pipfile for easy dependency management that does not mess with your system. | After having obtained the repository contents (either via .zip download or git clone), you can easily setup a pipenv virtual environment. The repo provides a Pipfile for easy dependency management that does not mess with your system. | ||||||
| @@ -18,6 +22,7 @@ After having obtained the repository contents (either via .zip download or git c | |||||||
| $ cd lostplaces-backend | $ cd lostplaces-backend | ||||||
| $ pipenv install  | $ pipenv install  | ||||||
| $ pipenv shell | $ pipenv shell | ||||||
|  | (lostplaces-backend) $ lostplaces/manage.py makemigrations | ||||||
| (lostplaces-backend) $ lostplaces/manage.py migrate | (lostplaces-backend) $ lostplaces/manage.py migrate | ||||||
| (lostplaces-backend) $ lostplaces/manage.py createsuperuser | (lostplaces-backend) $ lostplaces/manage.py createsuperuser | ||||||
| (lostplaces-backend) $ lostplaces/manage.py runserver | (lostplaces-backend) $ lostplaces/manage.py runserver | ||||||
| @@ -33,4 +38,65 @@ $ pipenv shell | |||||||
| Visit: [admin](http://localhost:8000/admin) for administrative backend or | Visit: [admin](http://localhost:8000/admin) for administrative backend or | ||||||
| [frontend](http://localhost:8000/) | [frontend](http://localhost:8000/) | ||||||
|  |  | ||||||
|  | ## Installing lostplaces | ||||||
|  |  | ||||||
|  | ### Install dependencies | ||||||
|  | Python3, Django3, easy-thumbnails, image, django-widget-tweaks | ||||||
|  | ``` | ||||||
|  | pip install --user django easy-thumbnails image django-widget-tweaks | ||||||
|  | ``` | ||||||
|  | Or, if you use pipenv | ||||||
|  | ``` | ||||||
|  | pipenv install | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Add 'lostplaces_app' to your INSTALLED_APPS setting like this | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | INSTALLED_APPS = [ | ||||||
|  | 	... | ||||||
|  | 	'lostplaces_app', | ||||||
|  | 	'easy_thumbnails', | ||||||
|  | 	'widget_tweaks', | ||||||
|  | ] | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Add this configuration to your settings.py | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | ... | ||||||
|  | AUTH_USER_MODEL = 'lostplaces_app.Explorer' | ||||||
|  |  | ||||||
|  | LOGIN_URL = reverse_lazy('login') | ||||||
|  |  | ||||||
|  | THUMBNAIL_ALIASES = { | ||||||
|  | 	'': { | ||||||
|  | 		'thumbnail': {'size': (300, 300), 'crop': False}, | ||||||
|  | 		'hero': {'size': (700, 700), 'crop': False}, | ||||||
|  | 		'large': {'size': (1920, 1920), 'crop': False}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Include the lostplaces URLconf in your project urls.py like this | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | from django.urls import path, include | ||||||
|  | ... | ||||||
|  | urlpatterns = [ | ||||||
|  | 	... | ||||||
|  | 	path('lostplaces/', include('lostplaces_app.urls')), | ||||||
|  | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Run ``python manage.py migrate`` to create the lost places models. | ||||||
|  |  | ||||||
|  | Start the development server and visit http://127.0.0.1:8000/admin/ | ||||||
|  |  | ||||||
|  | Visit http://127.0.0.1:8000/lostplaces/ to CRUD lost places. | ||||||
|  |  | ||||||
|  |  | ||||||
| Happy developing ;-) | Happy developing ;-) | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ SECRET_KEY = 'n$(bx8(^)*wz1ygn@-ekt7rl^1km*!_c+fwwjiua8m@-x_rpl0' | |||||||
| # SECURITY WARNING: don't run with debug turned on in production! | # SECURITY WARNING: don't run with debug turned on in production! | ||||||
| DEBUG = True | DEBUG = True | ||||||
|  |  | ||||||
| ALLOWED_HOSTS = [] | ALLOWED_HOSTS = ['localhost', '192.168.178.49'] | ||||||
|  |  | ||||||
|  |  | ||||||
| # Application definition | # Application definition | ||||||
| @@ -44,6 +44,7 @@ INSTALLED_APPS = [ | |||||||
|     'django.contrib.staticfiles', |     'django.contrib.staticfiles', | ||||||
|     'easy_thumbnails', |     'easy_thumbnails', | ||||||
|     'widget_tweaks', |     'widget_tweaks', | ||||||
|  | 	'taggit' | ||||||
| ] | ] | ||||||
|  |  | ||||||
| MIDDLEWARE = [ | MIDDLEWARE = [ | ||||||
| @@ -148,3 +149,5 @@ THUMBNAIL_ALIASES = { | |||||||
|         'large': {'size': (1920, 1920), 'crop': False}, |         'large': {'size': (1920, 1920), 'crop': False}, | ||||||
|     }, |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | SVG_ICONS_SOURCE_FILE = os.path.join(BASE_DIR, 'lostplaces_app', 'static', 'icons', 'icons.icomoon.json') | ||||||
| @@ -26,3 +26,4 @@ admin.site.register(Explorer, ExplorerAdmin) | |||||||
| admin.site.register(Voucher, VoucherAdmin) | admin.site.register(Voucher, VoucherAdmin) | ||||||
| admin.site.register(Place) | admin.site.register(Place) | ||||||
| admin.site.register(PlaceImage) | admin.site.register(PlaceImage) | ||||||
|  | admin.site.register(PhotoAlbum) | ||||||
|   | |||||||
| @@ -48,3 +48,7 @@ class PlaceImageCreateForm(forms.ModelForm): | |||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|         self.fields['filename'].required = False |         self.fields['filename'].required = False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TagSubmitForm(forms.Form): | ||||||
|  | 	tag_list = forms.CharField(max_length=500, required=False) | ||||||
| @@ -10,6 +10,7 @@ from django.db import models | |||||||
| from django.dispatch import receiver | from django.dispatch import receiver | ||||||
| from django.contrib.auth.models import AbstractUser | from django.contrib.auth.models import AbstractUser | ||||||
| from easy_thumbnails.fields import ThumbnailerImageField | from easy_thumbnails.fields import ThumbnailerImageField | ||||||
|  | from taggit.managers import TaggableManager | ||||||
|  |  | ||||||
| # Create your models here. | # Create your models here. | ||||||
|  |  | ||||||
| @@ -29,7 +30,7 @@ class Voucher(models.Model): | |||||||
|     Creation date is being set automatically during voucher creation.  |     Creation date is being set automatically during voucher creation.  | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     code = models.CharField(unique=True, max_length=10) |     code = models.CharField(unique=True, max_length=30) | ||||||
|     created = models.DateTimeField(auto_now_add=True) |     created = models.DateTimeField(auto_now_add=True) | ||||||
|     expires = models.DateField() |     expires = models.DateField() | ||||||
|  |  | ||||||
| @@ -55,6 +56,23 @@ class Place (models.Model): | |||||||
|     longitude = models.FloatField() |     longitude = models.FloatField() | ||||||
|     description = models.TextField() |     description = models.TextField() | ||||||
|  |  | ||||||
|  |     tags = TaggableManager(blank=True) | ||||||
|  |     # Get center position of LP-geocoordinates. | ||||||
|  |  | ||||||
|  |     def average_latlon(place_list): | ||||||
|  |         amount = len(place_list) | ||||||
|  |         # Init fill values to prevent None | ||||||
|  |         longitude = 0 | ||||||
|  |         latitude = 0 | ||||||
|  |      | ||||||
|  |         if amount > 0: | ||||||
|  |             for place in place_list: | ||||||
|  |                 longitude += place.longitude | ||||||
|  |                 latitude += place.latitude | ||||||
|  |             return (latitude / amount, longitude / amount) | ||||||
|  |  | ||||||
|  |         return (latitude, longitude) | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.name |         return self.name | ||||||
|  |  | ||||||
| @@ -126,3 +144,24 @@ def auto_delete_file_on_change(sender, instance, **kwargs): | |||||||
|     if not old_file == new_file: |     if not old_file == new_file: | ||||||
|         if os.path.isfile(old_file.path): |         if os.path.isfile(old_file.path): | ||||||
|             os.remove(old_file.path) |             os.remove(old_file.path) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ExternalLink(models.Model): | ||||||
|  |     url = models.URLField(max_length=200) | ||||||
|  |     label = models.CharField(max_length=100) | ||||||
|  |     submitted_by = models.ForeignKey( | ||||||
|  |         Explorer, | ||||||
|  |         on_delete=models.SET_NULL, | ||||||
|  |         null=True, | ||||||
|  |         blank=True,  | ||||||
|  |         related_name='external_links' | ||||||
|  |     ) | ||||||
|  |     submitted_when = models.DateTimeField(auto_now_add=True, null=True) | ||||||
|  |  | ||||||
|  | class PhotoAlbum(ExternalLink): | ||||||
|  |     place = models.ForeignKey( | ||||||
|  |         Place, | ||||||
|  |         on_delete=models.CASCADE, | ||||||
|  |         related_name='photo_albums', | ||||||
|  |         null=True | ||||||
|  |     ) | ||||||
							
								
								
									
										1
									
								
								lostplaces/lostplaces_app/static/icons/delete.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lostplaces/lostplaces_app/static/icons/delete.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <svg height="427pt" viewBox="-40 0 427 427.00131" width="427pt" xmlns="http://www.w3.org/2000/svg"><path d="m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/><path d="m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/><path d="m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0"/><path d="m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0"/></svg> | ||||||
| After Width: | Height: | Size: 1.9 KiB | 
| @@ -1 +1,4 @@ | |||||||
| <svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"><path d="M 3 7 A 1.0001 1.0001 0 1 0 3 9 L 27 9 A 1.0001 1.0001 0 1 0 27 7 L 3 7 z M 3 14 A 1.0001 1.0001 0 1 0 3 16 L 27 16 A 1.0001 1.0001 0 1 0 27 14 L 3 14 z M 3 21 A 1.0001 1.0001 0 1 0 3 23 L 27 23 A 1.0001 1.0001 0 1 0 27 21 L 3 21 z"/></svg> | <svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"> | ||||||
|  | 	<path | ||||||
|  | 		d="M 3 7 A 1.0001 1.0001 0 1 0 3 9 L 27 9 A 1.0001 1.0001 0 1 0 27 7 L 3 7 z M 3 14 A 1.0001 1.0001 0 1 0 3 16 L 27 16 A 1.0001 1.0001 0 1 0 27 14 L 3 14 z M 3 21 A 1.0001 1.0001 0 1 0 3 23 L 27 23 A 1.0001 1.0001 0 1 0 27 21 L 3 21 z" /> | ||||||
|  | 	</svg> | ||||||
| Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 358 B | 
							
								
								
									
										13
									
								
								lostplaces/lostplaces_app/static/icons/icons.icomoon.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lostplaces/lostplaces_app/static/icons/icons.icomoon.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  | 	"trash": { | ||||||
|  | 		"paths": [ | ||||||
|  | 			"m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0", | ||||||
|  | 			"m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0", | ||||||
|  | 			"m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0", | ||||||
|  | 			"m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" | ||||||
|  | 		], | ||||||
|  | 		"height": "427pt", | ||||||
|  | 		"width": "427pt", | ||||||
|  | 		"viewBox": "-40 0 427 427.00131" | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								lostplaces/lostplaces_app/static/jquery-ui.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lostplaces/lostplaces_app/static/jquery-ui.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								lostplaces/lostplaces_app/static/jquery.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								lostplaces/lostplaces_app/static/jquery.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								lostplaces/lostplaces_app/static/maps/ol.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								lostplaces/lostplaces_app/static/maps/ol.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | .ol-box{box-sizing:border-box;border-radius:2px;border:2px solid #00f}.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width;transition:all .25s}.ol-scale-bar{position:absolute;bottom:8px;left:8px}.ol-scale-step-marker{width:1px;height:15px;background-color:#000;float:right;z-Index:10}.ol-scale-step-text{position:absolute;bottom:-5px;font-size:12px;z-Index:11;color:#000;text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff}.ol-scale-text{position:absolute;font-size:14px;text-align:center;bottom:25px;color:#000;text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff}.ol-scale-singlebar{position:relative;height:10px;z-Index:9;border:1px solid #000}.ol-unsupported{display:none}.ol-unselectable,.ol-viewport{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-overlaycontainer,.ol-overlaycontainer-stopevent{pointer-events:none}.ol-overlaycontainer-stopevent>*,.ol-overlaycontainer>*{pointer-events:auto}.ol-selectable{-webkit-touch-callout:default;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.ol-grabbing{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.ol-grab{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.ol-control{position:absolute;background-color:rgba(255,255,255,.4);border-radius:4px;padding:2px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-control button span{pointer-events:none}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;font-size:1.2em;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:rgba(0,60,136,.7)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;color:#000;text-shadow:0 0 2px #fff}.ol-attribution li{display:inline;list-style:none}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit;vertical-align:middle}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;height:200px}.ol-zoomslider button{position:relative;height:10px}.ol-touch .ol-zoomslider{top:5.5em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:inline-block}.ol-overviewmap .ol-overviewmap-map{border:1px solid #7b98bc;height:150px;margin:2px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:1px;left:2px;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-overviewmap-box{border:2px dotted rgba(0,60,136,.7)}.ol-overviewmap .ol-overviewmap-box:hover{cursor:move} | ||||||
|  | /*# sourceMappingURL=ol.css.map */ | ||||||
							
								
								
									
										1
									
								
								lostplaces/lostplaces_app/static/maps/ol.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lostplaces/lostplaces_app/static/maps/ol.css.map
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | {"version":3,"sources":["src/ol/ol.css"],"names":[],"mappings":"AAAA,QACE,WAAY,WACZ,cAAe,IACf,OAAQ,IAAI,MAAM,KAGpB,mBACE,IAAK,IACL,MAAO,IACP,SAAU,SAGZ,eACE,WAAY,kBACZ,cAAe,IACf,OAAQ,IACR,KAAM,IACN,QAAS,IACT,SAAU,SAEZ,qBACE,OAAQ,IAAI,MAAM,KAClB,WAAY,KACZ,MAAO,KACP,UAAW,KACX,WAAY,OACZ,OAAQ,IACR,YAAa,QAAQ,CAAE,MACvB,WAAY,IAAI,KAElB,cACE,SAAU,SACV,OAAQ,IACR,KAAM,IAER,sBACE,MAAO,IACP,OAAQ,KACR,iBAAkB,KAClB,MAAO,MACP,QAAS,GAEX,oBACE,SAAU,SACV,OAAQ,KACR,UAAW,KACX,QAAS,GACT,MAAO,KACP,YAAa,KAAK,EAAE,IAAO,CAAE,EAAE,IAAI,IAAO,CAAE,IAAI,EAAE,IAAO,CAAE,EAAE,KAAK,KAEpE,eACE,SAAU,SACV,UAAW,KACX,WAAY,OACZ,OAAQ,KACR,MAAO,KACP,YAAa,KAAK,EAAE,IAAO,CAAE,EAAE,IAAI,IAAO,CAAE,IAAI,EAAE,IAAO,CAAE,EAAE,KAAK,KAEpE,oBACE,SAAU,SACV,OAAQ,KACR,QAAS,EACT,OAAQ,IAAI,MAAM,KAGpB,gBACE,QAAS,KAEG,iBAAd,aACE,sBAAuB,KACvB,oBAAqB,KACrB,iBAAkB,KAClB,gBAAiB,KACjB,YAAa,KACb,4BAA6B,YAE/B,qBAAsB,+BACpB,eAAgB,KAEQ,iCAA1B,uBACE,eAAgB,KAElB,eACE,sBAAuB,QACvB,oBAAqB,KACrB,iBAAkB,KAClB,gBAAiB,KACjB,YAAa,KAEf,aACE,OAAQ,iBACR,OAAQ,cACR,OAAQ,SAEV,SACE,OAAQ,KACR,OAAQ,aACR,OAAQ,UACR,OAAQ,KAEV,YACE,SAAU,SACV,iBAAkB,qBAClB,cAAe,IACf,QAAS,IAEX,kBACE,iBAAkB,qBAEpB,SACE,IAAK,KACL,KAAM,KAER,WACE,IAAK,KACL,MAAO,KACP,WAAY,QAAQ,KAAK,MAAM,CAAE,WAAW,GAAG,OAEjD,qBACE,QAAS,EACT,WAAY,OACZ,WAAY,QAAQ,KAAK,MAAM,CAAE,WAAW,GAAG,OAAO,KAExD,gBACE,IAAK,QACL,KAAM,KAER,gBACE,MAAO,KACP,IAAK,KAGP,mBACE,QAAS,MACT,OAAQ,IACR,QAAS,EACT,MAAO,KACP,UAAW,OACX,YAAa,IACb,gBAAiB,KACjB,WAAY,OACZ,OAAQ,QACR,MAAO,QACP,YAAa,KACb,iBAAkB,kBAClB,OAAQ,KACR,cAAe,IAEjB,qCACE,OAAQ,KACR,QAAS,EAEX,wBACE,eAAgB,KAElB,uBACE,YAAa,MAEf,YACE,QAAS,MACT,YAAa,IACb,UAAW,MACX,YAAa,UAEf,6BACE,UAAW,MAEb,0BACE,IAAK,MAGP,yBADA,yBAEE,gBAAiB,KACjB,iBAAkB,kBAEpB,qBACE,cAAe,IAAI,IAAI,EAAE,EAE3B,sBACE,cAAe,EAAE,EAAE,IAAI,IAIzB,gBACE,WAAY,MACZ,OAAQ,KACR,MAAO,KACP,UAAW,mBAGb,mBACE,OAAQ,EACR,QAAS,EAAE,KACX,MAAO,KACP,YAAa,EAAE,EAAE,IAAI,KAEvB,mBACE,QAAS,OACT,WAAY,KAEd,0CACE,QAAS,IAEX,oBACE,WAAY,IACZ,UAAW,QACX,eAAgB,OAEE,uBAApB,mBACE,QAAS,aAEX,gCACE,QAAS,KAEX,mCACE,WAAY,qBAEd,iCACE,OAAQ,EACR,MAAO,EACP,cAAe,IAAI,EAAE,EAEvB,qCACE,WAAY,MACZ,WAAY,MAEd,wCACE,QAAS,KAGX,eACE,IAAK,MACL,KAAM,KACN,OAAQ,MAEV,sBACE,SAAU,SACV,OAAQ,KAGV,yBACE,IAAK,MAGP,gBACE,KAAM,KACN,OAAQ,KAEV,iCACE,OAAQ,EACR,KAAM,EACN,cAAe,EAAE,IAAI,EAAE,EAEzB,oCACA,uBACE,QAAS,aAEX,oCACE,OAAQ,IAAI,MAAM,QAClB,OAAQ,MACR,OAAQ,IACR,MAAO,MAET,0CACE,OAAQ,IACR,KAAM,IACN,SAAU,SAEZ,iDACA,wCACE,QAAS,KAEX,mCACE,WAAY,qBAEd,oBACE,OAAQ,IAAI,OAAO,kBAGrB,0CACE,OAAQ"} | ||||||
							
								
								
									
										2
									
								
								lostplaces/lostplaces_app/static/maps/ol.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								lostplaces/lostplaces_app/static/maps/ol.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								lostplaces/lostplaces_app/static/maps/ol.js.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lostplaces/lostplaces_app/static/maps/ol.js.map
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | # Keeping these files up-to-date is something for CI / release management. | ||||||
|  | # But for now I noted the source urls down here for later reference. | ||||||
|  |  | ||||||
|  | https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/css/ol.css | ||||||
|  | https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/css/ol.css.map | ||||||
|  | https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/build/ol.js | ||||||
|  | https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.3.1/build/ol.js.map | ||||||
							
								
								
									
										548
									
								
								lostplaces/lostplaces_app/static/tagify.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										548
									
								
								lostplaces/lostplaces_app/static/tagify.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,548 @@ | |||||||
|  | :root { | ||||||
|  |     --tagify-dd-color-primary: rgb(53, 149, 246); | ||||||
|  |     --tagify-dd-bg-color: white | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify { | ||||||
|  |     --tags-border-color: #DDD; | ||||||
|  |     --tags-hover-border-color: #CCC; | ||||||
|  |     --tags-focus-border-color: #3595f6; | ||||||
|  |     --tag-bg: #E5E5E5; | ||||||
|  |     --tag-hover: #D3E2E2; | ||||||
|  |     --tag-text-color: black; | ||||||
|  |     --tag-text-color--edit: black; | ||||||
|  |     --tag-pad: 0.3em 0.5em; | ||||||
|  |     --tag-inset-shadow-size: 1.1em; | ||||||
|  |     --tag-invalid-color: #D39494; | ||||||
|  |     --tag-invalid-bg: rgba(211, 148, 148, 0.5); | ||||||
|  |     --tag-remove-bg: rgba(211, 148, 148, 0.3); | ||||||
|  |     --tag-remove-btn-color: black; | ||||||
|  |     --tag-remove-btn-bg: none; | ||||||
|  |     --tag-remove-btn-bg--hover: #c77777; | ||||||
|  |     --input-color: inherit; | ||||||
|  |     --tag--min-width: 1ch; | ||||||
|  |     --tag--max-width: auto; | ||||||
|  |     --tag-hide-transition: .3s; | ||||||
|  |     --placeholder-color: rgba(0, 0, 0, 0.4); | ||||||
|  |     --placeholder-color-focus: rgba(0, 0, 0, 0.25); | ||||||
|  |     --loader-size: .8em; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: flex-start; | ||||||
|  |     flex-wrap: wrap; | ||||||
|  |     border: 1px solid #ddd; | ||||||
|  |     border: 1px solid var(--tags-border-color); | ||||||
|  |     padding: 0; | ||||||
|  |     line-height: 1.1; | ||||||
|  |     cursor: text; | ||||||
|  |     outline: 0; | ||||||
|  |     position: relative; | ||||||
|  |     transition: .1s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes tags--bump { | ||||||
|  |     30% { | ||||||
|  |         transform: scale(1.2) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes rotateLoader { | ||||||
|  |     to { | ||||||
|  |         transform: rotate(1turn) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify:hover { | ||||||
|  |     border-color: #ccc; | ||||||
|  |     border-color: var(--tags-hover-border-color) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify.tagify--focus { | ||||||
|  |     transition: 0s; | ||||||
|  |     border-color: #3595f6; | ||||||
|  |     border-color: var(--tags-focus-border-color) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify[readonly]:not(.tagify--mix) { | ||||||
|  |     cursor: default | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify[readonly]:not(.tagify--mix)>.tagify__input { | ||||||
|  |     visibility: hidden; | ||||||
|  |     width: 0; | ||||||
|  |     margin: 5px 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify[readonly]:not(.tagify--mix) .tagify__tag__removeBtn { | ||||||
|  |     display: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify[readonly]:not(.tagify--mix) .tagify__tag>div { | ||||||
|  |     padding: .3em .5em; | ||||||
|  |     padding: var(--tag-pad) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify[readonly]:not(.tagify--mix) .tagify__tag>div::before { | ||||||
|  |     background: linear-gradient(45deg, var(--tag-bg) 25%, transparent 25%, transparent 50%, var(--tag-bg) 50%, var(--tag-bg) 75%, transparent 75%, transparent) 0/5px 5px; | ||||||
|  |     box-shadow: none; | ||||||
|  |     filter: brightness(.95) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--loading .tagify__input::before { | ||||||
|  |     content: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--loading .tagify__input::after { | ||||||
|  |     content: ''; | ||||||
|  |     vertical-align: middle; | ||||||
|  |     opacity: 1; | ||||||
|  |     width: .7em; | ||||||
|  |     height: .7em; | ||||||
|  |     width: var(--loader-size); | ||||||
|  |     height: var(--loader-size); | ||||||
|  |     border: 3px solid; | ||||||
|  |     border-color: #eee #bbb #888 transparent; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     animation: rotateLoader .4s infinite linear; | ||||||
|  |     margin: -2px 0 -2px .5em | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--loading .tagify__input:empty::after { | ||||||
|  |     margin-left: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify+input, | ||||||
|  | .tagify+textarea { | ||||||
|  |     display: none !important | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag { | ||||||
|  |     display: inline-flex; | ||||||
|  |     align-items: center; | ||||||
|  |     margin: 5px 0 5px 5px; | ||||||
|  |     position: relative; | ||||||
|  |     z-index: 1; | ||||||
|  |     outline: 0; | ||||||
|  |     cursor: default; | ||||||
|  |     transition: .13s ease-out | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag>div { | ||||||
|  |     vertical-align: top; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     max-width: 100%; | ||||||
|  |     padding: .3em .5em; | ||||||
|  |     padding: var(--tag-pad); | ||||||
|  |     color: #000; | ||||||
|  |     color: var(--tag-text-color); | ||||||
|  |     line-height: inherit; | ||||||
|  |     border-radius: 3px; | ||||||
|  |     -webkit-user-select: none; | ||||||
|  |     user-select: none; | ||||||
|  |     white-space: nowrap; | ||||||
|  |     transition: .13s ease-out | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag>div>* { | ||||||
|  |     white-space: pre-wrap; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-overflow: ellipsis; | ||||||
|  |     display: inline-block; | ||||||
|  |     vertical-align: top; | ||||||
|  |     min-width: var(--tag--min-width); | ||||||
|  |     max-width: var(--tag--max-width); | ||||||
|  |     transition: .8s ease, .1s color | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag>div>[contenteditable] { | ||||||
|  |     outline: 0; | ||||||
|  |     -webkit-user-select: text; | ||||||
|  |     user-select: text; | ||||||
|  |     cursor: text; | ||||||
|  |     margin: -2px; | ||||||
|  |     padding: 2px; | ||||||
|  |     max-width: 350px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag>div::before { | ||||||
|  |     content: ''; | ||||||
|  |     position: absolute; | ||||||
|  |     border-radius: inherit; | ||||||
|  |     left: 0; | ||||||
|  |     top: 0; | ||||||
|  |     right: 0; | ||||||
|  |     bottom: 0; | ||||||
|  |     z-index: -1; | ||||||
|  |     pointer-events: none; | ||||||
|  |     transition: 120ms ease; | ||||||
|  |     animation: tags--bump .3s ease-out 1; | ||||||
|  |     box-shadow: 0 0 0 1.1em #e5e5e5 inset; | ||||||
|  |     box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-bg) inset | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag:hover:not([readonly]) div::before { | ||||||
|  |     top: -2px; | ||||||
|  |     right: -2px; | ||||||
|  |     bottom: -2px; | ||||||
|  |     left: -2px; | ||||||
|  |     box-shadow: 0 0 0 1.1em #d3e2e2 inset; | ||||||
|  |     box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-hover) inset | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--loading { | ||||||
|  |     pointer-events: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--loading .tagify__tag__removeBtn { | ||||||
|  |     display: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--loading::after { | ||||||
|  |     --loader-size: .4em; | ||||||
|  |     content: ''; | ||||||
|  |     vertical-align: middle; | ||||||
|  |     opacity: 1; | ||||||
|  |     width: .7em; | ||||||
|  |     height: .7em; | ||||||
|  |     width: var(--loader-size); | ||||||
|  |     height: var(--loader-size); | ||||||
|  |     border: 3px solid; | ||||||
|  |     border-color: #eee #bbb #888 transparent; | ||||||
|  |     border-radius: 50%; | ||||||
|  |     animation: rotateLoader .4s infinite linear; | ||||||
|  |     margin: 0 .5em 0 -.1em | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--flash div::before { | ||||||
|  |     animation: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--hide { | ||||||
|  |     width: 0 !important; | ||||||
|  |     padding-left: 0; | ||||||
|  |     padding-right: 0; | ||||||
|  |     margin-left: 0; | ||||||
|  |     margin-right: 0; | ||||||
|  |     opacity: 0; | ||||||
|  |     transform: scale(0); | ||||||
|  |     transition: .3s; | ||||||
|  |     transition: var(--tag-hide-transition); | ||||||
|  |     pointer-events: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag.tagify--noAnim>div::before { | ||||||
|  |     animation: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span { | ||||||
|  |     opacity: .5 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before { | ||||||
|  |     box-shadow: 0 0 0 1.1em rgba(211, 148, 148, .5) inset !important; | ||||||
|  |     box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-invalid-bg) inset !important; | ||||||
|  |     transition: .2s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag[readonly] .tagify__tag__removeBtn { | ||||||
|  |     display: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag[readonly]>div::before { | ||||||
|  |     background: linear-gradient(45deg, var(--tag-bg) 25%, transparent 25%, transparent 50%, var(--tag-bg) 50%, var(--tag-bg) 75%, transparent 75%, transparent) 0/5px 5px; | ||||||
|  |     box-shadow: none; | ||||||
|  |     filter: brightness(.95) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--editable>div { | ||||||
|  |     color: #000; | ||||||
|  |     color: var(--tag-text-color--edit) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--editable>div::before { | ||||||
|  |     box-shadow: 0 0 0 2px #d3e2e2 inset !important; | ||||||
|  |     box-shadow: 0 0 0 2px var(--tag-hover) inset !important | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag--editable.tagify--invalid>div::before { | ||||||
|  |     box-shadow: 0 0 0 2px #d39494 inset !important; | ||||||
|  |     box-shadow: 0 0 0 2px var(--tag-invalid-color) inset !important | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag__removeBtn { | ||||||
|  |     order: 5; | ||||||
|  |     display: inline-flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: center; | ||||||
|  |     border-radius: 50px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     font: 14px/1 Arial; | ||||||
|  |     background: 0 0; | ||||||
|  |     background: var(--tag-remove-btn-bg); | ||||||
|  |     color: #000; | ||||||
|  |     color: var(--tag-remove-btn-color); | ||||||
|  |     width: 14px; | ||||||
|  |     height: 14px; | ||||||
|  |     margin-right: 4.66667px; | ||||||
|  |     margin-left: -4.66667px; | ||||||
|  |     transition: .2s ease-out | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag__removeBtn::after { | ||||||
|  |     content: "\00D7" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag__removeBtn:hover { | ||||||
|  |     color: #fff; | ||||||
|  |     background: #c77777; | ||||||
|  |     background: var(--tag-remove-btn-bg--hover) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag__removeBtn:hover+div>span { | ||||||
|  |     opacity: .5 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__tag__removeBtn:hover+div::before { | ||||||
|  |     box-shadow: 0 0 0 1.1em rgba(211, 148, 148, .3) inset !important; | ||||||
|  |     box-shadow: 0 0 0 var(--tag-inset-shadow-size) var(--tag-remove-bg) inset !important; | ||||||
|  |     transition: .2s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify:not(.tagify--mix) .tagify__input br { | ||||||
|  |     display: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify:not(.tagify--mix) .tagify__input * { | ||||||
|  |     display: inline; | ||||||
|  |     white-space: nowrap | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input { | ||||||
|  |     flex-grow: 1; | ||||||
|  |     display: inline-block; | ||||||
|  |     min-width: 110px; | ||||||
|  |     margin: 5px; | ||||||
|  |     padding: .3em .5em; | ||||||
|  |     padding: var(--tag-pad, .3em .5em); | ||||||
|  |     line-height: inherit; | ||||||
|  |     position: relative; | ||||||
|  |     white-space: pre-wrap; | ||||||
|  |     color: inherit; | ||||||
|  |     color: var(--input-color) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input:empty::before { | ||||||
|  |     transition: .2s ease-out; | ||||||
|  |     opacity: 1; | ||||||
|  |     transform: none; | ||||||
|  |     display: inline-block; | ||||||
|  |     width: auto | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--mix .tagify__input:empty::before { | ||||||
|  |     display: inline-block | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input:focus { | ||||||
|  |     outline: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input:focus::before { | ||||||
|  |     transition: .2s ease-out; | ||||||
|  |     opacity: 0; | ||||||
|  |     transform: translatex(6px) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media all and (-ms-high-contrast:none), | ||||||
|  | (-ms-high-contrast:active) { | ||||||
|  |     .tagify__input:focus::before { | ||||||
|  |         display: none | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @supports (-ms-ime-align:auto) { | ||||||
|  |     .tagify__input:focus::before { | ||||||
|  |         display: none | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input:focus:empty::before { | ||||||
|  |     transition: .2s ease-out; | ||||||
|  |     opacity: 1; | ||||||
|  |     transform: none; | ||||||
|  |     color: rgba(0, 0, 0, .25); | ||||||
|  |     color: var(--placeholder-color-focus) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @-moz-document url-prefix() { | ||||||
|  |     .tagify__input:focus:empty::after { | ||||||
|  |         display: none | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input::before { | ||||||
|  |     content: attr(data-placeholder); | ||||||
|  |     height: 1em; | ||||||
|  |     line-height: 1em; | ||||||
|  |     margin: auto 0; | ||||||
|  |     z-index: 1; | ||||||
|  |     color: rgba(0, 0, 0, .4); | ||||||
|  |     color: var(--placeholder-color); | ||||||
|  |     white-space: nowrap; | ||||||
|  |     pointer-events: none; | ||||||
|  |     opacity: 0; | ||||||
|  |     position: absolute | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--mix .tagify__input::before { | ||||||
|  |     display: none; | ||||||
|  |     position: static; | ||||||
|  |     line-height: inherit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input::after { | ||||||
|  |     content: attr(data-suggest); | ||||||
|  |     display: inline-block; | ||||||
|  |     white-space: pre; | ||||||
|  |     color: #000; | ||||||
|  |     opacity: .3; | ||||||
|  |     pointer-events: none; | ||||||
|  |     max-width: 100px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input .tagify__tag { | ||||||
|  |     margin: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__input .tagify__tag>div { | ||||||
|  |     padding-top: 0; | ||||||
|  |     padding-bottom: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--mix { | ||||||
|  |     display: block | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--mix .tagify__input { | ||||||
|  |     padding: 5px; | ||||||
|  |     margin: 0; | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     line-height: 1.5 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--mix .tagify__input::before { | ||||||
|  |     height: auto | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--mix .tagify__input::after { | ||||||
|  |     content: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--select::after { | ||||||
|  |     content: '>'; | ||||||
|  |     opacity: .5; | ||||||
|  |     position: absolute; | ||||||
|  |     top: 50%; | ||||||
|  |     right: 0; | ||||||
|  |     bottom: 0; | ||||||
|  |     font: 16px monospace; | ||||||
|  |     line-height: 8px; | ||||||
|  |     height: 8px; | ||||||
|  |     pointer-events: none; | ||||||
|  |     transform: translate(-150%, -50%) scaleX(1.2) rotate(90deg); | ||||||
|  |     transition: .2s ease-in-out | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--select[aria-expanded=true]::after { | ||||||
|  |     transform: translate(-150%, -50%) rotate(270deg) scaleY(1.2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--select .tagify__tag { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     right: 1.8em; | ||||||
|  |     bottom: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--select .tagify__tag div { | ||||||
|  |     display: none | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--select .tagify__input { | ||||||
|  |     width: 100% | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify--invalid { | ||||||
|  |     --tags-border-color: #D39494 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown { | ||||||
|  |     position: absolute; | ||||||
|  |     z-index: 9999; | ||||||
|  |     transform: translateY(1px); | ||||||
|  |     overflow: hidden | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown[placement=top] { | ||||||
|  |     margin-top: 0; | ||||||
|  |     transform: translateY(-100%) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown[placement=top] .tagify__dropdown__wrapper { | ||||||
|  |     border-top-width: 1px; | ||||||
|  |     border-bottom-width: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown[position=text] { | ||||||
|  |     box-shadow: 0 0 0 3px rgba(var(--tagify-dd-color-primary), .1); | ||||||
|  |     font-size: .9em | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown[position=text] .tagify__dropdown__wrapper { | ||||||
|  |     border-width: 1px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown__wrapper { | ||||||
|  |     max-height: 300px; | ||||||
|  |     overflow: hidden; | ||||||
|  |     background: #fff; | ||||||
|  |     background: var(--tagify-dd-bg-color); | ||||||
|  |     border: 1px solid #3595f6; | ||||||
|  |     border-color: var(--tagify-dd-color-primary); | ||||||
|  |     border-top-width: 0; | ||||||
|  |     box-shadow: 0 2px 4px -2px rgba(0, 0, 0, .2); | ||||||
|  |     transition: .25s cubic-bezier(0, 1, .5, 1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown__wrapper:hover { | ||||||
|  |     overflow: auto | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown--initial .tagify__dropdown__wrapper { | ||||||
|  |     max-height: 20px; | ||||||
|  |     transform: translateY(-1em) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper { | ||||||
|  |     transform: translateY(2em) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown__item { | ||||||
|  |     box-sizing: inherit; | ||||||
|  |     padding: .3em .5em; | ||||||
|  |     margin: 1px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     border-radius: 2px; | ||||||
|  |     position: relative; | ||||||
|  |     outline: 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown__item--active { | ||||||
|  |     background: #3595f6; | ||||||
|  |     background: var(--tagify-dd-color-primary); | ||||||
|  |     color: #fff | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tagify__dropdown__item:active { | ||||||
|  |     filter: brightness(105%) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								lostplaces/lostplaces_app/static/tagify.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lostplaces/lostplaces_app/static/tagify.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -2,33 +2,22 @@ | |||||||
|  |  | ||||||
| <!DOCTYPE html> | <!DOCTYPE html> | ||||||
| <html lang="en"> | <html lang="en"> | ||||||
|     <head> |  | ||||||
|  | <head> | ||||||
|  |     {% block additional_head %} | ||||||
|  |     {% endblock additional_head %} | ||||||
|  |  | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|     <link rel="stylesheet" href="{% static 'main.css' %}"> |     <link rel="stylesheet" href="{% static 'main.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 %} | ||||||
|     </title> |     </title> | ||||||
|  |  | ||||||
|         {% block additional_head %} | </head> | ||||||
|         {% endblock additional_head %} |  | ||||||
|  |  | ||||||
|         <script> | <body> | ||||||
|             document.addEventListener("DOMContentLoaded", function(){ |  | ||||||
|                     Array.from(document.getElementsByClassName('LP-Main__Sidebar')).forEach(function(element){ |  | ||||||
|                             element.classList.add('LP-Main__Sidebar--hidden') |  | ||||||
|                     }) |  | ||||||
|                     setTimeout(function(){ |  | ||||||
|                         Array.from(document.getElementsByClassName('LP-Main__Sidebar')).forEach(function(element){ |  | ||||||
|                             element.classList.remove('LP-Main__Sidebar--hidden') |  | ||||||
|                     }) |  | ||||||
|                 }, 500) |  | ||||||
|             }) |  | ||||||
|         </script> |  | ||||||
|          |  | ||||||
|     </head> |  | ||||||
|  |  | ||||||
|     <body> |  | ||||||
|     <div class="LP-Wrapper__Site"> |     <div class="LP-Wrapper__Site"> | ||||||
|         <header class="LP-Header"> |         <header class="LP-Header"> | ||||||
|             <div class="LP-Header__Logo"> |             <div class="LP-Header__Logo"> | ||||||
| @@ -53,7 +42,7 @@ | |||||||
|                 </span> |                 </span> | ||||||
|             </div> |             </div> | ||||||
|         </header> |         </header> | ||||||
|             <input id="toggle_sidebar" class="LP-Menu__Trigger" type="checkbox"/> |         <input id="toggle_sidebar" class="LP-Menu__Trigger" type="checkbox" /> | ||||||
|         <label id="toggle_sidebar_label" for="toggle_sidebar" class="LP-Menu__TriggerLabel"></label> |         <label id="toggle_sidebar_label" for="toggle_sidebar" class="LP-Menu__TriggerLabel"></label> | ||||||
|         <aside class="LP-Main__Sidebar"> |         <aside class="LP-Main__Sidebar"> | ||||||
|             <nav class="LP-Menu LP-Menu--sidebar"> |             <nav class="LP-Menu LP-Menu--sidebar"> | ||||||
| @@ -92,5 +81,6 @@ | |||||||
|             {% endblock maincontent %} |             {% endblock maincontent %} | ||||||
|         </main> |         </main> | ||||||
|     </div> |     </div> | ||||||
|     </body> | </body> | ||||||
|  |  | ||||||
| </html> | </html> | ||||||
| @@ -1,10 +1,15 @@ | |||||||
| {% extends 'global.html'%} | {% extends 'global.html'%} | ||||||
| {% load static %} | {% load static %} | ||||||
|  | {% block additional_head %} | ||||||
|  |     <link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css"> | ||||||
|  |     <script src="{% static 'maps/ol.js' %}"></script> | ||||||
|  | {% endblock additional_head %} | ||||||
|  |  | ||||||
| # {% block title %}Start{% endblock %} | # {% block title %}Start{% endblock %} | ||||||
|  |  | ||||||
| {% block maincontent %} | {% block maincontent %} | ||||||
|  |  | ||||||
|  | {% include 'partials/osm_map.html' %} | ||||||
| <div class="LP-PlaceGrid"> | <div class="LP-PlaceGrid"> | ||||||
|     <h1 class="LP-Headline LP-Headline">Explore the latest locations</h1> |     <h1 class="LP-Headline LP-Headline">Explore the latest locations</h1> | ||||||
|     <ul class="LP-PlaceGrid__Grid"> |     <ul class="LP-PlaceGrid__Grid"> | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| {% load widget_tweaks %} | {% load widget_tweaks %} | ||||||
|  |  | ||||||
| <div class="LP-Input {% if field.errors %} LP-Input--error {% endif %}"> | <div class="LP-Input {% if classes%}{{classes}}{% endif %} {% if field.errors %} LP-Input--error {% endif %}"> | ||||||
|     <label for="{{field.id_for_label}}" class="LP-Input__Label">{{field.label}}</label> |     <label for="{{field.id_for_label}}" class="LP-Input__Label">{{field.label}}</label> | ||||||
|     {% render_field field class="LP-Input__Field"%} |     {% with class="LP-Input__Field "%} | ||||||
|  |     {% render_field field class=class%} | ||||||
|  |     {% endwith %} | ||||||
|  |  | ||||||
|     <span class="LP-Input__Message"> |     <span class="LP-Input__Message"> | ||||||
|         {% if field.errors %} |         {% if field.errors %} | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								lostplaces/lostplaces_app/templates/partials/osm_map.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								lostplaces/lostplaces_app/templates/partials/osm_map.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  |  | ||||||
|  | <div id="map" class="map" style="height: 300px"></div> | ||||||
|  |     <div id="info" class="map-popup"></div> | ||||||
|  |  | ||||||
|  |         <script type="text/javascript"> | ||||||
|  |             var map = new ol.Map({ | ||||||
|  |             target: 'map', | ||||||
|  |             layers: [ | ||||||
|  |                 new ol.layer.Tile({ | ||||||
|  |                 source: new ol.source.OSM() | ||||||
|  |                 }), | ||||||
|  |             ], | ||||||
|  |             view: new ol.View({ | ||||||
|  |                 center: ol.proj.fromLonLat([{{place_map_center|last}}, {{place_map_center|first}}]), | ||||||
|  |                 zoom: 9 | ||||||
|  |             }) | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             var vectorSource = new ol.source.Vector({ | ||||||
|  |                 features: [ | ||||||
|  |                     {% for place in place_list %} | ||||||
|  |                         new ol.Feature({ | ||||||
|  |                             geometry: new ol.geom.Point( | ||||||
|  |                                 ol.proj.fromLonLat([{{place.longitude}},{{place.latitude}}]) | ||||||
|  |                             ), | ||||||
|  |                             url: '{% url 'place_detail' pk=place.pk %}', | ||||||
|  |                             name: '{{place.name}}' | ||||||
|  |                         }), | ||||||
|  |                     {% endfor %} | ||||||
|  |                 ] | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             var markerVectorLayer = new ol.layer.Vector({ | ||||||
|  |                 source: vectorSource, | ||||||
|  |                 style: new ol.style.Style({ | ||||||
|  |                         image: new ol.style.Icon({ | ||||||
|  |                             anchor: [0.5, 46], | ||||||
|  |                             anchorXUnits: 'fraction', | ||||||
|  |                             anchorYUnits: 'pixels', | ||||||
|  |                             scale: 0.3, | ||||||
|  |                             src: 'http://icons.iconarchive.com/icons/paomedia/small-n-flat/128/map-marker-icon.png' | ||||||
|  |                         }) | ||||||
|  |                 }) | ||||||
|  |             }); | ||||||
|  |             map.addLayer(markerVectorLayer); | ||||||
|  |  | ||||||
|  |             var overlay = new ol.Overlay({ | ||||||
|  |                 element: document.getElementById('info'), | ||||||
|  |                 positioning: 'bottom-left' | ||||||
|  |             }); | ||||||
|  |             overlay.setMap(map); | ||||||
|  |  | ||||||
|  |             map.on(['singleclick'], function(evt) { | ||||||
|  |                 var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) { | ||||||
|  |                     window.open(feature.get('url'), '_blank'); | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             map.on(['pointermove'], function(evt) { | ||||||
|  |                 var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) { | ||||||
|  |                     overlay.setPosition(evt.coordinate.map(element => element + 1)); | ||||||
|  |                     overlay.getElement().innerHTML = feature.get('name'); | ||||||
|  |                     return feature; | ||||||
|  |                 }); | ||||||
|  |                 overlay.getElement().style.display = feature ? '' : 'none'; | ||||||
|  |                 document.body.style.cursor = feature ? 'pointer' : ''; | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         </script> | ||||||
							
								
								
									
										53
									
								
								lostplaces/lostplaces_app/templates/partials/tagging.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lostplaces/lostplaces_app/templates/partials/tagging.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | <div class="LP-TagList"> | ||||||
|  |     <ul class="LP-TagList__List"> | ||||||
|  |         {% for tag in tag_list %} | ||||||
|  |         <li class="LP-TagList__Item"> | ||||||
|  |             <div class="LP-Tag"> | ||||||
|  |                 <p class="LP-Paragraph">{{tag}}</p> | ||||||
|  |             </div> | ||||||
|  |         </li> | ||||||
|  |         {% endfor %} | ||||||
|  |     </ul> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <form id="id_tag_submit_form" class="LP-Form LP-Form--inline LP-Form--tagging" method="POST" action="{{url}}"> | ||||||
|  |     <fieldset class="LP-Form__Fieldset"> | ||||||
|  |         <legend class="LP-Form__Legend">Tags hinzufügen</legend> | ||||||
|  |         {% csrf_token %} | ||||||
|  |         <div class="LP-Form__Composition LP-Form__Composition--breakable"> | ||||||
|  | 			<div class="LP-Form__Field LP-Form__Button LP-Input LP-Input--tagging"> | ||||||
|  |                 <button id="id_tag_submit_button" class="LP-Button"> Tags hinzufügen</button> | ||||||
|  |             </div> | ||||||
|  |             <div class="LP-Form__Field"> | ||||||
|  |                 {% include 'partials/form/inputField.html' with field=input_field classes="LP-Input--tagging" %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </fieldset> | ||||||
|  | </form> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  |     const input = document.getElementById('{{input_field.auto_id}}') | ||||||
|  |     const submit_form = document.getElementById('id_tag_submit_form') | ||||||
|  |     const submit_button = document.getElementById('id_tag_submit_button') | ||||||
|  |  | ||||||
|  |     submit_form.onsubmit = () => false | ||||||
|  |  | ||||||
|  |     const tagify = new Tagify(input, { | ||||||
|  | 		'whitelist': [ | ||||||
|  | 		{% for tag in all_tags %} | ||||||
|  | 			'{{tag}}', | ||||||
|  | 		{% endfor %} | ||||||
|  | 		] | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     const on_form_submit = function() { | ||||||
|  |         concat_value = '' | ||||||
|  |         console.log(tagify) | ||||||
|  |         concat_value = tagify.value.map(value => value.value).join(',') | ||||||
|  |         console.log(concat_value) | ||||||
|  |         input.value = concat_value | ||||||
|  |         submit_form.submit() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     submit_button.onclick = on_form_submit | ||||||
|  | </script> | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | {% extends 'global.html'%} | ||||||
|  |  | ||||||
|  | {% block title %}Submit a photo album{% endblock %} | ||||||
|  |  | ||||||
|  | {% block additional_menu_items %} | ||||||
|  | <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_edit' pk=place.pk %}" class="LP-Link"><span | ||||||
|  | 			class="LP-Link__Text">Edit place</span></a></li> | ||||||
|  | <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span | ||||||
|  | 			class="LP-Link__Text">Delete place</span></a></li> | ||||||
|  | {% endblock additional_menu_items %} | ||||||
|  |  | ||||||
|  | {% block maincontent %} | ||||||
|  | <form class="LP-Form" method="POST"> | ||||||
|  |     <fieldset class="LP-Form__Fieldset"> | ||||||
|  |         <legend class="LP-Form__Legend">Submit a photo album for {{place.name}}</legend> | ||||||
|  |         {% csrf_token %} | ||||||
|  |         <div class="LP-Form__Composition"> | ||||||
|  |             <div class="LP-Form__Field"> | ||||||
|  |                 {% include 'partials/form/inputField.html' with field=form.url %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="LP-Form__Composition"> | ||||||
|  |             <div class="LP-Form__Field"> | ||||||
|  |                 {% include 'partials/form/inputField.html' with field=form.label %} | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="LP-Form__Composition LP-Form__Composition--buttons"> | ||||||
|  |             <div class="LP-Form__Field LP-Form__Button LP-Input"> | ||||||
|  |                 <button class="LP-Button">Submit</button> | ||||||
|  |             </div> | ||||||
|  |             <div class="LP-Form__Field LP-Form__Button LP-Input"> | ||||||
|  |                 <a class="LP-Link" href="{% url 'place_detail' pk=place.id%}"> | ||||||
|  |                     <button type="button" class="LP-Button LP-Button--cancel">Cancel</button> | ||||||
|  |                 </a> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </fieldset> | ||||||
|  | </form> | ||||||
|  | {% endblock maincontent %} | ||||||
| @@ -1,12 +1,24 @@ | |||||||
| {% extends 'global.html'%} | {% extends 'global.html'%} | ||||||
|  |  | ||||||
| {% load static %} | {% load static %} | ||||||
| {% load thumbnail %} | {% load thumbnail %} | ||||||
|  | {% load svg_icon %} | ||||||
|  |  | ||||||
|  | {% block additional_head %} | ||||||
|  | <link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css"> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | <script src="{% static 'maps/ol.js' %}"></script> | ||||||
|  |  | ||||||
|  | <script src="{% static 'tagify.min.js' %}"></script> | ||||||
|  | <link rel="stylesheet" href="{% static 'minimal.css' %}" type="text/css"> | ||||||
|  | {% endblock additional_head %} | ||||||
|  |  | ||||||
| {% block title %}{{place.name}}{% endblock %} | {% block 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">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">Edit place</span></a></li> | ||||||
|     <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Delete place</span></a></li> | <li class="LP-Menu__Item LP-Menu__Item--additional"><a href="{% url 'place_delete' pk=place.pk %}" class="LP-Link"><span class="LP-Link__Text">Delete place</span></a></li> | ||||||
| {% endblock additional_menu_items %} | {% endblock additional_menu_items %} | ||||||
|  |  | ||||||
| {% block maincontent %} | {% block maincontent %} | ||||||
| @@ -25,10 +37,18 @@ | |||||||
|         <p class="LP-Paragraph">{{ place.description }}</p> |         <p class="LP-Paragraph">{{ place.description }}</p> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  |     <section class="LP-Section"> | ||||||
|  |  | ||||||
|  |         {% url 'place_tag_submit' place_id=place.id as tag_submit_url%} | ||||||
|  |         {% include 'partials/tagging.html' with tag_list=place.tags.all url=tag_submit_url input_field=tagging_form.tag_list%} | ||||||
|  |  | ||||||
|  |     </section> | ||||||
|  |  | ||||||
|     <section class="LP-Section"> |     <section class="LP-Section"> | ||||||
|         <h1 class="LP-Headline">Map-Links</h1> |         <h1 class="LP-Headline">Map-Links</h1> | ||||||
|  |         {% include 'partials/osm_map.html' %} | ||||||
|         <div class="LP-LinkList"> |         <div class="LP-LinkList"> | ||||||
|             <ul class="LP-LinkList__List"> |             <ul class="LP-LinkList__Container"> | ||||||
|                 <li class="LP-LinkList__Item"><a target="_blank" href="https://www.google.com/maps?q={{place.latitude}},{{place.longitude}}" class="LP-Link"><span class="LP-Text">Google Maps</span></a></li> |                 <li class="LP-LinkList__Item"><a target="_blank" href="https://www.google.com/maps?q={{place.latitude}},{{place.longitude}}" class="LP-Link"><span class="LP-Text">Google Maps</span></a></li> | ||||||
|                 <li class="LP-LinkList__Item"><a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude}},{{place.longitude}}&icon=true&bg=dop" class="LP-Link"><span class="LP-Text">TIM Online</span></a></li> |                 <li class="LP-LinkList__Item"><a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude}},{{place.longitude}}&icon=true&bg=dop" class="LP-Link"><span class="LP-Text">TIM Online</span></a></li> | ||||||
|                 <li class="LP-LinkList__Item"><a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude}}&mlon={{place.longitude}}&zoom=16" class="LP-Link"><span class="LP-Text">OSM</span></a></li> |                 <li class="LP-LinkList__Item"><a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude}}&mlon={{place.longitude}}&zoom=16" class="LP-Link"><span class="LP-Text">OSM</span></a></li> | ||||||
| @@ -36,10 +56,45 @@ | |||||||
|         </div> |         </div> | ||||||
|     </section> |     </section> | ||||||
|  |  | ||||||
|  |     <section class=" LP-Section"> | ||||||
|  |         <h1 class="LP-Headline">Photoalben</h1> | ||||||
|  |         <div class="LP-LinkList"> | ||||||
|  |             <ul class="LP-LinkList__Container"> | ||||||
|  |                 {% for photo_album in place.photo_albums.all %} | ||||||
|  |                 <li class="LP-LinkList__Item"> | ||||||
|  |                     <a target="_blank" href="{{photo_album.url}}" class="LP-Link"> | ||||||
|  |                         <span class="LP-Text">{{photo_album.label}}</span> | ||||||
|  |                     </a> | ||||||
|  |                     {% if user == photo_album.submitted_by or user ==  place.submitted_by %} | ||||||
|  |                     <a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album"> | ||||||
|  |                         <div class="RV-Iconized__Container RV-Iconized__Container--small"> | ||||||
|  |                             {% icon 'trash' className="RV-Iconized__Icon" %} | ||||||
|  |                         </div> | ||||||
|  |                     </a> | ||||||
|  |                     {% endif %} | ||||||
|  |                 </li> | ||||||
|  |                 {% endfor %} | ||||||
|  |                 <li class="LP-LinkList__Item"> | ||||||
|  |                     <a href="{% url 'photo_album_create' place_id=place.id %}" class="LP-Link"> | ||||||
|  |                         <div class="RV-Iconized__Container RV-Iconized__Container--small"> | ||||||
|  |                             <svg class="RV-Iconized__Icon" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" xml:space="preserve"> | ||||||
|  |                                 <g> | ||||||
|  |                                     <path d="M492,236H276V20c0-11.046-8.954-20-20-20c-11.046,0-20,8.954-20,20v216H20c-11.046,0-20,8.954-20,20s8.954,20,20,20h216 | ||||||
|  | 		v216c0,11.046,8.954,20,20,20s20-8.954,20-20V276h216c11.046,0,20-8.954,20-20C512,244.954,503.046,236,492,236z" /> | ||||||
|  |                                 </g> | ||||||
|  |                             </svg> | ||||||
|  |                             <span class="RV-Iconized__Text">Fotoalbum hinzufügen</span> | ||||||
|  |                         </div> | ||||||
|  |                     </a> | ||||||
|  |                 </li> | ||||||
|  |             </ul> | ||||||
|  |         </div> | ||||||
|  |     </section> | ||||||
|  |  | ||||||
|     <section class="LP-Section"> |     <section class="LP-Section"> | ||||||
|         <h1 class="LP-Headline">Bilder</h1> |         <h1 class="LP-Headline">Bilder</h1> | ||||||
|         <div class="LP-ImageGrid"> |         <div class="LP-ImageGrid"> | ||||||
|             <ul class="LP-ImageGrid__List"> |             <ul class="LP-ImageGrid__Container"> | ||||||
|                 {% for place_image in place.images.all %} |                 {% for place_image in place.images.all %} | ||||||
|                 <li class="LP-ImageGrid__Item"> |                 <li class="LP-ImageGrid__Item"> | ||||||
|                     <a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image" src="{{ place_image.filename.thumbnail.url }}"></a> |                     <a href="{{ place_image.filename.large.url }}" class="LP-Link"><img class="LP-Image" src="{{ place_image.filename.thumbnail.url }}"></a> | ||||||
|   | |||||||
| @@ -1,9 +1,16 @@ | |||||||
| {% extends 'global.html'%} | {% extends 'global.html'%} | ||||||
| {% load static %} | {% load static %} | ||||||
|  |  | ||||||
|  | {% block additional_head %} | ||||||
|  |     <link rel="stylesheet" href="{% static 'maps/ol.css' %}" type="text/css"> | ||||||
|  |     <script src="{% static 'maps/ol.js' %}"></script> | ||||||
|  | {% endblock additional_head %} | ||||||
|  |  | ||||||
| {% block title %}Lost Places{% endblock %} | {% block title %}Lost Places{% endblock %} | ||||||
|  |  | ||||||
| {% block maincontent %} | {% block maincontent %} | ||||||
|  |  | ||||||
|  | {% include 'partials/osm_map.html' %} | ||||||
| <div class="LP-PlaceList"> | <div class="LP-PlaceList"> | ||||||
|     <h1 class="LP-Headline">Listing our places</h1> |     <h1 class="LP-Headline">Listing our places</h1> | ||||||
|     <ul class="LP-PlaceList__List"> |     <ul class="LP-PlaceList__List"> | ||||||
|   | |||||||
| @@ -23,9 +23,9 @@ | |||||||
| 				<button class="LP-Button">Login</button> | 				<button class="LP-Button">Login</button> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
|     </div> |  | ||||||
|     </fieldset> |     </fieldset> | ||||||
| 
 |  | ||||||
| </form> | </form> | ||||||
| 
 | 
 | ||||||
|  | <p class="LP-Headline">No account? <a class="LP-Link" href="{% url 'signup' %}"><span class="LP-Link__Text">Sign up here</span></a></p> | ||||||
|  | 
 | ||||||
| {% endblock maincontent %} | {% endblock maincontent %} | ||||||
							
								
								
									
										5
									
								
								lostplaces/lostplaces_app/templates/svg_icon/icon.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								lostplaces/lostplaces_app/templates/svg_icon/icon.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <svg width="{{ width }}" height="{{ height }}" viewBox="{{viewBox}}" class="svg{% if className %} {{ className }}{% endif %}"> | ||||||
|  | 	{% for path in paths %} | ||||||
|  |     <path d="{{ path }}"></path> | ||||||
|  |     {% endfor %} | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 209 B | 
							
								
								
									
										31
									
								
								lostplaces/lostplaces_app/templatetags/svg_icon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								lostplaces/lostplaces_app/templatetags/svg_icon.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import json, os | ||||||
|  | from importlib import import_module | ||||||
|  |  | ||||||
|  | from django.core.cache import cache | ||||||
|  | from django.conf import settings | ||||||
|  | from django.template import Library, TemplateSyntaxError | ||||||
|  |  | ||||||
|  | #icons_json_path = getattr(settings, 'SVG_ICONS_SOURCE_FILE') | ||||||
|  | icons_json_path = os.path.join(settings.BASE_DIR, 'lostplaces_app', 'static', 'icons', 'icons.icomoon.json') | ||||||
|  | icons_json = json.load(open(icons_json_path)) | ||||||
|  |  | ||||||
|  | register = Library() | ||||||
|  |  | ||||||
|  | @register.inclusion_tag('svg_icon/icon.html') | ||||||
|  | def icon(name, **kwargs): | ||||||
|  |  | ||||||
|  | 	icon_config = icons_json.get(name, None) | ||||||
|  |  | ||||||
|  | 	if icon_config: | ||||||
|  |  | ||||||
|  | 		width = kwargs.get('width', icon_config.get('width', 16)) | ||||||
|  | 		height = kwargs.get('height', icon_config.get('heigh', 16)) | ||||||
|  | 		viewBox = kwargs.get('viewBox', icon_config.get('viewBox', '0 0 1024 1024')) | ||||||
|  |  | ||||||
|  | 		return { | ||||||
|  | 			'width': kwargs.get('size', width), | ||||||
|  | 			'height': kwargs.get('size', height), | ||||||
|  | 			'className': kwargs.get('className'), | ||||||
|  | 			'viewBox': viewBox, | ||||||
|  | 			'paths': icon_config.get('paths', ['']) | ||||||
|  | 		} | ||||||
| @@ -6,7 +6,10 @@ from .views import ( | |||||||
|     SignUpView,  |     SignUpView,  | ||||||
|     PlaceCreateView, |     PlaceCreateView, | ||||||
|     PlaceUpdateView, |     PlaceUpdateView, | ||||||
|     PlaceDeleteView |     PlaceDeleteView, | ||||||
|  | 	PhotoAlbumCreateView, | ||||||
|  | 	PhotoAlbumDeleteView, | ||||||
|  | 	PlaceTagSubmitView | ||||||
| ) | ) | ||||||
|  |  | ||||||
| urlpatterns = [ | urlpatterns = [ | ||||||
| @@ -14,7 +17,10 @@ urlpatterns = [ | |||||||
|     path('signup/', SignUpView.as_view(), name='signup'), |     path('signup/', SignUpView.as_view(), name='signup'), | ||||||
|     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/', PlaceListView.as_view(), name='place_list'), | ||||||
|  | 	path('place/tag/<int:place_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'), | ||||||
| ] | ] | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								lostplaces/lostplaces_app/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lostplaces/lostplaces_app/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | from lostplaces_app.views.base_views import * | ||||||
|  | from lostplaces_app.views.views import *  | ||||||
|  | from lostplaces_app.views.place_views import * | ||||||
							
								
								
									
										98
									
								
								lostplaces/lostplaces_app/views/base_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								lostplaces/lostplaces_app/views/base_views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | from django.views import View | ||||||
|  | from django.views.generic.edit import CreateView | ||||||
|  | from django.views.generic.detail import SingleObjectMixin | ||||||
|  |  | ||||||
|  | from django.contrib import messages | ||||||
|  | from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  |  | ||||||
|  | from django.shortcuts import redirect | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  |  | ||||||
|  | from lostplaces_app.models import Place | ||||||
|  |  | ||||||
|  | class IsAuthenticated(LoginRequiredMixin, View): | ||||||
|  |     redirect_field_name = 'redirect_to' | ||||||
|  |     permission_denied_message = 'Please login to proceed' | ||||||
|  |  | ||||||
|  |     def handle_no_permission(self): | ||||||
|  |         messages.error(self.request, self.permission_denied_message) | ||||||
|  |         return super().handle_no_permission() | ||||||
|  |  | ||||||
|  | class IsPlaceSubmitter(UserPassesTestMixin, View): | ||||||
|  |     place_submitter_error_message = None | ||||||
|  |  | ||||||
|  |     def get_place(self): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def test_func(self): | ||||||
|  |         """ Check if user is eligible to modify place. """ | ||||||
|  |  | ||||||
|  |         if not hasattr(self.request, 'user'): | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         if self.request.user.is_superuser: | ||||||
|  |             return True | ||||||
|  |          | ||||||
|  |         # Check if currently logged in user was the submitter | ||||||
|  |         place_obj = self.get_place() | ||||||
|  |  | ||||||
|  |         if place_obj and hasattr(place_obj, 'submitted_by') and self.request.user == place_obj.submitted_by: | ||||||
|  |             return True | ||||||
|  |  | ||||||
|  |         if self.place_submitter_error_message: | ||||||
|  |             messages.error(self.request, self.place_submitter_error_message) | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  | class PlaceAssetCreateView(IsAuthenticated, SuccessMessageMixin, CreateView): | ||||||
|  |     model = None | ||||||
|  |     fields = [] | ||||||
|  |     template_name = '' | ||||||
|  |     success_message = '' | ||||||
|  |  | ||||||
|  |     def get(self, request, place_id, *args, **kwargs): | ||||||
|  |         self.place = Place.objects.get(pk=place_id) | ||||||
|  |         return super().get(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     def post(self, request, place_id, *args, **kwargs): | ||||||
|  |         self.place = Place.objects.get(pk=place_id) | ||||||
|  |         response = super().post(request, *args, **kwargs) | ||||||
|  |         self.object.place = self.place | ||||||
|  |         self.object.submitted_by = request.user | ||||||
|  |         self.object.save() | ||||||
|  |         return response | ||||||
|  |  | ||||||
|  |     def get_context_data(self, **kwargs): | ||||||
|  |         context = super().get_context_data(**kwargs) | ||||||
|  |         context['place'] = self.place | ||||||
|  |         return context | ||||||
|  |  | ||||||
|  |     def get_success_url(self): | ||||||
|  |         return reverse_lazy('place_detail', kwargs={'pk': self.place.id}) | ||||||
|  |  | ||||||
|  | class PlaceAssetDeleteView(IsAuthenticated, IsPlaceSubmitter, SingleObjectMixin, View): | ||||||
|  |     model = None | ||||||
|  |     pk_url_kwarg = 'pk' | ||||||
|  |     success_message = '' | ||||||
|  |     permission_denied_message = '' | ||||||
|  |  | ||||||
|  |     def get_place(self): | ||||||
|  |         place_id = self.get_object().place.id | ||||||
|  |         return Place.objects.get(pk=place_id) | ||||||
|  |  | ||||||
|  |     def test_func(self): | ||||||
|  |         can_edit_place = super().test_func() | ||||||
|  |         if can_edit_place: | ||||||
|  |             return True | ||||||
|  |          | ||||||
|  |         if self.get_object().submitted_by == self.request.user: | ||||||
|  |             return True | ||||||
|  |          | ||||||
|  |         messages.error(self.request, self.permission_denied_message) | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def get(self, request, *args, **kwargs): | ||||||
|  |         place_id = self.get_object().place.id | ||||||
|  |         self.get_object().delete() | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return redirect(reverse_lazy('place_detail', kwargs={'pk': place_id})) | ||||||
| @@ -1,83 +1,56 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
| 
 |  | ||||||
| ''' Django views. ''' |  | ||||||
| from django.shortcuts import render, redirect, get_object_or_404 |  | ||||||
| from django.urls import reverse_lazy |  | ||||||
| from django.views.generic.edit import CreateView, UpdateView, DeleteView |  | ||||||
| from django.views.generic import ListView |  | ||||||
| from django.views import View | from django.views import View | ||||||
| from django.http import Http404 | from django.views.generic.edit import CreateView, UpdateView, DeleteView | ||||||
| from django.contrib import messages | from django.views.generic.detail import SingleObjectMixin | ||||||
| from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin | from django.views.generic import ListView | ||||||
| 
 | 
 | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| 
 | 
 | ||||||
| from .forms import ( | from django.shortcuts import render, redirect | ||||||
|     ExplorerCreationForm,  | from django.urls import reverse_lazy | ||||||
|     PlaceForm,  |  | ||||||
|     PlaceImageCreateForm |  | ||||||
| ) |  | ||||||
| from .models import Place, PlaceImage, Voucher |  | ||||||
| 
 | 
 | ||||||
| # Create your views here. | from lostplaces_app.models import Place, PlaceImage | ||||||
|  | from lostplaces_app.views import IsAuthenticated, IsPlaceSubmitter | ||||||
|  | from lostplaces_app.forms import PlaceForm, PlaceImageCreateForm, TagSubmitForm | ||||||
| 
 | 
 | ||||||
| # BaseView that checks if user is logged in. | from taggit.models import Tag | ||||||
| class IsAuthenticated(LoginRequiredMixin, View): |  | ||||||
|     redirect_field_name = 'redirect_to' |  | ||||||
| 
 |  | ||||||
| # BaseView that checks if logged in user is submitter of place. |  | ||||||
| class IsSubmitter(UserPassesTestMixin, View): |  | ||||||
|     def test_func(self): |  | ||||||
|         """ Check if user is eligible to modify place. """ |  | ||||||
|         if self.request.user.is_superuser: |  | ||||||
|             return True |  | ||||||
|          |  | ||||||
|         # Check if currently logged in user was the submitter |  | ||||||
|         place_obj = self.get_object() |  | ||||||
| 
 |  | ||||||
|         if self.request.user == place_obj.submitted_by: |  | ||||||
|             return True |  | ||||||
| 
 |  | ||||||
|         messages.error( |  | ||||||
|             self.request, 'You do not have permission to do this.') |  | ||||||
|         return False |  | ||||||
| 
 |  | ||||||
| class SignUpView(SuccessMessageMixin, CreateView): |  | ||||||
|     form_class = ExplorerCreationForm |  | ||||||
|     success_url = reverse_lazy('login') |  | ||||||
|     template_name = 'signup.html' |  | ||||||
|     success_message = 'User created.' |  | ||||||
| 
 | 
 | ||||||
| class PlaceListView(IsAuthenticated, ListView): | class PlaceListView(IsAuthenticated, ListView): | ||||||
|     paginate_by = 2 |     paginate_by = 5 | ||||||
|     model = Place |     model = Place | ||||||
|     template_name = 'place/place_list.html' |     template_name = 'place/place_list.html' | ||||||
|  |     ordering = ['name'] | ||||||
|  | 
 | ||||||
|  |     def get_context_data(self, **kwargs): | ||||||
|  |         context = super().get_context_data(**kwargs) | ||||||
|  |         context['place_map_center'] = Place.average_latlon(context['place_list']) | ||||||
|  |         return context | ||||||
| 
 | 
 | ||||||
| class PlaceDetailView(IsAuthenticated, View): | class PlaceDetailView(IsAuthenticated, View): | ||||||
|     def get(self, request, pk): |     def get(self, request, pk): | ||||||
|  |         place = Place.objects.get(pk=pk) | ||||||
|         context = { |         context = { | ||||||
|             'place': Place.objects.get(pk=pk) |             'place': place, | ||||||
|  |             'place_list': [ place ], | ||||||
|  |             'place_map_center': [ place.latitude, place.longitude ], | ||||||
|  |             'tagging_form': TagSubmitForm(), | ||||||
|  |             'all_tags': Tag.objects.all() | ||||||
|         } |         } | ||||||
|         return render(request, 'place/place_detail.html', context) |         return render(request, 'place/place_detail.html', context) | ||||||
| 
 | 
 | ||||||
| class HomeView(View): | class PlaceUpdateView(IsAuthenticated, IsPlaceSubmitter, SuccessMessageMixin, UpdateView): | ||||||
|     def get(self, request, *args, **kwargs): |  | ||||||
|         place_list = Place.objects.all().order_by('-submitted_when')[:10] |  | ||||||
|         context = { |  | ||||||
|             'place_list': place_list |  | ||||||
|         } |  | ||||||
|         return render(request, 'home.html', context) |  | ||||||
| 
 |  | ||||||
| class PlaceUpdateView(IsAuthenticated, IsSubmitter, SuccessMessageMixin, UpdateView): |  | ||||||
|     template_name = 'place/place_update.html' |     template_name = 'place/place_update.html' | ||||||
|     model = Place |     model = Place | ||||||
|     form_class = PlaceForm |     form_class = PlaceForm | ||||||
|     success_message = 'Successfully updated place.' |     success_message = 'Successfully updated place.' | ||||||
|  |     place_submitter_error_message = 'You do no have permissions to alter this place' | ||||||
| 
 | 
 | ||||||
|     def get_success_url(self): |     def get_success_url(self): | ||||||
|         return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk}) |         return reverse_lazy('place_detail', kwargs={'pk':self.get_object().pk}) | ||||||
| 
 | 
 | ||||||
|  |     def get_place(self): | ||||||
|  |         return self.get_object() | ||||||
|  | 
 | ||||||
| class PlaceCreateView(IsAuthenticated, View): | class PlaceCreateView(IsAuthenticated, View): | ||||||
| 
 | 
 | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
| @@ -134,13 +107,17 @@ class PlaceCreateView(IsAuthenticated, View): | |||||||
|             ) |             ) | ||||||
|             place_image.save() |             place_image.save() | ||||||
| 
 | 
 | ||||||
| class PlaceDeleteView(IsAuthenticated, IsSubmitter, DeleteView): | class PlaceDeleteView(IsAuthenticated, IsPlaceSubmitter, DeleteView): | ||||||
|     template_name = 'place/place_delete.html' |     template_name = 'place/place_delete.html' | ||||||
|     model = Place |     model = Place | ||||||
|     success_message = 'Successfully deleted place.' |     success_message = 'Successfully deleted place.' | ||||||
|     success_url = reverse_lazy('place_list') |     success_url = reverse_lazy('place_list') | ||||||
|     success_message = 'Place deleted' |     success_message = 'Place deleted' | ||||||
|  |     place_submitter_error_message = 'You do no have permission to delete this place' | ||||||
| 
 | 
 | ||||||
|     def delete(self, request, *args, **kwargs): |     def delete(self, request, *args, **kwargs): | ||||||
|         messages.success(self.request, self.success_message) |         messages.success(self.request, self.success_message) | ||||||
|         return super().delete(request, *args, **kwargs) |         return super().delete(request, *args, **kwargs) | ||||||
|  | 
 | ||||||
|  |     def get_place(self): | ||||||
|  |         return self.get_object() | ||||||
							
								
								
									
										58
									
								
								lostplaces/lostplaces_app/views/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								lostplaces/lostplaces_app/views/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | from django.views import View | ||||||
|  | from django.views.generic.edit import CreateView | ||||||
|  |  | ||||||
|  | from django.contrib.messages.views import SuccessMessageMixin | ||||||
|  | from django.contrib import messages | ||||||
|  | from django.urls import reverse_lazy | ||||||
|  | from django.shortcuts import render, redirect | ||||||
|  |  | ||||||
|  | from lostplaces_app.forms import ExplorerCreationForm, TagSubmitForm | ||||||
|  | from lostplaces_app.models import Place, PhotoAlbum | ||||||
|  | from lostplaces_app.views.base_views import IsAuthenticated | ||||||
|  |  | ||||||
|  | from lostplaces_app.views.base_views import ( | ||||||
|  |     PlaceAssetCreateView,  | ||||||
|  |     PlaceAssetDeleteView | ||||||
|  | ) | ||||||
|  | class SignUpView(SuccessMessageMixin, CreateView): | ||||||
|  |     form_class = ExplorerCreationForm | ||||||
|  |     success_url = reverse_lazy('login') | ||||||
|  |     template_name = 'signup.html' | ||||||
|  |     success_message = 'User created.' | ||||||
|  |  | ||||||
|  | class HomeView(View): | ||||||
|  |     def get(self, request, *args, **kwargs): | ||||||
|  |         place_list = Place.objects.all().order_by('-submitted_when')[:10] | ||||||
|  |         place_map_center = Place.average_latlon(place_list) | ||||||
|  |         context = { | ||||||
|  |             'place_list': place_list, | ||||||
|  |             'place_map_center': place_map_center | ||||||
|  |         } | ||||||
|  |         return render(request, 'home.html', context) | ||||||
|  |  | ||||||
|  | class PhotoAlbumCreateView(PlaceAssetCreateView): | ||||||
|  |     model = PhotoAlbum | ||||||
|  |     fields = ['url', 'label'] | ||||||
|  |     template_name = 'photo_album/photo_album_create.html' | ||||||
|  |     success_message = 'Photo Album submitted' | ||||||
|  |  | ||||||
|  | class PhotoAlbumDeleteView(PlaceAssetDeleteView): | ||||||
|  |     model = PhotoAlbum | ||||||
|  |     pk_url_kwarg = 'pk' | ||||||
|  |     success_message = 'Photo Album deleted' | ||||||
|  |     permission_denied_messsage = 'You do not have permissions to alter this photo album' | ||||||
|  |  | ||||||
|  | class PlaceTagSubmitView(IsAuthenticated, View): | ||||||
|  | 	def post(self, request, place_id, *args, **kwargs): | ||||||
|  | 		place = Place.objects.get(pk=place_id) | ||||||
|  | 		form = TagSubmitForm(request.POST) | ||||||
|  | 		if form.is_valid(): | ||||||
|  | 			tag_list_raw = form.cleaned_data['tag_list'] | ||||||
|  | 			tag_list_raw = tag_list_raw.strip().split(',') | ||||||
|  | 			tag_list = [] | ||||||
|  | 			for tag in tag_list_raw: | ||||||
|  | 				tag_list.append(tag.strip()) | ||||||
|  | 			place.tags.add(*tag_list) | ||||||
|  | 			place.save() | ||||||
|  |  | ||||||
|  | 		return redirect(reverse_lazy('place_detail', kwargs={'pk': place.id})) | ||||||
		Reference in New Issue
	
	Block a user