diff --git a/.gitignore b/.gitignore index 31a28420bcf271c00965ef478bf3567d705ac5a7..451f1e67f8cc4d245fac3ea107c16cbc0d51a1d1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,8 @@ pv/site_media/buttons/b-*.png pv/site_media/buttons/s-*.png pv/site_media/buttons/r-*.png pv/site_media/show_images/* +pv/site_media/note_images/* +pv/site_media/user_images/* +pv/site_media/__sized__/* pv/cache/* pv/mysql.cnf \ No newline at end of file diff --git a/profile/__init__.py b/profile/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/profile/admin.py b/profile/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..281aa6eee7e0e65ce99675fc9d3ef07aeb60f34a --- /dev/null +++ b/profile/admin.py @@ -0,0 +1,22 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.models import User + +from .models import Profile + +class ProfileInline(admin.StackedInline): + model = Profile + can_delete = False + verbose_name_plural = 'Profile' + fk_name = 'user' + +class CustomUserAdmin(UserAdmin): + inlines = (ProfileInline, ) + + def get_inline_instances(self, request, obj=None): + if not obj: + return list() + return super(CustomUserAdmin, self).get_inline_instances(request, obj) + +admin.site.unregister(User) +admin.site.register(User, CustomUserAdmin) \ No newline at end of file diff --git a/profile/migrations/0001_initial.py b/profile/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..cca279bd3d4f730051f677893fc262ad5e91ac69 --- /dev/null +++ b/profile/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-11-09 18:42 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import tinymce.models +import versatileimagefield.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('biography', tinymce.models.HTMLField(blank=True, null=True, verbose_name='Biography')), + ('website', models.URLField(blank=True, verbose_name='Website')), + ('googleplus_url', models.URLField(blank=True, verbose_name='Google+ URL')), + ('facebook_url', models.URLField(blank=True, verbose_name='Facebook URL')), + ('twitter_url', models.URLField(blank=True, verbose_name='Twitter URL')), + ('linkedin_url', models.URLField(blank=True, verbose_name='LinkedIn URL')), + ('youtube_url', models.URLField(blank=True, verbose_name='Youtube URL')), + ('dorftv_url', models.URLField(blank=True, verbose_name='DorfTV URL')), + ('cba_url', models.URLField(blank=True, verbose_name='CBA URL')), + ('cba_username', models.CharField(blank=True, max_length=60, verbose_name='CBA Username')), + ('cba_user_token', models.CharField(blank=True, max_length=255, verbose_name='CBA Token')), + ('ppoi', versatileimagefield.fields.PPOIField(default='0.5x0.5', editable=False, max_length=20, verbose_name='Image PPOI')), + ('height', models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Height')), + ('width', models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Width')), + ('image', versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', null=True, upload_to='user_images', verbose_name='Profile picture', width_field='width')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'profile', + }, + ), + ] diff --git a/profile/migrations/__init__.py b/profile/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/profile/models.py b/profile/models.py new file mode 100644 index 0000000000000000000000000000000000000000..40a04a8668ddca8cade034ebb1ec5bf8c7e70251 --- /dev/null +++ b/profile/models.py @@ -0,0 +1,43 @@ +from django.core.exceptions import ObjectDoesNotExist +from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.utils.translation import ugettext_lazy as _ +from versatileimagefield.fields import VersatileImageField, PPOIField +from django.conf import settings +import os + +from tinymce import models as tinymce_models + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + biography = tinymce_models.HTMLField(_("Biography"), blank=True, null=True) + website = models.URLField(_("Website"), blank=True) + googleplus_url = models.URLField(_("Google+ URL"), blank=True) + facebook_url = models.URLField(_("Facebook URL"), blank=True) + twitter_url = models.URLField(_("Twitter URL"), blank=True) + linkedin_url = models.URLField(_("LinkedIn URL"), blank=True) + youtube_url = models.URLField(_("Youtube URL"), blank=True) + dorftv_url = models.URLField(_("DorfTV URL"), blank=True) + cba_url = models.URLField(_("CBA URL"), blank=True) + cba_username = models.CharField(_("CBA Username"), blank=True, max_length=60) + cba_user_token = models.CharField(_("CBA Token"), blank=True, max_length=255) + ppoi = PPOIField('Image PPOI') + height = models.PositiveIntegerField('Image Height', blank=True, null=True, editable=False) + width = models.PositiveIntegerField('Image Width', blank=True, null=True,editable=False) + image = VersatileImageField(_("Profile picture"), blank=True, null=True, upload_to='user_images', width_field='width', height_field='height', ppoi_field='ppoi') + + def __str__(self): + return self.user.username + + class Meta: + db_table = 'profile' + + def save(self, *args, **kwargs): + super(Profile, self).save(*args, **kwargs) + + # Generate thumbnails + if self.image.name and settings.THUMBNAIL_SIZES: + for size in settings.THUMBNAIL_SIZES: + thumbnail = self.image.crop[size].name \ No newline at end of file diff --git a/profile/urls.py b/profile/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..7ac3efa351fc14074722a1b54094f59a3b786351 --- /dev/null +++ b/profile/urls.py @@ -0,0 +1,5 @@ +from django.contrib import admin + +admin.autodiscover() + +urlpatterns = [] \ No newline at end of file diff --git a/program/admin.py b/program/admin.py index 2ea867845224199b9577f76a7fbf98a7a4437497..df279ff9c652fa2c470551d2c04f1d58228c112a 100644 --- a/program/admin.py +++ b/program/admin.py @@ -2,6 +2,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.contrib import admin from django.utils.translation import ugettext_lazy as _ from django.shortcuts import render +from django.conf import settings from .models import Type, MusicFocus, Category, Topic, RTRCategory, Host, Note, RRule, Schedule, Show, TimeSlot from .forms import MusicFocusForm, CollisionForm @@ -94,6 +95,7 @@ class NoteAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request=None, **kwargs): four_weeks_ago = datetime.now() - timedelta(weeks=4) in_twelf_weeks = datetime.now() + timedelta(weeks=12) + if db_field.name == 'timeslot': try: timeslot_id = int(request.get_full_path().split('/')[-2]) @@ -107,8 +109,11 @@ class NoteAdmin(admin.ModelAdmin): return super(NoteAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) def save_model(self, request, obj, form, change): + if not change: + obj.user = request.user obj.save() + class TimeSlotInline(admin.TabularInline): model = TimeSlot ordering = ('-end',) @@ -117,7 +122,7 @@ class TimeSlotInline(admin.TabularInline): class ScheduleAdmin(admin.ModelAdmin): actions = ('renew',) inlines = (TimeSlotInline,) - fields = (('rrule', 'byweekday'), ('dstart', 'tstart', 'tend'), 'until', 'is_repetition', 'automation_id') + fields = (('rrule', 'byweekday'), ('dstart', 'tstart', 'tend'), 'until', 'is_repetition', 'automation_id', 'fallback_playlist') list_display = ('get_show_name', 'byweekday', 'rrule', 'tstart', 'tend', 'until') list_filter = (ActiveSchedulesFilter, 'byweekday', 'rrule', 'is_repetition') ordering = ('byweekday', 'dstart') @@ -155,9 +160,9 @@ class ShowAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} search_fields = ('name', 'short_description', 'description') fields = ( - 'predecessor', 'type', 'name', 'slug', 'image', 'short_description', 'description', + 'predecessor', 'type', 'name', 'slug', 'image', 'logo', 'short_description', 'description', 'email', 'website', 'hosts', 'owners', 'category', 'rtrcategory', 'topic', - 'musicfocus', + 'musicfocus', 'fallback_pool', 'cba_series_id', ) class Media: @@ -221,6 +226,11 @@ class ShowAdmin(admin.ModelAdmin): if request.POST.get('step') == None: # First save-show submit + # Generate thumbnails + if form.instance.image.name and settings.THUMBNAIL_SIZES: + for size in settings.THUMBNAIL_SIZES: + thumbnail = form.instance.image.crop[size].name + # Save show data only form.save(); diff --git a/program/migrations/0012_auto_20171108_1846.py b/program/migrations/0012_auto_20171109_1843.py similarity index 85% rename from program/migrations/0012_auto_20171108_1846.py rename to program/migrations/0012_auto_20171109_1843.py index eddb203e055017af3ebac34fe0889fb23e43491e..27c9511380358f52d1e00163fae86b05ba9fb2dc 100644 --- a/program/migrations/0012_auto_20171108_1846.py +++ b/program/migrations/0012_auto_20171109_1843.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-11-08 18:46 +# Generated by Django 1.11.3 on 2017-11-09 18:43 from __future__ import unicode_literals from django.conf import settings from django.db import migrations, models import django.db.models.deletion import tinymce.models +import versatileimagefield.fields class Migration(migrations.Migration): @@ -63,8 +64,8 @@ class Migration(migrations.Migration): ('last_updated', models.DateTimeField(auto_now=True, null=True)), ], options={ - 'verbose_name': 'Schedule', - 'verbose_name_plural': 'Schedules', + 'verbose_name': 'Program slot', + 'verbose_name_plural': 'Program slots', 'ordering': ('dstart', 'tstart'), }, ), @@ -142,10 +143,20 @@ class Migration(migrations.Migration): name='cba_id', field=models.IntegerField(blank=True, null=True, verbose_name='CBA ID'), ), + migrations.AddField( + model_name='note', + name='height', + field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Height'), + ), migrations.AddField( model_name='note', name='image', - field=models.ImageField(blank=True, null=True, upload_to='note_images', verbose_name='Featured image'), + field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', null=True, upload_to='note_images', verbose_name='Featured image', width_field='width'), + ), + migrations.AddField( + model_name='note', + name='ppoi', + field=versatileimagefield.fields.PPOIField(default='0.5x0.5', editable=False, max_length=20, verbose_name='Image PPOI'), ), migrations.AddField( model_name='note', @@ -163,6 +174,11 @@ class Migration(migrations.Migration): name='user', field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='users', to=settings.AUTH_USER_MODEL), ), + migrations.AddField( + model_name='note', + name='width', + field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Width'), + ), migrations.AddField( model_name='show', name='cba_series_id', @@ -173,11 +189,26 @@ class Migration(migrations.Migration): name='fallback_pool', field=models.CharField(blank=True, max_length=255, verbose_name='Fallback Pool'), ), + migrations.AddField( + model_name='show', + name='height', + field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Height'), + ), migrations.AddField( model_name='show', name='logo', field=models.ImageField(blank=True, null=True, upload_to='show_images', verbose_name='Logo'), ), + migrations.AddField( + model_name='show', + name='ppoi', + field=versatileimagefield.fields.PPOIField(default='0.5x0.5', editable=False, max_length=20, verbose_name='Image PPOI'), + ), + migrations.AddField( + model_name='show', + name='width', + field=models.PositiveIntegerField(blank=True, editable=False, null=True, verbose_name='Image Width'), + ), migrations.AlterField( model_name='musicfocus', name='big_button', @@ -196,7 +227,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='show', name='image', - field=models.ImageField(blank=True, null=True, upload_to='show_images', verbose_name='Image'), + field=versatileimagefield.fields.VersatileImageField(blank=True, height_field='height', null=True, upload_to='show_images', verbose_name='Image', width_field='width'), ), migrations.AlterField( model_name='timeslot', diff --git a/program/models.py b/program/models.py index 8404731a862060fa0d627cda986e814355293be1..5835db6fb728dbe3de63111536cdcd0f007c4f30 100644 --- a/program/models.py +++ b/program/models.py @@ -4,6 +4,8 @@ from django.urls import reverse from django.db import models from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from versatileimagefield.fields import VersatileImageField, PPOIField +from django.conf import settings from tinymce import models as tinymce_models @@ -255,7 +257,10 @@ class Show(models.Model): musicfocus = models.ManyToManyField(MusicFocus, blank=True, related_name='shows', verbose_name=_("Music focus")) name = models.CharField(_("Name"), max_length=255) slug = models.CharField(_("Slug"), max_length=255, unique=True) - image = models.ImageField(_("Image"), blank=True, null=True, upload_to='show_images') + ppoi = PPOIField('Image PPOI') + height = models.PositiveIntegerField('Image Height', blank=True, null=True, editable=False) + width = models.PositiveIntegerField('Image Width', blank=True, null=True,editable=False) + image = VersatileImageField(_("Image"), blank=True, null=True, upload_to='show_images', width_field='width', height_field='height', ppoi_field='ppoi') logo = models.ImageField(_("Logo"), blank=True, null=True, upload_to='show_images') short_description = models.CharField(_("Short description"), max_length=64) description = tinymce_models.HTMLField(_("Description"), blank=True, null=True) @@ -333,7 +338,8 @@ class Schedule(models.Model): tend = models.TimeField(_("End time")) until = models.DateField(_("Last date")) is_repetition = models.BooleanField(_("Is repetition"), default=False) - automation_id = models.IntegerField(_("Automation ID"), blank=True, null=True, choices=get_automation_id_choices()) + fallback_playlist = models.IntegerField(_("Fallback Playlist"), blank=True, null=True) + automation_id = models.IntegerField(_("Automation ID"), blank=True, null=True, choices=get_automation_id_choices()) # Deprecated created = models.DateTimeField(auto_now_add=True, editable=False, null=True) #-> both see https://stackoverflow.com/questions/1737017/django-auto-now-and-auto-now-add last_updated = models.DateTimeField(auto_now=True, editable=False, null=True) @@ -582,7 +588,10 @@ class Note(models.Model): slug = models.SlugField(_("Slug"), max_length=32, unique=True) summary = tinymce_models.HTMLField(_("Summary"), blank=True) content = tinymce_models.HTMLField(_("Content")) - image = models.ImageField(_("Featured image"), blank=True, null=True, upload_to='note_images') + ppoi = PPOIField('Image PPOI') + height = models.PositiveIntegerField('Image Height', blank=True, null=True, editable=False) + width = models.PositiveIntegerField('Image Width', blank=True, null=True,editable=False) + image = VersatileImageField(_("Featured image"), blank=True, null=True, upload_to='note_images', width_field='width', height_field='height', ppoi_field='ppoi') status = models.IntegerField(_("Status"), choices=STATUS_CHOICES, default=1) start = models.DateTimeField(editable=False) show = models.ForeignKey(Show, editable=False, related_name='notes') @@ -603,4 +612,9 @@ class Note(models.Model): self.start = self.timeslot.start self.show = self.timeslot.schedule.show - super(Note, self).save(*args, **kwargs) \ No newline at end of file + super(Note, self).save(*args, **kwargs) + + # Generate thumbnails + if self.image.name and settings.THUMBNAIL_SIZES: + for size in settings.THUMBNAIL_SIZES: + thumbnail = self.image.crop[size].name \ No newline at end of file diff --git a/pv/settings.py b/pv/settings.py index 6a6c2515378e199488c6c1131170e6737ea45b06..654c0caea608971ee26c9714bbab190450c412d0 100644 --- a/pv/settings.py +++ b/pv/settings.py @@ -87,9 +87,13 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', 'program', 'nop', + 'profile', 'tinymce', + 'versatileimagefield', ) +THUMBNAIL_SIZES = ['200x200', '150x150'] + TINYMCE_JS_URL = '/static/js/tiny_mce/tiny_mce.js' TINYMCE_DEFAULT_CONFIG = { 'plugins': 'contextmenu', diff --git a/requirements.txt b/requirements.txt index 549fc0316ff60921a5bc4bddfce536c7362d5004..1a32e6057c7de8d2891e470f589a34ff2cff9d60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ Django==1.11.3 -MySQL-python==1.2.5 +mysqlclient Pillow==4.2.1 PyYAML==3.12 django-tinymce==2.6.0 python-dateutil==2.6.0 +django-versatileimagefield==1.8.1 \ No newline at end of file