Compare commits
	
		
			24 Commits
		
	
	
		
			ddd0f8c903
			...
			feature/50
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1a110e7582 | |||
| a7c1ffd695 | |||
| 87389de4ea | |||
| 9290bcc6b3 | |||
| a6dbf1c716 | |||
| 670b99a7e5 | |||
| 1b87dd2edf | |||
| c4a9296712 | |||
| a37a445986 | |||
| 056d937f15 | |||
| 86f04c08ff | |||
| e14ed52eeb | |||
| 72bfc97fbd | |||
| d787651d05 | |||
| f5fb33c2c5 | |||
| a5bcde301c | |||
| 14a5387e2d | |||
| dd4b4f936b | |||
| cf9a5474b8 | |||
| 961d28e336 | |||
| 4dbd8ae82d | |||
| c3ea5cdccd | |||
| caa69d6e6e | |||
| 5f9ef08e3b | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -79,6 +79,7 @@ Pipfile.lock
 | 
			
		||||
__pypackages__/
 | 
			
		||||
 | 
			
		||||
# Environments
 | 
			
		||||
.env
 | 
			
		||||
.venv
 | 
			
		||||
env/
 | 
			
		||||
venv/
 | 
			
		||||
@@ -93,4 +94,4 @@ venv.bak/
 | 
			
		||||
.pypirc
 | 
			
		||||
 | 
			
		||||
# Django Migrations for Development branches
 | 
			
		||||
django_lostplaces/lostplaces/migrations/*
 | 
			
		||||
django_lostplaces/lostplaces/migrations/*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								Pipfile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								Pipfile
									
									
									
									
									
								
							@@ -14,7 +14,6 @@ pandoc = "*"
 | 
			
		||||
pylint-django = "*"
 | 
			
		||||
setuptools = "*"
 | 
			
		||||
django-nose = "*"
 | 
			
		||||
invoke = "*"
 | 
			
		||||
 | 
			
		||||
[packages]
 | 
			
		||||
django = "*"
 | 
			
		||||
@@ -31,7 +30,3 @@ showmigrations = "django_lostplaces/manage.py showmigrations"
 | 
			
		||||
makemigrations = "django_lostplaces/manage.py makemigrations --no-input"
 | 
			
		||||
migrate = "django_lostplaces/manage.py migrate"
 | 
			
		||||
build = "django_lostplaces/setup.py bdist_wheel --universal"
 | 
			
		||||
createsuperuser = "django_lostplaces/manage.py createsuperuser --noinput --username admin --email admin@example.org"
 | 
			
		||||
createsuperuser_prompt = "django_lostplaces/manage.py createsuperuser"
 | 
			
		||||
quickstart = "invoke quickstart"
 | 
			
		||||
security = "pipenv check"
 | 
			
		||||
							
								
								
									
										17
									
								
								Readme.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								Readme.md
									
									
									
									
									
								
							@@ -35,7 +35,7 @@ Right now it depends on the following non-core Python 3 libraries. These can be
 | 
			
		||||
 | 
			
		||||
# Installing a development instance
 | 
			
		||||
## Clone the repository
 | 
			
		||||
`git clone https://git.commander1024.de/Commander1024/lostplaces-backend`
 | 
			
		||||
`git clone https://git.mowoe.com/reverend/lostplaces-backend.git`
 | 
			
		||||
## Setting up a (pipenv) virtual environment for development
 | 
			
		||||
 | 
			
		||||
After having obtained the repository contents (either via .zip download or git clone), you can easily setup a [pipenv](https://docs.pipenv.org/) virtual environment. The repo provides a Pipfile for easy dependency management that does not mess with your system.
 | 
			
		||||
@@ -65,21 +65,6 @@ Visit: [admin](http://localhost:8000/admin) for administrative backend or
 | 
			
		||||
 | 
			
		||||
Happy developing ;-)
 | 
			
		||||
 | 
			
		||||
# Pipenv Scripts
 | 
			
		||||
This project comes with a bunch of convinient scripts, like:
 | 
			
		||||
|Script|Description|
 | 
			
		||||
|---|---|
 | 
			
		||||
|test|Runs the tests|
 | 
			
		||||
|server|Starts a **development** server|
 | 
			
		||||
|dbshell|Opens a shell session in the database|
 | 
			
		||||
|showmigrations|Lists all Migrations|
 | 
			
		||||
|makemigrations|Creates a migration|
 | 
			
		||||
|migrate|Applies unapplied migrations|
 | 
			
		||||
|build|Builds this project into a wheel file|
 | 
			
		||||
|createsuperuser|Creates a superuser with the username **admin** and the password **develop**. This is for development and demo instances only!
 | 
			
		||||
|quickstart|Runs *migrate*, *createsuperuser* and *server*|
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Installing a productive instance
 | 
			
		||||
 | 
			
		||||
Currently there are two ways to deploy the lostplaces project:
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -24,7 +24,7 @@ user.explorer
 | 
			
		||||
Currently the explorer profile is used by the abstract model 'Submittable' and has the following realated names/fiels:
 | 
			
		||||
- [places](###place) A list containing all (lost) places the user has submitted  
 | 
			
		||||
- [placeimages](###placeimages) A list containing all images relating a place that a user has submitted
 | 
			
		||||
- [photoalbums](###photoalbums) A list of all photo albums a explorere has submitted
 | 
			
		||||
- [externallinks](###externallinks) A list of all photo albums a explorere has submitted
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Taggable
 | 
			
		||||
@@ -141,15 +141,3 @@ This model represents an URL to an external website. External Link is an PlaceAs
 | 
			
		||||
the URL to the target
 | 
			
		||||
`label`  
 | 
			
		||||
the label that is shown on the website
 | 
			
		||||
 | 
			
		||||
### PhotoAlbum
 | 
			
		||||
Loacation: `lostplaces.models.external_link.PhototAlbum`  
 | 
			
		||||
Import From: `lostplaces.models.PhotoAlbum`
 | 
			
		||||
#### Super Classes
 | 
			
		||||
- django's `models.Model`
 | 
			
		||||
- [lostplaces.models.Submittable](###submittable)
 | 
			
		||||
- [lostplaces.models.PlaceAsset](###placeasset)
 | 
			
		||||
- [lostplaces.models.ExternalLink](###externallink)
 | 
			
		||||
 | 
			
		||||
A photo album is a link to an external site that is meant to contain photos of the place it is referenced in. It 
 | 
			
		||||
does not have any fields, just the ones inherited from it's super class [ExternalLink](###externallink).
 | 
			
		||||
@@ -21,8 +21,8 @@ class VoucherAdmin(admin.ModelAdmin):
 | 
			
		||||
    
 | 
			
		||||
    valid.boolean = True
 | 
			
		||||
 | 
			
		||||
class PhotoAlbumsAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('label', 'place', 'url' )
 | 
			
		||||
class ExternalLinksAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('label', 'place', 'url', 'linktype' )
 | 
			
		||||
 | 
			
		||||
class PlacesAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ('name', 'submitted_by', 'submitted_when')
 | 
			
		||||
@@ -34,5 +34,4 @@ admin.site.register(Explorer)
 | 
			
		||||
admin.site.register(Voucher, VoucherAdmin)
 | 
			
		||||
admin.site.register(Place, PlacesAdmin)
 | 
			
		||||
admin.site.register(PlaceImage, PlaceImagesAdmin)
 | 
			
		||||
admin.site.register(PhotoAlbum, PhotoAlbumsAdmin)
 | 
			
		||||
admin.site.register(PlaceVoting)
 | 
			
		||||
admin.site.register(ExternalLink, ExternalLinksAdmin)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ class SignupVoucherForm(UserCreationForm):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def is_valid(self):
 | 
			
		||||
        super_result = super().is_valid()
 | 
			
		||||
        super().is_valid()
 | 
			
		||||
        submitted_voucher = self.cleaned_data.get('voucher')
 | 
			
		||||
        try:
 | 
			
		||||
            fetched_voucher = Voucher.objects.get(code=submitted_voucher)
 | 
			
		||||
@@ -35,7 +35,7 @@ class SignupVoucherForm(UserCreationForm):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        fetched_voucher.delete()
 | 
			
		||||
        return super_result
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
class ExplorerUserChangeForm(UserChangeForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								django_lostplaces/lostplaces/migrations/0004_gory_fix.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								django_lostplaces/lostplaces/migrations/0004_gory_fix.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
# Generated by Django 3.1.4 on 2020-12-25 16:02
 | 
			
		||||
 | 
			
		||||
import django.core.validators
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import easy_thumbnails.fields
 | 
			
		||||
from lostplaces.models.models import generate_profile_image_filename
 | 
			
		||||
from lostplaces.models.place import generate_place_image_filename
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0003_voucher'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='bio',
 | 
			
		||||
            field=models.TextField(blank=True, help_text='Describe yourself, your preferences, etc. in a few sentences.', null=True, verbose_name='Biography / Description'),
 | 
			
		||||
        ),
 | 
			
		||||
#        migrations.AddField(
 | 
			
		||||
#            model_name='explorer',
 | 
			
		||||
#            name='favorite_places',
 | 
			
		||||
#            field=models.ManyToManyField(blank=True, related_name='explorer_favorites', to='lostplaces.Place', verbose_name='Explorers favorite places'),
 | 
			
		||||
#        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='profile_image',
 | 
			
		||||
            field=easy_thumbnails.fields.ThumbnailerImageField(blank=True, help_text='Optional profile image for display in Explorer profile', null=True, upload_to=generate_profile_image_filename, verbose_name='Profile image'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='label',
 | 
			
		||||
            field=models.CharField(max_length=100, verbose_name='link text'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='submitted_by',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='photoalbums', to='lostplaces.explorer', verbose_name='Submitter'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='submitted_when',
 | 
			
		||||
            field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='url',
 | 
			
		||||
            field=models.URLField(verbose_name='URL'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='description',
 | 
			
		||||
            field=models.TextField(help_text="Description of the place: e.g. how to get there, where to be careful, the place's history...", verbose_name='Description'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='latitude',
 | 
			
		||||
            field=models.FloatField(help_text='Latitude in decimal format: e. g. 41.40338', validators=[django.core.validators.MinValueValidator(-90), django.core.validators.MaxValueValidator(90)], verbose_name='Latitude'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='location',
 | 
			
		||||
            field=models.CharField(max_length=50, verbose_name='Location'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='longitude',
 | 
			
		||||
            field=models.FloatField(help_text='Longitude in decimal format: e. g. 2.17403', validators=[django.core.validators.MinValueValidator(-180), django.core.validators.MaxValueValidator(180)], verbose_name='Longitude'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='name',
 | 
			
		||||
            field=models.CharField(max_length=50, verbose_name='Name'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='submitted_by',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='places', to='lostplaces.explorer', verbose_name='Submitter'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='submitted_when',
 | 
			
		||||
            field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='placeimage',
 | 
			
		||||
            name='description',
 | 
			
		||||
            field=models.TextField(blank=True, verbose_name='Description'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='placeimage',
 | 
			
		||||
            name='filename',
 | 
			
		||||
            field=easy_thumbnails.fields.ThumbnailerImageField(help_text='Optional: One or more images to upload', upload_to=generate_place_image_filename, verbose_name='Filename(s)'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='placeimage',
 | 
			
		||||
            name='submitted_by',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='placeimages', to='lostplaces.explorer', verbose_name='Submitter'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='placeimage',
 | 
			
		||||
            name='submitted_when',
 | 
			
		||||
            field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='voucher',
 | 
			
		||||
            name='created_when',
 | 
			
		||||
            field=models.DateTimeField(auto_now_add=True, verbose_name='Creation date'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='voucher',
 | 
			
		||||
            name='expires_when',
 | 
			
		||||
            field=models.DateTimeField(verbose_name='Expiration date'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
# Generated by Django 3.1.4 on 2020-12-25 18:14
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0004_gory_fix'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='visited_places',
 | 
			
		||||
            field=models.ManyToManyField(blank=True, related_name='explorer_visits', to='lostplaces.Place', verbose_name='Explorers visited places'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -1,85 +0,0 @@
 | 
			
		||||
# Generated by Django 3.2.10 on 2021-12-31 17:20
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import easy_thumbnails.fields
 | 
			
		||||
import lostplaces.models.models
 | 
			
		||||
import lostplaces.models.place
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0004_release_0_1_3'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='bio',
 | 
			
		||||
            field=models.TextField(blank=True, help_text='Describe yourself, your preferences, etc. in a few sentences.', null=True, verbose_name='Biography / Description'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='favorite_places',
 | 
			
		||||
            field=models.ManyToManyField(blank=True, related_name='explorer_favorites', to='lostplaces.Place', verbose_name='Explorers favorite places'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='level',
 | 
			
		||||
            field=models.IntegerField(choices=[(1, 'Newbie'), (2, 'Scout'), (3, 'Explorer'), (4, 'Journalist'), (5, 'Housekeeper')], default=1),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='profile_image',
 | 
			
		||||
            field=easy_thumbnails.fields.ThumbnailerImageField(blank=True, help_text='Optional profile image for display in Explorer profile', null=True, upload_to=lostplaces.models.generate_profile_image_filename, verbose_name='Profile image'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='explorer',
 | 
			
		||||
            name='visited_places',
 | 
			
		||||
            field=models.ManyToManyField(blank=True, related_name='explorer_visits', to='lostplaces.Place', verbose_name='Explorers visited places'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='hero',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='place_heros', to='lostplaces.placeimage'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='place',
 | 
			
		||||
            name='level',
 | 
			
		||||
            field=models.IntegerField(choices=[(1, 'Ruin'), (2, 'Vandalized'), (3, 'Natures Treasure'), (4, 'Lost in History'), (5, 'Time Capsule')], default=5),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='placeimage',
 | 
			
		||||
            name='filename',
 | 
			
		||||
            field=easy_thumbnails.fields.ThumbnailerImageField(help_text='Optional: One or more images to upload', upload_to=lostplaces.models.place.generate_place_image_filename, verbose_name='Images'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='PlaceVoting',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('submitted_when', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date')),
 | 
			
		||||
                ('created_when', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')),
 | 
			
		||||
                ('expires_when', models.DateTimeField(verbose_name='Expiration date')),
 | 
			
		||||
                ('vote', models.IntegerField(choices=[(1, 'Ruin'), (2, 'Vandalized'), (3, 'Natures Treasure'), (4, 'Lost in History'), (5, 'Time Capsule')])),
 | 
			
		||||
                ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='placevotings', to='lostplaces.place')),
 | 
			
		||||
                ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='placevotings', to='lostplaces.explorer', verbose_name='Submitter')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='DummyAsset',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('submitted_when', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date')),
 | 
			
		||||
                ('name', models.CharField(max_length=50)),
 | 
			
		||||
                ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dummyassets', to='lostplaces.place')),
 | 
			
		||||
                ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dummyassets', to='lostplaces.explorer', verbose_name='Submitter')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
# Generated by Django 3.2.5 on 2021-07-16 11:15
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0004_release_0_1_3'),
 | 
			
		||||
        ('lostplaces', '0005_add_visited_places'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
# Generated by Django 3.2.7 on 2021-10-01 19:50
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0007_auto_20211001_1925'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='linktype',
 | 
			
		||||
            field=models.CharField(blank=True, max_length=20, null=True, verbose_name='link type'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
# Generated by Django 3.2.7 on 2021-10-01 20:13
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0008_photoalbum_linktype'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='ExternalLink',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('submitted_when', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Submission date')),
 | 
			
		||||
                ('subclass', models.IntegerField(default=-1)),
 | 
			
		||||
                ('subclassid', models.IntegerField(default=-1)),
 | 
			
		||||
                ('url', models.URLField(verbose_name='URL')),
 | 
			
		||||
                ('label', models.CharField(max_length=100, verbose_name='link text')),
 | 
			
		||||
                ('linktype', models.CharField(blank=True, max_length=20, null=True, verbose_name='link type')),
 | 
			
		||||
                ('place', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='externallinks', to='lostplaces.place')),
 | 
			
		||||
                ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='externallinks', to='lostplaces.explorer', verbose_name='Submitter')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='externallink_ptr',
 | 
			
		||||
            field=models.OneToOneField(auto_created=True, null=True, blank=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, serialize=False, to='lostplaces.externallink'),
 | 
			
		||||
            preserve_default=False,
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,22 @@
 | 
			
		||||
# Generated by Django 3.2.7 on 2021-10-01 20:32
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0009_photoalbum_externallink_interim'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        # insert data from subclass into parent class with subclass 'number' and primary key/id
 | 
			
		||||
        migrations.RunSQL("""INSERT INTO lostplaces_externallink (submitted_when, url, label, place_id, submitted_by_id, subclass, subclassid)
 | 
			
		||||
                        SELECT submitted_when, url, label, place_id, submitted_by_id, 1, id
 | 
			
		||||
                        FROM lostplaces_photoalbum;"""
 | 
			
		||||
 | 
			
		||||
        ),
 | 
			
		||||
        # update subclass primary key to point to parent class (notice composite key values):
 | 
			
		||||
        migrations.RunSQL("UPDATE lostplaces_photoalbum SET externallink_ptr_id=lostplaces_externallink.id FROM lostplaces_externallink WHERE lostplaces_externallink.subclassid=lostplaces_photoalbum.id AND lostplaces_externallink.subclass=1;"
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
# Generated by Django 3.2.7 on 2021-10-01 22:48
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0010_photoalbum_externallink_datamigration'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='externallink',
 | 
			
		||||
            name='subclass',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='externallink',
 | 
			
		||||
            name='subclassid',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='id',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='label',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='linktype',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='place',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='submitted_by',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='submitted_when',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='url',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='photoalbum',
 | 
			
		||||
            name='externallink_ptr',
 | 
			
		||||
            field=models.OneToOneField(auto_created=True, default=-1, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='lostplaces.externallink'),
 | 
			
		||||
            preserve_default=False,
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
# Generated by Django 3.2.7 on 2021-10-02 02:37
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('lostplaces', '0011_remove_migrated_photoalbum_data'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='externallink',
 | 
			
		||||
            name='linktype',
 | 
			
		||||
            field=models.CharField(blank=True, choices=[('youtube', 'YouTube'), ('vimeo', 'Vimeo'), ('flickr', 'Flickr'), ('googlephotos', 'Google Photos'), ('photoalbum', 'Photo album')], max_length=20, null=True, verbose_name='link type'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='PhotoAlbum',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -3,10 +3,23 @@ from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from lostplaces.models.place import PlaceAsset
 | 
			
		||||
 | 
			
		||||
LINK_TYPES = (
 | 
			
		||||
    ('youtube', 'YouTube'),
 | 
			
		||||
    ('vimeo', "Vimeo"),
 | 
			
		||||
    ('flickr', 'Flickr'),
 | 
			
		||||
    ('googlephotos', "Google Photos"),
 | 
			
		||||
    ('photoalbum', "Photo album"),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
LINK_DOMAINS = {
 | 
			
		||||
    'youtu.be': 'youtube',
 | 
			
		||||
    'y2u.be': 'youtube',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ExternalLink(PlaceAsset):
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
        abstract = False
 | 
			
		||||
 | 
			
		||||
    url = models.URLField(
 | 
			
		||||
        max_length=200,
 | 
			
		||||
@@ -16,6 +29,10 @@ class ExternalLink(PlaceAsset):
 | 
			
		||||
        max_length=100,
 | 
			
		||||
        verbose_name=_('link text')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
class PhotoAlbum(ExternalLink):
 | 
			
		||||
    pass
 | 
			
		||||
    linktype = models.CharField(
 | 
			
		||||
        choices=LINK_TYPES,
 | 
			
		||||
        max_length=20,
 | 
			
		||||
        verbose_name=_('link type'),
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import os
 | 
			
		||||
from math import floor
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
@@ -7,7 +6,7 @@ from django.dispatch import receiver
 | 
			
		||||
from django.db.models.signals import post_delete, pre_save
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from lostplaces.models.abstract_models import Submittable, Taggable, Mapable, Expireable
 | 
			
		||||
from lostplaces.models.abstract_models import Submittable, Taggable, Mapable
 | 
			
		||||
 | 
			
		||||
from easy_thumbnails.fields import ThumbnailerImageField
 | 
			
		||||
from easy_thumbnails.files import get_thumbnailer
 | 
			
		||||
@@ -16,7 +15,7 @@ PLACE_LEVELS = (
 | 
			
		||||
    (1, 'Ruin'),
 | 
			
		||||
    (2, 'Vandalized'),
 | 
			
		||||
    (3, 'Natures Treasure'),
 | 
			
		||||
    (4, 'Lost in History'),
 | 
			
		||||
    (4, 'Long Time no See'),
 | 
			
		||||
    (5, 'Time Capsule')
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -59,15 +58,10 @@ class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
        return reverse('place_detail', kwargs={'pk': self.pk})
 | 
			
		||||
        
 | 
			
		||||
    def get_hero_index_in_queryset(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Calculates the index of the hero image within
 | 
			
		||||
        the list / queryset of images. Necessary for
 | 
			
		||||
        the lightbox.
 | 
			
		||||
        '''
 | 
			
		||||
        for i in range(0, len(self.placeimages.all())):
 | 
			
		||||
            image = self.placeimages.all()[i]
 | 
			
		||||
            if image == self.hero:
 | 
			
		||||
                return i
 | 
			
		||||
	            return i
 | 
			
		||||
        return None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@@ -90,27 +84,6 @@ class Place(Submittable, Taggable, Mapable):
 | 
			
		||||
 | 
			
		||||
        return {'latitude': latitude, 'longitude': longitude}
 | 
			
		||||
 | 
			
		||||
    def calculate_place_level(self):
 | 
			
		||||
        self.remove_expired_votes()
 | 
			
		||||
 | 
			
		||||
        if self.placevotings.count() == 0:
 | 
			
		||||
            self.level = 5
 | 
			
		||||
            self.save()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        level = 0
 | 
			
		||||
 | 
			
		||||
        for vote in self.placevotings.all():
 | 
			
		||||
            level += vote.vote
 | 
			
		||||
 | 
			
		||||
        self.level = floor(level / self.placevotings.count())
 | 
			
		||||
        self.save()
 | 
			
		||||
        
 | 
			
		||||
    def remove_expired_votes(self):
 | 
			
		||||
        for vote in self.placevotings.all():
 | 
			
		||||
            if vote.is_expired:
 | 
			
		||||
                vote.delete()
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
@@ -207,13 +180,3 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
 | 
			
		||||
    new_file = instance.filename
 | 
			
		||||
    if not old_file == new_file:
 | 
			
		||||
        old_file.delete(save=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlaceVoting(PlaceAsset, Expireable):
 | 
			
		||||
    vote = models.IntegerField(choices=PLACE_LEVELS)
 | 
			
		||||
    
 | 
			
		||||
    def get_human_readable_level(self):
 | 
			
		||||
        return PLACE_LEVELS[self.vote - 1][1]
 | 
			
		||||
 | 
			
		||||
    def get_all_choices(self):
 | 
			
		||||
        return reversed(PLACE_LEVELS)
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -104,16 +104,16 @@
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
<section class=" LP-Section">
 | 
			
		||||
	<h1 class="LP-Headline">{% translate 'Photo albums submitted by' %} {{explorer.user.username}}</h1>
 | 
			
		||||
	<h1 class="LP-Headline">{% translate 'External links submitted by' %} {{explorer.user.username}}</h1>
 | 
			
		||||
	<div class="LP-LinkList">
 | 
			
		||||
		<ul class="LP-LinkList__Container">
 | 
			
		||||
			{% for photo_album in assets.photoalbums.all %}
 | 
			
		||||
			{% for external_link in assets.externallinks.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 target="_blank" href="{{external_link.url}}" class="LP-Link">
 | 
			
		||||
					<span class="LP-Text">{{external_link.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">
 | 
			
		||||
				{% if user.explorer == external_link.submitted_by%}
 | 
			
		||||
				<a href="{% url 'external_link_delete' pk=external_link.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>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
{% extends 'global.html'%}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{% translate 'Submit a photo album' %}{% endblock %}
 | 
			
		||||
{% block title %}{% translate 'Submit an external link' %}{% 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
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
{% block maincontent %}
 | 
			
		||||
<form class="LP-Form" method="POST">
 | 
			
		||||
    <fieldset class="LP-Form__Fieldset">
 | 
			
		||||
        <legend class="LP-Form__Legend">{% translate 'Submit a photo album for' %} {{place.name}}</legend>
 | 
			
		||||
        <legend class="LP-Form__Legend">{% translate 'Submit an external link for' %} {{place.name}}</legend>
 | 
			
		||||
        {% csrf_token %}
 | 
			
		||||
        <div class="LP-Form__Composition">
 | 
			
		||||
            <div class="LP-Form__Field">
 | 
			
		||||
@@ -27,9 +27,15 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="LP-Form__Composition">
 | 
			
		||||
            <div class="LP-Form__Field">
 | 
			
		||||
                {% include 'partials/form/inputField.html' with field=form.linktype %}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--buttons">
 | 
			
		||||
            {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
</form>
 | 
			
		||||
{% endblock maincontent %}
 | 
			
		||||
{% endblock maincontent %}
 | 
			
		||||
@@ -12,7 +12,6 @@
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <link rel="stylesheet" href="{% static 'main.css' %}">
 | 
			
		||||
	<link rel="stylesheet" href="{% static 'materialdesignicons.css' %}">
 | 
			
		||||
    <link rel="icon" type="image/png" href="{% static 'favicon.ico' %}">
 | 
			
		||||
    <title>
 | 
			
		||||
        {% block title %}Urban Exploration{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
				<a href="#image{{forloop.counter0|add:-1}}" class="LP-ImageGrid__Previous">Previous</a>
 | 
			
		||||
				{% endif %}
 | 
			
		||||
				<span class="LP-ImageGrid__Close LP-ImageGrid__DeleteItem" title="Schließen">
 | 
			
		||||
					<a href="#thumbnail{{forloop.counter0}}" class="LP-Link">
 | 
			
		||||
					<a href="#" class="LP-Link">
 | 
			
		||||
						<img class="LP-Icon" src="{% static 'icons/cancel.svg' %}"/>
 | 
			
		||||
					</a>
 | 
			
		||||
				</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<div class="LP-Voting">
 | 
			
		||||
	<div class="LP-Voting__Left">
 | 
			
		||||
		<h2 class="LP-Headline">
 | 
			
		||||
			Place level
 | 
			
		||||
		</h2>
 | 
			
		||||
		<div class="LP-Voting__Choices">
 | 
			
		||||
			{% for choice in voting.all_choices %}
 | 
			
		||||
				<a href="{% url 'place_vote' place_id=place.id vote=choice.0 %}" class="LP-Voting__Vote {% if choice.0 <= place.level %} LP-Voting__Vote--overall{% endif %}" title="Vote place as "{{choice.1}}"">
 | 
			
		||||
					<i class="mdi mdi-shield-home"></i>
 | 
			
		||||
					<span class="LP-Voting__Label">
 | 
			
		||||
						{{choice.1}}
 | 
			
		||||
					</span>
 | 
			
		||||
				</a>
 | 
			
		||||
			{% endfor %}
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="LP-Voting__CurrentVote">
 | 
			
		||||
			{{place.get_level_display}}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	{% if voting.users_vote %}
 | 
			
		||||
		<div class="LP-Voting__Right">
 | 
			
		||||
			<div class="LP-Voting__Info">
 | 
			
		||||
				<div class="LP-Voting__UserVote">
 | 
			
		||||
					<span class="LP-Voting__InfoLabel">You voted this place as</span>
 | 
			
		||||
					<span class="LP-Voting__Vote">{{voting.users_vote.get_human_readable_level}}  <i>Level {{voting.users_vote.vote}}</i></span>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="LP-Voting__Expiration">
 | 
			
		||||
					<span class="LP-Voting__InfoLabel">Your vote expires on</span>
 | 
			
		||||
					<span class="LP-Voting__Date">
 | 
			
		||||
						<time datetime="{{voting.expires_when|date:'Y-m-d'}}">
 | 
			
		||||
							{{voting.users_vote.expires_when|date:'d.m.Y'}}
 | 
			
		||||
						</time>
 | 
			
		||||
					</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{% endif %}
 | 
			
		||||
</div>
 | 
			
		||||
@@ -40,77 +40,39 @@
 | 
			
		||||
        <p class="LP-Paragraph">{{ place.description }}</p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
	<div class="LP-Quickinfo">
 | 
			
		||||
	    <section class="LP-Section">
 | 
			
		||||
			{% url 'place_tag_submit' place_id=place.id as tag_submit_url %}
 | 
			
		||||
			{% partial tagging %}
 | 
			
		||||
				{% set config=tagging_config %}
 | 
			
		||||
			{% endpartial %}
 | 
			
		||||
		</section>
 | 
			
		||||
		
 | 
			
		||||
		{{votingplace.vote}}
 | 
			
		||||
		<section class="LP-Section">
 | 
			
		||||
			{% partial voting %}
 | 
			
		||||
				{% set place=place %}
 | 
			
		||||
				{% set voting=placevoting %}
 | 
			
		||||
			{% endpartial %}
 | 
			
		||||
		</section>
 | 
			
		||||
	</div>
 | 
			
		||||
    <section class="LP-Section">
 | 
			
		||||
 | 
			
		||||
        {% url 'place_tag_submit' place_id=place.id as tag_submit_url%}
 | 
			
		||||
		{% partial tagging %}
 | 
			
		||||
			{% set config=tagging_config %}
 | 
			
		||||
		{% endpartial %}
 | 
			
		||||
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
    <section class="LP-Section">
 | 
			
		||||
        <h1 class="LP-Headline">{% translate 'Map links' %}</h1>
 | 
			
		||||
        {% partial osm_map config=mapping_config %}
 | 
			
		||||
		<ul class="LP-LinkList">
 | 
			
		||||
			<li class="LP-LinkList__Item">
 | 
			
		||||
				<a target="_blank" href="https://www.google.com/maps?q={{place.latitude|safe}},{{place.longitude|safe}}" class="LP-Link">
 | 
			
		||||
					<span class="LP-Text RV-Iconized">
 | 
			
		||||
						<span class="RV-Iconized__Text">
 | 
			
		||||
							Google Maps
 | 
			
		||||
						</span>
 | 
			
		||||
						<span class="RV-Iconized__Icon RV-Iconized__Icon--left">
 | 
			
		||||
							<i class="mdi mdi-map-outline"></i>
 | 
			
		||||
						</span>
 | 
			
		||||
					</span>
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li class="LP-LinkList__Item">
 | 
			
		||||
				<a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude|safe}},{{place.longitude|safe}}&icon=true&bg=dop" class="LP-Link">
 | 
			
		||||
					<span class="LP-Text RV-Iconized">
 | 
			
		||||
						<span class="RV-Iconized__Text">
 | 
			
		||||
							TIM Online
 | 
			
		||||
						</span>
 | 
			
		||||
						<span class="RV-Iconized__Icon RV-Iconized__Icon--left">
 | 
			
		||||
							<i class="mdi mdi-map-outline"></i>
 | 
			
		||||
						</span>
 | 
			
		||||
					</span>
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
			<li class="LP-LinkList__Item">
 | 
			
		||||
				<a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude|safe}}&mlon={{place.longitude|safe}}&zoom=16" class="LP-Link">
 | 
			
		||||
					<span class="LP-Text RV-Iconized">
 | 
			
		||||
						<span class="RV-Iconized__Text">
 | 
			
		||||
							OSM
 | 
			
		||||
						</span>
 | 
			
		||||
						<span class="RV-Iconized__Icon RV-Iconized__Icon--left">
 | 
			
		||||
							<i class="mdi mdi-map-outline"></i>
 | 
			
		||||
						</span>
 | 
			
		||||
					</span>
 | 
			
		||||
				</a>
 | 
			
		||||
			</li>
 | 
			
		||||
		</ul>
 | 
			
		||||
        <div class="LP-LinkList">
 | 
			
		||||
            <ul class="LP-LinkList__Container">
 | 
			
		||||
                <li class="LP-LinkList__Item"><a target="_blank" href="https://www.google.com/maps?q={{place.latitude|safe}},{{place.longitude|safe}}" class="LP-Link"><span class="LP-Text">Google Maps</span></a></li>
 | 
			
		||||
                <li class="LP-LinkList__Item"><a target="_blank" href="https://www.tim-online.nrw.de/tim-online2/?center={{place.latitude|safe}},{{place.longitude|safe}}&icon=true&bg=dop" class="LP-Link"><span class="LP-Text">TIM Online</span></a></li>
 | 
			
		||||
                <li class="LP-LinkList__Item"><a target="_blank" href="http://www.openstreetmap.org/?mlat={{place.latitude|safe}}&mlon={{place.longitude|safe}}&zoom=16" class="LP-Link"><span class="LP-Text">OSM</span></a></li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </section>
 | 
			
		||||
 | 
			
		||||
    <section class=" LP-Section">
 | 
			
		||||
        <h1 class="LP-Headline">{% translate 'Photo albums' %}</h1>
 | 
			
		||||
        <h2 class="LP-Headline">{% translate 'External links' %}</h2>
 | 
			
		||||
        <div class="LP-LinkList">
 | 
			
		||||
            <ul class="LP-LinkList__Container">
 | 
			
		||||
                {% for photo_album in place.photoalbums.all %}
 | 
			
		||||
                {% for external_link in place.externallinks.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 target="_blank" href="{{external_link.url}}" class="LP-Link">
 | 
			
		||||
                        <span class="mdi mdi-36px mdi-{{external_link.linktype}}"></span>
 | 
			
		||||
                        <span class="LP-Text">{{external_link.label}}</span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                    {% if user.explorer == photo_album.submitted_by or user.explorer == place.submitted_by %}
 | 
			
		||||
                    <a href="{% url 'photo_album_delete' pk=photo_album.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete Photo Album">
 | 
			
		||||
                    {% if user.explorer == external_link.submitted_by or user.explorer == place.submitted_by %}
 | 
			
		||||
                    <a href="{% url 'external_link_delete' pk=external_link.pk%}" class="LP-Link LP-LinkList__ItemHover" title="Delete external link">
 | 
			
		||||
                        <div class="RV-Iconized__Container RV-Iconized__Container--small">
 | 
			
		||||
                            {% icon 'trash' className="RV-Iconized__Icon" %}
 | 
			
		||||
                        </div>
 | 
			
		||||
@@ -119,7 +81,7 @@
 | 
			
		||||
                </li>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
                <li class="LP-LinkList__Item">
 | 
			
		||||
                    <a href="{% url 'photo_album_create' place_id=place.id %}" class="LP-Link">
 | 
			
		||||
                    <a href="{% url 'external_link_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>
 | 
			
		||||
@@ -127,7 +89,7 @@
 | 
			
		||||
		v216c0,11.046,8.954,20,20,20s20-8.954,20-20V276h216c11.046,0,20-8.954,20-20C512,244.954,503.046,236,492,236z" />
 | 
			
		||||
                                </g>
 | 
			
		||||
                            </svg>
 | 
			
		||||
                            <span class="RV-Iconized__Text">{% translate 'Add photo album' %}</span>
 | 
			
		||||
                            <span class="RV-Iconized__Text">{% translate 'Add external link' %}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@
 | 
			
		||||
        
 | 
			
		||||
        {% translate 'Update' as action %}
 | 
			
		||||
        <div class="LP-Form__Composition LP-Form__Composition--buttons">
 | 
			
		||||
            {% include 'partials/form/submit.html' with referrer=request.META.HTTP_REFERER action=action %}
 | 
			
		||||
            {% include 'partials/form/submit.html' with referer=request.META.HTTP_REFERER action=action %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ from lostplaces.tests.views import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
class TestHomeView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        user = User.objects.create_user(
 | 
			
		||||
@@ -32,32 +33,6 @@ class TestHomeView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
        )
 | 
			
		||||
        place.tags.add('I a tag', 'testlocation')
 | 
			
		||||
        place.save()
 | 
			
		||||
        
 | 
			
		||||
        # Creating a place with level one to test against
 | 
			
		||||
        # unauth's users and users with level 1
 | 
			
		||||
        Place.objects.create(
 | 
			
		||||
            name='Im a place level 1',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
            longitude=7.0,
 | 
			
		||||
            description='This is just a test, do not worry',
 | 
			
		||||
            level=1
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Creating a place with level two to test against
 | 
			
		||||
        # unauth's users and users above level 1
 | 
			
		||||
        Place.objects.create(
 | 
			
		||||
            name='Im a place level 2',
 | 
			
		||||
            submitted_when=timezone.now(),
 | 
			
		||||
            submitted_by=user.explorer,
 | 
			
		||||
            location='Testtown',
 | 
			
		||||
            latitude=50.5,
 | 
			
		||||
            longitude=7.0,
 | 
			
		||||
            description='This is just a test, do not worry',
 | 
			
		||||
            level=2
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.client = Client()
 | 
			
		||||
@@ -105,26 +80,7 @@ class TestHomeView(GlobalTemplateTestCaseMixin, ViewTestCase):
 | 
			
		||||
            ),
 | 
			
		||||
            msg='Expecting the test place to show up on the homepage'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        print(response.content.decode().replace('\n', ''))
 | 
			
		||||
        self.assertNotEqual(
 | 
			
		||||
            None,
 | 
			
		||||
            re.search(
 | 
			
		||||
                """Im a place level 1""",
 | 
			
		||||
                response.content.decode().replace('\n', '')
 | 
			
		||||
            ),
 | 
			
		||||
            msg="Expecting the level 1 places to show up on the homepage publicly"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            None,
 | 
			
		||||
            re.search(
 | 
			
		||||
                """Im a place level 2""",
 | 
			
		||||
                response.content.decode().replace('\n', '')
 | 
			
		||||
            ),
 | 
			
		||||
            msg="Expecting the level 2 places to *not* show up on the homepage publicly"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
    def test_map_authenticated(self):
 | 
			
		||||
        """
 | 
			
		||||
        Testing there is a map showing all the lates places
 | 
			
		||||
 
 | 
			
		||||
@@ -19,11 +19,10 @@ from lostplaces.views import (
 | 
			
		||||
    PlaceVisitDeleteView,
 | 
			
		||||
    PlaceImageCreateView,
 | 
			
		||||
    PlaceImageDeleteView,
 | 
			
		||||
    PhotoAlbumCreateView,
 | 
			
		||||
	PhotoAlbumDeleteView,
 | 
			
		||||
    ExternalLinkCreateView,
 | 
			
		||||
	ExternalLinkDeleteView,
 | 
			
		||||
    ExplorerProfileView,
 | 
			
		||||
    ExplorerProfileUpdateView,
 | 
			
		||||
    PlaceVoteView
 | 
			
		||||
    ExplorerProfileUpdateView
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
@@ -48,9 +47,7 @@ urlpatterns = [
 | 
			
		||||
 | 
			
		||||
    path('place_image/create/<int:place_id>/', PlaceImageCreateView.as_view(), name='place_image_create'),
 | 
			
		||||
    path('place_image/delete/<int:pk>/', PlaceImageDeleteView.as_view(), name='place_image_delete'),
 | 
			
		||||
    
 | 
			
		||||
    path('place/vote/<int:place_id>/<int:vote>', PlaceVoteView.as_view(), name='place_vote'),
 | 
			
		||||
 | 
			
		||||
	path('photo_album/create/<int:place_id>/', PhotoAlbumCreateView.as_view(), name='photo_album_create'),
 | 
			
		||||
	path('photo_album/delete/<int:pk>/', PhotoAlbumDeleteView.as_view(), name='photo_album_delete')
 | 
			
		||||
    path('external_link/create/<int:place_id>/', ExternalLinkCreateView.as_view(), name='external_link_create'),
 | 
			
		||||
	path('external_link/delete/<int:pk>/', ExternalLinkDeleteView.as_view(), name='external_link_delete')
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
from django.db.models.functions import Lower
 | 
			
		||||
 | 
			
		||||
@@ -11,18 +10,11 @@ from django.views.generic.detail import SingleObjectMixin
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.contrib.messages.views import SuccessMessageMixin
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from django.shortcuts import render, redirect, get_object_or_404
 | 
			
		||||
from django.urls import reverse_lazy, reverse
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import (
 | 
			
		||||
    Place,
 | 
			
		||||
    PlaceImage,
 | 
			
		||||
    PlaceVoting,
 | 
			
		||||
    PLACE_LEVELS
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from lostplaces.models import Place, PlaceImage
 | 
			
		||||
from lostplaces.views.base_views import (
 | 
			
		||||
    IsAuthenticatedMixin,
 | 
			
		||||
    IsPlaceSubmitterMixin,
 | 
			
		||||
@@ -54,10 +46,8 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
    def get_place(self):
 | 
			
		||||
        return get_object_or_404(Place, pk=self.kwargs['pk'])
 | 
			
		||||
 | 
			
		||||
    def get(self, request, pk): 
 | 
			
		||||
    def get(self, request, pk):        
 | 
			
		||||
        place = self.get_place()
 | 
			
		||||
        place.calculate_place_level()
 | 
			
		||||
        explorer = request.user.explorer
 | 
			
		||||
 | 
			
		||||
        context = {
 | 
			
		||||
            'place': place,
 | 
			
		||||
@@ -71,10 +61,6 @@ class PlaceDetailView(IsAuthenticatedMixin, IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
                'tagged_item': place,
 | 
			
		||||
                'submit_url': reverse('place_tag_submit', kwargs={'tagged_id': place.id}),
 | 
			
		||||
                'delete_url_name': 'place_tag_delete'
 | 
			
		||||
            },
 | 
			
		||||
            'placevoting': {
 | 
			
		||||
                'users_vote': PlaceVoting.objects.filter(place=place, submitted_by=explorer).first(),
 | 
			
		||||
                'all_choices': reversed(PLACE_LEVELS)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return render(request, 'place/place_detail.html', context)
 | 
			
		||||
@@ -199,32 +185,3 @@ class PlaceVisitDeleteView(IsAuthenticatedMixin, View):
 | 
			
		||||
            request.user.explorer.save()
 | 
			
		||||
 | 
			
		||||
        return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))                    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlaceVoteView(IsEligibleToSeePlaceMixin, View):
 | 
			
		||||
    delta = timedelta(weeks=24)
 | 
			
		||||
    
 | 
			
		||||
    def get(self, request, place_id, vote):
 | 
			
		||||
        place = get_object_or_404(Place, id=place_id)
 | 
			
		||||
        explorer = request.user.explorer
 | 
			
		||||
        
 | 
			
		||||
        voting = PlaceVoting.objects.filter(
 | 
			
		||||
            submitted_by=explorer,
 | 
			
		||||
            place=place
 | 
			
		||||
        ).first()
 | 
			
		||||
        
 | 
			
		||||
        if voting is None:
 | 
			
		||||
            voting = PlaceVoting.objects.create(
 | 
			
		||||
                submitted_by=explorer,
 | 
			
		||||
                place=place,
 | 
			
		||||
                vote=vote,
 | 
			
		||||
                expires_when=timezone.now()+self.delta
 | 
			
		||||
            )
 | 
			
		||||
            messages.success(self.request, _('Vote submitted'))
 | 
			
		||||
        else:
 | 
			
		||||
            voting.expires_when=timezone.now()+self.delta
 | 
			
		||||
            voting.vote = vote
 | 
			
		||||
            messages.success(self.request, _('Your vote has been update'))
 | 
			
		||||
 | 
			
		||||
        voting.save()
 | 
			
		||||
        return redirect_referer_or(request, reverse('place_detail', kwargs={'pk': place.pk}))
 | 
			
		||||
@@ -12,7 +12,7 @@ from django.http import HttpResponseForbidden
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from lostplaces.forms import SignupVoucherForm, TagSubmitForm
 | 
			
		||||
from lostplaces.models import Place, PhotoAlbum
 | 
			
		||||
from lostplaces.models import Place, ExternalLink, external_links
 | 
			
		||||
from lostplaces.views.base_views import IsAuthenticatedMixin
 | 
			
		||||
from lostplaces.common import redirect_referer_or
 | 
			
		||||
 | 
			
		||||
@@ -42,23 +42,35 @@ class HomeView(IsAuthenticatedMixin, View):
 | 
			
		||||
        return render(request, 'home.html', context)
 | 
			
		||||
 | 
			
		||||
    def handle_no_permission(self):
 | 
			
		||||
        place_list = Place.objects.filter(level=1)[:5]
 | 
			
		||||
        place_list = Place.objects.all().order_by('-submitted_when')[:5]
 | 
			
		||||
        context = {
 | 
			
		||||
            'place_list': place_list
 | 
			
		||||
        }
 | 
			
		||||
        return render(self.request, 'home_unauth.html', context)
 | 
			
		||||
 | 
			
		||||
class PhotoAlbumCreateView(PlaceAssetCreateView):
 | 
			
		||||
    model = PhotoAlbum
 | 
			
		||||
    fields = ['url', 'label']
 | 
			
		||||
    template_name = 'photo_album/photo_album_create.html'
 | 
			
		||||
    success_message = _('Photo album link submitted')
 | 
			
		||||
class ExternalLinkCreateView(PlaceAssetCreateView):
 | 
			
		||||
    model = ExternalLink
 | 
			
		||||
    fields = ['url', 'label', 'linktype']
 | 
			
		||||
    template_name = 'external_link/external_link_create.html'
 | 
			
		||||
    success_message = _('External link submitted')
 | 
			
		||||
 | 
			
		||||
class PhotoAlbumDeleteView(PlaceAssetDeleteView):
 | 
			
		||||
    model = PhotoAlbum
 | 
			
		||||
    def post(self, request, place_id, *args, **kwargs):
 | 
			
		||||
        response = super().post(request, place_id, *args, **kwargs)
 | 
			
		||||
        if not self.object.linktype:
 | 
			
		||||
            for domain, link_type in external_links.LINK_DOMAINS.items():
 | 
			
		||||
                if domain in self.object.url:
 | 
			
		||||
                    self.object.linktype = link_type
 | 
			
		||||
                    self.object.save()
 | 
			
		||||
                    break
 | 
			
		||||
                else:
 | 
			
		||||
                    self.object.linktype = None
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
class ExternalLinkDeleteView(PlaceAssetDeleteView):
 | 
			
		||||
    model = ExternalLink
 | 
			
		||||
    pk_url_kwarg = 'pk'
 | 
			
		||||
    success_message = _('Photo album link deleted')
 | 
			
		||||
    permission_denied_messsage = _('You are not allowed to edit this photo album link')
 | 
			
		||||
    success_message = _('External link deleted')
 | 
			
		||||
    permission_denied_messsage = _('You are not allowed to edit this external link')
 | 
			
		||||
 | 
			
		||||
class PlaceTagSubmitView(IsAuthenticatedMixin, View):
 | 
			
		||||
	def post(self, request, tagged_id, *args, **kwargs):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,13 @@ from setuptools import setup, find_packages
 | 
			
		||||
with open('Readme.md') as f:
 | 
			
		||||
    readme = f.read()
 | 
			
		||||
 | 
			
		||||
# Keep PEP 440 for version identification in mind
 | 
			
		||||
# https://www.python.org/dev/peps/pep-0440/#post-releases
 | 
			
		||||
setup(
 | 
			
		||||
    name='django-lostplaces',
 | 
			
		||||
    version='0.1.4.post2',
 | 
			
		||||
    version='0.1.3',
 | 
			
		||||
    description='A django app to manage lost places',
 | 
			
		||||
    author='Reverend, Commander1024',
 | 
			
		||||
    author_email='reverend@reverend2048.de, commander@commander1024.de',
 | 
			
		||||
    url='https://git.commander1024.de/Commander1024/lostplaces-backend',
 | 
			
		||||
    author='Reverend',
 | 
			
		||||
    author_email='reverend@reverend2048.de',
 | 
			
		||||
    url='https://git.mowoe.com/reverend/lostplaces-backend',
 | 
			
		||||
    packages=find_packages(exclude=['django_lostplaces']),
 | 
			
		||||
	long_description=readme,
 | 
			
		||||
	long_description_content_type='text/markdown',
 | 
			
		||||
@@ -35,4 +33,4 @@ setup(
 | 
			
		||||
    ],
 | 
			
		||||
    include_package_data=True,
 | 
			
		||||
    license='MIT'
 | 
			
		||||
)
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								tasks.py
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								tasks.py
									
									
									
									
									
								
							@@ -1,21 +0,0 @@
 | 
			
		||||
from invoke import task
 | 
			
		||||
 | 
			
		||||
@task
 | 
			
		||||
def quickstart(c):
 | 
			
		||||
    commands = [
 | 
			
		||||
        'pipenv run migrate',
 | 
			
		||||
        'pipenv run createsuperuser',
 | 
			
		||||
        'pipenv run server'
 | 
			
		||||
    ]
 | 
			
		||||
    c.run(' && '.join(commands))
 | 
			
		||||
    
 | 
			
		||||
@task
 | 
			
		||||
def live(c):
 | 
			
		||||
    commands = [
 | 
			
		||||
        'pipenv check',
 | 
			
		||||
        'pipenv run test',
 | 
			
		||||
        'pipenv run migrate',
 | 
			
		||||
        'pipenv run createsuperuser_prompt'
 | 
			
		||||
        'pipenv run server'
 | 
			
		||||
    ]
 | 
			
		||||
    c.run(' && '.join(commands))
 | 
			
		||||
		Reference in New Issue
	
	Block a user