74 Commits

Author SHA1 Message Date
c5f6355f19 Updated translations (de). 2020-12-25 16:07:50 +01:00
dac63e99ba Added Edit button to profile page. 2020-12-25 16:00:29 +01:00
9f369456d5 Changed easy_thumbnails image/thumb deletion. Now working. 2020-12-25 15:42:02 +01:00
011a58f6b3 Use thumbnail in profile_update. 2020-12-25 14:29:38 +01:00
67ce6cb50b Added working explorer_profile_image upload. 2020-12-25 14:06:53 +01:00
86cc7f23fe description_fuckery. 2020-12-25 13:19:34 +01:00
3eaa186b66 Delete profile_image on user_deletion. 2020-12-25 12:53:52 +01:00
cc59254ba4 Added clean_username function to validate form. 2020-12-25 12:24:09 +01:00
67a6517716 Comment improved. 2020-12-25 11:14:54 +01:00
0f5474c2d3 half-working :P 2020-12-25 11:14:21 +01:00
17e71b71d4 Fix typo. 2020-12-25 08:04:35 +01:00
b1aa4473e9 Added failsafe, if there is no profile_image. 2020-12-25 02:11:02 +01:00
871acd1dce Improved class comment. 2020-12-25 01:41:09 +01:00
14ca45d111 Comments for customizations of UserChangeForm. 2020-12-25 01:36:14 +01:00
c71fe4cf2d Added new translations (de) 2020-12-25 01:31:43 +01:00
7359bf5fab ExplorerChangeForms, with deactivated username field. 2020-12-25 01:31:01 +01:00
4f0182fc3e Minor textual changes for consistency. 2020-12-25 01:28:23 +01:00
30f259fb4d WIP-commit of translations to test against fuzzy detection. 2020-12-24 23:51:40 +01:00
53f89caef5 Added a ModelForm to change profile details. 2020-12-24 20:31:52 +01:00
3855fb28d7 Rename ExplorerCreationForm to SignupVoucherForm. 2020-12-24 20:25:05 +01:00
3a20a60f05 Remove obsolete ExplorerChangeForm. 2020-12-24 20:22:33 +01:00
648264c9fc Remove obsoleted imports. 2020-12-24 20:13:06 +01:00
a7c8848fd6 Allow user to provide his first and last name on registration. 2020-12-24 19:36:41 +01:00
07ea2f164c Name and E-Mail is only shown when populated in profile. 2020-12-24 19:13:55 +01:00
b0396f5223 Added bio and email link to profile template. 2020-12-24 19:04:04 +01:00
88bffbef8e Added Explorer bio / description field. 2020-12-24 18:53:30 +01:00
2022a924c4 Added output of profile image to profile_page. 2020-12-24 18:30:29 +01:00
d5827b2006 Fixed filename generation. 2020-12-24 18:14:40 +01:00
a9013d9673 Pulled favorite_places out of explorer instance. 2020-12-24 18:03:31 +01:00
0c38ca9a15 Revert changes, as it was redundant. 2020-12-24 18:03:07 +01:00
734d09df90 Add favorite_places to user profile page. 2020-12-24 17:59:05 +01:00
e601b9bf6b Merge commit '6f6bd6376d1ef2410a728cff2e9b883560c7f5c1' into feature/30-user-profile-fields 2020-12-24 17:34:09 +01:00
6f6bd6376d Merge branch 'develop' of mowoe.com:reverend/lostplaces-backend into develop 2020-12-24 17:33:02 +01:00
dc2a703c39 More fuckery 2020-12-24 17:27:48 +01:00
063777ece4 Merge branch '27-Favorite-System' into develop 2020-12-24 17:27:20 +01:00
4675e5814f Indention and stupid mistakes fix 2020-12-24 17:26:07 +01:00
35e0f912fe Add profile_pic 2020-12-24 17:24:02 +01:00
07fe1bc3ca Rename place_image_path function. 2020-12-24 17:23:20 +01:00
4a43a4bf37 typo 2020-12-24 16:43:43 +01:00
61cf148417 Displaying favorite icon on detail page 2020-12-24 16:43:33 +01:00
d547ee9db3 favorite partial 2020-12-24 16:43:14 +01:00
c7b699f615 Changed link location in place teaser 2020-12-24 16:43:01 +01:00
86f95a5dd0 New icon 2020-12-24 16:42:34 +01:00
916c4b80f7 Filed not required in django admin 2020-12-24 16:42:27 +01:00
b31dc9fc5f Url name changed 2020-12-24 16:41:30 +01:00
8f048369bf Favorite/Unfavorite Views 2020-12-24 15:56:33 +01:00
f974469996 formatting 2020-12-24 15:56:02 +01:00
dbbd7b0802 rename migrationfile 2020-12-24 15:55:37 +01:00
d04e986419 Added display of name and e-mail. 2020-12-24 15:33:46 +01:00
cbbda88850 Remove debug output. 2020-12-24 15:13:40 +01:00
981c440ce3 Fix voucher class description 2020-12-24 15:13:31 +01:00
c7368f5c44 Merge commit '20dd880a11a008ba95cd5aedce59590445430831' into develop 2020-12-24 14:57:09 +01:00
abca946883 Model for favorite system 2020-12-24 14:48:11 +01:00
20dd880a11 Indention Error 2020-12-19 15:31:13 +01:00
946c3091c0 Typo "fix" 2020-12-19 15:31:04 +01:00
4bacd1a28d Recopied migration files 2020-12-19 14:47:59 +01:00
086789aa88 delete miration for release 2020-12-19 14:35:20 +01:00
3c5b447d4c New CSS 2020-11-29 19:23:44 +01:00
cfbb876d2c #41 2020-11-29 19:23:39 +01:00
4e0bfef483 Incorporating Profile View into Project 2020-11-29 19:23:22 +01:00
ccdb49c8ea Profile View 2020-11-29 19:22:55 +01:00
90dd281f7c Common functionalities 2020-11-29 19:22:38 +01:00
d9df6e67a8 Hassling with migrations 2020-11-29 19:22:27 +01:00
cd20e730ba Merge branch 'master' into develop 2020-11-27 07:58:53 +01:00
c613d15e89 Updated Readme 2020-11-27 07:57:56 +01:00
2380398fa9 #36 and #37 Place Teaser 2020-10-12 20:57:04 +02:00
4c5b9c7648 Dumped a nice missing-image image. Needs resizing and logic. 2020-10-11 23:08:14 +02:00
91a3f3171d Merge branch 'feature/localization' into develop 2020-10-11 21:57:30 +02:00
4e366d94c9 Fixing Error on sign up 2020-10-06 22:53:35 +02:00
773390b09f Moved files so only the app is packed 2020-10-04 23:23:02 +02:00
ac0121d96e removed old config 2020-10-04 23:22:53 +02:00
a0c4f2cad3 Merge branch 'develop' into master 2020-10-04 22:19:01 +02:00
66bd9f648b Merge branch 'develop' into master 2020-10-04 15:43:41 +02:00
485eb7b886 Bumping version 2020-10-01 22:06:12 +02:00
31 changed files with 916 additions and 141 deletions

View File

@@ -22,7 +22,7 @@ Right now it depends on the following non-core Python 3 libraries. These can be
* [django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks) Tweak the form field rendering in templates, not in python-level form definitions. * [django-widget-tweaks](https://github.com/jazzband/django-widget-tweaks) Tweak the form field rendering in templates, not in python-level form definitions.
* [django-taggit](https://github.com/jazzband/django-taggit) A simpler approach to tagging with Django. * [django-taggit](https://github.com/jazzband/django-taggit) A simpler approach to tagging with Django.
### Bundled Dependencies ### Bundled Dependencies
We also leverage some other great OpenSource projects' code. We bundle those in the distribution to obsolete the need to pull those files from monitored CDNs. We also leverage some other great OpenSource projects' code. We bundle those in the distribution to obsolete the need to pull those files from monitored CDNs.
* [OpenLayers](https://openlayers.org/) [6.4.3] OpenLayers makes it easy to put a dynamic map in any web page. It can display map tiles, vector data and markers loaded from any source. * [OpenLayers](https://openlayers.org/) [6.4.3] OpenLayers makes it easy to put a dynamic map in any web page. It can display map tiles, vector data and markers loaded from any source.
@@ -88,7 +88,9 @@ Run `django_lostplaces/managy.py collectstatic` and you should be ready to go.
If you haven't already setup a django instance, see [django's documentation](https://docs.djangoproject.com/en/3.1/topics/install/). If you haven't already setup a django instance, see [django's documentation](https://docs.djangoproject.com/en/3.1/topics/install/).
After that, download the desired release (probably the latest one) [from the releases page](https://git.mowoe.com/reverend/lostplaces-backend/releases) and install it using `pip install --user name-of-the-file.tar.gz` Afterwards, either
- download the desired release [from the releases page](https://git.mowoe.com/reverend/lostplaces-backend/releases) and install it using `pip install --user name-of-the-file.tar.gz`
- or install the latest release using `pip install django-lostplaces`
*Note: You can run pip install without the --user flag, which will require root privileges and introduces potential security issues.* *Note: You can run pip install without the --user flag, which will require root privileges and introduces potential security issues.*
@@ -144,3 +146,7 @@ Before making the django instance public, you should tweak the config `settings.
Run `django_lostplaces/manage.py collectstatic` you should be ready to go. Run `django_lostplaces/manage.py collectstatic` you should be ready to go.
### Contact
If you run into any issues, have any questions or If you are interested in this project in general, feel free to get in touch with us via [reverend@reverend2048.de](mailto:reverend@reverend2048.de), we do speak English and German.

View File

@@ -1,3 +1,4 @@
exclude
include LICENSE include LICENSE
include Readme.rst include Readme.rst
include Pipfile include Pipfile

View File

@@ -9,8 +9,6 @@ from django.contrib.auth.admin import UserAdmin
from django.utils import timezone from django.utils import timezone
from lostplaces.models import * from lostplaces.models import *
from lostplaces.forms import ExplorerCreationForm, ExplorerChangeForm
# Register your models here. # Register your models here.
class VoucherAdmin(admin.ModelAdmin): class VoucherAdmin(admin.ModelAdmin):

View File

@@ -0,0 +1,11 @@
def get_all_subclasses(cls):
'''
Gets all subclasses recursively, does not contain
abstract classes
'''
subclass_list = []
for subclass in cls.__subclasses__():
if not subclass._meta.abstract:
subclass_list.append(subclass)
subclass_list += get_all_subclasses(subclass)
return subclass_list

View File

@@ -8,12 +8,12 @@ from django.db import models
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from lostplaces.models import Place, PlaceImage, Voucher from lostplaces.models import Place, PlaceImage, Voucher, Explorer
class ExplorerCreationForm(UserCreationForm): class SignupVoucherForm(UserCreationForm):
class Meta: class Meta:
model = User model = User
fields = ('username', 'email') fields = ('username', 'email', 'first_name', 'last_name')
voucher = forms.CharField( voucher = forms.CharField(
max_length=30, max_length=30,
help_text=_('The Voucher you got from an administrator') help_text=_('The Voucher you got from an administrator')
@@ -35,10 +35,33 @@ class ExplorerCreationForm(UserCreationForm):
fetched_voucher.delete() fetched_voucher.delete()
return True return True
class ExplorerChangeForm(UserChangeForm): class ExplorerUserChangeForm(UserChangeForm):
class Meta: class Meta:
model = User model = User
fields = ('username', 'email') fields = [ 'username', 'first_name', 'last_name', 'email' ]
''' Hide password hint.'''
password = None
''' Display username, but display it non-editable and not required. '''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].required = False
self.fields['username'].help_text = None
self.fields['username'].widget.attrs['disabled'] = 'disabled'
def clean_username(self):
# As shown in the above answer.
instance = getattr(self, 'instance', None)
if instance:
return instance.username
else:
return self.cleaned_data.get('username', None)
class ExplorerChangeForm(forms.ModelForm):
class Meta:
model = Explorer
fields = '__all__'
exclude = ['user', 'favorite_places']
class PlaceForm(forms.ModelForm): class PlaceForm(forms.ModelForm):
class Meta: class Meta:

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-11 21:53+0200\n" "POT-Creation-Date: 2020-12-25 16:04+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Commander1024 <commander@commander1024.de>\n" "Last-Translator: Commander1024 <commander@commander1024.de>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -30,7 +30,7 @@ msgstr "Ungültiger Voucher"
msgid "Expired voucher" msgid "Expired voucher"
msgstr "Abgelaufener Voucher" msgstr "Abgelaufener Voucher"
#: models/abstract_models.py:29 #: models/abstract_models.py:29 templates/explorer/profile.html:31
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -74,6 +74,22 @@ msgstr "Adresse (URL)"
msgid "link text" msgid "link text"
msgstr "Linktext" msgstr "Linktext"
#: models/models.py:47
msgid "Biography"
msgstr "Beschreibung"
#: models/models.py:48
msgid "Describe yourself, your preferences, etc. in a few sentences."
msgstr "Beschreibe Dich selbst, Deine Vorlieben, usw. in ein paar Sätzen."
#: models/models.py:56
msgid "Profile image"
msgstr "Profilbild"
#: models/models.py:57
msgid "Optional profile image for display in Explorer profile"
msgstr "Optionales Profilbild zur Anzeige im Explorerprofil"
#: models/place.py:21 #: models/place.py:21
msgid "Location" msgid "Location"
msgstr "Ort" msgstr "Ort"
@@ -110,38 +126,92 @@ msgstr "Du wirst in 5 Sekunden weitergeleitet"
msgid "Go Back" msgid "Go Back"
msgstr "Zurück" msgstr "Zurück"
#: templates/explorer/profile.html:41
msgid "E-mail"
msgstr "E-Mail"
#: templates/explorer/profile.html:52
msgid "Joined"
msgstr "Beigetreten"
#: templates/explorer/profile.html:60
#, fuzzy
#| msgid "All Places"
msgid "Places"
msgstr "Alle Places"
#: templates/explorer/profile.html:68
msgid "Place assets"
msgstr ""
#: templates/explorer/profile.html:76
#, fuzzy
#| msgid "Edit Explorer profile"
msgid "Edit Profile"
msgstr "Explorerprofil bearbeiten"
#: templates/explorer/profile.html:87
msgid "Favorite places"
msgstr "Favoriten"
#: templates/explorer/profile.html:103
msgid "Places submitted by"
msgstr "Places hinzugefügt von"
#: templates/explorer/profile.html:118
msgid "Images submitted by"
msgstr "Bilder hinzugefügt von"
#: templates/explorer/profile.html:140
msgid "Photo albums submitted by"
msgstr "Fotoalben hinzugefügt von"
#: templates/explorer/profile_update.html:6
#: templates/explorer/profile_update.html:12
msgid "Edit Explorer profile"
msgstr "Explorerprofil bearbeiten"
#: templates/explorer/profile_update.html:48
#: templates/place/place_update.html:42
msgid "Update"
msgstr "Aktualisieren"
#: templates/global.html:32 #: templates/global.html:32
msgid "Logout" msgid "Logout"
msgstr "Ausloggen" msgstr "Ausloggen"
#: templates/global.html:34 #: templates/global.html:33
msgid "Profile"
msgstr "Profil"
#: templates/global.html:35
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: templates/global.html:39 templates/registration/login.html:4 #: templates/global.html:40 templates/registration/login.html:4
#: templates/registration/login.html:10 templates/registration/login.html:23 #: templates/registration/login.html:10 templates/registration/login.html:23
msgid "Login" msgid "Login"
msgstr "Anmelden" msgstr "Anmelden"
#: templates/global.html:40 templates/registration/login.html:29 #: templates/global.html:41 templates/registration/login.html:29
#: templates/signup.html:6 templates/signup.html:12 templates/signup.html:41 #: templates/signup.html:6 templates/signup.html:12 templates/signup.html:49
msgid "Sign up" msgid "Sign up"
msgstr "Registrieren" msgstr "Registrieren"
#: templates/global.html:50 templates/home.html:10 #: templates/global.html:51 templates/home.html:10
msgid "Home" msgid "Home"
msgstr "Startseite" msgstr "Startseite"
#: templates/global.html:51 #: templates/global.html:52
msgid "UrBex Codex" msgid "UrBex Codex"
msgstr "UrBex Codex" msgstr "UrBex Codex"
#: templates/global.html:56 templates/place/place_create.html:5 #: templates/global.html:57 templates/place/place_create.html:5
#: templates/place/place_create.html:10 #: templates/place/place_create.html:10
msgid "Create place" msgid "Create place"
msgstr "Place erstellen" msgstr "Place erstellen"
#: templates/global.html:57 #: templates/global.html:58
msgid "All places" msgid "All places"
msgstr "Alle Places" msgstr "Alle Places"
@@ -194,6 +264,14 @@ msgstr "Abschicken"
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: templates/partials/icons/place_favorite.html:6
msgid "Remove from favorites"
msgstr "Aus den Favoriten entfernen"
#: templates/partials/icons/place_favorite.html:10
msgid "Save as favorite"
msgstr "In den Favoriten speichern"
#: templates/partials/nav/footer.html:64 #: templates/partials/nav/footer.html:64
msgid "Made by" msgid "Made by"
msgstr "Erstellt von" msgstr "Erstellt von"
@@ -318,10 +396,6 @@ msgstr "Alle Places"
msgid "Our lost places" msgid "Our lost places"
msgstr "Unsere Lost Places" msgstr "Unsere Lost Places"
#: templates/place/place_update.html:42
msgid "Update"
msgstr "Aktualisieren"
#: templates/place_image/place_image_create.html:7 #: templates/place_image/place_image_create.html:7
msgid "Submit images to a place" msgid "Submit images to a place"
msgstr "Bilder zu einem Place hinzufügen" msgstr "Bilder zu einem Place hinzufügen"
@@ -334,6 +408,16 @@ msgstr "Noch kein Konto?"
msgid "Please login to proceed" msgid "Please login to proceed"
msgstr "Bitte log Dich ein um fortzufahren" msgstr "Bitte log Dich ein um fortzufahren"
#: views/explorer_views.py:78
#, fuzzy
#| msgid "Successfully updated place"
msgid "Successfully updated Explorer profile"
msgstr "Place erfolgreich aktualisiert"
#: views/explorer_views.py:84 views/place_views.py:105
msgid "Please fill in all required fields."
msgstr "Bitte füll alle benötigten Felder aus."
#: views/place_image_views.py:26 #: views/place_image_views.py:26
msgid "Image(s) submitted successfully" msgid "Image(s) submitted successfully"
msgstr "Bild(er) erfolgreich hinzugefügt" msgstr "Bild(er) erfolgreich hinzugefügt"
@@ -358,10 +442,6 @@ msgstr "Du darfst diesen Place nicht bearbeiten"
msgid "Successfully created place" msgid "Successfully created place"
msgstr "Place erfolgreich erstellt" msgstr "Place erfolgreich erstellt"
#: views/place_views.py:105
msgid "Please fill in all required fields."
msgstr "Bitte füll alle benötigten Felder aus."
#: views/place_views.py:112 #: views/place_views.py:112
msgid "Successfully deleted place" msgid "Successfully deleted place"
msgstr "Place erfolgreich gelöscht" msgstr "Place erfolgreich gelöscht"
@@ -385,3 +465,8 @@ msgstr "Fotoalbum-Link gelöscht"
#: views/views.py:60 #: views/views.py:60
msgid "You are not allowed to edit this photo album link" msgid "You are not allowed to edit this photo album link"
msgstr "Du darfst diesen Fotoalbum-Link nicht bearbeiten" msgstr "Du darfst diesen Fotoalbum-Link nicht bearbeiten"
#, fuzzy
#~| msgid "Filename(s)"
#~ msgid "Filename"
#~ msgstr "Dateiname(n)"

View File

@@ -19,4 +19,4 @@ class Migration(migrations.Migration):
migrations.DeleteModel( migrations.DeleteModel(
name='Expireable' name='Expireable'
) )
] ]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.1.1 on 2020-10-04 19:37
# Edited by reverend
import datetime
from django.db import migrations, models
import django.utils.timezone
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('lostplaces', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='Voucher'
),
migrations.DeleteModel(
name='Expireable'
)
]

View File

@@ -22,4 +22,4 @@ class Migration(migrations.Migration):
'abstract': False, 'abstract': False,
}, },
), ),
] ]

View File

@@ -6,19 +6,34 @@
database. database.
''' '''
import os
import uuid import uuid
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models.signals import post_save from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from lostplaces.models.abstract_models import Expireable from lostplaces.models.abstract_models import Expireable
from lostplaces.models.place import Place
from easy_thumbnails.fields import ThumbnailerImageField
from easy_thumbnails.files import get_thumbnailer
def generate_profile_image_filename(instance, filename):
"""
Callback for generating filename for uploaded explorer profile images.
Returns filename as: explorer_pk-username.jpg
"""
return 'explorers/' + str(instance.user.pk) + '-' + str(instance.user.username) + '.' + filename.split('.')[-1]
class Explorer(models.Model): class Explorer(models.Model):
""" """
Profile that is linked to the a User. Profile that is linked to the Django user.
Every user has a profile. Every user has a profile.
Provides additional attributes for user profile.
""" """
user = models.OneToOneField( user = models.OneToOneField(
@@ -26,10 +41,31 @@ class Explorer(models.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='explorer' related_name='explorer'
) )
bio = models.TextField(
blank=True,
null=True,
verbose_name=_('Biography / Description'),
help_text=_('Describe yourself, your preferences, etc. in a few sentences.')
)
profile_image = ThumbnailerImageField(
blank=True,
null=True,
upload_to=generate_profile_image_filename,
resize_source=dict(size=(400, 400),
sharpen=True),
verbose_name=_('Profile image'),
help_text=_('Optional profile image for display in Explorer profile')
)
favorite_places = models.ManyToManyField(
Place,
related_name='favorite_places',
verbose_name='Explorers favorite places',
blank=True
)
def __str__(self): def __str__(self):
return self.user.username return self.user.username
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):
if created: if created:
@@ -39,13 +75,34 @@ def create_user_profile(sender, instance, created, **kwargs):
def save_user_profile(sender, instance, **kwargs): def save_user_profile(sender, instance, **kwargs):
instance.explorer.save() instance.explorer.save()
@receiver(pre_save, sender=Explorer)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding `Explorer` object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_file = Explorer.objects.get(pk=instance.pk).profile_image
except Explorer.DoesNotExist:
return False
print("Deleting:", old_file)
new_file = instance.profile_image
if not old_file == new_file:
old_file.delete(save=False)
class Voucher(Expireable): class Voucher(Expireable):
""" """
Vouchers are authorization to created_when = models.DateTimeField(auto_now_add=True) Vouchers are authorization tokens to allow the registration of new users.
expires_when = models.DateTimeField()kens to allow the registration of new users.
A voucher has a code, a creation and a deletion date, which are all A voucher has a code, a creation and a deletion date, which are all
positional. Creation date is being set automatically during voucher positional. Creation date is being set automatically during voucher
creation. creation.
created_when = models.DateTimeField(auto_now_add=True)
expires_when = models.DateTimeField()
""" """
code = models.CharField(unique=True, max_length=30) code = models.CharField(unique=True, max_length=30)
@@ -56,4 +113,3 @@ class Voucher(Expireable):
def __str__(self): def __str__(self):
return "Voucher " + str(self.code) return "Voucher " + str(self.code)

View File

@@ -49,10 +49,10 @@ class Place(Submittable, Taggable, Mapable):
return self.name return self.name
def generate_image_upload_path(instance, filename): def generate_place_image_filename(instance, filename):
""" """
Callback for generating path for uploaded images. Callback for generating filename for uploaded place images.
Returns filename as: place_pk-placename{-rnd_string}.jpg Returns filename as: place_pk-placename{-number}.jpg
""" """
return 'places/' + str(instance.place.pk) + '-' + str(instance.place.name) + '.' + filename.split('.')[-1] return 'places/' + str(instance.place.pk) + '-' + str(instance.place.name) + '.' + filename.split('.')[-1]
@@ -72,7 +72,7 @@ class PlaceAsset(Submittable):
null=True null=True
) )
class PlaceImage (Submittable): class PlaceImage(PlaceAsset):
""" """
PlaceImage defines an image file object that points to a file in uploads/. PlaceImage defines an image file object that points to a file in uploads/.
Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES. Intermediate image sizes are generated as defined in THUMBNAIL_ALIASES.
@@ -84,7 +84,7 @@ class PlaceImage (Submittable):
verbose_name=_('Description'), verbose_name=_('Description'),
) )
filename = ThumbnailerImageField( filename = ThumbnailerImageField(
upload_to=generate_image_upload_path, upload_to=generate_place_image_filename,
resize_source=dict(size=(2560, 2560), resize_source=dict(size=(2560, 2560),
sharpen=True), sharpen=True),
verbose_name=_('Filename(s)'), verbose_name=_('Filename(s)'),
@@ -104,7 +104,6 @@ class PlaceImage (Submittable):
return 'Image ' + str(self.pk) return 'Image ' + str(self.pk)
# These two auto-delete files from filesystem when they are unneeded: # These two auto-delete files from filesystem when they are unneeded:
@receiver(post_delete, sender=PlaceImage) @receiver(post_delete, sender=PlaceImage)
@@ -118,7 +117,6 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
thumbmanager = get_thumbnailer(instance.filename) thumbmanager = get_thumbnailer(instance.filename)
thumbmanager.delete(save=False) thumbmanager.delete(save=False)
@receiver(pre_save, sender=PlaceImage) @receiver(pre_save, sender=PlaceImage)
def auto_delete_file_on_change(sender, instance, **kwargs): def auto_delete_file_on_change(sender, instance, **kwargs):
""" """
@@ -137,5 +135,4 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
# No need to delete thumbnails, as they will be overwritten on regeneration. # No need to delete thumbnails, as they will be overwritten on regeneration.
new_file = instance.filename new_file = instance.filename
if not old_file == new_file: if not old_file == new_file:
if os.path.isfile(old_file.path): old_file.delete(save=False)
os.remove(old_file.path)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -862,10 +862,9 @@ body {
margin: 0 3px; } margin: 0 3px; }
.LP-PlaceTeaser { .LP-PlaceTeaser {
width: 280px; } width: 100%; }
.LP-PlaceTeaser .LP-PlaceTeaser__Image { .LP-PlaceTeaser .LP-PlaceTeaser__Image {
height: 165px; width: 100%;
width: 280px;
overflow: hidden; } overflow: hidden; }
.LP-PlaceTeaser .LP-PlaceTeaser__Image .LP-Image { .LP-PlaceTeaser .LP-PlaceTeaser__Image .LP-Image {
max-width: unset; max-width: unset;
@@ -873,6 +872,8 @@ body {
object-fit: cover; } object-fit: cover; }
.LP-PlaceTeaser .LP-PlaceTeaser__Meta { .LP-PlaceTeaser .LP-PlaceTeaser__Meta {
display: flex; display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 5px; padding: 5px;
@@ -883,19 +884,30 @@ body {
padding: 0; padding: 0;
margin: 0; margin: 0;
margin-top: 5px; margin-top: 5px;
font-size: 0.9rem; } font-size: 1.3rem; }
.LP-PlaceTeaser .LP-PlaceTeaser__Meta .LP-Headline { .LP-PlaceTeaser .LP-PlaceTeaser__Meta .LP-Headline {
font-family: "Montserrat", Helvetica, sans-serif; font-family: "Montserrat", Helvetica, sans-serif;
color: #565656; color: #565656;
font-size: 1rem; font-size: 2rem;
padding: 0px; padding: 0px;
margin: 0px; } margin: 0px; }
.LP-PlaceTeaser .LP-PlaceTeaser__Meta .LP-Icon {
height: 2em;
width: 2em; }
.LP-PlaceTeaser .LP-PlaceTeaser__Description { .LP-PlaceTeaser .LP-PlaceTeaser__Description {
font-family: "Montserrat", Helvetica, sans-serif; font-family: "Montserrat", Helvetica, sans-serif;
color: #565656; color: #565656;
display: none; } order: 3;
flex-basis: 100%; }
.LP-PlaceTeaser .LP-PlaceTeaser__Description .LP-Paragraph {
font-size: 22px; }
@media (min-width: 650px) { @media (min-width: 650px) {
.LP-PlaceTeaser {
width: 280px; }
.LP-PlaceTeaser__Image {
width: 280px;
height: 165px; }
.LP-PlaceTeaser--extended { .LP-PlaceTeaser--extended {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -904,12 +916,15 @@ body {
height: 165px; } height: 165px; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta { .LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta {
margin: 0; margin: 0;
padding: 5px;
padding-left: 25px; padding-left: 25px;
display: flex; display: flex;
flex-wrap: nowrap;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
align-items: flex-start; } align-items: flex-start;
gap: unset; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-Headline, .LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-Paragraph {
font-size: unset; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-PlaceTeaser__Info .LP-Headline { .LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-PlaceTeaser__Info .LP-Headline {
font-size: 28px; } font-size: 28px; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-PlaceTeaser__Icons { .LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-PlaceTeaser__Icons {
@@ -917,10 +932,16 @@ body {
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-PlaceTeaser__Icons ul { .LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-PlaceTeaser__Icons ul {
padding: 0; padding: 0;
margin: 0; } margin: 0; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Meta .LP-Icon {
height: 20px;
width: 20px; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Description { .LP-PlaceTeaser--extended .LP-PlaceTeaser__Description {
display: block; display: block;
max-height: 55px; max-height: 55px;
overflow: hidden; } overflow: hidden;
order: unset; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Description .LP-Paragraph {
font-size: unset; }
.LP-PlaceTeaser--extended .LP-PlaceTeaser__Image { .LP-PlaceTeaser--extended .LP-PlaceTeaser__Image {
height: 165px; height: 165px;
width: 280px; width: 280px;
@@ -1006,6 +1027,31 @@ body {
.LP-MessageList .LP-MessageList__Item { .LP-MessageList .LP-MessageList__Item {
margin: 5px 0; } margin: 5px 0; }
.LP-UserInfo {
display: flex;
flex-direction: column;
background-color: #D7CEC7;
max-width: 400px;
width: 100%;
min-width: 300px; }
.LP-UserInfo__UserName {
background-color: #C09F80; }
.LP-UserInfo__UserName .LP-Headline {
color: black;
padding: 0;
margin: 0;
text-align: center;
padding: 5px;
font-size: 25px; }
.LP-UserInfo__Meta {
padding: 5px; }
.LP-UserInfo__Meta * {
font-family: "Montserrat", Helvetica, sans-serif;
font-size: 18px; }
.LP-UserInfo__Meta .LP-UserInfo__Key {
padding-right: 25px;
font-weight: bold; }
.LP-Header { .LP-Header {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1069,7 +1115,9 @@ body {
box-shadow: 0 0 2px #565656; } box-shadow: 0 0 2px #565656; }
.LP-PlaceList .LP-PlaceList__List { .LP-PlaceList .LP-PlaceList__List {
list-style-type: none; } list-style-type: none;
padding: 0;
margin: 0; }
.LP-PlaceList .LP-PlaceList__List .LP-Link .LP-Place:hover { .LP-PlaceList .LP-PlaceList__List .LP-Link .LP-Place:hover {
color: #565656; color: #565656;
background-color: #f9f9f9; background-color: #f9f9f9;
@@ -1080,6 +1128,10 @@ body {
.LP-PlaceList .LP-PlaceList__List .LP-PlaceList__Item { .LP-PlaceList .LP-PlaceList__List .LP-PlaceList__Item {
max-width: 900px; max-width: 900px;
margin: 18px 0; } margin: 18px 0; }
@media (max-width: 750px) {
.LP-PlaceList .LP-PlaceList__List .LP-PlaceList__Item {
width: 100%;
margin-bottom: 35px; } }
.LP-PlaceList .LP-Pagination { .LP-PlaceList .LP-Pagination {
margin-top: 50px; } margin-top: 50px; }
@@ -1661,6 +1713,18 @@ body {
padding: .5em; padding: .5em;
border-radius: 2px; } border-radius: 2px; }
.LP-UserProfile {
display: flex;
flex-direction: row; }
.LP-UserProfile__Bio {
flex-grow: 5;
flex-shrink: 10;
padding-right: 35px; }
.LP-UserProfile__Info {
flex-grow: 10;
flex-shrink: 1;
flex-basis: max-content; }
.LP-MainContainer { .LP-MainContainer {
margin: 0 auto; margin: 0 auto;
max-width: 1280px; } max-width: 1280px; }
@@ -1673,8 +1737,9 @@ body {
width: 100%; } } width: 100%; } }
.LP-PlaceDetail .LP-PlaceDetail__Image { .LP-PlaceDetail .LP-PlaceDetail__Image {
width: 700px; max-width: 700px;
max-height: 500px; max-height: 500px;
width: 50%;
box-shadow: 0 0 10px #565656; box-shadow: 0 0 10px #565656;
box-shadow: 0 0 10px #565656; box-shadow: 0 0 10px #565656;
object-fit: cover; object-fit: cover;
@@ -1682,15 +1747,16 @@ body {
margin: 0; margin: 0;
padding: 0; padding: 0;
float: right; float: right;
margin-left: 35px; margin-left: 25px;
margin-bottom: 35px; margin-bottom: 25px;
margin-right: 35px; margin-right: 25px;
overflow: hidden; } overflow: hidden; }
@media (max-width: 1000px) { @media (max-width: 750px) {
.LP-PlaceDetail .LP-PlaceDetail__Header .LP-PlaceDetail__Image { .LP-PlaceDetail .LP-PlaceDetail__Header .LP-PlaceDetail__Image {
float: none; float: none;
width: 100%; width: 100%;
max-width: unset;
height: auto; height: auto;
margin: 0; margin: 0;
padding: 0; padding: 0;

View File

@@ -0,0 +1,161 @@
{% extends 'global.html'%}
{% load static %}
{% load i18n %}
{% load svg_icon %}
{% block maincontent %}
<div class="LP-UserProfile">
<div class="LP-UserProfile__Bio">
<h2 class="LP-Headline">Bio</h2>
<p class="LP-Paragraph">{{explorer.bio}}</p>
</div>
<div class="LP-UserProfile__Info">
<div class="LP-UserInfo">
<div class="LP-UserInfo__UserName">
<h1 class="LP-Headline">{{explorer.user.username}}</h1>
</div>
<div class="LP-UserInfo__Meta">
<table>
<tr>
{% if explorer.profile_image %}
<figure class="LP-UserInfo__Image">
<img src="{{ explorer.profile_image.url }}" class="LP-Image" />
</figure>
{% endif %}
<tr>
{% if explorer.user.first_name %}
<td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Name' %}</span>
</td>
<td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{explorer.user.first_name}} {{explorer.user.last_name}}</span>
</td>
{% endif %}
</tr>
<tr>
{% if explorer.user.email %}
<td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'E-mail' %}</span>
</td>
<td class="LP-UserInfo__Value">
<span class="LP-Paragraph">
<a href="{{explorer.user.email}}" class="LP-Link">{{explorer.user.email}}</a>
</span>
</td>
{% endif %}
</tr>
<tr>
<td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Joined' %}</span>
</td>
<td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{explorer.user.date_joined|date:'d.m.Y'}}</span>
</td>
</tr>
<tr>
<td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Places'%}</span>
</td>
<td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{place_count}}</span>
</td>
</tr>
<tr>
<td class="LP-UserInfo__Key">
<span class="LP-Paragraph">{% trans 'Place assets'%}</span>
</td>
<td class="LP-UserInfo__Value">
<span class="LP-Paragraph">{{asset_count}}</span>
</td>
</tr>
<tr>
<td>
<a href="/explorer/update/"><button class="LP-Button LP-Button">{% trans 'Edit Profile' %}</button></a>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<section class="LP-Section">
<div class="LP-PlaceList">
<h1 class="LP-Headline">{% trans 'Favorite places' %}</h1>
<ul class="LP-PlaceList__List">
{% for place in explorer.favorite_places.all %}
<li class="LP-PlaceList__Item">
{% include 'partials/place_teaser.html' with place=place extended=True %}
</li>
{% endfor %}
</ul>
{% include 'partials/nav/pagination.html' %}
</div>
</section>
<section class="LP-Section">
<div class="LP-PlaceList">
<h1 class="LP-Headline">{% trans 'Places submitted by' %} {{explorer.user.username}}</h1>
<ul class="LP-PlaceList__List">
{% for place in place_list %}
<li class="LP-PlaceList__Item">
{% include 'partials/place_teaser.html' with place=place extended=True %}
</li>
{% endfor %}
</ul>
{% include 'partials/nav/pagination.html' %}
</div>
</section>
<section class="LP-Section">
<h1 class="LP-Headline">{% trans 'Images submitted by' %} {{explorer.user.username}}</h1>
<div class="LP-ImageGrid">
<ul class="LP-ImageGrid__Container">
{% for place_image in assets.placeimages.all %}
<li class="LP-ImageGrid__Item">
<a href="{{ place_image.filename.large.url }}" class="LP-Link">
<img class="LP-Image" src="{{ place_image.filename.thumbnail.url }}">
</a>
{% if user.explorer == place_image.submitted_by%}
<span class="LP-ImageGrid__DeleteItem" title="Bild löschen">
<a href="{% url 'place_image_delete' pk=place_image.id %}" class="LP-Link">
<img class="LP-Icon" src="{% static 'icons/cancel.svg' %}" />
</a>
</span>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</section>
<section class=" LP-Section">
<h1 class="LP-Headline">{% trans 'Photo albums submitted by' %} {{explorer.user.username}}</h1>
<div class="LP-LinkList">
<ul class="LP-LinkList__Container">
{% for photo_album in assets.photoalbums.all %}
<li class="LP-LinkList__Item">
<a target="_blank" href="{{photo_album.url}}" class="LP-Link">
<span class="LP-Text">{{photo_album.label}}</span>
</a>
{% if user.explorer == photo_album.submitted_by%}
<a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album">
<div class="RV-Iconized__Container RV-Iconized__Container--small">
{% icon 'trash' className="RV-Iconized__Icon" %}
</div>
</a>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</section>
{% endblock maincontent %}

View File

@@ -0,0 +1,55 @@
{% extends 'global.html'%}
{% load i18n %}
{% load thumbnail %}
{% load widget_tweaks %}
# {% block title %}{% trans 'Edit Explorer profile' %}{% endblock %}
{% block maincontent %}
<form class="LP-Form" method="POST" enctype="multipart/form-data">
<fieldset class="LP-Form__Fieldset">
<legend class="LP-Form__Legend">{% trans 'Edit Explorer profile' %}</legend>
{% csrf_token %}
<div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.username %}
</div>
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.email %}
</div>
</div>
<div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.first_name %}
</div>
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=explorer_user_change_form.last_name %}
</div>
</div>
<div class="LP-Form__Composition">
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=explorer_change_form.bio %}
</div>
</div>
<div class="LP-Form__Composition">
{% if explorer_image %}
<div class="LP-Form__Field">
<img src="{% thumbnail explorer_image 200x200 %}"/>
</div>
{% endif %}
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=explorer_change_form.profile_image %}
</div>
</div>
{% trans 'Update' as action %}
<div class="LP-Form__Composition LP-Form__Composition--buttons">
{% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
</div>
</fieldset>
</form>
{% endblock maincontent %}

View File

@@ -29,9 +29,10 @@
<span class="LP-Paragraph"> <span class="LP-Paragraph">
{% if user.is_authenticated %} {% if user.is_authenticated %}
Hi {{ user.username }}! Hi {{ user.username }}!
<a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% trans 'Logout' %}</span></a> <a class="LP-Link" href="{% url 'logout' %}"><span class="LP-Link__Text">{% trans 'Logout' %}</span></a> |
<a class="LP-Link" href="{% url 'explorer_profile' explorer_id=user.pk%}"><span class="LP-Link__Text">{% trans 'Profile' %}</span></a>
{% if user.is_superuser %} {% if user.is_superuser %}
<a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% trans 'Admin' %}</span></a> | <a class="LP-Link" href="{% url 'admin:index' %}" target="_blank"><span class="LP-Link__Text">{% trans 'Admin' %}</span></a>
{% endif %} {% endif %}
{% else %} {% else %}

View File

@@ -21,30 +21,7 @@
<ul class="LP-PlaceGrid__Grid"> <ul class="LP-PlaceGrid__Grid">
{% for place in place_list %} {% for place in place_list %}
<li class="LP-PlaceGrid__Item"> <li class="LP-PlaceGrid__Item">
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link"> {% include 'partials/place_teaser.html' with place=place%}
<article class="LP-PlaceTeaser">
<div class="LP-PlaceTeaser__Image">
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url}}" />
</div>
<div class="LP-PlaceTeaser__Meta">
<div class="LP-PlaceTeaser__Info">
<span class="LP-PlaceTeaser__Title">
<h1 class="LP-Headline LP-Headline--teaser">{{place.name|truncatechars:19}}</h1>
</span>
<span class="LP-PlaceTeaser__Detail">
<p class="LP-Paragraph">{{place.location|truncatechars:25}}</p>
</span>
</div>
<div class="LP-PlaceTeaser__Icons">
<ul class="LP-Icon__List">
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/favourite.svg' %}" /></li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/location.svg' %}" /></li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
</ul>
</div>
</div>
</article>
</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -0,0 +1,14 @@
{%load static %}
{% load i18n %}
{% if request.user %}
{% if place in request.user.explorer.favorite_places.all %}
<a href="{% url 'place_unfavorite' place_id=place.id %}" class="LP-Link" title="{% trans 'Remove from favorites' %}">
<img class="LP-Icon" src="{% static '/icons/favourite_filled.svg' %}" />
</a>
{%else%}
<a href="{% url 'place_favorite' place_id=place.id %}" class="LP-Link" title="{% trans 'Save as favorite' %}">
<img class="LP-Icon" src="{% static '/icons/favourite.svg' %}" />
</a>
{% endif %}
{% endif %}

View File

@@ -0,0 +1,39 @@
{%load static %}
<article class="LP-PlaceTeaser {% if extended %} LP-PlaceTeaser--extended{% endif %}">
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link">
<div class="LP-PlaceTeaser__Image">
{% if place.placeimages.all|length > 0 %}
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url}}" />
{% else %}
<img class="LP-Image" src="{% static 'images/missing_image.png' %}" />
{% endif %}
</div>
</a>
<div class="LP-PlaceTeaser__Meta">
<div class="LP-PlaceTeaser__Info">
<span class="LP-PlaceTeaser__Title">
<h1 class="LP-Headline LP-Headline--teaser">{{place.name|truncatechars:19}}</h1>
</span>
<span class="LP-PlaceTeaser__Detail">
<p class="LP-Paragraph">{{place.location|truncatechars:25}}</p>
</span>
</div>
<div class="LP-PlaceTeaser__Description">
<p class="LP-Paragraph">
{% if place.description|length > 210 %}
{{place.description|truncatechars:210|truncatewords:-1}}
{% else %}
{{place.description}}
{% endif %}
</p>
</div>
<div class="LP-PlaceTeaser__Icons">
<ul class="LP-Icon__List">
<li class="LP-Icon__Item">{% include 'partials/icons/place_favorite.html' with place=place%}</li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/location.svg' %}" /></li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
</ul>
</div>
</div>
</article>

View File

@@ -23,7 +23,7 @@
<article class="LP-PlaceDetail"> <article class="LP-PlaceDetail">
<header class="LP-PlaceDetail__Header"> <header class="LP-PlaceDetail__Header">
<h1 class="LP-Headline">{{ place.name }}</h1> <h1 class="LP-Headline">{{ place.name }} {% include 'partials/icons/place_favorite.html' %}</h1>
{% if place.placeimages.first.filename.hero.url %} {% if place.placeimages.first.filename.hero.url %}
<figure class="LP-PlaceDetail__Image"> <figure class="LP-PlaceDetail__Image">
<img src="{{ place.placeimages.first.filename.hero.url }}" class="LP-Image" /> <img src="{{ place.placeimages.first.filename.hero.url }}" class="LP-Image" />

View File

@@ -17,39 +17,7 @@
<ul class="LP-PlaceList__List"> <ul class="LP-PlaceList__List">
{% for place in place_list %} {% for place in place_list %}
<li class="LP-PlaceList__Item"> <li class="LP-PlaceList__Item">
<a href="{% url 'place_detail' pk=place.pk %}" class="LP-Link"> {% include 'partials/place_teaser.html' with place=place extended=True %}
<article class="LP-PlaceTeaser LP-PlaceTeaser--extended">
<div class="LP-PlaceTeaser__Image">
<img class="LP-Image" src="{{ place.placeimages.first.filename.thumbnail.url }}" />
</div>
<div class="LP-PlaceTeaser__Meta">
<div class="LP-PlaceTeaser__Info">
<span class="LP-PlaceTeaser__Title">
<h2 class="LP-Headline LP-Headline--teaser">{{place.name}}</h2>
</span>
<span class="LP-PlaceTeaser__Detail">
<p class="LP-Paragraph">{{place.location}}</p>
</span>
</div>
<div class="LP-PlaceTeaser__Description">
<p class="LP-Paragraph">
{% if place.description|length > 210 %}
{{place.description|truncatechars:210|truncatewords:-1}}
{% else %}
{{place.description}}
{% endif %}
</p>
</div>
<div class="LP-PlaceTeaser__Icons">
<ul class="LP-Icon__List">
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/favourite.svg' %}" /></li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/location.svg' %}" /></li>
<li class="LP-Icon__Item"><img class="LP-Icon" src="{% static '/icons/flag.svg' %}" /></li>
</ul>
</div>
</div>
</article>
</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -19,6 +19,14 @@
{% include 'partials/form/inputField.html' with field=form.email %} {% include 'partials/form/inputField.html' with field=form.email %}
</div> </div>
</div> </div>
<div class="LP-Form__Composition LP-Form__Composition--breakable">
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=form.first_name %}
</div>
<div class="LP-Form__Field">
{% include 'partials/form/inputField.html' with field=form.last_name %}
</div>
</div>
<div class="LP-Form__Composition"> <div class="LP-Form__Composition">
<div class="LP-Form__Field"> <div class="LP-Form__Field">

View File

@@ -4,15 +4,15 @@ from django import forms
from django.utils import timezone from django.utils import timezone
from lostplaces.tests.forms import FormTestCase from lostplaces.tests.forms import FormTestCase
from lostplaces.forms import ExplorerCreationForm from lostplaces.forms import SignupVoucherForm
from lostplaces.models.models import Voucher from lostplaces.models.models import Voucher
class ExplorerCreationFormTestCase(FormTestCase): class SignupVoucherFormTestCase(FormTestCase):
""" """
This test case only tests for the voucher since all other aspects don't realy matter This test case only tests for the voucher since all other aspects don't realy matter
to this project and are already tested by django to this project and are already tested by django
""" """
form = ExplorerCreationForm form = SignupVoucherForm
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@@ -37,7 +37,7 @@ class ExplorerCreationFormTestCase(FormTestCase):
) )
def test_validation_valid(self): def test_validation_valid(self):
form = ExplorerCreationForm(self.post_data) form = SignupVoucherForm(self.post_data)
self.assertTrue( self.assertTrue(
form.is_valid(), form.is_valid(),
msg='Expecting the %s to validate' % ( msg='Expecting the %s to validate' % (
@@ -49,7 +49,7 @@ class ExplorerCreationFormTestCase(FormTestCase):
self.post_data = { self.post_data = {
'voucher': 'Imanotacode123' 'voucher': 'Imanotacode123'
} }
form = ExplorerCreationForm(self.post_data) form = SignupVoucherForm(self.post_data)
self.assertFalse( self.assertFalse(
form.is_valid(), form.is_valid(),
msg='Expecting the %s to not validate' % ( msg='Expecting the %s to not validate' % (

View File

@@ -11,11 +11,15 @@ from lostplaces.views import (
PlaceDeleteView, PlaceDeleteView,
PlaceTagDeleteView, PlaceTagDeleteView,
PlaceTagSubmitView, PlaceTagSubmitView,
PlaceFavoriteView,
PlaceUnfavoriteView,
PhotoAlbumCreateView, PhotoAlbumCreateView,
PhotoAlbumDeleteView, PhotoAlbumDeleteView,
PlaceImageCreateView, PlaceImageCreateView,
PlaceImageDeleteView, PlaceImageDeleteView,
FlatView FlatView,
ExplorerProfileView,
ExplorerProfileUpdateView
) )
urlpatterns = [ urlpatterns = [
@@ -33,5 +37,11 @@ urlpatterns = [
# POST-only URLs for tag submission # POST-only URLs for tag submission
path('place/tag/<int:tagged_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'), path('place/tag/<int:tagged_id>', PlaceTagSubmitView.as_view(), name='place_tag_submit'),
path('place/tag/delete/<int:tagged_id>/<int:tag_id>', PlaceTagDeleteView.as_view(), name='place_tag_delete') path('place/tag/delete/<int:tagged_id>/<int:tag_id>', PlaceTagDeleteView.as_view(), name='place_tag_delete'),
path('explorer/<int:explorer_id>/', ExplorerProfileView.as_view(), name='explorer_profile'),
path('explorer/update/', ExplorerProfileUpdateView.as_view(), name='explorer_profile_update'),
path('explorer/favorite/<int:place_id>/', PlaceFavoriteView.as_view(), name='place_favorite'),
path('explorer/unfavorite/<int:place_id>/', PlaceUnfavoriteView.as_view(), name='place_unfavorite')
] ]

View File

@@ -4,4 +4,5 @@
from lostplaces.views.base_views import * from lostplaces.views.base_views import *
from lostplaces.views.views import * from lostplaces.views.views import *
from lostplaces.views.place_views import * from lostplaces.views.place_views import *
from lostplaces.views.place_image_views import * from lostplaces.views.place_image_views import *
from lostplaces.views.explorer_views import *

View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.views import View
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.contrib import messages
from lostplaces.common import get_all_subclasses
from lostplaces.views.base_views import IsAuthenticatedMixin
from lostplaces.models.models import Explorer
from lostplaces.models.place import Place, PlaceAsset
from lostplaces.forms import ExplorerChangeForm, ExplorerUserChangeForm
class ExplorerProfileView(IsAuthenticatedMixin, View):
def get(self, request, explorer_id):
explorer = get_object_or_404(Explorer, pk=explorer_id)
place_list = Place.objects.filter(submitted_by=explorer)
place_count = place_list.count()
context={
'explorer': explorer,
'place_count': place_count,
'place_list': place_list,
'assets': {}
}
asset_count = 0
for subclass in get_all_subclasses(PlaceAsset): # kinda ugly, but PlaceAsset cannot be abstract according to django ORM
objects = subclass.objects.filter(submitted_by=explorer)
context['assets'][subclass.__name__.lower()+'s'] = objects
asset_count += objects.count()
context['asset_count'] = asset_count
return render(
request=request,
template_name='explorer/profile.html',
context=context
)
class ExplorerProfileUpdateView(IsAuthenticatedMixin, View):
success_message = ''
permission_denied_message = ''
def get(self, request, *args, **kwargs):
context = {
'explorer_user_change_form': ExplorerUserChangeForm(instance=request.user),
'explorer_change_form': ExplorerChangeForm(instance=request.user.explorer)
}
if request.user.explorer.profile_image:
context['explorer_image'] = request.user.explorer.profile_image
return render(request, 'explorer/profile_update.html', context)
def post(self, request, *args, **kwargs):
print(request.POST)
explorer_user_change_form = ExplorerUserChangeForm(
request.POST,
instance=request.user
)
explorer_change_form = ExplorerChangeForm(
request.POST,
request.FILES,
instance=request.user.explorer
)
if explorer_change_form.is_valid() and explorer_user_change_form.is_valid():
explorer_user_change_form.save()
explorer_change_form.save()
print(explorer_change_form.cleaned_data)
messages.success(
self.request,
_('Successfully updated Explorer profile')
)
else:
# Usually the browser should have checked the form before sending.
messages.error(
self.request,
_('Please fill in all required fields.')
)
return redirect(reverse_lazy('explorer_profile_update'))

View File

@@ -119,3 +119,36 @@ class PlaceDeleteView(IsAuthenticatedMixin, IsPlaceSubmitterMixin, DeleteView):
def get_place(self): def get_place(self):
return self.get_object() return self.get_object()
class PlaceFavoriteView(IsAuthenticatedMixin, View):
def get(self, request, place_id):
place = get_object_or_404(Place, id=place_id)
if request.user is not None:
request.user.explorer.favorite_places.add(place)
request.user.explorer.save()
referer = request.META.get('HTTP_REFERER')
if referer is not None:
return redirect(referer)
else:
return redirect(
reverse_lazy('place_detail', kwargs={'pk': place.pk})
)
class PlaceUnfavoriteView(IsAuthenticatedMixin, View):
def get(self, request, place_id):
place = get_object_or_404(Place, id=place_id)
if request.user is not None:
request.user.explorer.favorite_places.remove(place)
request.user.explorer.save()
referer = request.META.get('HTTP_REFERER')
if referer is not None:
return redirect(referer)
else:
return redirect(
reverse_lazy('place_detail', kwargs={'pk': place.pk})
)

View File

@@ -11,7 +11,7 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from lostplaces.forms import ExplorerCreationForm, TagSubmitForm from lostplaces.forms import SignupVoucherForm, TagSubmitForm
from lostplaces.models import Place, PhotoAlbum from lostplaces.models import Place, PhotoAlbum
from lostplaces.views.base_views import IsAuthenticatedMixin from lostplaces.views.base_views import IsAuthenticatedMixin
@@ -23,7 +23,7 @@ from lostplaces.views.base_views import (
from taggit.models import Tag from taggit.models import Tag
class SignUpView(SuccessMessageMixin, CreateView): class SignUpView(SuccessMessageMixin, CreateView):
form_class = ExplorerCreationForm form_class = SignupVoucherForm
success_url = reverse_lazy('login') success_url = reverse_lazy('login')
template_name = 'signup.html' template_name = 'signup.html'
success_message = _('User created') success_message = _('User created')

View File

@@ -1,8 +0,0 @@
# Config options for coverage
# Docs: https://coverage.readthedocs.io/en/latest/config.html
[coverage:run]
source = .
[coverage:report]
show_missing = True

9
setup.py → django_lostplaces/setup.py Normal file → Executable file
View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from setuptools import setup, find_packages from setuptools import setup, find_packages
with open('Readme.md') as f: with open('../Readme.md') as f:
readme = f.read() readme = f.read()
setup( setup(
@@ -13,7 +13,7 @@ setup(
author='Reverend', author='Reverend',
author_email='reverend@reverend2048.de', author_email='reverend@reverend2048.de',
url='https://git.mowoe.com/reverend/lostplaces-backend', url='https://git.mowoe.com/reverend/lostplaces-backend',
packages=find_packages(exclude=['lostplaces']), packages=find_packages(exclude=['django_lostplaces']),
long_description=readme, long_description=readme,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
classifiers=[ classifiers=[
@@ -31,5 +31,6 @@ setup(
'django-widget-tweaks', 'django-widget-tweaks',
'django-taggit' 'django-taggit'
], ],
include_package_data=True include_package_data=True,
license='MIT'
) )